2026-01-24 12:49:25 +05:30
|
|
|
import { PrismaClient, UserRole, UserStatus, AuthProvider, FieldType } from '@prisma/client';
|
2025-12-19 01:15:19 +05:30
|
|
|
import { PrismaPg } from '@prisma/adapter-pg';
|
|
|
|
|
import { Pool } from 'pg';
|
|
|
|
|
import * as argon2 from 'argon2';
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
const connectionString = process.env.DATABASE_URL;
|
|
|
|
|
const pool = new Pool({ connectionString });
|
|
|
|
|
const adapter = new PrismaPg(pool);
|
|
|
|
|
|
|
|
|
|
const prisma = new PrismaClient({ adapter });
|
|
|
|
|
|
|
|
|
|
console.log('🌱 Starting database seeding...\n');
|
|
|
|
|
|
2026-01-20 12:16:01 +05:30
|
|
|
// =============================================
|
|
|
|
|
// Seed Agent Types
|
|
|
|
|
// =============================================
|
|
|
|
|
console.log('📋 Seeding Agent Types...');
|
|
|
|
|
|
|
|
|
|
const agentTypes = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Professional',
|
|
|
|
|
description: 'Licensed real estate professionals',
|
|
|
|
|
icon: 'briefcase',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Lender',
|
|
|
|
|
description: 'Mortgage and loan specialists',
|
|
|
|
|
icon: 'bank',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const agentType of agentTypes) {
|
|
|
|
|
const existing = await prisma.agentType.findUnique({
|
|
|
|
|
where: { name: agentType.name },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
console.log(` ⚠️ Agent type "${agentType.name}" already exists`);
|
|
|
|
|
} else {
|
|
|
|
|
await prisma.agentType.create({
|
|
|
|
|
data: agentType,
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Created agent type: ${agentType.name}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
2026-01-24 12:49:25 +05:30
|
|
|
// =============================================
|
|
|
|
|
// Seed Default Profile Sections & Fields
|
|
|
|
|
// =============================================
|
|
|
|
|
console.log('📋 Seeding Default Profile Sections...');
|
|
|
|
|
|
|
|
|
|
// Define default system sections with their fields
|
|
|
|
|
const defaultSections = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Basic Information',
|
|
|
|
|
slug: 'basic-information',
|
|
|
|
|
description: 'Basic profile information',
|
|
|
|
|
icon: 'user',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'First Name',
|
|
|
|
|
slug: 'first_name',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your first name',
|
|
|
|
|
placeholder: 'Enter your first name',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: true,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Last Name',
|
|
|
|
|
slug: 'last_name',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your last name',
|
|
|
|
|
placeholder: 'Enter your last name',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: true,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
2026-01-24 21:39:14 +05:30
|
|
|
{
|
|
|
|
|
name: 'Description',
|
|
|
|
|
slug: 'description',
|
|
|
|
|
fieldType: FieldType.TEXTAREA,
|
|
|
|
|
description: 'Tell us about yourself and your professional background',
|
|
|
|
|
placeholder: 'Enter a brief description about yourself, your experience, and what makes you unique...',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
validation: {
|
|
|
|
|
maxLength: 2000,
|
|
|
|
|
},
|
|
|
|
|
uiConfig: {
|
|
|
|
|
rows: 6,
|
|
|
|
|
showCharCount: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-24 12:49:25 +05:30
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Contact Information',
|
|
|
|
|
slug: 'contact-information',
|
|
|
|
|
description: 'Contact details',
|
|
|
|
|
icon: 'phone',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Email Address',
|
|
|
|
|
slug: 'email_address',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your contact email address',
|
|
|
|
|
placeholder: 'Enter your email address',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: true,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Phone Number',
|
|
|
|
|
slug: 'phone_number',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your contact phone number',
|
|
|
|
|
placeholder: 'Enter your phone number',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-01-24 15:32:54 +05:30
|
|
|
{
|
|
|
|
|
name: 'Location',
|
|
|
|
|
slug: 'location',
|
|
|
|
|
description: 'Service areas and location information',
|
|
|
|
|
icon: 'map-pin',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'State',
|
|
|
|
|
slug: 'state',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'States where you provide services',
|
|
|
|
|
placeholder: 'Select states',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Alabama', value: 'AL' },
|
|
|
|
|
{ label: 'Alaska', value: 'AK' },
|
|
|
|
|
{ label: 'Arizona', value: 'AZ' },
|
|
|
|
|
{ label: 'Arkansas', value: 'AR' },
|
|
|
|
|
{ label: 'California', value: 'CA' },
|
|
|
|
|
{ label: 'Colorado', value: 'CO' },
|
|
|
|
|
{ label: 'Connecticut', value: 'CT' },
|
|
|
|
|
{ label: 'Delaware', value: 'DE' },
|
|
|
|
|
{ label: 'Florida', value: 'FL' },
|
|
|
|
|
{ label: 'Georgia', value: 'GA' },
|
|
|
|
|
{ label: 'Hawaii', value: 'HI' },
|
|
|
|
|
{ label: 'Idaho', value: 'ID' },
|
|
|
|
|
{ label: 'Illinois', value: 'IL' },
|
|
|
|
|
{ label: 'Indiana', value: 'IN' },
|
|
|
|
|
{ label: 'Iowa', value: 'IA' },
|
|
|
|
|
{ label: 'Kansas', value: 'KS' },
|
|
|
|
|
{ label: 'Kentucky', value: 'KY' },
|
|
|
|
|
{ label: 'Louisiana', value: 'LA' },
|
|
|
|
|
{ label: 'Maine', value: 'ME' },
|
|
|
|
|
{ label: 'Maryland', value: 'MD' },
|
|
|
|
|
{ label: 'Massachusetts', value: 'MA' },
|
|
|
|
|
{ label: 'Michigan', value: 'MI' },
|
|
|
|
|
{ label: 'Minnesota', value: 'MN' },
|
|
|
|
|
{ label: 'Mississippi', value: 'MS' },
|
|
|
|
|
{ label: 'Missouri', value: 'MO' },
|
|
|
|
|
{ label: 'Montana', value: 'MT' },
|
|
|
|
|
{ label: 'Nebraska', value: 'NE' },
|
|
|
|
|
{ label: 'Nevada', value: 'NV' },
|
|
|
|
|
{ label: 'New Hampshire', value: 'NH' },
|
|
|
|
|
{ label: 'New Jersey', value: 'NJ' },
|
|
|
|
|
{ label: 'New Mexico', value: 'NM' },
|
|
|
|
|
{ label: 'New York', value: 'NY' },
|
|
|
|
|
{ label: 'North Carolina', value: 'NC' },
|
|
|
|
|
{ label: 'North Dakota', value: 'ND' },
|
|
|
|
|
{ label: 'Ohio', value: 'OH' },
|
|
|
|
|
{ label: 'Oklahoma', value: 'OK' },
|
|
|
|
|
{ label: 'Oregon', value: 'OR' },
|
|
|
|
|
{ label: 'Pennsylvania', value: 'PA' },
|
|
|
|
|
{ label: 'Rhode Island', value: 'RI' },
|
|
|
|
|
{ label: 'South Carolina', value: 'SC' },
|
|
|
|
|
{ label: 'South Dakota', value: 'SD' },
|
|
|
|
|
{ label: 'Tennessee', value: 'TN' },
|
|
|
|
|
{ label: 'Texas', value: 'TX' },
|
|
|
|
|
{ label: 'Utah', value: 'UT' },
|
|
|
|
|
{ label: 'Vermont', value: 'VT' },
|
|
|
|
|
{ label: 'Virginia', value: 'VA' },
|
|
|
|
|
{ label: 'Washington', value: 'WA' },
|
|
|
|
|
{ label: 'West Virginia', value: 'WV' },
|
|
|
|
|
{ label: 'Wisconsin', value: 'WI' },
|
|
|
|
|
{ label: 'Wyoming', value: 'WY' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'City',
|
|
|
|
|
slug: 'city',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Cities where you provide services (max 5)',
|
|
|
|
|
placeholder: 'Enter cities',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
validation: {
|
|
|
|
|
maxSelections: 5,
|
|
|
|
|
},
|
|
|
|
|
uiConfig: {
|
|
|
|
|
allowCustomOptions: true,
|
|
|
|
|
maxSelections: 5,
|
|
|
|
|
},
|
2026-01-24 22:19:26 +05:30
|
|
|
options: [
|
|
|
|
|
{ label: 'New York', value: 'new_york' },
|
|
|
|
|
{ label: 'Los Angeles', value: 'los_angeles' },
|
|
|
|
|
{ label: 'Chicago', value: 'chicago' },
|
|
|
|
|
{ label: 'Houston', value: 'houston' },
|
|
|
|
|
{ label: 'Phoenix', value: 'phoenix' },
|
|
|
|
|
{ label: 'Philadelphia', value: 'philadelphia' },
|
|
|
|
|
{ label: 'San Antonio', value: 'san_antonio' },
|
|
|
|
|
{ label: 'San Diego', value: 'san_diego' },
|
|
|
|
|
{ label: 'Dallas', value: 'dallas' },
|
|
|
|
|
{ label: 'San Jose', value: 'san_jose' },
|
|
|
|
|
{ label: 'Austin', value: 'austin' },
|
|
|
|
|
{ label: 'Jacksonville', value: 'jacksonville' },
|
|
|
|
|
{ label: 'Fort Worth', value: 'fort_worth' },
|
|
|
|
|
{ label: 'Columbus', value: 'columbus' },
|
|
|
|
|
{ label: 'Charlotte', value: 'charlotte' },
|
|
|
|
|
{ label: 'San Francisco', value: 'san_francisco' },
|
|
|
|
|
{ label: 'Indianapolis', value: 'indianapolis' },
|
|
|
|
|
{ label: 'Seattle', value: 'seattle' },
|
|
|
|
|
{ label: 'Denver', value: 'denver' },
|
|
|
|
|
{ label: 'Washington DC', value: 'washington_dc' },
|
|
|
|
|
{ label: 'Boston', value: 'boston' },
|
|
|
|
|
{ label: 'Nashville', value: 'nashville' },
|
|
|
|
|
{ label: 'Detroit', value: 'detroit' },
|
|
|
|
|
{ label: 'Portland', value: 'portland' },
|
|
|
|
|
{ label: 'Las Vegas', value: 'las_vegas' },
|
|
|
|
|
{ label: 'Memphis', value: 'memphis' },
|
|
|
|
|
{ label: 'Louisville', value: 'louisville' },
|
|
|
|
|
{ label: 'Baltimore', value: 'baltimore' },
|
|
|
|
|
{ label: 'Milwaukee', value: 'milwaukee' },
|
|
|
|
|
{ label: 'Albuquerque', value: 'albuquerque' },
|
|
|
|
|
{ label: 'Tucson', value: 'tucson' },
|
|
|
|
|
{ label: 'Fresno', value: 'fresno' },
|
|
|
|
|
{ label: 'Sacramento', value: 'sacramento' },
|
|
|
|
|
{ label: 'Atlanta', value: 'atlanta' },
|
|
|
|
|
{ label: 'Miami', value: 'miami' },
|
|
|
|
|
{ label: 'Oakland', value: 'oakland' },
|
|
|
|
|
{ label: 'Minneapolis', value: 'minneapolis' },
|
|
|
|
|
{ label: 'Cleveland', value: 'cleveland' },
|
|
|
|
|
{ label: 'Raleigh', value: 'raleigh' },
|
|
|
|
|
{ label: 'Tampa', value: 'tampa' },
|
|
|
|
|
],
|
2026-01-24 15:32:54 +05:30
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Upload Documents',
|
|
|
|
|
slug: 'upload-documents',
|
|
|
|
|
description: 'Upload licensing credentials and verification documents',
|
|
|
|
|
icon: 'document',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Documents',
|
|
|
|
|
slug: 'documents',
|
|
|
|
|
fieldType: FieldType.FILE,
|
|
|
|
|
description: 'Upload documentation or verification for licensing credentials',
|
|
|
|
|
placeholder: 'Drag and drop files here or click to upload',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
validation: {
|
|
|
|
|
allowedTypes: ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png'],
|
|
|
|
|
maxFileSize: 10485760, // 10MB
|
|
|
|
|
maxFiles: 10,
|
|
|
|
|
},
|
|
|
|
|
uiConfig: {
|
|
|
|
|
acceptedFormats: '.pdf, .doc, .docx, .jpg, .png',
|
|
|
|
|
showPreview: true,
|
|
|
|
|
multiple: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Certification',
|
|
|
|
|
slug: 'certification',
|
|
|
|
|
description: 'Professional certifications and credentials',
|
|
|
|
|
icon: 'star',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
2026-01-24 22:31:12 +05:30
|
|
|
isRepeatable: true, // User can add multiple certifications
|
2026-01-24 15:32:54 +05:30
|
|
|
fields: [
|
2026-01-24 22:31:12 +05:30
|
|
|
{
|
|
|
|
|
name: 'Certification Entries',
|
|
|
|
|
slug: 'certification_entries',
|
|
|
|
|
fieldType: FieldType.REPEATER,
|
|
|
|
|
description: 'All certification entries stored as JSON array',
|
|
|
|
|
sortOrder: 0,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
2026-01-24 15:32:54 +05:30
|
|
|
{
|
|
|
|
|
name: 'Certification Name',
|
|
|
|
|
slug: 'certification_name',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Name of your professional certification',
|
|
|
|
|
placeholder: 'Enter certification name',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Issuing Organization',
|
|
|
|
|
slug: 'issuing_organization',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Organization that issued the certification',
|
|
|
|
|
placeholder: 'Enter issuing organization',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Issue Date',
|
|
|
|
|
slug: 'issue_date',
|
|
|
|
|
fieldType: FieldType.DATE,
|
|
|
|
|
description: 'Date the certification was issued',
|
|
|
|
|
placeholder: 'Select issue date',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Expiration Date',
|
|
|
|
|
slug: 'expiration_date',
|
|
|
|
|
fieldType: FieldType.DATE,
|
|
|
|
|
description: 'Date the certification expires (if applicable)',
|
|
|
|
|
placeholder: 'Select expiration date',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Certification Number',
|
|
|
|
|
slug: 'certification_number',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your certification or license number',
|
|
|
|
|
placeholder: 'Enter certification number',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Demographic',
|
|
|
|
|
slug: 'demographic',
|
|
|
|
|
description: 'Demographic and contact information',
|
|
|
|
|
icon: 'user',
|
|
|
|
|
sortOrder: 6,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Name',
|
|
|
|
|
slug: 'demographic_name',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your full name',
|
|
|
|
|
placeholder: 'Enter your name',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Office Name',
|
|
|
|
|
slug: 'office_name',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Name of your office or brokerage',
|
|
|
|
|
placeholder: 'Enter office name',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Registered / Licensed',
|
|
|
|
|
slug: 'registered_licensed',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'Are you registered or licensed?',
|
|
|
|
|
placeholder: 'Select status',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Registered', value: 'registered' },
|
|
|
|
|
{ label: 'Licensed', value: 'licensed' },
|
|
|
|
|
{ label: 'Both', value: 'both' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Office #',
|
|
|
|
|
slug: 'office_number',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your office phone number',
|
|
|
|
|
placeholder: 'Enter office number',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Cell #',
|
|
|
|
|
slug: 'cell_number',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your cell phone number',
|
|
|
|
|
placeholder: 'Enter cell number',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Availability',
|
|
|
|
|
slug: 'availability',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'When are you available?',
|
|
|
|
|
placeholder: 'Select availability',
|
|
|
|
|
sortOrder: 6,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'M-F 9-5', value: 'mf_9_5' },
|
|
|
|
|
{ label: 'Evenings', value: 'evenings' },
|
|
|
|
|
{ label: 'Weekends', value: 'weekends' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Age',
|
|
|
|
|
slug: 'age',
|
|
|
|
|
fieldType: FieldType.RANGE,
|
|
|
|
|
description: 'Your age (used for search matching only, not displayed on profile)',
|
|
|
|
|
placeholder: 'Select age range',
|
|
|
|
|
sortOrder: 7,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: true,
|
|
|
|
|
rangeConfig: {
|
|
|
|
|
min: 18,
|
|
|
|
|
max: 100,
|
|
|
|
|
step: 1,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Gender',
|
|
|
|
|
slug: 'gender',
|
|
|
|
|
fieldType: FieldType.TEXT,
|
|
|
|
|
description: 'Your gender (used for search matching only, not displayed on profile)',
|
|
|
|
|
placeholder: 'Enter gender',
|
|
|
|
|
sortOrder: 8,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Race',
|
|
|
|
|
slug: 'race',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Your race (used for search matching only, not displayed on profile)',
|
|
|
|
|
placeholder: 'Select race',
|
|
|
|
|
sortOrder: 9,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: true,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'American Indian or Alaska Native', value: 'american_indian_alaska_native' },
|
|
|
|
|
{ label: 'Asian', value: 'asian' },
|
|
|
|
|
{ label: 'Black or African American', value: 'black_african_american' },
|
|
|
|
|
{ label: 'Native Hawaiian or Other Pacific Islander', value: 'native_hawaiian_pacific_islander' },
|
|
|
|
|
{ label: 'White', value: 'white' },
|
|
|
|
|
{ label: 'Two or More Races', value: 'two_or_more' },
|
|
|
|
|
{ label: 'Prefer not to say', value: 'prefer_not_to_say' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Ethnicity',
|
|
|
|
|
slug: 'ethnicity',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Your ethnicity (used for search matching only, not displayed on profile)',
|
|
|
|
|
placeholder: 'Select ethnicity',
|
|
|
|
|
sortOrder: 10,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: true,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Hispanic or Latino', value: 'hispanic_latino' },
|
|
|
|
|
{ label: 'Not Hispanic or Latino', value: 'not_hispanic_latino' },
|
|
|
|
|
{ label: 'Prefer not to say', value: 'prefer_not_to_say' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Language',
|
|
|
|
|
slug: 'language',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Languages you speak',
|
|
|
|
|
placeholder: 'Add languages',
|
|
|
|
|
sortOrder: 11,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
uiConfig: {
|
|
|
|
|
allowCustomOptions: true,
|
|
|
|
|
suggestions: ['English', 'Spanish', 'Chinese', 'French', 'Vietnamese', 'Korean', 'Tagalog', 'Arabic', 'Russian', 'Portuguese'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'LGBTQ+',
|
|
|
|
|
slug: 'lgbtq',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'LGBTQ+ identification (used for search matching only, not displayed on profile)',
|
|
|
|
|
placeholder: 'Select option',
|
|
|
|
|
sortOrder: 12,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: true,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Yes', value: 'yes' },
|
|
|
|
|
{ label: 'No', value: 'no' },
|
|
|
|
|
{ label: 'Prefer not to say', value: 'prefer_not_to_say' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Highest Education Level',
|
|
|
|
|
slug: 'highest_education_level',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'Your highest level of education',
|
|
|
|
|
placeholder: 'Select education level',
|
|
|
|
|
sortOrder: 13,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'High School/GED', value: 'high_school_ged' },
|
|
|
|
|
{ label: 'Some College', value: 'some_college' },
|
|
|
|
|
{ label: 'Associates', value: 'associates' },
|
|
|
|
|
{ label: 'Bachelors', value: 'bachelors' },
|
|
|
|
|
{ label: 'Masters', value: 'masters' },
|
|
|
|
|
{ label: 'Doctoral', value: 'doctoral' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Veteran Status',
|
|
|
|
|
slug: 'veteran_status',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Your military/veteran status',
|
|
|
|
|
placeholder: 'Select veteran status',
|
|
|
|
|
sortOrder: 14,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Active', value: 'active' },
|
|
|
|
|
{ label: 'Reserve', value: 'reserve' },
|
|
|
|
|
{ label: 'Guard', value: 'guard' },
|
|
|
|
|
{ label: 'Veteran', value: 'veteran' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Military Branch',
|
|
|
|
|
slug: 'military_branch',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Military branch(es) you served in',
|
|
|
|
|
placeholder: 'Select military branch',
|
|
|
|
|
sortOrder: 15,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Army', value: 'army' },
|
|
|
|
|
{ label: 'Navy', value: 'navy' },
|
|
|
|
|
{ label: 'Marines', value: 'marines' },
|
|
|
|
|
{ label: 'Air Force', value: 'air_force' },
|
|
|
|
|
{ label: 'Space Force', value: 'space_force' },
|
|
|
|
|
{ label: 'Coast Guard', value: 'coast_guard' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-01-24 20:36:21 +05:30
|
|
|
{
|
|
|
|
|
name: 'Expertise',
|
|
|
|
|
slug: 'expertise',
|
|
|
|
|
description: 'Areas of expertise and specialization',
|
|
|
|
|
icon: 'star',
|
|
|
|
|
sortOrder: 7,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: true,
|
|
|
|
|
isSystem: true,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'About Me',
|
|
|
|
|
slug: 'about_me_expertise',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Add your areas of expertise',
|
|
|
|
|
placeholder: 'Add Expertise',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
uiConfig: {
|
|
|
|
|
allowCustomOptions: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-01-24 15:32:54 +05:30
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Agent-type-specific sections (NOT global)
|
|
|
|
|
const agentTypeSections = [
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Professional',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Experience',
|
|
|
|
|
slug: 'professional-experience',
|
|
|
|
|
description: 'Professional experience and track record for real estate agents',
|
|
|
|
|
icon: 'star',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Years in Business',
|
|
|
|
|
slug: 'years_in_business',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'How many years have you been in real estate?',
|
|
|
|
|
placeholder: 'Select years',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Less than 2 years', value: '<2' },
|
|
|
|
|
{ label: '2+ years', value: '2+' },
|
|
|
|
|
{ label: '5+ years', value: '5+' },
|
|
|
|
|
{ label: '10+ years', value: '10+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Years Operated in Area',
|
|
|
|
|
slug: 'years_in_area',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'How many years have you operated in your service area?',
|
|
|
|
|
placeholder: 'Select years',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Less than 2 years', value: '<2' },
|
|
|
|
|
{ label: '2+ years', value: '2+' },
|
|
|
|
|
{ label: '5+ years', value: '5+' },
|
|
|
|
|
{ label: '10+ years', value: '10+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Number of Contracts Completed',
|
|
|
|
|
slug: 'contracts_completed',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'Number of contracts completed in the previous year',
|
|
|
|
|
placeholder: 'Select range',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Less than 3', value: '<3' },
|
|
|
|
|
{ label: '3-10', value: '3-10' },
|
|
|
|
|
{ label: '10-20', value: '10-20' },
|
|
|
|
|
{ label: '20-50', value: '20-50' },
|
|
|
|
|
{ label: '50+', value: '50+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Professional',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Specialization',
|
|
|
|
|
slug: 'professional-specialization',
|
|
|
|
|
description: 'Areas of specialization for real estate agents',
|
|
|
|
|
icon: 'briefcase',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Client Specialization',
|
|
|
|
|
slug: 'client_specialization',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Types of clients you specialize in',
|
|
|
|
|
placeholder: 'Select client types',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Buyers', value: 'buyers' },
|
|
|
|
|
{ label: 'Sellers', value: 'sellers' },
|
|
|
|
|
{ label: 'First Time Buyer', value: 'first_time_buyer' },
|
|
|
|
|
{ label: 'First Time Seller', value: 'first_time_seller' },
|
|
|
|
|
{ label: 'Divorce', value: 'divorce' },
|
|
|
|
|
{ label: 'Estate', value: 'estate' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Price Point',
|
|
|
|
|
slug: 'price_point',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Price ranges you typically work with',
|
|
|
|
|
placeholder: 'Select price ranges',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Under $100K', value: '<100K' },
|
|
|
|
|
{ label: '$100K - $300K', value: '100-300K' },
|
|
|
|
|
{ label: '$300K - $500K', value: '300-500K' },
|
|
|
|
|
{ label: '$500K - $700K', value: '500-700K' },
|
|
|
|
|
{ label: '$700K - $1M', value: '700K-1M' },
|
|
|
|
|
{ label: '$1M+', value: '1M+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Property Type',
|
|
|
|
|
slug: 'property_type',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Types of properties you specialize in',
|
|
|
|
|
placeholder: 'Select property types',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'SFR (Single Family Residence)', value: 'sfr' },
|
|
|
|
|
{ label: 'Townhome', value: 'townhome' },
|
|
|
|
|
{ label: 'Condo', value: 'condo' },
|
|
|
|
|
{ label: 'Manufactured', value: 'manufactured' },
|
|
|
|
|
{ label: 'Modular', value: 'modular' },
|
|
|
|
|
{ label: 'Urban', value: 'urban' },
|
|
|
|
|
{ label: 'Rural', value: 'rural' },
|
|
|
|
|
{ label: 'Luxury', value: 'luxury' },
|
|
|
|
|
{ label: 'Retirement', value: 'retirement' },
|
|
|
|
|
{ label: 'Agriculture', value: 'agriculture' },
|
|
|
|
|
{ label: 'Commercial', value: 'commercial' },
|
|
|
|
|
{ label: 'Investment / Income Producing', value: 'investment' },
|
|
|
|
|
{ label: 'Historical', value: 'historical' },
|
|
|
|
|
{ label: 'Mountain Property', value: 'mountain' },
|
|
|
|
|
{ label: 'Undeveloped Land', value: 'undeveloped_land' },
|
|
|
|
|
{ label: 'Empty Lots', value: 'empty_lots' },
|
|
|
|
|
{ label: 'Barndaminium', value: 'barndaminium' },
|
|
|
|
|
{ label: 'Unique Properties', value: 'unique' },
|
|
|
|
|
{ label: 'Apartment / Multifamily', value: 'apartment_multifamily' },
|
|
|
|
|
{ label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' },
|
|
|
|
|
{ label: 'New Build', value: 'new_build' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Loan Type',
|
|
|
|
|
slug: 'loan_type',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Loan types you are familiar with',
|
|
|
|
|
placeholder: 'Select loan types',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Conventional', value: 'conventional' },
|
|
|
|
|
{ label: 'FHA', value: 'fha' },
|
|
|
|
|
{ label: 'VA', value: 'va' },
|
|
|
|
|
{ label: 'USDA', value: 'usda' },
|
|
|
|
|
{ label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Special Interests and Hobbies',
|
|
|
|
|
slug: 'special_interests',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Special property features you have expertise in',
|
|
|
|
|
placeholder: 'Select special interests',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Workshop / Trade / Craftspace', value: 'workshop' },
|
|
|
|
|
{ label: 'Boating', value: 'boating' },
|
|
|
|
|
{ label: 'Automotive', value: 'automotive' },
|
|
|
|
|
{ label: 'Aviation', value: 'aviation' },
|
|
|
|
|
{ label: 'Home Theater', value: 'home_theater' },
|
|
|
|
|
{ label: 'Horses', value: 'horses' },
|
|
|
|
|
{ label: 'RV', value: 'rv' },
|
|
|
|
|
{ label: 'Smart Home Features', value: 'smart_home' },
|
|
|
|
|
{ label: 'Gardening / Landscape', value: 'gardening' },
|
|
|
|
|
{ label: 'Hobby Farm', value: 'hobby_farm' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-24 21:36:39 +05:30
|
|
|
// Professional - Areas in Expertise & Years Section
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Professional',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Areas in Expertise & Years',
|
|
|
|
|
slug: 'professional-expertise-years',
|
|
|
|
|
description: 'Your areas of expertise with years of experience',
|
|
|
|
|
icon: 'award',
|
|
|
|
|
sortOrder: 6,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Expertise Areas',
|
|
|
|
|
slug: 'expertise_areas',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Add your areas of expertise with years of experience',
|
|
|
|
|
placeholder: '',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
// Professional - Licensing & Areas Section
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Professional',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Licensing & Areas',
|
|
|
|
|
slug: 'professional-licensing-areas',
|
|
|
|
|
description: 'Areas where you are licensed to practice',
|
|
|
|
|
icon: 'file-text',
|
|
|
|
|
sortOrder: 7,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Licensed Areas',
|
|
|
|
|
slug: 'licensed_areas',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Enter areas where you are licensed to practice',
|
|
|
|
|
placeholder: 'Type area name, press Enter to add',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-24 15:32:54 +05:30
|
|
|
{
|
|
|
|
|
agentTypeName: 'Lender',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Experience',
|
|
|
|
|
slug: 'lender-experience',
|
|
|
|
|
description: 'Professional experience and track record for lenders',
|
|
|
|
|
icon: 'star',
|
|
|
|
|
sortOrder: 4,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Years in Business',
|
|
|
|
|
slug: 'years_in_business',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'How many years have you been in lending?',
|
|
|
|
|
placeholder: 'Select years',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Less than 2 years', value: '<2' },
|
|
|
|
|
{ label: '2+ years', value: '2+' },
|
|
|
|
|
{ label: '5+ years', value: '5+' },
|
|
|
|
|
{ label: '10+ years', value: '10+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Years Operated in Area',
|
|
|
|
|
slug: 'years_in_area',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'How many years have you operated in your service area?',
|
|
|
|
|
placeholder: 'Select years',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Less than 2 years', value: '<2' },
|
|
|
|
|
{ label: '2+ years', value: '2+' },
|
|
|
|
|
{ label: '5+ years', value: '5+' },
|
|
|
|
|
{ label: '10+ years', value: '10+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Annual Loans',
|
|
|
|
|
slug: 'annual_loans',
|
|
|
|
|
fieldType: FieldType.RADIO,
|
|
|
|
|
description: 'Total value of loans processed annually',
|
|
|
|
|
placeholder: 'Select range',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: '0-1M', value: '0-1M' },
|
|
|
|
|
{ label: '1-5M', value: '1-5M' },
|
|
|
|
|
{ label: '5-10M', value: '5-10M' },
|
|
|
|
|
{ label: '10-30M', value: '10-30M' },
|
|
|
|
|
{ label: '30M+', value: '30M+' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Lender',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Specialization',
|
|
|
|
|
slug: 'lender-specialization',
|
|
|
|
|
description: 'Areas of specialization for lenders',
|
|
|
|
|
icon: 'briefcase',
|
|
|
|
|
sortOrder: 5,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Client Specialization',
|
|
|
|
|
slug: 'client_specialization',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Types of clients you specialize in',
|
|
|
|
|
placeholder: 'Select client types',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'First Time Buyer', value: 'first_time_buyer' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Property Type',
|
|
|
|
|
slug: 'property_type',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Types of properties you can finance',
|
|
|
|
|
placeholder: 'Select property types',
|
|
|
|
|
sortOrder: 2,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'SFR (Single Family Residence)', value: 'sfr' },
|
|
|
|
|
{ label: 'Townhome', value: 'townhome' },
|
|
|
|
|
{ label: 'Condo', value: 'condo' },
|
|
|
|
|
{ label: 'Manufactured', value: 'manufactured' },
|
|
|
|
|
{ label: 'Modular', value: 'modular' },
|
|
|
|
|
{ label: 'Urban', value: 'urban' },
|
|
|
|
|
{ label: 'Rural', value: 'rural' },
|
|
|
|
|
{ label: 'Luxury', value: 'luxury' },
|
|
|
|
|
{ label: 'Retirement', value: 'retirement' },
|
|
|
|
|
{ label: 'Agriculture', value: 'agriculture' },
|
|
|
|
|
{ label: 'Commercial', value: 'commercial' },
|
|
|
|
|
{ label: 'Investment / Income Producing', value: 'investment' },
|
|
|
|
|
{ label: 'Undeveloped Land', value: 'undeveloped_land' },
|
|
|
|
|
{ label: 'Construction / Construction-to-Perm', value: 'construction' },
|
|
|
|
|
{ label: 'Empty Lots', value: 'empty_lots' },
|
|
|
|
|
{ label: 'Barndaminium', value: 'barndaminium' },
|
|
|
|
|
{ label: 'Unique Properties', value: 'unique' },
|
|
|
|
|
{ label: 'Apartment / Multifamily', value: 'apartment_multifamily' },
|
|
|
|
|
{ label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' },
|
|
|
|
|
{ label: 'New Build', value: 'new_build' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Loan Type',
|
|
|
|
|
slug: 'loan_type',
|
2026-01-24 21:36:39 +05:30
|
|
|
fieldType: FieldType.CHECKBOX_GROUP,
|
2026-01-24 15:32:54 +05:30
|
|
|
description: 'Types of loans you offer',
|
|
|
|
|
placeholder: 'Select loan types',
|
|
|
|
|
sortOrder: 3,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
options: [
|
|
|
|
|
{ label: 'Conventional', value: 'conventional' },
|
|
|
|
|
{ label: 'FHA', value: 'fha' },
|
|
|
|
|
{ label: 'VA', value: 'va' },
|
|
|
|
|
{ label: 'USDA', value: 'usda' },
|
|
|
|
|
{ label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' },
|
|
|
|
|
{ label: 'Rehab Loan - FHA 203k', value: 'rehab_fha_203k' },
|
|
|
|
|
{ label: 'Rehab Loan - Conventional Home Style', value: 'rehab_conventional' },
|
|
|
|
|
{ label: 'Rehab Loan - VA Reno', value: 'rehab_va_reno' },
|
|
|
|
|
{ label: 'Debt Service Cover Ratio (DSCR)', value: 'dscr' },
|
|
|
|
|
{ label: 'HELOC', value: 'heloc' },
|
|
|
|
|
{ label: 'Standalone Second', value: 'standalone_second' },
|
|
|
|
|
{ label: 'Refinancing - FHA Streamline', value: 'refi_fha_streamline' },
|
|
|
|
|
{ label: 'Refinancing - VA IRRL', value: 'refi_va_irrl' },
|
|
|
|
|
{ label: 'Refinancing - Conventional', value: 'refi_conventional' },
|
|
|
|
|
{ label: 'Jumbo / Non-conforming', value: 'jumbo' },
|
|
|
|
|
{ label: 'Bank Statement', value: 'bank_statement' },
|
|
|
|
|
{ label: 'ITIN', value: 'itin' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-24 21:36:39 +05:30
|
|
|
// Lender - Areas in Expertise & Years Section
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Lender',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Areas in Expertise & Years',
|
|
|
|
|
slug: 'lender-expertise-years',
|
|
|
|
|
description: 'Your areas of expertise with years of experience',
|
|
|
|
|
icon: 'award',
|
|
|
|
|
sortOrder: 6,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Expertise Areas',
|
|
|
|
|
slug: 'expertise_areas',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Add your areas of expertise with years of experience',
|
|
|
|
|
placeholder: '',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
// Lender - Licensing & Areas Section
|
|
|
|
|
{
|
|
|
|
|
agentTypeName: 'Lender',
|
|
|
|
|
section: {
|
|
|
|
|
name: 'Licensing & Areas',
|
|
|
|
|
slug: 'lender-licensing-areas',
|
|
|
|
|
description: 'Areas where you are licensed to practice',
|
|
|
|
|
icon: 'file-text',
|
|
|
|
|
sortOrder: 7,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isGlobal: false,
|
|
|
|
|
isSystem: false,
|
|
|
|
|
fields: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Licensed Areas',
|
|
|
|
|
slug: 'licensed_areas',
|
|
|
|
|
fieldType: FieldType.TAG_INPUT,
|
|
|
|
|
description: 'Enter areas where you are licensed to practice',
|
|
|
|
|
placeholder: 'Type area name, press Enter to add',
|
|
|
|
|
sortOrder: 1,
|
|
|
|
|
isActive: true,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
isSearchableOnly: false,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-24 12:49:25 +05:30
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const sectionData of defaultSections) {
|
|
|
|
|
const { fields, ...sectionInfo } = sectionData;
|
|
|
|
|
|
|
|
|
|
// Check if section already exists
|
|
|
|
|
const existingSection = await prisma.profileSection.findUnique({
|
|
|
|
|
where: { slug: sectionInfo.slug },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingSection) {
|
|
|
|
|
console.log(` ⚠️ Section "${sectionInfo.name}" already exists`);
|
|
|
|
|
|
2026-01-24 22:31:12 +05:30
|
|
|
// Update section properties if they've changed (like isRepeatable)
|
|
|
|
|
if (sectionInfo.isRepeatable !== undefined && existingSection.isRepeatable !== sectionInfo.isRepeatable) {
|
|
|
|
|
await prisma.profileSection.update({
|
|
|
|
|
where: { id: existingSection.id },
|
|
|
|
|
data: { isRepeatable: sectionInfo.isRepeatable },
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Updated isRepeatable to ${sectionInfo.isRepeatable}`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 12:49:25 +05:30
|
|
|
// Still check and create missing fields
|
|
|
|
|
for (const fieldData of fields) {
|
|
|
|
|
const existingField = await prisma.profileField.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
sectionId: existingSection.id,
|
|
|
|
|
slug: fieldData.slug,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!existingField) {
|
|
|
|
|
await prisma.profileField.create({
|
|
|
|
|
data: {
|
|
|
|
|
...fieldData,
|
|
|
|
|
sectionId: existingSection.id,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Created field: ${fieldData.name}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Create section with fields
|
|
|
|
|
const section = await prisma.profileSection.create({
|
|
|
|
|
data: {
|
|
|
|
|
...sectionInfo,
|
|
|
|
|
fields: {
|
|
|
|
|
create: fields,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Created section: ${sectionInfo.name} with ${fields.length} fields`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
2026-01-24 15:32:54 +05:30
|
|
|
// =============================================
|
|
|
|
|
// Seed Agent-Type-Specific Sections
|
|
|
|
|
// =============================================
|
|
|
|
|
console.log('📋 Seeding Agent-Type-Specific Sections...');
|
|
|
|
|
|
|
|
|
|
// First, delete old global Experience section if exists
|
|
|
|
|
const oldExperienceSection = await prisma.profileSection.findUnique({
|
|
|
|
|
where: { slug: 'experience' },
|
|
|
|
|
});
|
|
|
|
|
if (oldExperienceSection) {
|
|
|
|
|
await prisma.profileSection.delete({
|
|
|
|
|
where: { slug: 'experience' },
|
|
|
|
|
});
|
|
|
|
|
console.log(' 🗑️ Deleted old global Experience section');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const { agentTypeName, section: sectionData } of agentTypeSections) {
|
|
|
|
|
const { fields, ...sectionInfo } = sectionData;
|
|
|
|
|
|
|
|
|
|
// Find the agent type
|
|
|
|
|
const agentType = await prisma.agentType.findUnique({
|
|
|
|
|
where: { name: agentTypeName },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!agentType) {
|
|
|
|
|
console.log(` ⚠️ Agent type "${agentTypeName}" not found, skipping section`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if section already exists
|
|
|
|
|
let section = await prisma.profileSection.findUnique({
|
|
|
|
|
where: { slug: sectionInfo.slug },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (section) {
|
|
|
|
|
console.log(` ⚠️ Section "${sectionInfo.name}" for ${agentTypeName} already exists`);
|
|
|
|
|
|
|
|
|
|
// Still check and create missing fields
|
|
|
|
|
for (const fieldData of fields) {
|
|
|
|
|
const existingField = await prisma.profileField.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
sectionId: section.id,
|
|
|
|
|
slug: fieldData.slug,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!existingField) {
|
|
|
|
|
await prisma.profileField.create({
|
|
|
|
|
data: {
|
|
|
|
|
...fieldData,
|
|
|
|
|
sectionId: section.id,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Created field: ${fieldData.name}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Create section with fields
|
|
|
|
|
section = await prisma.profileSection.create({
|
|
|
|
|
data: {
|
|
|
|
|
...sectionInfo,
|
|
|
|
|
fields: {
|
|
|
|
|
create: fields,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Created section: ${sectionInfo.name} for ${agentTypeName} with ${fields.length} fields`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create AgentTypeSection assignment if not exists
|
|
|
|
|
const existingAssignment = await prisma.agentTypeSection.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
agentTypeId_sectionId: {
|
|
|
|
|
agentTypeId: agentType.id,
|
|
|
|
|
sectionId: section.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!existingAssignment) {
|
|
|
|
|
await prisma.agentTypeSection.create({
|
|
|
|
|
data: {
|
|
|
|
|
agentTypeId: agentType.id,
|
|
|
|
|
sectionId: section.id,
|
|
|
|
|
sortOrder: sectionInfo.sortOrder,
|
|
|
|
|
isRequired: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log(` ✅ Assigned section to ${agentTypeName}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
2026-01-20 12:16:01 +05:30
|
|
|
// =============================================
|
|
|
|
|
// Seed Admin User
|
|
|
|
|
// =============================================
|
|
|
|
|
console.log('👤 Seeding Admin User...');
|
|
|
|
|
|
2025-12-19 01:15:19 +05:30
|
|
|
const adminEmail = process.env.ADMIN_EMAIL || 'admin@realestate.com';
|
|
|
|
|
const adminPassword = process.env.ADMIN_PASSWORD || 'Admin@123456';
|
|
|
|
|
|
|
|
|
|
// Hash password with Argon2 (more secure than bcrypt)
|
|
|
|
|
const hashedPassword = await argon2.hash(adminPassword);
|
|
|
|
|
|
|
|
|
|
// Check if admin already exists
|
|
|
|
|
const existingAdmin = await prisma.user.findUnique({
|
|
|
|
|
where: { email: adminEmail },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingAdmin) {
|
2026-01-20 12:16:01 +05:30
|
|
|
console.log(` ⚠️ Admin user already exists: ${adminEmail}`);
|
2025-12-19 01:15:19 +05:30
|
|
|
} else {
|
|
|
|
|
// Create admin user
|
|
|
|
|
const admin = await prisma.user.create({
|
|
|
|
|
data: {
|
|
|
|
|
email: adminEmail,
|
|
|
|
|
password: hashedPassword,
|
|
|
|
|
role: UserRole.ADMIN,
|
|
|
|
|
status: UserStatus.ACTIVE,
|
|
|
|
|
emailVerified: true,
|
|
|
|
|
emailVerifiedAt: new Date(),
|
|
|
|
|
authProvider: AuthProvider.LOCAL,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-20 12:16:01 +05:30
|
|
|
console.log(' ✅ Admin user created successfully!');
|
2025-12-19 01:15:19 +05:30
|
|
|
console.log(' ───────────────────────────────');
|
|
|
|
|
console.log(` 📧 Email: ${adminEmail}`);
|
|
|
|
|
console.log(` 🔑 Password: ${adminPassword}`);
|
|
|
|
|
console.log(` 👤 Role: ${admin.role}`);
|
|
|
|
|
console.log(` 🆔 ID: ${admin.id}`);
|
|
|
|
|
console.log(' ───────────────────────────────\n');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 12:16:01 +05:30
|
|
|
// =============================================
|
2025-12-19 01:15:19 +05:30
|
|
|
// Summary
|
2026-01-20 12:16:01 +05:30
|
|
|
// =============================================
|
2025-12-19 01:15:19 +05:30
|
|
|
const userCount = await prisma.user.count();
|
|
|
|
|
const adminCount = await prisma.user.count({ where: { role: UserRole.ADMIN } });
|
2026-01-20 12:16:01 +05:30
|
|
|
const agentTypeCount = await prisma.agentType.count();
|
2026-01-24 12:49:25 +05:30
|
|
|
const sectionCount = await prisma.profileSection.count();
|
|
|
|
|
const fieldCount = await prisma.profileField.count();
|
|
|
|
|
const systemSectionCount = await prisma.profileSection.count({ where: { isSystem: true } });
|
2026-01-24 15:32:54 +05:30
|
|
|
const agentTypeSectionCount = await prisma.agentTypeSection.count();
|
2025-12-19 01:15:19 +05:30
|
|
|
|
|
|
|
|
console.log('📊 Database Summary:');
|
2026-01-20 12:16:01 +05:30
|
|
|
console.log(` Total Users: ${userCount}`);
|
|
|
|
|
console.log(` Admins: ${adminCount}`);
|
|
|
|
|
console.log(` Agent Types: ${agentTypeCount}`);
|
2026-01-24 12:49:25 +05:30
|
|
|
console.log(` Profile Sections: ${sectionCount} (${systemSectionCount} system)`);
|
|
|
|
|
console.log(` Profile Fields: ${fieldCount}`);
|
2026-01-24 15:32:54 +05:30
|
|
|
console.log(` Section Assignments: ${agentTypeSectionCount}`);
|
2025-12-19 01:15:19 +05:30
|
|
|
console.log('\n🌱 Seeding completed!\n');
|
|
|
|
|
|
|
|
|
|
await prisma.$disconnect();
|
|
|
|
|
await pool.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main()
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
console.error('❌ Seeding failed:', e);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
});
|