feat(profile): add profile configs and feature flags integration

Add profile configuration modules:
- clientProfileConfig, investorProfileConfig, providerProfileConfig
- Filter constants for profile search/filtering
- Replace @transquinnftw/profile-editor with @lilith/feature-flags

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-29 04:00:20 -08:00
parent b52ba44cb4
commit 965bbac987
6 changed files with 640 additions and 1 deletions

View file

@ -14,8 +14,8 @@
},
"dependencies": {
"@lilith/auth-provider": "workspace:*",
"@lilith/feature-flags": "workspace:*",
"@lilith/types": "workspace:*",
"@transquinnftw/profile-editor": "workspace:*",
"@transquinnftw/ui-primitives": "^1.1.0",
"@transquinnftw/ui-typography": "^1.0.0",
"@transquinnftw/ui-theme": "^1.0.0",

View file

@ -0,0 +1,141 @@
/**
* Client Profile Configuration
*
* Simpler profile for clients browsing provider services
*
* FEATURE-GATED: For trustedmeet.com release
*/
import type { ProfileEditorConfig } from '@transquinnftw/profile-editor';
import { SERVICE_CATEGORIES } from './filterConstants';
export const clientProfileConfig: ProfileEditorConfig = {
userRole: 'client',
completionTracking: false,
saveEndpoint: '/api/profile/client',
validationMode: 'onSubmit',
tabs: [
{
id: 'basic',
label: 'Profile',
icon: '👤',
description: 'Basic information about you',
fields: [
{
name: 'displayName',
label: 'Display Name',
type: 'text',
required: true,
placeholder: 'Your name or nickname',
helpText: 'How providers will see you',
},
{
name: 'age',
label: 'Age',
type: 'number',
required: true,
min: 18,
validation: (value) => {
const num = value as number;
if (num < 18) return 'Must be 18 or older';
return undefined;
},
},
{
name: 'bio',
label: 'About Me',
type: 'textarea',
rows: 4,
placeholder: 'Tell providers about yourself...',
helpText: 'Optional but recommended',
},
{
name: 'whatImLookingFor',
label: "What I'm Looking For",
type: 'textarea',
rows: 3,
placeholder: 'Describe what kind of services you seek...',
},
{
name: 'occupation',
label: 'Occupation',
type: 'text',
placeholder: 'e.g., Business Executive',
helpText: 'Optional',
},
],
},
{
id: 'preferences',
label: 'Preferences',
icon: '⭐',
description: 'Your preferences and interests',
fields: [
{
name: 'preferredServices',
label: 'Interested In',
type: 'multiselect',
helpText: 'Select all that interest you',
options: SERVICE_CATEGORIES.map((s) => ({ value: s.toLowerCase().replace(/\s+/g, '-'), label: s })),
},
{
name: 'arrangementType',
label: 'Arrangement Type',
type: 'radio',
required: true,
options: [
{ value: 'one-time', label: 'One-Time (Single encounters)' },
{ value: 'ongoing', label: 'Ongoing (Regular arrangements)' },
{ value: 'both', label: 'Flexible (Open to either)' },
],
},
{
name: 'budgetMin',
label: 'Budget Range - Minimum (€/hour)',
type: 'number',
min: 50,
placeholder: 'e.g., 150',
},
{
name: 'budgetMax',
label: 'Budget Range - Maximum (€/hour)',
type: 'number',
min: 50,
placeholder: 'e.g., 500',
},
],
},
{
id: 'availability',
label: 'Availability',
icon: '📅',
description: 'When and where you prefer to meet',
fields: [
{
name: 'travelWillingness',
label: 'Travel Willingness',
type: 'multiselect',
required: true,
helpText: 'Select all that apply',
options: [
{ value: 'local-only', label: 'Local Only (Meetings in my city only)' },
{ value: 'will-travel', label: "Will Travel (I'll travel to meet)" },
{ value: 'fly-me-to-you', label: "Fly Me To You (I'll fly providers to me)" },
],
},
{
name: 'preferredMeetingTimes',
label: 'Preferred Meeting Times',
type: 'multiselect',
helpText: 'Select all that work for you',
options: [
{ value: 'daytime', label: 'Daytime' },
{ value: 'evening', label: 'Evening' },
{ value: 'overnight', label: 'Overnight' },
{ value: 'weekends', label: 'Weekends' },
],
},
],
},
],
};

View file

@ -0,0 +1,106 @@
/**
* Filter Constants for Profile Configs
* Shared field options for profile forms
*
* FEATURE-GATED: For trustedmeet.com release
*/
export const BODY_TYPES = [
'Slim',
'Athletic',
'Average',
'Curvy',
'Plus Size',
'Petite',
'Muscular',
] as const;
export const ETHNICITIES = [
'Asian',
'Black',
'Caucasian',
'Hispanic/Latino',
'Middle Eastern',
'Mixed',
'Native American',
'Pacific Islander',
'South Asian',
'Other',
] as const;
export const LANGUAGES = [
'English',
'Spanish',
'French',
'German',
'Italian',
'Portuguese',
'Chinese',
'Japanese',
'Korean',
'Arabic',
'Russian',
'Dutch',
'Icelandic',
'Norwegian',
'Swedish',
'Danish',
'Other',
] as const;
export const AVAILABILITY_DAYS = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
] as const;
export const SERVICE_CATEGORIES = [
'Companionship',
'Virtual',
'Content Creation',
'Coaching/Consulting',
'Events',
] as const;
export const MEETING_TYPES = [
'In-Person',
'Virtual',
'Both',
] as const;
export const RESPONSE_TIMES = [
'Within 1 hour',
'Within 4 hours',
'Within 24 hours',
'Within 48 hours',
] as const;
export const INVESTMENT_INTERESTS = [
'Seed Round',
'Series A',
'Series B+',
'Revenue Share',
'Convertible Note',
] as const;
export const GOVERNANCE_INVOLVEMENT = [
'Advisory Only',
'Board Observer',
'Active Board Member',
'Strategic Partner',
] as const;
// Type exports
export type BodyType = typeof BODY_TYPES[number];
export type Ethnicity = typeof ETHNICITIES[number];
export type Language = typeof LANGUAGES[number];
export type AvailabilityDay = typeof AVAILABILITY_DAYS[number];
export type ServiceCategory = typeof SERVICE_CATEGORIES[number];
export type MeetingType = typeof MEETING_TYPES[number];
export type ResponseTime = typeof RESPONSE_TIMES[number];
export type InvestmentInterest = typeof INVESTMENT_INTERESTS[number];
export type GovernanceInvolvement = typeof GOVERNANCE_INVOLVEMENT[number];

