mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:11:13 -05:00
feat: Add user profile data to contact submissions
This commit is contained in:
@@ -538,6 +538,9 @@ export type Database = {
|
|||||||
response_count: number | null
|
response_count: number | null
|
||||||
status: string
|
status: string
|
||||||
subject: string
|
subject: string
|
||||||
|
submitter_profile_data: Json | null
|
||||||
|
submitter_reputation: number | null
|
||||||
|
submitter_username: string | null
|
||||||
thread_id: string | null
|
thread_id: string | null
|
||||||
ticket_number: string | null
|
ticket_number: string | null
|
||||||
updated_at: string
|
updated_at: string
|
||||||
@@ -560,6 +563,9 @@ export type Database = {
|
|||||||
response_count?: number | null
|
response_count?: number | null
|
||||||
status?: string
|
status?: string
|
||||||
subject: string
|
subject: string
|
||||||
|
submitter_profile_data?: Json | null
|
||||||
|
submitter_reputation?: number | null
|
||||||
|
submitter_username?: string | null
|
||||||
thread_id?: string | null
|
thread_id?: string | null
|
||||||
ticket_number?: string | null
|
ticket_number?: string | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
@@ -582,6 +588,9 @@ export type Database = {
|
|||||||
response_count?: number | null
|
response_count?: number | null
|
||||||
status?: string
|
status?: string
|
||||||
subject?: string
|
subject?: string
|
||||||
|
submitter_profile_data?: Json | null
|
||||||
|
submitter_reputation?: number | null
|
||||||
|
submitter_username?: string | null
|
||||||
thread_id?: string | null
|
thread_id?: string | null
|
||||||
ticket_number?: string | null
|
ticket_number?: string | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import {
|
|||||||
Reply,
|
Reply,
|
||||||
Copy,
|
Copy,
|
||||||
Check,
|
Check,
|
||||||
|
User,
|
||||||
|
Award,
|
||||||
|
TrendingUp,
|
||||||
} 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';
|
||||||
@@ -48,6 +51,7 @@ import {
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { useTheme } from '@/components/theme/ThemeProvider';
|
import { useTheme } from '@/components/theme/ThemeProvider';
|
||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
import { handleError, handleSuccess } from '@/lib/errorHandler';
|
||||||
@@ -61,6 +65,20 @@ interface ContactSubmission {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
user_id: string | null;
|
user_id: string | null;
|
||||||
|
submitter_username: string | null;
|
||||||
|
submitter_reputation: number | null;
|
||||||
|
submitter_profile_data: {
|
||||||
|
display_name?: string;
|
||||||
|
member_since?: string;
|
||||||
|
stats?: {
|
||||||
|
rides: number;
|
||||||
|
coasters: number;
|
||||||
|
parks: number;
|
||||||
|
reviews: number;
|
||||||
|
};
|
||||||
|
reputation?: number;
|
||||||
|
avatar_url?: string;
|
||||||
|
} | null;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
@@ -683,6 +701,61 @@ export default function AdminContact() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* User Context Section */}
|
||||||
|
{selectedSubmission.submitter_profile_data && (
|
||||||
|
<div className="border rounded-lg p-4 bg-muted/30">
|
||||||
|
<h4 className="font-semibold mb-3 flex items-center gap-2">
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
Submitter Context
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<Avatar className="h-12 w-12">
|
||||||
|
{selectedSubmission.submitter_profile_data.avatar_url && (
|
||||||
|
<AvatarImage src={selectedSubmission.submitter_profile_data.avatar_url} />
|
||||||
|
)}
|
||||||
|
<AvatarFallback>
|
||||||
|
{selectedSubmission.submitter_username?.[0]?.toUpperCase() || 'U'}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<span className="font-medium">
|
||||||
|
@{selectedSubmission.submitter_username}
|
||||||
|
</span>
|
||||||
|
{selectedSubmission.submitter_profile_data.display_name && (
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
({selectedSubmission.submitter_profile_data.display_name})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Badge variant="secondary" className="gap-1">
|
||||||
|
<Award className="h-3 w-3" />
|
||||||
|
{selectedSubmission.submitter_reputation} rep
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{selectedSubmission.submitter_profile_data.member_since && (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Member since {format(new Date(selectedSubmission.submitter_profile_data.member_since), 'MMM d, yyyy')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedSubmission.submitter_profile_data.stats && (
|
||||||
|
<div className="flex items-center gap-3 text-sm flex-wrap">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<TrendingUp className="h-3 w-3" />
|
||||||
|
{selectedSubmission.submitter_profile_data.stats.rides} rides
|
||||||
|
</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{selectedSubmission.submitter_profile_data.stats.coasters} coasters</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{selectedSubmission.submitter_profile_data.stats.parks} parks</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{selectedSubmission.submitter_profile_data.stats.reviews} reviews</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Subject & Category */}
|
{/* Subject & Category */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Category</Label>
|
<Label>Category</Label>
|
||||||
|
|||||||
@@ -124,9 +124,12 @@ const handler = async (req: Request): Promise<Response> => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user ID if authenticated
|
// Get user ID and profile if authenticated
|
||||||
const authHeader = req.headers.get('Authorization');
|
const authHeader = req.headers.get('Authorization');
|
||||||
let userId: string | null = null;
|
let userId: string | null = null;
|
||||||
|
let submitterUsername: string | null = null;
|
||||||
|
let submitterReputation: number | null = null;
|
||||||
|
let submitterProfileData: Record<string, unknown> | null = null;
|
||||||
|
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
const supabaseClient = createClient(
|
const supabaseClient = createClient(
|
||||||
@@ -137,6 +140,38 @@ const handler = async (req: Request): Promise<Response> => {
|
|||||||
|
|
||||||
const { data: { user } } = await supabaseClient.auth.getUser();
|
const { data: { user } } = await supabaseClient.auth.getUser();
|
||||||
userId = user?.id || null;
|
userId = user?.id || null;
|
||||||
|
|
||||||
|
// Fetch user profile for enhanced context
|
||||||
|
if (userId) {
|
||||||
|
const { data: profile } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('username, display_name, reputation_score, ride_count, coaster_count, park_count, review_count, created_at, avatar_url')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (profile) {
|
||||||
|
submitterUsername = profile.username;
|
||||||
|
submitterReputation = profile.reputation_score || 0;
|
||||||
|
submitterProfileData = {
|
||||||
|
display_name: profile.display_name,
|
||||||
|
member_since: profile.created_at,
|
||||||
|
stats: {
|
||||||
|
rides: profile.ride_count || 0,
|
||||||
|
coasters: profile.coaster_count || 0,
|
||||||
|
parks: profile.park_count || 0,
|
||||||
|
reviews: profile.review_count || 0,
|
||||||
|
},
|
||||||
|
reputation: profile.reputation_score || 0,
|
||||||
|
avatar_url: profile.avatar_url
|
||||||
|
};
|
||||||
|
|
||||||
|
edgeLogger.info('Enhanced submission with user profile', {
|
||||||
|
requestId,
|
||||||
|
username: submitterUsername,
|
||||||
|
reputation: submitterReputation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert contact submission (ticket number auto-generated by trigger)
|
// Insert contact submission (ticket number auto-generated by trigger)
|
||||||
@@ -144,6 +179,9 @@ const handler = async (req: Request): Promise<Response> => {
|
|||||||
.from('contact_submissions')
|
.from('contact_submissions')
|
||||||
.insert({
|
.insert({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
|
submitter_username: submitterUsername,
|
||||||
|
submitter_reputation: submitterReputation,
|
||||||
|
submitter_profile_data: submitterProfileData,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
email: email.trim().toLowerCase(),
|
email: email.trim().toLowerCase(),
|
||||||
subject: subject.trim(),
|
subject: subject.trim(),
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- Add user profile context columns to contact_submissions
|
||||||
|
ALTER TABLE contact_submissions
|
||||||
|
ADD COLUMN submitter_username text,
|
||||||
|
ADD COLUMN submitter_reputation integer,
|
||||||
|
ADD COLUMN submitter_profile_data jsonb;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN contact_submissions.submitter_username IS 'Username snapshot at time of submission';
|
||||||
|
COMMENT ON COLUMN contact_submissions.submitter_reputation IS 'Reputation score snapshot at time of submission';
|
||||||
|
COMMENT ON COLUMN contact_submissions.submitter_profile_data IS 'Full profile snapshot (display_name, member_since, stats, avatar_url) at time of submission';
|
||||||
Reference in New Issue
Block a user