Phone formatting works, start card moved, registration styling changed

This commit is contained in:
2026-02-01 15:16:12 -06:00
parent 235156a9ee
commit af27190e29
3 changed files with 202 additions and 182 deletions

View File

@@ -47,6 +47,15 @@ const DynamicFormField = ({
const hasError = errors.length > 0; const hasError = errors.length > 0;
const errorMessage = errors[0]; const errorMessage = errors[0];
const formatPhoneNumber = (rawValue) => {
const digits = String(rawValue || '').replace(/\D/g, '').slice(0, 10);
if (digits.length <= 3) return digits;
if (digits.length <= 6) {
return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
}
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
};
// Common input className // Common input className
const inputClassName = `h-14 rounded-xl border-2 ${ const inputClassName = `h-14 rounded-xl border-2 ${
hasError hasError
@@ -59,6 +68,11 @@ const DynamicFormField = ({
const { value: newValue, type: inputType, checked } = e.target; const { value: newValue, type: inputType, checked } = e.target;
if (inputType === 'checkbox') { if (inputType === 'checkbox') {
onChange(id, checked); onChange(id, checked);
return;
}
if (type === 'phone') {
onChange(id, formatPhoneNumber(newValue));
return;
} else { } else {
onChange(id, newValue); onChange(id, newValue);
} }
@@ -111,6 +125,8 @@ const DynamicFormField = ({
value={value || ''} value={value || ''}
onChange={handleInputChange} onChange={handleInputChange}
placeholder={placeholder} placeholder={placeholder}
inputMode={type === 'phone' ? 'numeric' : undefined}
maxLength={type === 'phone' ? 14 : undefined}
className={inputClassName} className={inputClassName}
data-testid={`field-${id}`} data-testid={`field-${id}`}
/> />

View File

@@ -51,6 +51,7 @@ import {
Zap, Zap,
Copy, Copy,
X, X,
Grip
} from 'lucide-react'; } from 'lucide-react';
// Field type icons // Field type icons
@@ -492,7 +493,6 @@ const AdminRegistrationBuilder = () => {
)} )}
{/* Main Builder Layout */} {/* Main Builder Layout */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Left Sidebar - Steps & Sections */} {/* Left Sidebar - Steps & Sections */}
<div className="lg:col-span-3"> <div className="lg:col-span-3">
<Card className="p-4"> <Card className="p-4">
@@ -503,14 +503,13 @@ const AdminRegistrationBuilder = () => {
</Button> </Button>
</div> </div>
<div className="space-y-2"> <div className="flex space-y-2">
{sortedSteps.map((step, index) => ( {sortedSteps.map((step, index) => (
<div <div
key={step.id} key={step.id}
className={`p-3 rounded-lg border cursor-pointer transition-colors ${ className={`p-3 rounded-t-lg border cursor-pointer transition-colors ${selectedStep === step.id
selectedStep === step.id ? ' bg-brand-lavender/10 border-b-4 border-b-brand-dark-lavender'
? 'border-brand-purple bg-brand-lavender/10' : ''
: 'border-gray-200 hover:border-gray-300'
}`} }`}
onClick={() => { onClick={() => {
setSelectedStep(step.id); setSelectedStep(step.id);
@@ -520,11 +519,12 @@ const AdminRegistrationBuilder = () => {
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<GripVertical className="h-4 w-4 text-gray-400" /> <div className="font-medium">Step: </div>
<span className="font-medium text-sm">{step.title}</span> <span className="font-medium text-sm">{step.title}</span>
</div> </div>
{/* Mod Buttons */}
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button {/* <Button
size="icon" size="icon"
variant="ghost" variant="ghost"
className="h-6 w-6" className="h-6 w-6"
@@ -547,17 +547,17 @@ const AdminRegistrationBuilder = () => {
disabled={index === sortedSteps.length - 1} disabled={index === sortedSteps.length - 1}
> >
<ChevronDown className="h-3 w-3" /> <ChevronDown className="h-3 w-3" />
</Button> </Button> */}
<Button <Button
size="icon" size="icon"
variant="ghost" variant="ghost"
className="h-6 w-6 text-red-500 hover:text-red-700" className="h-6 w-6 text-red-500 hover:text-white ml-2"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDeleteStep(step.id); handleDeleteStep(step.id);
}} }}
> >
<Trash2 className="h-3 w-3" /> <Trash2 className="size-3" />
</Button> </Button>
</div> </div>
</div> </div>
@@ -579,8 +579,7 @@ const AdminRegistrationBuilder = () => {
{sortedSections.map((section) => ( {sortedSections.map((section) => (
<div <div
key={section.id} key={section.id}
className={`p-3 rounded-lg border cursor-pointer transition-colors ${ className={`p-3 rounded-lg border cursor-pointer transition-colors ${selectedSection === section.id
selectedSection === section.id
? 'border-brand-purple bg-brand-lavender/10' ? 'border-brand-purple bg-brand-lavender/10'
: 'border-gray-200 hover:border-gray-300' : 'border-gray-200 hover:border-gray-300'
}`} }`}
@@ -610,14 +609,21 @@ const AdminRegistrationBuilder = () => {
)} )}
</Card> </Card>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Center - Form Canvas */} {/* Center - Form Canvas */}
<div className="lg:col-span-6"> <div className="lg:col-span-9">
<Card className="p-6"> <Card className="p-6">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<div className='relative -mx-11 -my-2 flex gap-2 items-center'>
<Grip className="size-10 text-gray-400 py-2 bg-background" />
<h2 className="text-xl font-semibold"> <h2 className="text-xl font-semibold">
{currentStep?.title || 'Select a step'} {currentStep?.title || 'Select a step'}
</h2> </h2>
</div>
{selectedSection && ( {selectedSection && (
<Button size="sm" onClick={() => setAddFieldDialogOpen(true)}> <Button size="sm" onClick={() => setAddFieldDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
@@ -637,8 +643,7 @@ const AdminRegistrationBuilder = () => {
return ( return (
<div <div
key={section.id} key={section.id}
className={`mb-6 p-4 rounded-lg border-2 ${ className={`mb-6 p-4 rounded-lg border-2 ${selectedSection === section.id
selectedSection === section.id
? 'border-brand-purple' ? 'border-brand-purple'
: 'border-dashed border-gray-200' : 'border-dashed border-gray-200'
}`} }`}
@@ -656,8 +661,7 @@ const AdminRegistrationBuilder = () => {
return ( return (
<div <div
key={field.id} key={field.id}
className={`p-3 rounded-lg border cursor-pointer transition-all ${ className={`p-3 rounded-lg border cursor-pointer transition-all ${selectedField === field.id
selectedField === field.id
? 'border-brand-purple bg-brand-lavender/5 ring-2 ring-brand-purple/20' ? 'border-brand-purple bg-brand-lavender/5 ring-2 ring-brand-purple/20'
: 'border-gray-200 hover:border-gray-300' : 'border-gray-200 hover:border-gray-300'
} ${field.is_fixed ? 'bg-gray-50' : ''}`} } ${field.is_fixed ? 'bg-gray-50' : ''}`}
@@ -719,23 +723,7 @@ const AdminRegistrationBuilder = () => {
)} )}
</Card> </Card>
{/* Conditional Rules */}
<Card className="p-6 mt-6">
<div className="flex justify-between items-center mb-4">
<div className="flex items-center gap-2">
<Zap className="h-5 w-5 text-yellow-500" />
<h2 className="text-lg font-semibold">Conditional Rules</h2>
</div>
<Button size="sm" variant="outline" onClick={() => setConditionalDialogOpen(true)}>
<Settings className="h-4 w-4 mr-2" />
Manage Rules
</Button>
</div>
<div className="text-sm text-muted-foreground">
{schema?.conditional_rules?.length || 0} conditional rule(s) configured
</div>
</Card>
</div> </div>
{/* Right Sidebar - Field Properties */} {/* Right Sidebar - Field Properties */}
@@ -912,6 +900,23 @@ const AdminRegistrationBuilder = () => {
</div> </div>
)} )}
</Card> </Card>
{/* Conditional Rules */}
<Card className="p-6 mt-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" />
<h2 className="text-lg font-semibold">Conditional Rules</h2>
</div>
<Button size="sm" variant="outline" onClick={() => setConditionalDialogOpen(true)}>
<Settings className="h-4 w-4 mr-2" />
Manage Rules
</Button>
</div>
<div className="text-sm text-muted-foreground">
{schema?.conditional_rules?.length || 0} conditional rule(s) configured
</div>
</Card>
</div> </div>
</div> </div>
@@ -1120,8 +1125,7 @@ const AdminRegistrationBuilder = () => {
.map((field) => ( .map((field) => (
<div <div
key={field.id} key={field.id}
className={`${ className={`${field.width === 'full'
field.width === 'full'
? 'col-span-2' ? 'col-span-2'
: field.width === 'third' : field.width === 'third'
? 'col-span-1' ? 'col-span-1'

View File

@@ -354,13 +354,7 @@ const AdminValidations = () => {
Quick Overview Quick Overview
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4"> <div className="grid grid-cols-2 md:grid-cols-5 gap-4">
<StatCard
title="Total Pending"
value={loading ? '-' : pendingUsers.length}
icon={Users}
iconBgClass="text-brand-purple"
dataTestId="stat-total-users"
/>
<StatCard <StatCard
title="Awaiting Email" title="Awaiting Email"
@@ -394,7 +388,13 @@ const AdminValidations = () => {
dataTestId="stat-rejected" dataTestId="stat-rejected"
/> />
<StatCard
title="Total Pending"
value={loading ? '-' : pendingUsers.length}
icon={Users}
iconBgClass="text-brand-purple"
dataTestId="stat-total-users"
/>
</div> </div>
</Card> </Card>