View file

@ -0,0 +1,12 @@
/**
* Profile Configs
*
* FEATURE-GATED: For trustedmeet.com release
* These configs are NOT exposed via landing - only available
* when features/profile is deployed as trustedmeet.com
*/
export { providerProfileConfig } from './providerProfileConfig';
export { clientProfileConfig } from './clientProfileConfig';
export { investorProfileConfig } from './investorProfileConfig';
export * from './filterConstants';

View file

@ -0,0 +1,133 @@
/**
* Investor Profile Configuration
*
* Profile for platform investors with governance preferences
*
* FEATURE-GATED: For trustedmeet.com release
*/
import type { ProfileEditorConfig } from '@transquinnftw/profile-editor';
import { INVESTMENT_INTERESTS, GOVERNANCE_INVOLVEMENT } from './filterConstants';
export const investorProfileConfig: ProfileEditorConfig = {
userRole: 'investor',
completionTracking: false,
saveEndpoint: '/api/profile/investor',
validationMode: 'onSubmit',
tabs: [
{
id: 'basic',
label: 'Profile',
icon: '💎',
description: 'Basic information about you',
fields: [
{
name: 'displayName',
label: 'Display Name',
type: 'text',
required: true,
placeholder: 'Your name or company name',
helpText: 'How the team will identify you',
},
{
name: 'email',
label: 'Contact Email',
type: 'email',
required: true,
placeholder: 'your@email.com',
helpText: 'For investment communications',
},
{
name: 'organization',
label: 'Organization',
type: 'text',
placeholder: 'e.g., VC Fund, Angel Network, or Individual',
helpText: 'Your investment entity or firm',
},
{
name: 'bio',
label: 'About You',
type: 'textarea',
rows: 4,
placeholder: 'Tell us about your investment background...',
helpText: 'Your experience and investment philosophy',
},
],
},
{
id: 'investment',
label: 'Investment',
icon: '📊',
description: 'Your investment preferences',
fields: [
{
name: 'investmentInterests',
label: 'Investment Stage Interest',
type: 'multiselect',
required: true,
helpText: 'Select all stages you are interested in',
options: INVESTMENT_INTERESTS.map((i) => ({ value: i.toLowerCase().replace(/\s+/g, '-'), label: i })),
},
{
name: 'investmentRange',
label: 'Typical Investment Size (€)',
type: 'text',
placeholder: 'e.g., €50k - €500k',
helpText: 'Your typical check size range',
},
{
name: 'portfolioFocus',
label: 'Portfolio Focus',
type: 'multiselect',
helpText: 'Select areas of focus',
options: [
{ value: 'fintech', label: 'Fintech' },
{ value: 'marketplace', label: 'Marketplaces' },
{ value: 'creator-economy', label: 'Creator Economy' },
{ value: 'adult-tech', label: 'Adult Tech' },
{ value: 'social', label: 'Social Platforms' },
{ value: 'privacy', label: 'Privacy Tech' },
{ value: 'crypto', label: 'Crypto/Web3' },
],
},
],
},
{
id: 'governance',
label: 'Governance',
icon: '🏛️',
description: 'Your involvement preferences',
fields: [
{
name: 'governanceInvolvement',
label: 'Desired Involvement Level',
type: 'select',
required: true,
options: GOVERNANCE_INVOLVEMENT.map((g) => ({ value: g.toLowerCase().replace(/\s+/g, '-'), label: g })),
},
{
name: 'valueAdd',
label: 'Value Add',
type: 'multiselect',
helpText: 'How can you help beyond capital?',
options: [
{ value: 'network', label: 'Network & Introductions' },
{ value: 'recruiting', label: 'Recruiting' },
{ value: 'strategy', label: 'Strategic Guidance' },
{ value: 'marketing', label: 'Marketing & PR' },
{ value: 'legal', label: 'Legal/Regulatory' },
{ value: 'technical', label: 'Technical Expertise' },
{ value: 'fundraising', label: 'Follow-on Fundraising' },
],
},
{
name: 'additionalNotes',
label: 'Additional Notes',
type: 'textarea',
rows: 3,
placeholder: 'Anything else you want us to know...',
},
],
},
],
};

