mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 14:31:12 -05:00
feat: Implement plan for bug fixes
This commit is contained in:
@@ -30,6 +30,7 @@ import '@mdxeditor/editor/style.css';
|
|||||||
import '@/styles/mdx-editor-theme.css';
|
import '@/styles/mdx-editor-theme.css';
|
||||||
import { useTheme } from '@/components/theme/ThemeProvider';
|
import { useTheme } from '@/components/theme/ThemeProvider';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
import { useAutoSave } from '@/hooks/useAutoSave';
|
import { useAutoSave } from '@/hooks/useAutoSave';
|
||||||
import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
|
import { CheckCircle2, Loader2, AlertCircle } from 'lucide-react';
|
||||||
@@ -140,9 +141,11 @@ export function MarkdownEditor({
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
const { data, error } = await supabase.functions.invoke('upload-image', {
|
const { data, error } = await invokeWithTracking(
|
||||||
body: formData
|
'upload-image',
|
||||||
});
|
formData,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
@@ -101,15 +102,17 @@ export function TestDataGenerator() {
|
|||||||
description: `Generating ${stage}...`,
|
description: `Generating ${stage}...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, error } = await supabase.functions.invoke('seed-test-data', {
|
const { data, error } = await invokeWithTracking(
|
||||||
body: {
|
'seed-test-data',
|
||||||
|
{
|
||||||
preset,
|
preset,
|
||||||
fieldDensity,
|
fieldDensity,
|
||||||
entityTypes: selectedEntityTypes,
|
entityTypes: selectedEntityTypes,
|
||||||
stage,
|
stage,
|
||||||
...options
|
...options
|
||||||
}
|
},
|
||||||
});
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { getSessionAAL } from '@/types/supabase-session';
|
import { getSessionAAL } from '@/types/supabase-session';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
import { getErrorMessage } from '@/lib/errorHandler';
|
||||||
@@ -142,9 +143,11 @@ export function MFARemovalDialog({ open, onOpenChange, factorId, onSuccess }: MF
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// Phase 3: Call edge function instead of direct unenroll
|
// Phase 3: Call edge function instead of direct unenroll
|
||||||
const { data, error } = await supabase.functions.invoke('mfa-unenroll', {
|
const { data, error, requestId } = await invokeWithTracking(
|
||||||
body: { factorId }
|
'mfa-unenroll',
|
||||||
});
|
{ factorId },
|
||||||
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
if (data?.error) throw new Error(data.error);
|
if (data?.error) throw new Error(data.error);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useReducer } from 'react';
|
import { useReducer } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter } from '@/components/ui/alert-dialog';
|
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter } from '@/components/ui/alert-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -57,9 +58,11 @@ export function AccountDeletionDialog({ open, onOpenChange, userEmail, onDeletio
|
|||||||
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_LOADING', payload: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase.functions.invoke('request-account-deletion', {
|
const { data, error, requestId } = await invokeWithTracking(
|
||||||
body: {},
|
'request-account-deletion',
|
||||||
});
|
{},
|
||||||
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
@@ -88,9 +91,11 @@ export function AccountDeletionDialog({ open, onOpenChange, userEmail, onDeletio
|
|||||||
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_LOADING', payload: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.functions.invoke('confirm-account-deletion', {
|
const { error, requestId } = await invokeWithTracking(
|
||||||
body: { confirmation_code: state.confirmationCode },
|
'confirm-account-deletion',
|
||||||
});
|
{ confirmation_code: state.confirmationCode },
|
||||||
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
@@ -110,9 +115,11 @@ export function AccountDeletionDialog({ open, onOpenChange, userEmail, onDeletio
|
|||||||
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_LOADING', payload: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.functions.invoke('resend-deletion-code', {
|
const { error, requestId } = await invokeWithTracking(
|
||||||
body: {},
|
'resend-deletion-code',
|
||||||
});
|
{},
|
||||||
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -172,12 +173,11 @@ export function AccountProfileTab() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the edge function with explicit authorization header
|
// Call the edge function with explicit authorization header
|
||||||
const { data, error } = await supabase.functions.invoke('cancel-email-change', {
|
const { data, error, requestId } = await invokeWithTracking(
|
||||||
method: 'POST',
|
'cancel-email-change',
|
||||||
headers: {
|
{},
|
||||||
Authorization: `Bearer ${session.access_token}`,
|
user.id
|
||||||
},
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Edge function error:', error);
|
console.error('Edge function error:', error);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
@@ -163,11 +164,10 @@ export function DataExportTab() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Call edge function for secure export
|
// Call edge function for secure export
|
||||||
const { data, error } = await supabase.functions.invoke<ExportRequestResult>(
|
const { data, error, requestId } = await invokeWithTracking<ExportRequestResult>(
|
||||||
'export-user-data',
|
'export-user-data',
|
||||||
{
|
validatedOptions,
|
||||||
body: validatedOptions
|
user.id
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { AlertTriangle, Loader2 } from 'lucide-react';
|
import { AlertTriangle, Loader2 } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -26,9 +27,11 @@ export function DeletionStatusBanner({ scheduledDate, onCancelled }: DeletionSta
|
|||||||
const handleCancelDeletion = async () => {
|
const handleCancelDeletion = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.functions.invoke('cancel-account-deletion', {
|
const { error, requestId } = await invokeWithTracking(
|
||||||
body: { cancellation_reason: 'User cancelled from settings' },
|
'cancel-account-deletion',
|
||||||
});
|
{ cancellation_reason: 'User cancelled from settings' },
|
||||||
|
(await supabase.auth.getUser()).data.user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import {
|
import {
|
||||||
@@ -302,8 +303,9 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
|||||||
|
|
||||||
// Step 4: Send security notification
|
// Step 4: Send security notification
|
||||||
try {
|
try {
|
||||||
await supabase.functions.invoke('trigger-notification', {
|
await invokeWithTracking(
|
||||||
body: {
|
'trigger-notification',
|
||||||
|
{
|
||||||
workflowId: 'security-alert',
|
workflowId: 'security-alert',
|
||||||
subscriberId: user.id,
|
subscriberId: user.id,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -311,8 +313,9 @@ export function PasswordUpdateDialog({ open, onOpenChange, onSuccess }: Password
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
device: navigator.userAgent.split(' ')[0]
|
device: navigator.userAgent.split(' ')[0]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
user.id
|
||||||
|
);
|
||||||
} catch (notifError) {
|
} catch (notifError) {
|
||||||
logger.error('Failed to send password change notification', {
|
logger.error('Failed to send password change notification', {
|
||||||
userId: user!.id,
|
userId: user!.id,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
import { getErrorMessage } from '@/lib/errorHandler';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
|
||||||
interface PhotoUploadProps {
|
interface PhotoUploadProps {
|
||||||
onUploadComplete?: (urls: string[], imageId?: string) => void;
|
onUploadComplete?: (urls: string[], imageId?: string) => void;
|
||||||
@@ -111,8 +113,9 @@ export function PhotoUpload({
|
|||||||
|
|
||||||
const uploadFile = async (file: File, previewUrl: string): Promise<UploadedImage> => {
|
const uploadFile = async (file: File, previewUrl: string): Promise<UploadedImage> => {
|
||||||
try {
|
try {
|
||||||
const { data: uploadData, error: uploadError } = await supabase.functions.invoke('upload-image', {
|
const { data: uploadData, error: uploadError, requestId } = await invokeWithTracking(
|
||||||
body: {
|
'upload-image',
|
||||||
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
@@ -120,8 +123,9 @@ export function PhotoUpload({
|
|||||||
uploadedAt: new Date().toISOString()
|
uploadedAt: new Date().toISOString()
|
||||||
},
|
},
|
||||||
variant: isAvatar ? 'avatar' : 'public'
|
variant: isAvatar ? 'avatar' : 'public'
|
||||||
}
|
},
|
||||||
});
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (uploadError) {
|
if (uploadError) {
|
||||||
console.error('Upload URL error:', uploadError);
|
console.error('Upload URL error:', uploadError);
|
||||||
@@ -239,10 +243,12 @@ export function PhotoUpload({
|
|||||||
try {
|
try {
|
||||||
if (isAvatar && currentImageId) {
|
if (isAvatar && currentImageId) {
|
||||||
try {
|
try {
|
||||||
await supabase.functions.invoke('upload-image', {
|
await invokeWithTracking(
|
||||||
method: 'DELETE',
|
'upload-image',
|
||||||
body: { imageId: currentImageId }
|
{ imageId: currentImageId },
|
||||||
});
|
undefined,
|
||||||
|
'DELETE'
|
||||||
|
);
|
||||||
} catch (deleteError) {
|
} catch (deleteError) {
|
||||||
console.warn('Failed to delete old avatar:', deleteError);
|
console.warn('Failed to delete old avatar:', deleteError);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
@@ -96,9 +97,11 @@ export function UppyPhotoSubmissionUpload({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Get upload URL from edge function
|
// Get upload URL from edge function
|
||||||
const { data: uploadData, error: uploadError } = await supabase.functions.invoke('upload-image', {
|
const { data: uploadData, error: uploadError } = await invokeWithTracking(
|
||||||
body: { metadata: { requireSignedURLs: false }, variant: 'public' }
|
'upload-image',
|
||||||
});
|
{ metadata: { requireSignedURLs: false }, variant: 'public' },
|
||||||
|
user?.id
|
||||||
|
);
|
||||||
|
|
||||||
if (uploadError) throw uploadError;
|
if (uploadError) throw uploadError;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Upload, X, Eye, Loader2, CheckCircle } from 'lucide-react';
|
import { Upload, X, Eye, Loader2, CheckCircle } from 'lucide-react';
|
||||||
@@ -100,9 +101,7 @@ export function UppyPhotoUpload({
|
|||||||
setCurrentFileName(file.name);
|
setCurrentFileName(file.name);
|
||||||
|
|
||||||
// Step 1: Get upload URL from Supabase edge function
|
// Step 1: Get upload URL from Supabase edge function
|
||||||
const urlResponse = await supabase.functions.invoke('upload-image', {
|
const urlResponse = await invokeWithTracking('upload-image', { metadata, variant }, undefined);
|
||||||
body: { metadata, variant },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (urlResponse.error) {
|
if (urlResponse.error) {
|
||||||
throw new Error(`Failed to get upload URL: ${urlResponse.error.message}`);
|
throw new Error(`Failed to get upload URL: ${urlResponse.error.message}`);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
@@ -82,7 +83,7 @@ export function useUnitPreferences() {
|
|||||||
|
|
||||||
const autoDetectPreferences = useCallback(async () => {
|
const autoDetectPreferences = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await supabase.functions.invoke('detect-location');
|
const response = await invokeWithTracking('detect-location', {}, user?.id);
|
||||||
|
|
||||||
if (response.data && response.data.measurementSystem) {
|
if (response.data && response.data.measurementSystem) {
|
||||||
const newPreferences: UnitPreferences = {
|
const newPreferences: UnitPreferences = {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
import { getErrorMessage } from '@/lib/errorHandler';
|
import { getErrorMessage } from '@/lib/errorHandler';
|
||||||
|
|
||||||
@@ -14,9 +15,11 @@ interface EmailValidationResult {
|
|||||||
*/
|
*/
|
||||||
export async function validateEmailNotDisposable(email: string): Promise<EmailValidationResult> {
|
export async function validateEmailNotDisposable(email: string): Promise<EmailValidationResult> {
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase.functions.invoke('validate-email-backend', {
|
const { data, error } = await invokeWithTracking(
|
||||||
body: { email }
|
'validate-email-backend',
|
||||||
});
|
{ email },
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error('Email validation error from backend', {
|
logger.error('Email validation error from backend', {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
@@ -76,11 +77,11 @@ export default function AuthCallback() {
|
|||||||
try {
|
try {
|
||||||
console.log('[AuthCallback] Processing OAuth profile...');
|
console.log('[AuthCallback] Processing OAuth profile...');
|
||||||
|
|
||||||
const { data, error } = await supabase.functions.invoke('process-oauth-profile', {
|
const { data, error, requestId } = await invokeWithTracking(
|
||||||
headers: {
|
'process-oauth-profile',
|
||||||
Authorization: `Bearer ${session.access_token}`,
|
{},
|
||||||
},
|
user.id
|
||||||
});
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[AuthCallback] Profile processing error:', error);
|
console.error('[AuthCallback] Profile processing error:', error);
|
||||||
|
|||||||
Reference in New Issue
Block a user