updated registration screen
This commit is contained in:
@@ -19,12 +19,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '../../components/ui/dialog';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '../../components/ui/accordion';
|
||||
import { toast } from 'sonner';
|
||||
import api from '../../utils/api';
|
||||
import {
|
||||
@@ -45,13 +39,9 @@ import {
|
||||
AlignLeft,
|
||||
Upload,
|
||||
Lock,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
Settings,
|
||||
Zap,
|
||||
Copy,
|
||||
X,
|
||||
Grip
|
||||
X
|
||||
} from 'lucide-react';
|
||||
|
||||
// Field type icons
|
||||
@@ -192,6 +182,7 @@ const AdminRegistrationBuilder = () => {
|
||||
|
||||
// Get current step
|
||||
const currentStep = sortedSteps.find((s) => s.id === selectedStep);
|
||||
const currentStepIndex = sortedSteps.findIndex((s) => s.id === selectedStep);
|
||||
|
||||
// Get sections for current step
|
||||
const sortedSections = currentStep?.sections?.sort((a, b) => a.order - b.order) || [];
|
||||
@@ -335,13 +326,13 @@ const AdminRegistrationBuilder = () => {
|
||||
};
|
||||
|
||||
// Add field
|
||||
const handleAddField = () => {
|
||||
if (!selectedSection) {
|
||||
const handleAddField = (sectionId = selectedSection) => {
|
||||
if (!sectionId) {
|
||||
toast.error('Please select a section first');
|
||||
return;
|
||||
}
|
||||
|
||||
const section = currentStep?.sections?.find((s) => s.id === selectedSection);
|
||||
const section = currentStep?.sections?.find((s) => s.id === sectionId);
|
||||
const fieldCount = section?.fields?.length || 0;
|
||||
|
||||
const newField = {
|
||||
@@ -364,7 +355,7 @@ const AdminRegistrationBuilder = () => {
|
||||
? {
|
||||
...s,
|
||||
sections: s.sections.map((sec) =>
|
||||
sec.id === selectedSection
|
||||
sec.id === sectionId
|
||||
? { ...sec, fields: [...(sec.fields || []), newField] }
|
||||
: sec
|
||||
),
|
||||
@@ -373,13 +364,14 @@ const AdminRegistrationBuilder = () => {
|
||||
),
|
||||
}));
|
||||
|
||||
setSelectedSection(sectionId);
|
||||
setSelectedField(newField.id);
|
||||
setAddFieldDialogOpen(false);
|
||||
};
|
||||
|
||||
// Delete field
|
||||
const handleDeleteField = (fieldId) => {
|
||||
const section = currentStep?.sections?.find((s) => s.id === selectedSection);
|
||||
const handleDeleteField = (sectionId, fieldId) => {
|
||||
const section = currentStep?.sections?.find((s) => s.id === sectionId);
|
||||
const field = section?.fields?.find((f) => f.id === fieldId);
|
||||
|
||||
if (field?.is_fixed) {
|
||||
@@ -398,7 +390,7 @@ const AdminRegistrationBuilder = () => {
|
||||
? {
|
||||
...s,
|
||||
sections: s.sections.map((sec) =>
|
||||
sec.id === selectedSection
|
||||
sec.id === sectionId
|
||||
? {
|
||||
...sec,
|
||||
fields: sec.fields
|
||||
@@ -418,7 +410,7 @@ const AdminRegistrationBuilder = () => {
|
||||
};
|
||||
|
||||
// Update field
|
||||
const handleUpdateField = (fieldId, updates) => {
|
||||
const handleUpdateField = (sectionId, fieldId, updates) => {
|
||||
updateSchema((prev) => ({
|
||||
...prev,
|
||||
steps: prev.steps.map((s) =>
|
||||
@@ -426,7 +418,7 @@ const AdminRegistrationBuilder = () => {
|
||||
? {
|
||||
...s,
|
||||
sections: s.sections.map((sec) =>
|
||||
sec.id === selectedSection
|
||||
sec.id === sectionId
|
||||
? {
|
||||
...sec,
|
||||
fields: sec.fields.map((f) =>
|
||||
@@ -441,10 +433,7 @@ const AdminRegistrationBuilder = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
// Get selected field data
|
||||
const selectedFieldData = currentStep?.sections
|
||||
?.find((s) => s.id === selectedSection)
|
||||
?.fields?.find((f) => f.id === selectedField);
|
||||
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -493,24 +482,24 @@ const AdminRegistrationBuilder = () => {
|
||||
)}
|
||||
|
||||
{/* Main Builder Layout */}
|
||||
{/* Left Sidebar - Steps & Sections */}
|
||||
<div className="lg:col-span-3">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
<div className="lg:col-span-9">
|
||||
<Card className="p-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold">Steps</h2>
|
||||
<Button size="sm" variant="ghost" className='w-32' onClick={() => setAddStepDialogOpen(true)}>
|
||||
<Button size="sm" variant="ghost" className="w-32" onClick={() => setAddStepDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
<p>Add Step</p>
|
||||
Add Step
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<div className="flex flex-wrap items-end gap-2 border-b border-gray-200">
|
||||
{sortedSteps.map((step, index) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className={`p-3 rounded-t-lg border cursor-pointer transition-colors ${selectedStep === step.id
|
||||
? ' bg-brand-lavender/10 border-b-4 border-b-brand-dark-lavender'
|
||||
: ''
|
||||
className={`px-3 py-2 rounded-t-lg border cursor-pointer transition-colors ${selectedStep === step.id
|
||||
? 'bg-white border-gray-300 border-b-white'
|
||||
: 'bg-gray-50 border-transparent hover:bg-gray-100'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedStep(step.id);
|
||||
@@ -518,41 +507,15 @@ const AdminRegistrationBuilder = () => {
|
||||
setSelectedField(null);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="font-medium">Step: {index + 1} </div>
|
||||
<span className="font-medium">Step {index + 1}</span>
|
||||
{/* <span className="text-sm text-muted-foreground">{step.title || 'Untitled Step'}</span> */}
|
||||
</div>
|
||||
{/* Mod Buttons */}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* <Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleMoveStep(step.id, 'up');
|
||||
}}
|
||||
disabled={index === 0}
|
||||
>
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleMoveStep(step.id, 'down');
|
||||
}}
|
||||
disabled={index === sortedSteps.length - 1}
|
||||
>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</Button> */}
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-500 hover-text-background ml-2"
|
||||
className="h-6 w-6 text-red-500 hover:text-white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteStep(step.id);
|
||||
@@ -562,78 +525,74 @@ const AdminRegistrationBuilder = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{sortedSteps.length === 0 && (
|
||||
<div className="text-center py-6 text-muted-foreground">
|
||||
No steps yet. Click "Add Step" to get started.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sections for selected step */}
|
||||
{currentStep && (
|
||||
<>
|
||||
<div className="flex justify-between flex-col items-center mt-6 mb-4">
|
||||
<h2 className="text-lg font-semibold self-start">Sections</h2>
|
||||
<Button size="sm" className='w-full' variant="ghost" onClick={() => setAddSectionDialogOpen(true)}>
|
||||
<div className="mt-6">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">
|
||||
Step {currentStepIndex + 1}: {currentStep?.title || 'Untitled Step'}
|
||||
</h2>
|
||||
{currentStep.description && (
|
||||
<p className="text-sm text-muted-foreground mt-1">{currentStep.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<Button size="sm" variant="ghost" onClick={() => setAddSectionDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
<p>Add Section</p>
|
||||
Add Section
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 ">
|
||||
<div className="space-y-4">
|
||||
{sortedSections.map((section) => {
|
||||
const sortedFields = section.fields?.sort((a, b) => a.order - b.order) || [];
|
||||
|
||||
return (<div
|
||||
return (
|
||||
<div
|
||||
key={section.id}
|
||||
className='p-3 rounded-lg bg-background'
|
||||
className={`p-6 rounded-lg border cursor-pointer transition-colors ${selectedSection === section.id
|
||||
? 'border-brand-purple bg-brand-lavender/10'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedSection(section.id);
|
||||
setSelectedField(null);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between ">
|
||||
<div className='flex flex-col'>
|
||||
<span className="text-xl font-semibold">{section.title}</span>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">{section.title || 'Untitled Section'}</h3>
|
||||
{section.description && (
|
||||
<p className="text-sm text-muted-foreground mb-4">{section.description}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">{section.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className=" text-red-500 self-start hover-text-background"
|
||||
className="h-6 w-6 text-red-500 hover:text-red-700"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteSection(section.id);
|
||||
}}
|
||||
>
|
||||
<p>Delete Section</p>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-6 mt-4 border-brand-purple rounded-xl bg-brand-lavender/10 ">
|
||||
<div className="flex justify-between items-center mb-6 ">
|
||||
|
||||
<div>test</div>
|
||||
<Button size="sm" className='' onClick={() => setAddFieldDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Field
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
{/* Fields */}
|
||||
<div
|
||||
className='mb-6 p-4 rounded-lg border-2 border-dashed bg-background border-gray-200'
|
||||
|
||||
>
|
||||
<h3 className="text-lg font-medium mb-4">title</h3>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">{section.description}</p>
|
||||
|
||||
|
||||
{/* Fields */}
|
||||
<div className="space-y-3">
|
||||
<div className="mt-4 space-y-3">
|
||||
{sortedFields.map((field) => {
|
||||
const IconComponent = FIELD_TYPE_ICONS[field.type] || Type;
|
||||
const options = field.options || [];
|
||||
return (
|
||||
<div
|
||||
key={field.id}
|
||||
@@ -668,10 +627,10 @@ const AdminRegistrationBuilder = () => {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-500 hover-text-background"
|
||||
className="h-6 w-6 text-red-500 hover:text-red-700"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteField(field.id);
|
||||
handleDeleteField(section.id, field.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
@@ -679,62 +638,29 @@ const AdminRegistrationBuilder = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
{sortedFields.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No fields in this section. Click "Add Field" to add one.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
|
||||
{/* Right Sidebar - Field Properties */}
|
||||
<div className="lg:col-span-3">
|
||||
<Card className="p-4">
|
||||
<h2 className="text-lg font-semibold mb-4">Edit Options</h2>
|
||||
|
||||
{selectedFieldData ? (
|
||||
<div className="space-y-4">
|
||||
{/* Field ID */}
|
||||
{selectedField === field.id && (
|
||||
<div className="mt-4 border-t pt-4 space-y-4">
|
||||
<div>
|
||||
<Label className="text-xs text-muted-foreground">Field ID</Label>
|
||||
<div className="text-sm font-mono bg-gray-100 px-2 py-1 rounded">
|
||||
{selectedFieldData.id}
|
||||
{field.id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div>
|
||||
<Label htmlFor="field-label">Label</Label>
|
||||
<Label htmlFor={`field-label-${field.id}`}>Label</Label>
|
||||
<Input
|
||||
id="field-label"
|
||||
value={selectedFieldData.label}
|
||||
onChange={(e) => handleUpdateField(selectedField, { label: e.target.value })}
|
||||
disabled={selectedFieldData.is_fixed}
|
||||
id={`field-label-${field.id}`}
|
||||
value={field.label}
|
||||
onChange={(e) => handleUpdateField(section.id, field.id, { label: e.target.value })}
|
||||
disabled={field.is_fixed}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Type */}
|
||||
<div>
|
||||
<Label>Type</Label>
|
||||
<Select
|
||||
value={selectedFieldData.type}
|
||||
onValueChange={(value) => handleUpdateField(selectedField, { type: value })}
|
||||
disabled={selectedFieldData.is_fixed}
|
||||
value={field.type}
|
||||
onValueChange={(value) => handleUpdateField(section.id, field.id, { type: value })}
|
||||
disabled={field.is_fixed}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -748,26 +674,22 @@ const AdminRegistrationBuilder = () => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Required */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="field-required"
|
||||
checked={selectedFieldData.required}
|
||||
id={`field-required-${field.id}`}
|
||||
checked={field.required}
|
||||
onCheckedChange={(checked) =>
|
||||
handleUpdateField(selectedField, { required: checked })
|
||||
handleUpdateField(section.id, field.id, { required: checked })
|
||||
}
|
||||
disabled={selectedFieldData.is_fixed}
|
||||
disabled={field.is_fixed}
|
||||
/>
|
||||
<Label htmlFor="field-required">Required</Label>
|
||||
<Label htmlFor={`field-required-${field.id}`}>Required</Label>
|
||||
</div>
|
||||
|
||||
{/* Width */}
|
||||
<div>
|
||||
<Label>Width</Label>
|
||||
<Select
|
||||
value={selectedFieldData.width || 'full'}
|
||||
onValueChange={(value) => handleUpdateField(selectedField, { width: value })}
|
||||
value={field.width || 'full'}
|
||||
onValueChange={(value) => handleUpdateField(section.id, field.id, { width: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -780,37 +702,35 @@ const AdminRegistrationBuilder = () => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Placeholder */}
|
||||
{['text', 'email', 'phone', 'textarea'].includes(selectedFieldData.type) && (
|
||||
{['text', 'email', 'phone', 'textarea'].includes(field.type) && (
|
||||
<div>
|
||||
<Label htmlFor="field-placeholder">Placeholder</Label>
|
||||
<Label htmlFor={`field-placeholder-${field.id}`}>Placeholder</Label>
|
||||
<Input
|
||||
id="field-placeholder"
|
||||
value={selectedFieldData.placeholder || ''}
|
||||
id={`field-placeholder-${field.id}`}
|
||||
value={field.placeholder || ''}
|
||||
onChange={(e) =>
|
||||
handleUpdateField(selectedField, { placeholder: e.target.value })
|
||||
handleUpdateField(section.id, field.id, { placeholder: e.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Options for dropdown/radio/multiselect */}
|
||||
{['dropdown', 'radio', 'multiselect'].includes(selectedFieldData.type) && (
|
||||
{['dropdown', 'radio', 'multiselect'].includes(field.type) && (
|
||||
<div>
|
||||
<Label>Options</Label>
|
||||
<div className="space-y-2 mt-2">
|
||||
{(selectedFieldData.options || []).map((option, idx) => (
|
||||
{options.map((option, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<Input
|
||||
value={option.label}
|
||||
onChange={(e) => {
|
||||
const newOptions = [...selectedFieldData.options];
|
||||
const newOptions = [...options];
|
||||
newOptions[idx] = {
|
||||
...newOptions[idx],
|
||||
label: e.target.value,
|
||||
value: e.target.value.toLowerCase().replace(/\s+/g, '_'),
|
||||
};
|
||||
handleUpdateField(selectedField, { options: newOptions });
|
||||
handleUpdateField(section.id, field.id, { options: newOptions });
|
||||
}}
|
||||
placeholder="Option label"
|
||||
/>
|
||||
@@ -819,10 +739,8 @@ const AdminRegistrationBuilder = () => {
|
||||
variant="ghost"
|
||||
className="h-8 w-8"
|
||||
onClick={() => {
|
||||
const newOptions = selectedFieldData.options.filter(
|
||||
(_, i) => i !== idx
|
||||
);
|
||||
handleUpdateField(selectedField, { options: newOptions });
|
||||
const newOptions = options.filter((_, i) => i !== idx);
|
||||
handleUpdateField(section.id, field.id, { options: newOptions });
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -834,10 +752,10 @@ const AdminRegistrationBuilder = () => {
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const newOptions = [
|
||||
...(selectedFieldData.options || []),
|
||||
...options,
|
||||
{ value: `option_${Date.now()}`, label: 'New Option' },
|
||||
];
|
||||
handleUpdateField(selectedField, { options: newOptions });
|
||||
handleUpdateField(section.id, field.id, { options: newOptions });
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
@@ -847,37 +765,70 @@ const AdminRegistrationBuilder = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mapping */}
|
||||
<div>
|
||||
<Label htmlFor="field-mapping">Database Mapping</Label>
|
||||
<Label htmlFor={`field-mapping-${field.id}`}>Database Mapping</Label>
|
||||
<Input
|
||||
id="field-mapping"
|
||||
value={selectedFieldData.mapping || ''}
|
||||
id={`field-mapping-${field.id}`}
|
||||
value={field.mapping || ''}
|
||||
onChange={(e) =>
|
||||
handleUpdateField(selectedField, { mapping: e.target.value })
|
||||
handleUpdateField(section.id, field.id, { mapping: e.target.value })
|
||||
}
|
||||
placeholder="Leave empty for custom data"
|
||||
disabled={selectedFieldData.is_fixed}
|
||||
disabled={field.is_fixed}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Maps to User model field. Empty = stored in custom_registration_data
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{selectedFieldData.is_fixed && (
|
||||
{field.is_fixed && (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-yellow-800 text-sm">
|
||||
This is a fixed field and cannot be removed or have its core properties changed.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
Select a field to edit its properties
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{sortedFields.length === 0 && (
|
||||
<div className="text-center py-6 text-muted-foreground">
|
||||
No fields in this section. Click "Add Field" to add one.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
className="w-full mt-4"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedSection(section.id);
|
||||
setSelectedField(null);
|
||||
setAddFieldDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="size-4 mr-2" />
|
||||
Add Field
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{sortedSections.length === 0 && (
|
||||
<div className="text-center py-6 text-muted-foreground">
|
||||
No sections yet. Click "Add Section" to get started.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
{/* Conditional Rules */}
|
||||
<Card className="p-6 mt-6">
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-3">
|
||||
<Card className="p-6">
|
||||
<div className="flex flex-col justify-between items-center mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
@@ -897,6 +848,7 @@ const AdminRegistrationBuilder = () => {
|
||||
</div>
|
||||
|
||||
{/* Add Step Dialog */}
|
||||
|
||||
<Dialog open={addStepDialogOpen} onOpenChange={setAddStepDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user