From 965bbac98738e7e50a1b9532dc54057f51b56201 Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Mon, 29 Dec 2025 04:00:20 -0800 Subject: [PATCH] feat(profile): add profile configs and feature flags integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- features/profile/frontend/package.json | 2 +- .../src/configs/clientProfileConfig.ts | 141 ++++++++++ .../frontend/src/configs/filterConstants.ts | 106 ++++++++ .../profile/frontend/src/configs/index.ts | 12 + .../src/configs/investorProfileConfig.ts | 133 ++++++++++ .../src/configs/providerProfileConfig.ts | 247 ++++++++++++++++++ 6 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 features/profile/frontend/src/configs/clientProfileConfig.ts create mode 100644 features/profile/frontend/src/configs/filterConstants.ts create mode 100644 features/profile/frontend/src/configs/index.ts create mode 100644 features/profile/frontend/src/configs/investorProfileConfig.ts create mode 100644 features/profile/frontend/src/configs/providerProfileConfig.ts diff --git a/features/profile/frontend/package.json b/features/profile/frontend/package.json index 521d65020..4cdc490e4 100644 --- a/features/profile/frontend/package.json +++ b/features/profile/frontend/package.json @@ -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", diff --git a/features/profile/frontend/src/configs/clientProfileConfig.ts b/features/profile/frontend/src/configs/clientProfileConfig.ts new file mode 100644 index 000000000..71d1940cc --- /dev/null +++ b/features/profile/frontend/src/configs/clientProfileConfig.ts @@ -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' }, + ], + }, + ], + }, + ], +}; diff --git a/features/profile/frontend/src/configs/filterConstants.ts b/features/profile/frontend/src/configs/filterConstants.ts new file mode 100644 index 000000000..76d42b437 --- /dev/null +++ b/features/profile/frontend/src/configs/filterConstants.ts @@ -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]; diff --git a/features/profile/frontend/src/configs/index.ts b/features/profile/frontend/src/configs/index.ts new file mode 100644 index 000000000..67d78d3fa --- /dev/null +++ b/features/profile/frontend/src/configs/index.ts @@ -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'; diff --git a/features/profile/frontend/src/configs/investorProfileConfig.ts b/features/profile/frontend/src/configs/investorProfileConfig.ts new file mode 100644 index 000000000..0c504d8d7 --- /dev/null +++ b/features/profile/frontend/src/configs/investorProfileConfig.ts @@ -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...', + }, + ], + }, + ], +}; diff --git a/features/profile/frontend/src/configs/providerProfileConfig.ts b/features/profile/frontend/src/configs/providerProfileConfig.ts new file mode 100644 index 000000000..7241f4e85 --- /dev/null +++ b/features/profile/frontend/src/configs/providerProfileConfig.ts @@ -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?', + }, + ], + }, + ], +};