feat: Add user profile data to contact submissions

This commit is contained in:
gpt-engineer-app[bot]
2025-10-28 19:14:51 +00:00
parent 8300243bb2
commit 2ebb2eafec
4 changed files with 130 additions and 1 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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(),

View File

@@ -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';