feat: add script and data to update state and city location options for CO, NE, ND, and SD

This commit is contained in:
pradeepkumar
2026-04-12 00:01:21 +05:30
parent 91336e5ead
commit b66b5c963b
3 changed files with 8128 additions and 167 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,17 @@
import { PrismaClient, UserRole, UserStatus, AuthProvider, FieldType } from '@prisma/client'; import {
PrismaClient,
UserRole,
UserStatus,
AuthProvider,
FieldType,
} from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg'; import { Pool } from 'pg';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import {
US_STATES_CO_NE_ND_SD,
US_CITIES_CO_NE_ND_SD,
} from './data/us-cities-co-ne-nd-sd';
async function main() { async function main() {
const connectionString = process.env.DATABASE_URL; const connectionString = process.env.DATABASE_URL;
@@ -92,8 +102,10 @@ async function main() {
name: 'Description', name: 'Description',
slug: 'description', slug: 'description',
fieldType: FieldType.TEXTAREA, fieldType: FieldType.TEXTAREA,
description: 'Tell us about yourself and your professional background', description:
placeholder: 'Enter a brief description about yourself, your experience, and what makes you unique...', 'Tell us about yourself and your professional background',
placeholder:
'Enter a brief description about yourself, your experience, and what makes you unique...',
sortOrder: 3, sortOrder: 3,
isActive: true, isActive: true,
isRequired: false, isRequired: false,
@@ -162,58 +174,7 @@ async function main() {
isActive: true, isActive: true,
isRequired: false, isRequired: false,
isSearchableOnly: false, isSearchableOnly: false,
options: [ options: US_STATES_CO_NE_ND_SD,
{ 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', name: 'City',
@@ -232,48 +193,7 @@ async function main() {
allowCustomOptions: true, allowCustomOptions: true,
maxSelections: 5, maxSelections: 5,
}, },
options: [ options: US_CITIES_CO_NE_ND_SD,
{ 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' },
],
}, },
], ],
}, },
@@ -291,7 +211,8 @@ async function main() {
name: 'Documents', name: 'Documents',
slug: 'documents', slug: 'documents',
fieldType: FieldType.FILE, fieldType: FieldType.FILE,
description: 'Upload documentation or verification for licensing credentials', description:
'Upload documentation or verification for licensing credentials',
placeholder: 'Drag and drop files here or click to upload', placeholder: 'Drag and drop files here or click to upload',
sortOrder: 1, sortOrder: 1,
isActive: true, isActive: true,
@@ -478,7 +399,8 @@ async function main() {
name: 'Age', name: 'Age',
slug: 'age', slug: 'age',
fieldType: FieldType.RANGE, fieldType: FieldType.RANGE,
description: 'Your age (used for search matching only, not displayed on profile)', description:
'Your age (used for search matching only, not displayed on profile)',
placeholder: 'Select age range', placeholder: 'Select age range',
sortOrder: 7, sortOrder: 7,
isActive: true, isActive: true,
@@ -494,7 +416,8 @@ async function main() {
name: 'Gender', name: 'Gender',
slug: 'gender', slug: 'gender',
fieldType: FieldType.TEXT, fieldType: FieldType.TEXT,
description: 'Your gender (used for search matching only, not displayed on profile)', description:
'Your gender (used for search matching only, not displayed on profile)',
placeholder: 'Enter gender', placeholder: 'Enter gender',
sortOrder: 8, sortOrder: 8,
isActive: true, isActive: true,
@@ -505,17 +428,27 @@ async function main() {
name: 'Race', name: 'Race',
slug: 'race', slug: 'race',
fieldType: FieldType.CHECKBOX_GROUP, fieldType: FieldType.CHECKBOX_GROUP,
description: 'Your race (used for search matching only, not displayed on profile)', description:
'Your race (used for search matching only, not displayed on profile)',
placeholder: 'Select race', placeholder: 'Select race',
sortOrder: 9, sortOrder: 9,
isActive: true, isActive: true,
isRequired: false, isRequired: false,
isSearchableOnly: true, isSearchableOnly: true,
options: [ options: [
{ label: 'American Indian or Alaska Native', value: 'american_indian_alaska_native' }, {
label: 'American Indian or Alaska Native',
value: 'american_indian_alaska_native',
},
{ label: 'Asian', value: 'asian' }, { 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: '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: 'White', value: 'white' },
{ label: 'Two or More Races', value: 'two_or_more' }, { label: 'Two or More Races', value: 'two_or_more' },
{ label: 'Prefer not to say', value: 'prefer_not_to_say' }, { label: 'Prefer not to say', value: 'prefer_not_to_say' },
@@ -525,7 +458,8 @@ async function main() {
name: 'Ethnicity', name: 'Ethnicity',
slug: 'ethnicity', slug: 'ethnicity',
fieldType: FieldType.CHECKBOX_GROUP, fieldType: FieldType.CHECKBOX_GROUP,
description: 'Your ethnicity (used for search matching only, not displayed on profile)', description:
'Your ethnicity (used for search matching only, not displayed on profile)',
placeholder: 'Select ethnicity', placeholder: 'Select ethnicity',
sortOrder: 10, sortOrder: 10,
isActive: true, isActive: true,
@@ -549,14 +483,26 @@ async function main() {
isSearchableOnly: false, isSearchableOnly: false,
uiConfig: { uiConfig: {
allowCustomOptions: true, allowCustomOptions: true,
suggestions: ['English', 'Spanish', 'Chinese', 'French', 'Vietnamese', 'Korean', 'Tagalog', 'Arabic', 'Russian', 'Portuguese'], suggestions: [
'English',
'Spanish',
'Chinese',
'French',
'Vietnamese',
'Korean',
'Tagalog',
'Arabic',
'Russian',
'Portuguese',
],
}, },
}, },
{ {
name: 'LGBTQ+', name: 'LGBTQ+',
slug: 'lgbtq', slug: 'lgbtq',
fieldType: FieldType.RADIO, fieldType: FieldType.RADIO,
description: 'LGBTQ+ identification (used for search matching only, not displayed on profile)', description:
'LGBTQ+ identification (used for search matching only, not displayed on profile)',
placeholder: 'Select option', placeholder: 'Select option',
sortOrder: 12, sortOrder: 12,
isActive: true, isActive: true,
@@ -660,7 +606,8 @@ async function main() {
section: { section: {
name: 'Experience', name: 'Experience',
slug: 'professional-experience', slug: 'professional-experience',
description: 'Professional experience and track record for real estate agents', description:
'Professional experience and track record for real estate agents',
icon: 'star', icon: 'star',
sortOrder: 4, sortOrder: 4,
isActive: true, isActive: true,
@@ -688,7 +635,8 @@ async function main() {
name: 'Years Operated in Area', name: 'Years Operated in Area',
slug: 'years_in_area', slug: 'years_in_area',
fieldType: FieldType.RADIO, fieldType: FieldType.RADIO,
description: 'How many years have you operated in your service area?', description:
'How many years have you operated in your service area?',
placeholder: 'Select years', placeholder: 'Select years',
sortOrder: 2, sortOrder: 2,
isActive: true, isActive: true,
@@ -801,7 +749,10 @@ async function main() {
{ label: 'Empty Lots', value: 'empty_lots' }, { label: 'Empty Lots', value: 'empty_lots' },
{ label: 'Barndaminium', value: 'barndaminium' }, { label: 'Barndaminium', value: 'barndaminium' },
{ label: 'Unique Properties', value: 'unique' }, { label: 'Unique Properties', value: 'unique' },
{ label: 'Apartment / Multifamily', value: 'apartment_multifamily' }, {
label: 'Apartment / Multifamily',
value: 'apartment_multifamily',
},
{ label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' }, { label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' },
{ label: 'New Build', value: 'new_build' }, { label: 'New Build', value: 'new_build' },
], ],
@@ -821,7 +772,10 @@ async function main() {
{ label: 'FHA', value: 'fha' }, { label: 'FHA', value: 'fha' },
{ label: 'VA', value: 'va' }, { label: 'VA', value: 'va' },
{ label: 'USDA', value: 'usda' }, { label: 'USDA', value: 'usda' },
{ label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' }, {
label: 'Downpayment Assistance Programs',
value: 'downpayment_assistance',
},
], ],
}, },
{ {
@@ -937,7 +891,8 @@ async function main() {
name: 'Years Operated in Area', name: 'Years Operated in Area',
slug: 'years_in_area', slug: 'years_in_area',
fieldType: FieldType.RADIO, fieldType: FieldType.RADIO,
description: 'How many years have you operated in your service area?', description:
'How many years have you operated in your service area?',
placeholder: 'Select years', placeholder: 'Select years',
sortOrder: 2, sortOrder: 2,
isActive: true, isActive: true,
@@ -993,9 +948,7 @@ async function main() {
isActive: true, isActive: true,
isRequired: false, isRequired: false,
isSearchableOnly: false, isSearchableOnly: false,
options: [ options: [{ label: 'First Time Buyer', value: 'first_time_buyer' }],
{ label: 'First Time Buyer', value: 'first_time_buyer' },
],
}, },
{ {
name: 'Property Type', name: 'Property Type',
@@ -1021,11 +974,17 @@ async function main() {
{ label: 'Commercial', value: 'commercial' }, { label: 'Commercial', value: 'commercial' },
{ label: 'Investment / Income Producing', value: 'investment' }, { label: 'Investment / Income Producing', value: 'investment' },
{ label: 'Undeveloped Land', value: 'undeveloped_land' }, { label: 'Undeveloped Land', value: 'undeveloped_land' },
{ label: 'Construction / Construction-to-Perm', value: 'construction' }, {
label: 'Construction / Construction-to-Perm',
value: 'construction',
},
{ label: 'Empty Lots', value: 'empty_lots' }, { label: 'Empty Lots', value: 'empty_lots' },
{ label: 'Barndaminium', value: 'barndaminium' }, { label: 'Barndaminium', value: 'barndaminium' },
{ label: 'Unique Properties', value: 'unique' }, { label: 'Unique Properties', value: 'unique' },
{ label: 'Apartment / Multifamily', value: 'apartment_multifamily' }, {
label: 'Apartment / Multifamily',
value: 'apartment_multifamily',
},
{ label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' }, { label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' },
{ label: 'New Build', value: 'new_build' }, { label: 'New Build', value: 'new_build' },
], ],
@@ -1045,16 +1004,28 @@ async function main() {
{ label: 'FHA', value: 'fha' }, { label: 'FHA', value: 'fha' },
{ label: 'VA', value: 'va' }, { label: 'VA', value: 'va' },
{ label: 'USDA', value: 'usda' }, { label: 'USDA', value: 'usda' },
{ label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' }, {
label: 'Downpayment Assistance Programs',
value: 'downpayment_assistance',
},
{ label: 'Rehab Loan - FHA 203k', value: 'rehab_fha_203k' }, { label: 'Rehab Loan - FHA 203k', value: 'rehab_fha_203k' },
{ label: 'Rehab Loan - Conventional Home Style', value: 'rehab_conventional' }, {
label: 'Rehab Loan - Conventional Home Style',
value: 'rehab_conventional',
},
{ label: 'Rehab Loan - VA Reno', value: 'rehab_va_reno' }, { label: 'Rehab Loan - VA Reno', value: 'rehab_va_reno' },
{ label: 'Debt Service Cover Ratio (DSCR)', value: 'dscr' }, { label: 'Debt Service Cover Ratio (DSCR)', value: 'dscr' },
{ label: 'HELOC', value: 'heloc' }, { label: 'HELOC', value: 'heloc' },
{ label: 'Standalone Second', value: 'standalone_second' }, { label: 'Standalone Second', value: 'standalone_second' },
{ label: 'Refinancing - FHA Streamline', value: 'refi_fha_streamline' }, {
label: 'Refinancing - FHA Streamline',
value: 'refi_fha_streamline',
},
{ label: 'Refinancing - VA IRRL', value: 'refi_va_irrl' }, { label: 'Refinancing - VA IRRL', value: 'refi_va_irrl' },
{ label: 'Refinancing - Conventional', value: 'refi_conventional' }, {
label: 'Refinancing - Conventional',
value: 'refi_conventional',
},
{ label: 'Jumbo / Non-conforming', value: 'jumbo' }, { label: 'Jumbo / Non-conforming', value: 'jumbo' },
{ label: 'Bank Statement', value: 'bank_statement' }, { label: 'Bank Statement', value: 'bank_statement' },
{ label: 'ITIN', value: 'itin' }, { label: 'ITIN', value: 'itin' },
@@ -1131,12 +1102,17 @@ async function main() {
console.log(` ⚠️ Section "${sectionInfo.name}" already exists`); console.log(` ⚠️ Section "${sectionInfo.name}" already exists`);
// Update section properties if they've changed (like isRepeatable) // Update section properties if they've changed (like isRepeatable)
if (sectionInfo.isRepeatable !== undefined && existingSection.isRepeatable !== sectionInfo.isRepeatable) { if (
sectionInfo.isRepeatable !== undefined &&
existingSection.isRepeatable !== sectionInfo.isRepeatable
) {
await prisma.profileSection.update({ await prisma.profileSection.update({
where: { id: existingSection.id }, where: { id: existingSection.id },
data: { isRepeatable: sectionInfo.isRepeatable }, data: { isRepeatable: sectionInfo.isRepeatable },
}); });
console.log(` ✅ Updated isRepeatable to ${sectionInfo.isRepeatable}`); console.log(
` ✅ Updated isRepeatable to ${sectionInfo.isRepeatable}`,
);
} }
// Still check and create missing fields, or update existing fields with options // Still check and create missing fields, or update existing fields with options
@@ -1157,11 +1133,25 @@ async function main() {
}); });
console.log(` ✅ Created field: ${fieldData.name}`); console.log(` ✅ Created field: ${fieldData.name}`);
} else { } else {
// Update existing field with options if options are defined in seed but missing in DB // Update existing field with options if options are defined in seed but missing in DB.
// Exception: `state` and `city` options come from an Excel source of truth and should
// always overwrite DB values so re-seeding syncs fresh data.
const updates: Record<string, unknown> = {}; const updates: Record<string, unknown> = {};
const fd = fieldData as any; // Cast to any to access optional properties const fd = fieldData as Partial<{
options: unknown;
validation: unknown;
uiConfig: unknown;
}>;
const alwaysOverwriteOptions =
fieldData.slug === 'state' || fieldData.slug === 'city';
if (fd.options && (!existingField.options || (Array.isArray(existingField.options) && existingField.options.length === 0))) { if (
fd.options &&
(alwaysOverwriteOptions ||
!existingField.options ||
(Array.isArray(existingField.options) &&
existingField.options.length === 0))
) {
updates.options = fd.options; updates.options = fd.options;
} }
if (fd.validation && !existingField.validation) { if (fd.validation && !existingField.validation) {
@@ -1176,13 +1166,15 @@ async function main() {
where: { id: existingField.id }, where: { id: existingField.id },
data: updates, data: updates,
}); });
console.log(` ✅ Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`); console.log(
` ✅ Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`,
);
} }
} }
} }
} else { } else {
// Create section with fields // Create section with fields
const section = await prisma.profileSection.create({ await prisma.profileSection.create({
data: { data: {
...sectionInfo, ...sectionInfo,
fields: { fields: {
@@ -1190,7 +1182,9 @@ async function main() {
}, },
}, },
}); });
console.log(` ✅ Created section: ${sectionInfo.name} with ${fields.length} fields`); console.log(
` ✅ Created section: ${sectionInfo.name} with ${fields.length} fields`,
);
} }
} }
@@ -1221,7 +1215,9 @@ async function main() {
}); });
if (!agentType) { if (!agentType) {
console.log(` ⚠️ Agent type "${agentTypeName}" not found, skipping section`); console.log(
` ⚠️ Agent type "${agentTypeName}" not found, skipping section`,
);
continue; continue;
} }
@@ -1231,7 +1227,9 @@ async function main() {
}); });
if (section) { if (section) {
console.log(` ⚠️ Section "${sectionInfo.name}" for ${agentTypeName} already exists`); console.log(
` ⚠️ Section "${sectionInfo.name}" for ${agentTypeName} already exists`,
);
// Still check and create missing fields, or update existing fields with options // Still check and create missing fields, or update existing fields with options
for (const fieldData of fields) { for (const fieldData of fields) {
@@ -1253,9 +1251,18 @@ async function main() {
} else { } else {
// Update existing field with options if options are defined in seed but missing in DB // Update existing field with options if options are defined in seed but missing in DB
const updates: Record<string, unknown> = {}; const updates: Record<string, unknown> = {};
const fd = fieldData as any; // Cast to any to access optional properties const fd = fieldData as Partial<{
options: unknown;
validation: unknown;
uiConfig: unknown;
}>;
if (fd.options && (!existingField.options || (Array.isArray(existingField.options) && existingField.options.length === 0))) { if (
fd.options &&
(!existingField.options ||
(Array.isArray(existingField.options) &&
existingField.options.length === 0))
) {
updates.options = fd.options; updates.options = fd.options;
} }
if (fd.validation && !existingField.validation) { if (fd.validation && !existingField.validation) {
@@ -1270,7 +1277,9 @@ async function main() {
where: { id: existingField.id }, where: { id: existingField.id },
data: updates, data: updates,
}); });
console.log(` ✅ Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`); console.log(
` ✅ Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`,
);
} }
} }
} }
@@ -1284,7 +1293,9 @@ async function main() {
}, },
}, },
}); });
console.log(` ✅ Created section: ${sectionInfo.name} for ${agentTypeName} with ${fields.length} fields`); console.log(
` ✅ Created section: ${sectionInfo.name} for ${agentTypeName} with ${fields.length} fields`,
);
} }
// Create AgentTypeSection assignment if not exists // Create AgentTypeSection assignment if not exists
@@ -1363,10 +1374,12 @@ async function main() {
pageSlug: 'landing', pageSlug: 'landing',
sectionKey: 'hero', sectionKey: 'hero',
content: { content: {
headline: '"Discover verified, top-rated real estate professionals to guide your buying, selling, or investing journey."', headline:
'"Discover verified, top-rated real estate professionals to guide your buying, selling, or investing journey."',
description: 'Discover verified, top-rated real estate professionals', description: 'Discover verified, top-rated real estate professionals',
ctaButtonText: 'See All', ctaButtonText: 'See All',
helperText: 'Connect with trusted local agents to explore homes, compare options, and make smarter decisions.', helperText:
'Connect with trusted local agents to explore homes, compare options, and make smarter decisions.',
}, },
}, },
{ {
@@ -1374,27 +1387,32 @@ async function main() {
sectionKey: 'features', sectionKey: 'features',
content: { content: {
title: 'Find Trusted Real Estate Professionals On Demand', title: 'Find Trusted Real Estate Professionals On Demand',
subtitle: 'Quickly connect with the right agents exactly when you need them.', subtitle:
'Quickly connect with the right agents exactly when you need them.',
features: [ features: [
{ {
iconPath: '/assets/icons/hourglass-icon.svg', iconPath: '/assets/icons/hourglass-icon.svg',
title: 'Hire Quickly', title: 'Hire Quickly',
description: 'Connect with experienced local real estate agents who match your needs and preferences. Our simple process helps you get started quickly and move forward without delays or unnecessary complexity.', description:
'Connect with experienced local real estate agents who match your needs and preferences. Our simple process helps you get started quickly and move forward without delays or unnecessary complexity.',
}, },
{ {
iconPath: '/assets/icons/verified-badge-icon.svg', iconPath: '/assets/icons/verified-badge-icon.svg',
title: 'Verified Agents', title: 'Verified Agents',
description: 'All agents are carefully identity-verified and background-checked to ensure trust and safety. You can confidently work with professionals who meet quality and reliability standards.', description:
'All agents are carefully identity-verified and background-checked to ensure trust and safety. You can confidently work with professionals who meet quality and reliability standards.',
}, },
{ {
iconPath: '/assets/icons/star-orange-icon.svg', iconPath: '/assets/icons/star-orange-icon.svg',
title: 'Top Rated', title: 'Top Rated',
description: 'Work with highly rated agents known for strong expertise and positive client feedback. Their consistent performance helps you make informed and confident property decisions.', description:
'Work with highly rated agents known for strong expertise and positive client feedback. Their consistent performance helps you make informed and confident property decisions.',
}, },
{ {
iconPath: '/assets/icons/trusted-people-icon.svg', iconPath: '/assets/icons/trusted-people-icon.svg',
title: 'Trusted by Thousands', title: 'Trusted by Thousands',
description: 'Thousands of buyers, sellers, and renters trust our platform to find reliable agents. Our professionals help people navigate property journeys with confidence and ease.', description:
'Thousands of buyers, sellers, and renters trust our platform to find reliable agents. Our professionals help people navigate property journeys with confidence and ease.',
}, },
], ],
featuredAgents: [ featuredAgents: [
@@ -1438,7 +1456,14 @@ async function main() {
subtitle: '(Residential Property Expert)', subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA', location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.', experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'], expertise: [
'Residential',
'Rental',
'Commercial',
'Inspection',
'Land',
'Rental',
],
imageUrl: '/assets/images/professional-1.jpg', imageUrl: '/assets/images/professional-1.jpg',
}, },
{ {
@@ -1446,7 +1471,14 @@ async function main() {
subtitle: '(Residential Property Expert)', subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA', location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.', experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'], expertise: [
'Residential',
'Rental',
'Commercial',
'Inspection',
'Land',
'Rental',
],
imageUrl: '/assets/images/professional-2.jpg', imageUrl: '/assets/images/professional-2.jpg',
}, },
{ {
@@ -1454,7 +1486,14 @@ async function main() {
subtitle: '(Residential Property Expert)', subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA', location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.', experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'], expertise: [
'Residential',
'Rental',
'Commercial',
'Inspection',
'Land',
'Rental',
],
imageUrl: '/assets/images/professional-3.jpg', imageUrl: '/assets/images/professional-3.jpg',
}, },
], ],
@@ -1464,7 +1503,13 @@ async function main() {
subtitle: '(Mortgage Specialist)', subtitle: '(Mortgage Specialist)',
location: 'Los Angeles, CA', location: 'Los Angeles, CA',
experience: '10+ years in mortgage lending.', experience: '10+ years in mortgage lending.',
expertise: ['FHA Loans', 'Conventional', 'VA Loans', 'Refinancing', 'Jumbo'], expertise: [
'FHA Loans',
'Conventional',
'VA Loans',
'Refinancing',
'Jumbo',
],
imageUrl: '/assets/images/professional-1.jpg', imageUrl: '/assets/images/professional-1.jpg',
}, },
{ {
@@ -1472,7 +1517,13 @@ async function main() {
subtitle: '(Senior Loan Officer)', subtitle: '(Senior Loan Officer)',
location: 'Seattle, WA', location: 'Seattle, WA',
experience: '7+ years in lending.', experience: '7+ years in lending.',
expertise: ['Jumbo Loans', 'Refinancing', 'Investment', 'First-time', 'USDA'], expertise: [
'Jumbo Loans',
'Refinancing',
'Investment',
'First-time',
'USDA',
],
imageUrl: '/assets/images/professional-2.jpg', imageUrl: '/assets/images/professional-2.jpg',
}, },
{ {
@@ -1480,7 +1531,13 @@ async function main() {
subtitle: '(Home Loan Advisor)', subtitle: '(Home Loan Advisor)',
location: 'Austin, TX', location: 'Austin, TX',
experience: '5+ years in mortgage industry.', experience: '5+ years in mortgage industry.',
expertise: ['First-time Buyers', 'FHA', 'USDA Loans', 'VA Loans', 'Conventional'], expertise: [
'First-time Buyers',
'FHA',
'USDA Loans',
'VA Loans',
'Conventional',
],
imageUrl: '/assets/images/professional-3.jpg', imageUrl: '/assets/images/professional-3.jpg',
}, },
], ],
@@ -1491,8 +1548,10 @@ async function main() {
sectionKey: 'testimonials', sectionKey: 'testimonials',
content: { content: {
title: 'Consumer Protection is Our Foundation', title: 'Consumer Protection is Our Foundation',
subtitle: "Buying or selling your home is one of the largest decisions you will make in your life. Don't let yourself be chased by salesmen. Find the service provider that matches your unique needs.", subtitle:
ratingInfo: 'Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.', "Buying or selling your home is one of the largest decisions you will make in your life. Don't let yourself be chased by salesmen. Find the service provider that matches your unique needs.",
ratingInfo:
'Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.',
stats: [ stats: [
{ {
iconPath: '/assets/icons/cities-icon.svg', iconPath: '/assets/icons/cities-icon.svg',
@@ -1509,21 +1568,24 @@ async function main() {
{ {
rating: 5, rating: 5,
title: 'No Longer Blood in the Water', title: 'No Longer Blood in the Water',
content: 'Using other sites to find a realtor ended with my phone ringing constantly. My number was sold off to multiple lists and it was so overwhelming. RE-Quest gave me confidence that my information was protected and I decided who I was in contact with.', content:
'Using other sites to find a realtor ended with my phone ringing constantly. My number was sold off to multiple lists and it was so overwhelming. RE-Quest gave me confidence that my information was protected and I decided who I was in contact with.',
author: 'Alice T.', author: 'Alice T.',
role: '', role: '',
}, },
{ {
rating: 5, rating: 5,
title: 'Finding Someone Who Worked with Me', title: 'Finding Someone Who Worked with Me',
content: 'RE-Quest let me narrow down my search terms, so I knew before I called that they had experience with the loan program I needed. I loved the in-app chat feature to talk to my short list before committing to a phone call.', content:
'RE-Quest let me narrow down my search terms, so I knew before I called that they had experience with the loan program I needed. I loved the in-app chat feature to talk to my short list before committing to a phone call.',
author: 'Kevin R.', author: 'Kevin R.',
role: '', role: '',
}, },
{ {
rating: 5, rating: 5,
title: '', title: '',
content: 'The search terms on RE-Quest allowed me to show off my expertise as an agent. Now when leads reach out to me, they already have an idea of what kind of agent I am. Instant connection!', content:
'The search terms on RE-Quest allowed me to show off my expertise as an agent. Now when leads reach out to me, they already have an idea of what kind of agent I am. Instant connection!',
author: 'Riley S.', author: 'Riley S.',
role: '', role: '',
}, },
@@ -1536,14 +1598,16 @@ async function main() {
sectionKey: 'contactDetails', sectionKey: 'contactDetails',
content: { content: {
title: 'Get In Touch', title: 'Get In Touch',
description: 'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.', description:
'Have a question about a property or need assistance? Fill out the form below and our team will get back to you shortly.',
email: '123support@gmail.com', email: '123support@gmail.com',
phone: '1234567890', phone: '1234567890',
phoneHours: 'Mon-Fri 9am-6pm', phoneHours: 'Mon-Fri 9am-6pm',
officeAddress: '123 Market Street', officeAddress: '123 Market Street',
officeCity: 'New York CA 234737', officeCity: 'New York CA 234737',
mapUrl: '', mapUrl: '',
directionsUrl: 'https://maps.google.com/?q=123+Market+Street+New+York+CA+234737', directionsUrl:
'https://maps.google.com/?q=123+Market+Street+New+York+CA+234737',
}, },
}, },
{ {
@@ -1551,7 +1615,8 @@ async function main() {
sectionKey: 'cta', sectionKey: 'cta',
content: { content: {
title: 'Ready to find an agent?', title: 'Ready to find an agent?',
description: 'Discover trusted agents and start your property journey with confidence.', description:
'Discover trusted agents and start your property journey with confidence.',
buttonText: 'Start Your Search', buttonText: 'Start Your Search',
buttonLink: '/user/profiles', buttonLink: '/user/profiles',
}, },
@@ -1581,7 +1646,9 @@ async function main() {
isPublished: true, isPublished: true,
}, },
}); });
console.log(` 🔄 Updated CMS content: ${cms.pageSlug}/${cms.sectionKey}`); console.log(
` 🔄 Updated CMS content: ${cms.pageSlug}/${cms.sectionKey}`,
);
} else { } else {
await prisma.cmsContent.create({ await prisma.cmsContent.create({
data: { data: {
@@ -1591,7 +1658,9 @@ async function main() {
isPublished: true, isPublished: true,
}, },
}); });
console.log(` ✅ Created CMS content: ${cms.pageSlug}/${cms.sectionKey}`); console.log(
` ✅ Created CMS content: ${cms.pageSlug}/${cms.sectionKey}`,
);
} }
} }
@@ -1630,7 +1699,9 @@ async function main() {
console.log(` ⚠️ Plan "${plan.name}" already exists`); console.log(` ⚠️ Plan "${plan.name}" already exists`);
} else { } else {
await prisma.subscriptionPlan.create({ data: plan }); await prisma.subscriptionPlan.create({ data: plan });
console.log(` ✅ Created plan: ${plan.name} ($${(plan.amount / 100).toFixed(2)}/${plan.interval})`); console.log(
` ✅ Created plan: ${plan.name} ($${(plan.amount / 100).toFixed(2)}/${plan.interval})`,
);
} }
} }
@@ -1640,11 +1711,15 @@ async function main() {
// Summary // Summary
// ============================================= // =============================================
const userCount = await prisma.user.count(); const userCount = await prisma.user.count();
const adminCount = await prisma.user.count({ where: { role: UserRole.ADMIN } }); const adminCount = await prisma.user.count({
where: { role: UserRole.ADMIN },
});
const agentTypeCount = await prisma.agentType.count(); const agentTypeCount = await prisma.agentType.count();
const sectionCount = await prisma.profileSection.count(); const sectionCount = await prisma.profileSection.count();
const fieldCount = await prisma.profileField.count(); const fieldCount = await prisma.profileField.count();
const systemSectionCount = await prisma.profileSection.count({ where: { isSystem: true } }); const systemSectionCount = await prisma.profileSection.count({
where: { isSystem: true },
});
const agentTypeSectionCount = await prisma.agentTypeSection.count(); const agentTypeSectionCount = await prisma.agentTypeSection.count();
const cmsContentCount = await prisma.cmsContent.count(); const cmsContentCount = await prisma.cmsContent.count();
const planCount = await prisma.subscriptionPlan.count(); const planCount = await prisma.subscriptionPlan.count();
@@ -1653,7 +1728,9 @@ async function main() {
console.log(` Total Users: ${userCount}`); console.log(` Total Users: ${userCount}`);
console.log(` Admins: ${adminCount}`); console.log(` Admins: ${adminCount}`);
console.log(` Agent Types: ${agentTypeCount}`); console.log(` Agent Types: ${agentTypeCount}`);
console.log(` Profile Sections: ${sectionCount} (${systemSectionCount} system)`); console.log(
` Profile Sections: ${sectionCount} (${systemSectionCount} system)`,
);
console.log(` Profile Fields: ${fieldCount}`); console.log(` Profile Fields: ${fieldCount}`);
console.log(` Section Assignments: ${agentTypeSectionCount}`); console.log(` Section Assignments: ${agentTypeSectionCount}`);
console.log(` CMS Content: ${cmsContentCount}`); console.log(` CMS Content: ${cmsContentCount}`);
@@ -1664,8 +1741,7 @@ async function main() {
await pool.end(); await pool.end();
} }
main() main().catch((e) => {
.catch((e) => { console.error('❌ Seeding failed:', e);
console.error('❌ Seeding failed:', e); process.exit(1);
process.exit(1); });
});

View File

@@ -0,0 +1,54 @@
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';
import {
US_STATES_CO_NE_ND_SD,
US_CITIES_CO_NE_ND_SD,
} from './data/us-cities-co-ne-nd-sd';
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
console.error('❌ DATABASE_URL env var is required');
process.exit(1);
}
const pool = new Pool({ connectionString });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
async function updateFieldOptions(slug: 'state' | 'city', options: unknown[]) {
const fields = await prisma.profileField.findMany({
where: { slug },
select: { id: true, name: true, section: { select: { slug: true } } },
});
if (fields.length === 0) {
console.log(` ⚠️ No ProfileField rows found with slug="${slug}"`);
return;
}
for (const field of fields) {
await prisma.profileField.update({
where: { id: field.id },
data: { options: options as object },
});
console.log(
` ✅ Updated ${field.section.slug}/${slug} (${options.length} options)`,
);
}
}
async function main() {
console.log('🌱 Updating state + city ProfileField options from Excel source...');
await updateFieldOptions('state', US_STATES_CO_NE_ND_SD);
await updateFieldOptions('city', US_CITIES_CO_NE_ND_SD);
console.log('✅ Done.');
}
main()
.catch((e) => {
console.error('❌ Failed to update location options:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});