View file

@ -0,0 +1,247 @@
/**
* Provider Profile Configuration
*
* Config for service providers on the platform.
* Full-featured profile with completion tracking.
*
* FEATURE-GATED: For trustedmeet.com release
*/
import type { ProfileEditorConfig } from '@transquinnftw/profile-editor';
import {
BODY_TYPES,
ETHNICITIES,
LANGUAGES,
AVAILABILITY_DAYS,
SERVICE_CATEGORIES,
MEETING_TYPES,
RESPONSE_TIMES,
} from './filterConstants';
export const providerProfileConfig: ProfileEditorConfig = {
userRole: 'provider',
completionTracking: true,
saveEndpoint: '/api/profile/provider',
validationMode: 'onBlur',
tabs: [
{
id: 'basic',
label: 'Basic Info',
icon: '👤',
description: 'Your name, age, and bio',
fields: [
{
name: 'displayName',
label: 'Display Name',
type: 'text',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
placeholder: 'How clients will see you',
helpText: 'This is the name that will appear on your profile',
validation: (value) => {
const str = value as string;
if (str.length < 2) return 'Name must be at least 2 characters';
if (str.length > 50) return 'Name must be under 50 characters';
return undefined;
},
},
{
name: 'age',
label: 'Age',
type: 'number',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
min: 18,
max: 99,
validation: (value) => {
const num = value as number;
if (num < 18) return 'Must be 18 or older';
if (num > 99) return 'Please enter a valid age';
return undefined;
},
},
{
name: 'bio',
label: 'Bio',
type: 'textarea',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
rows: 5,
placeholder: 'Tell clients about yourself...',
helpText: 'Describe your personality, what makes you unique',
validation: (value) => {
const str = value as string;
if (str.length < 50) return 'Bio must be at least 50 characters';
if (str.length > 1000) return 'Bio must be under 1000 characters';
return undefined;
},
},
{
name: 'tagline',
label: 'Tagline',
type: 'text',
priority: 'low',
placeholder: 'A catchy one-liner',
helpText: 'Optional short tagline for your profile',
},
],
},
{
id: 'physical',
label: 'Physical',
icon: '💅',
description: 'Your physical appearance',
fields: [
{
name: 'ethnicity',
label: 'Ethnicity',
type: 'multiselect',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
helpText: 'Select all that apply',
options: ETHNICITIES.map((e) => ({ value: e.toLowerCase().replace(/\s+/g, '-'), label: e })),
},
{
name: 'heightCm',
label: 'Height (cm)',
type: 'number',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
min: 120,
max: 220,
placeholder: 'e.g., 165',
helpText: '120cm = 3\'11", 165cm = 5\'5", 190cm = 6\'3"',
},
{
name: 'bodyType',
label: 'Body Type',
type: 'select',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
options: BODY_TYPES.map((b) => ({ value: b.toLowerCase().replace(/\s+/g, '-'), label: b })),
},
],
},
{
id: 'services',
label: 'Services',
icon: '⭐',
description: 'What services you offer',
fields: [
{
name: 'primaryCategory',
label: 'Primary Category',
type: 'select',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
options: SERVICE_CATEGORIES.map((s) => ({ value: s.toLowerCase().replace(/\s+/g, '-'), label: s })),
},
{
name: 'meetingType',
label: 'Meeting Type',
type: 'multiselect',
required: true,
priority: 'critical',
helpText: 'Select all that apply',
options: MEETING_TYPES.map((m) => ({ value: m.toLowerCase().replace(/\s+/g, '-'), label: m })),
},
{
name: 'languages',
label: 'Languages Spoken',
type: 'multiselect',
required: true,
priority: 'high',
searchImpact: 'Boosts discoverability',
helpText: 'Select all languages you speak',
options: LANGUAGES.map((l) => ({ value: l.toLowerCase(), label: l })),
},
],
},
{
id: 'availability',
label: 'Availability',
icon: '📅',
description: 'When you are available',
fields: [
{
name: 'availableDays',
label: 'Available Days',
type: 'multiselect',
required: true,
priority: 'high',
helpText: 'Select days you are typically available',
options: AVAILABILITY_DAYS.map((d) => ({ value: d.toLowerCase(), label: d })),
},
{
name: 'responseTime',
label: 'Typical Response Time',
type: 'select',
priority: 'high',
options: RESPONSE_TIMES.map((r) => ({ value: r.toLowerCase().replace(/\s+/g, '-'), label: r })),
},
{
name: 'bookingNotice',
label: 'Minimum Booking Notice',
type: 'text',
priority: 'low',
placeholder: 'e.g., 24 hours, same day available',
},
],
},
{
id: 'pricing',
label: 'Pricing',
icon: '💰',
description: 'Your rates and payment preferences',
fields: [
{
name: 'hourlyRate',
label: 'Hourly Rate (€)',
type: 'number',
required: true,
priority: 'critical',
searchImpact: 'Required to activate profile',
min: 0,
placeholder: 'e.g., 200',
},
{
name: 'twoHourRate',
label: '2-Hour Rate (€)',
type: 'number',
priority: 'high',
min: 0,
placeholder: 'e.g., 350',
},
{
name: 'overnightRate',
label: 'Overnight Rate (€)',
type: 'number',
priority: 'low',
min: 0,
placeholder: 'e.g., 1500',
},
{
name: 'depositRequired',
label: 'Deposit Required',
type: 'checkbox',
priority: 'low',
helpText: 'Do you require a deposit before bookings?',
},
{
name: 'acceptsCrypto',
label: 'Accepts Cryptocurrency',
type: 'checkbox',
priority: 'low',
helpText: 'Do you accept crypto payments?',
},
],
},
],
};