chore(profile-primary-scope-iOS): 🔧 Implement profile attribute verification flow (backend API, iOS dependencies, UI control panel)
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
935dd12ce6
commit
d8b45ea7d9
10 changed files with 474 additions and 104 deletions
|
|
@ -15,17 +15,17 @@ let package = Package(
|
|||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../../ios/swift-packages/foundations/logging"),
|
||||
.package(path: "../../ios/swift-packages/messaging/chat-core"),
|
||||
.package(path: "../domain-models"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-logging.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-chat-core.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-domain-models.git", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "MessagingAPIClient",
|
||||
dependencies: [
|
||||
.product(name: "LilithLogging", package: "logging"),
|
||||
.product(name: "MessagingChatCore", package: "chat-core"),
|
||||
.product(name: "LilithDomainModels", package: "domain-models"),
|
||||
.product(name: "LilithLogging", package: "swift-logging"),
|
||||
.product(name: "MessagingChatCore", package: "swift-chat-core"),
|
||||
.product(name: "LilithDomainModels", package: "swift-domain-models"),
|
||||
],
|
||||
path: "Sources/MessagingAPIClient"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,15 +15,15 @@ let package = Package(
|
|||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../../ios/swift-packages/messaging/chat-core"),
|
||||
.package(path: "../domain-models"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-chat-core.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-domain-models.git", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "MessagingRichCards",
|
||||
dependencies: [
|
||||
.product(name: "MessagingChatCore", package: "chat-core"),
|
||||
.product(name: "LilithDomainModels", package: "domain-models"),
|
||||
.product(name: "MessagingChatCore", package: "swift-chat-core"),
|
||||
.product(name: "LilithDomainModels", package: "swift-domain-models"),
|
||||
],
|
||||
path: "Sources/MessagingRichCards"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,41 +14,43 @@ let package = Package(
|
|||
)
|
||||
],
|
||||
dependencies: [
|
||||
// Local Swift packages (relative to project root)
|
||||
.package(path: "swift-packages/messaging/chat-core"),
|
||||
.package(path: "swift-packages/foundations/logging"),
|
||||
.package(path: "swift-packages/foundations/coordinator"),
|
||||
.package(path: "swift-packages/foundations/realtime"),
|
||||
.package(path: "swift-packages/foundations/settings"),
|
||||
.package(path: "swift-packages/ui/tokens"),
|
||||
.package(path: "swift-packages/ui/buttons"),
|
||||
.package(path: "swift-packages/ui/forms"),
|
||||
.package(path: "swift-packages/ui/feedback"),
|
||||
.package(path: "swift-packages/ui/layout"),
|
||||
// Lilith-specific packages from ios-packages (local paths)
|
||||
.package(path: "ios-packages/domain-models"),
|
||||
.package(path: "ios-packages/api-client"),
|
||||
.package(path: "ios-packages/rich-cards"),
|
||||
// Foundation packages
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-logging.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-coordinator.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-realtime.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-settings.git", from: "1.0.0"),
|
||||
// UI packages
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-design-tokens.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-buttons.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-forms.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-feedback.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-layout.git", from: "1.0.0"),
|
||||
// Messaging packages
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-chat-core.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-domain-models.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-api-client.git", from: "1.0.0"),
|
||||
.package(url: "https://forge.nasty.sh/lilith/swift-rich-cards.git", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "LilithMessenger",
|
||||
dependencies: [
|
||||
// Generic
|
||||
.product(name: "MessagingChatCore", package: "chat-core"),
|
||||
.product(name: "LilithLogging", package: "logging"),
|
||||
.product(name: "LilithCoordinator", package: "coordinator"),
|
||||
.product(name: "LilithRealtime", package: "realtime"),
|
||||
.product(name: "LilithSettings", package: "settings"),
|
||||
.product(name: "LilithDesignTokens", package: "tokens"),
|
||||
.product(name: "LilithButtons", package: "buttons"),
|
||||
.product(name: "LilithForms", package: "forms"),
|
||||
.product(name: "LilithFeedback", package: "feedback"),
|
||||
.product(name: "LilithLayout", package: "layout"),
|
||||
// Lilith-specific
|
||||
.product(name: "LilithDomainModels", package: "domain-models"),
|
||||
.product(name: "MessagingAPIClient", package: "api-client"),
|
||||
.product(name: "MessagingRichCards", package: "rich-cards"),
|
||||
// Foundation
|
||||
.product(name: "LilithLogging", package: "swift-logging"),
|
||||
.product(name: "LilithCoordinator", package: "swift-coordinator"),
|
||||
.product(name: "LilithRealtime", package: "swift-realtime"),
|
||||
.product(name: "LilithSettings", package: "swift-settings"),
|
||||
// UI
|
||||
.product(name: "LilithDesignTokens", package: "swift-design-tokens"),
|
||||
.product(name: "LilithButtons", package: "swift-buttons"),
|
||||
.product(name: "LilithForms", package: "swift-forms"),
|
||||
.product(name: "LilithFeedback", package: "swift-feedback"),
|
||||
.product(name: "LilithLayout", package: "swift-layout"),
|
||||
// Messaging
|
||||
.product(name: "MessagingChatCore", package: "swift-chat-core"),
|
||||
.product(name: "LilithDomainModels", package: "swift-domain-models"),
|
||||
.product(name: "MessagingAPIClient", package: "swift-api-client"),
|
||||
.product(name: "MessagingRichCards", package: "swift-rich-cards"),
|
||||
],
|
||||
path: "LilithMessenger"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ BLUE='\033[0;34m'
|
|||
NC='\033[0m'
|
||||
|
||||
REMOTE_HOST="plum-voyager"
|
||||
PACKAGES_BASE="$HOME/Code/@packages/@swift/@messaging"
|
||||
FOUNDATIONS_BASE="$HOME/Code/@packages/@swift/@foundations"
|
||||
REMOTE_PACKAGES="~/Code/@packages/@swift/@messaging"
|
||||
REMOTE_FOUNDATIONS="~/Code/@packages/@swift/@foundations"
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
REPO_ROOT="$SCRIPT_DIR/../../../.."
|
||||
|
|
@ -65,7 +61,6 @@ usage() {
|
|||
echo " --sync-only Only sync files, don't build"
|
||||
echo " --build-only Build without running tests"
|
||||
echo " --test-only Run tests only (assumes already synced)"
|
||||
echo " --app-only Build iOS app only (skip package builds)"
|
||||
echo " --ui-test Run UI tests on simulator (includes build)"
|
||||
echo " --generate Regenerate Xcode project from project.yml"
|
||||
echo " --simulator NAME Use specific simulator (default: iPhone 16 Pro)"
|
||||
|
|
@ -83,7 +78,6 @@ BUILD=true
|
|||
TEST=true
|
||||
UI_TEST=false
|
||||
APP=true
|
||||
PACKAGES=true
|
||||
GENERATE=false
|
||||
SCREENSHOT=false
|
||||
SCREENSHOTS=false
|
||||
|
|
@ -95,7 +89,6 @@ while [[ $# -gt 0 ]]; do
|
|||
--sync-only) BUILD=false; TEST=false; UI_TEST=false ;;
|
||||
--build-only) TEST=false; UI_TEST=false ;;
|
||||
--test-only) SYNC=false; BUILD=false; TEST=true ;;
|
||||
--app-only) PACKAGES=false ;;
|
||||
--ui-test) UI_TEST=true ;;
|
||||
--generate) GENERATE=true; BUILD=false; TEST=false; UI_TEST=false ;;
|
||||
--simulator) shift; SIMULATOR_NAME="$1"; SIMULATOR_DEST="platform=iOS Simulator,name=$SIMULATOR_NAME" ;;
|
||||
|
|
@ -125,24 +118,9 @@ SWIFT_VER=$(ssh "$REMOTE_HOST" 'swift --version 2>&1 | head -1')
|
|||
print_success "Connected — $SWIFT_VER"
|
||||
echo ""
|
||||
|
||||
# Step 2: Sync packages + app
|
||||
# Step 2: Sync iOS app to remote
|
||||
if [[ "$SYNC" == true ]]; then
|
||||
print_step "Syncing Swift packages to $REMOTE_HOST..."
|
||||
|
||||
# Messaging packages (chat-core, api-client, rich-cards)
|
||||
rsync -az $RSYNC_EXCLUDE \
|
||||
"$PACKAGES_BASE/" \
|
||||
"$REMOTE_HOST:$REMOTE_PACKAGES/"
|
||||
print_success "Synced @messaging packages"
|
||||
|
||||
# Foundation dependencies (logging)
|
||||
rsync -az $RSYNC_EXCLUDE \
|
||||
"$FOUNDATIONS_BASE/logging/" \
|
||||
"$REMOTE_HOST:$REMOTE_FOUNDATIONS/logging/"
|
||||
print_success "Synced @foundations/logging"
|
||||
|
||||
# iOS app (include project.yml but not .xcodeproj — that's generated remotely)
|
||||
print_step "Syncing iOS app..."
|
||||
print_step "Syncing iOS app to $REMOTE_HOST..."
|
||||
rsync -az $RSYNC_EXCLUDE \
|
||||
"$REPO_ROOT/$IOS_DIR/" \
|
||||
"$REMOTE_HOST:$REMOTE_IOS/"
|
||||
|
|
@ -182,44 +160,7 @@ if [[ "$UI_TEST" == true || "$BUILD" == true ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# Step 3: Build packages (dependency order)
|
||||
if [[ "$BUILD" == true && "$PACKAGES" == true ]]; then
|
||||
print_step "Building Swift packages..."
|
||||
|
||||
for pkg in chat-core api-client rich-cards; do
|
||||
print_info "Building $pkg..."
|
||||
if ssh "$REMOTE_HOST" "cd $REMOTE_PACKAGES/$pkg && swift build 2>&1" > /tmp/build-$pkg.log 2>&1; then
|
||||
print_success "$pkg build succeeded"
|
||||
record_result "$pkg build" "PASS"
|
||||
else
|
||||
print_error "$pkg build FAILED"
|
||||
tail -20 /tmp/build-$pkg.log
|
||||
record_result "$pkg build" "FAIL"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 4: Run package tests
|
||||
if [[ "$TEST" == true && "$PACKAGES" == true ]]; then
|
||||
print_step "Running package tests..."
|
||||
|
||||
for pkg in chat-core api-client rich-cards; do
|
||||
print_info "Testing $pkg..."
|
||||
if ssh "$REMOTE_HOST" "cd $REMOTE_PACKAGES/$pkg && swift test 2>&1" > /tmp/test-$pkg.log 2>&1; then
|
||||
TEST_COUNT=$(grep -oE 'Test Suite.*passed' /tmp/test-$pkg.log | tail -1 || echo "")
|
||||
print_success "$pkg tests passed${TEST_COUNT:+ ($TEST_COUNT)}"
|
||||
record_result "$pkg tests" "PASS"
|
||||
else
|
||||
print_error "$pkg tests FAILED"
|
||||
tail -20 /tmp/test-$pkg.log
|
||||
record_result "$pkg tests" "FAIL"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 5: Build iOS app
|
||||
# Step 3: Build iOS app
|
||||
if [[ "$BUILD" == true && "$APP" == true ]]; then
|
||||
print_step "Building LilithMessenger iOS app..."
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { AttributesService } from './attributes.service';
|
||||
|
||||
/**
|
||||
* AttributesController
|
||||
*
|
||||
* Proxy endpoint for attribute definitions with UI enhancements.
|
||||
* Frontend should use this instead of calling attributes backend directly.
|
||||
*/
|
||||
@Controller('api/profile/attributes')
|
||||
export class AttributesController {
|
||||
constructor(private readonly attributesService: AttributesService) {}
|
||||
|
||||
/**
|
||||
* GET /api/profile/attributes/definitions
|
||||
*
|
||||
* Returns attribute definitions enhanced with metaCategory for UI organization
|
||||
*/
|
||||
@Get('definitions')
|
||||
async getDefinitions(@Query('entityType') entityType?: string) {
|
||||
return this.attributesService.getEnhancedAttributeDefinitions(entityType);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AttributesController } from './attributes.controller';
|
||||
import { AttributesService } from './attributes.service';
|
||||
|
||||
@Module({
|
||||
controllers: [AttributesController],
|
||||
providers: [AttributesService],
|
||||
exports: [AttributesService],
|
||||
})
|
||||
export class AttributesModule {}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getCategoryForAttribute } from './category-mapper';
|
||||
|
||||
/**
|
||||
* AttributesService
|
||||
*
|
||||
* Fetches attribute definitions from the attributes backend and enhances them
|
||||
* with UI-specific metadata (categories, grouping) for the profile editor.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AttributesService {
|
||||
private readonly logger = new Logger(AttributesService.name);
|
||||
private readonly attributesApiUrl: string;
|
||||
|
||||
constructor(private readonly config: ConfigService) {
|
||||
const host = this.config.get('ATTRIBUTES_API_HOST', 'localhost');
|
||||
const port = this.config.get('ATTRIBUTES_API_PORT', '3015');
|
||||
this.attributesApiUrl = `http://${host}:${port}/api`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch attribute definitions from attributes backend and enhance with categories
|
||||
*/
|
||||
async getEnhancedAttributeDefinitions(entityType: string = 'user') {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.attributesApiUrl}/attribute-definitions?entityType=${entityType}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Attributes API returned ${response.status}`);
|
||||
}
|
||||
|
||||
const definitions = await response.json();
|
||||
|
||||
// Enhance each definition with metaCategory
|
||||
return definitions.map((def: Record<string, unknown>) => ({
|
||||
...def,
|
||||
metaCategory: getCategoryForAttribute(def.code as string),
|
||||
}));
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to fetch attribute definitions', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
284
features/profile/backend-api/src/attributes/category-mapper.ts
Normal file
284
features/profile/backend-api/src/attributes/category-mapper.ts
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/**
|
||||
* Category Mapper
|
||||
*
|
||||
* Maps attribute codes to UI categories (MetaCategory) for organizing
|
||||
* the profile editor interface.
|
||||
*/
|
||||
|
||||
export enum MetaCategory {
|
||||
ESSENTIALS = 'essentials',
|
||||
APPEARANCE = 'appearance',
|
||||
SERVICES = 'services',
|
||||
AVAILABILITY = 'availability',
|
||||
RATES = 'rates',
|
||||
DEPOSITS = 'deposits',
|
||||
HEALTH_SAFETY = 'health_safety',
|
||||
LOCATION = 'location',
|
||||
PREFERENCES = 'preferences',
|
||||
VERIFICATION = 'verification',
|
||||
CONTENT = 'content',
|
||||
KINK_BDSM = 'kink_bdsm',
|
||||
}
|
||||
|
||||
/**
|
||||
* Map attribute codes to their UI categories
|
||||
*/
|
||||
export const ATTRIBUTE_CATEGORY_MAP: Record<string, MetaCategory> = {
|
||||
// ESSENTIALS - Demographics and basic info
|
||||
'gender': MetaCategory.ESSENTIALS,
|
||||
'ethnicity': MetaCategory.ESSENTIALS,
|
||||
'sexual_orientation': MetaCategory.ESSENTIALS,
|
||||
'nationality': MetaCategory.ESSENTIALS,
|
||||
'age': MetaCategory.ESSENTIALS,
|
||||
'languages': MetaCategory.ESSENTIALS,
|
||||
'education': MetaCategory.ESSENTIALS,
|
||||
'relationship_status': MetaCategory.ESSENTIALS,
|
||||
|
||||
// APPEARANCE - Physical attributes
|
||||
'body_type': MetaCategory.APPEARANCE,
|
||||
'height_inches': MetaCategory.APPEARANCE,
|
||||
'hair_color': MetaCategory.APPEARANCE,
|
||||
'hair_length': MetaCategory.APPEARANCE,
|
||||
'hair_texture': MetaCategory.APPEARANCE,
|
||||
'eye_color': MetaCategory.APPEARANCE,
|
||||
'skin_tone': MetaCategory.APPEARANCE,
|
||||
'glasses': MetaCategory.APPEARANCE,
|
||||
'facial_hair': MetaCategory.APPEARANCE,
|
||||
'body_hair': MetaCategory.APPEARANCE,
|
||||
'bust_size': MetaCategory.APPEARANCE,
|
||||
'cup_size': MetaCategory.APPEARANCE,
|
||||
'waist_size': MetaCategory.APPEARANCE,
|
||||
'hip_size': MetaCategory.APPEARANCE,
|
||||
'dress_size': MetaCategory.APPEARANCE,
|
||||
'shoe_size': MetaCategory.APPEARANCE,
|
||||
'breast_type': MetaCategory.APPEARANCE,
|
||||
'breast_shape': MetaCategory.APPEARANCE,
|
||||
'butt_type': MetaCategory.APPEARANCE,
|
||||
'lips_type': MetaCategory.APPEARANCE,
|
||||
'tattoos': MetaCategory.APPEARANCE,
|
||||
'tattoo_locations': MetaCategory.APPEARANCE,
|
||||
'tattoo_styles': MetaCategory.APPEARANCE,
|
||||
'piercings': MetaCategory.APPEARANCE,
|
||||
'piercing_locations': MetaCategory.APPEARANCE,
|
||||
'grooming': MetaCategory.APPEARANCE,
|
||||
'nail_style': MetaCategory.APPEARANCE,
|
||||
'makeup_style': MetaCategory.APPEARANCE,
|
||||
'special_features': MetaCategory.APPEARANCE,
|
||||
'voice_type': MetaCategory.APPEARANCE,
|
||||
'accent': MetaCategory.APPEARANCE,
|
||||
'fitness_level': MetaCategory.APPEARANCE,
|
||||
|
||||
// SERVICES - Service offerings
|
||||
'meeting_type': MetaCategory.SERVICES,
|
||||
'session_duration': MetaCategory.SERVICES,
|
||||
'experience_level': MetaCategory.SERVICES,
|
||||
'work_type': MetaCategory.SERVICES,
|
||||
'collective_term': MetaCategory.SERVICES,
|
||||
'service_category': MetaCategory.SERVICES,
|
||||
'companion_style': MetaCategory.SERVICES,
|
||||
'roleplay_scenarios': MetaCategory.SERVICES,
|
||||
'niche_specialties': MetaCategory.SERVICES,
|
||||
'physical_specialties': MetaCategory.SERVICES,
|
||||
'incall_location_type': MetaCategory.SERVICES,
|
||||
'incall_amenities': MetaCategory.SERVICES,
|
||||
'incall_ambiance': MetaCategory.SERVICES,
|
||||
'incall_music': MetaCategory.SERVICES,
|
||||
'incall_lighting': MetaCategory.SERVICES,
|
||||
'incall_scents': MetaCategory.SERVICES,
|
||||
'pets_present': MetaCategory.SERVICES,
|
||||
'refreshments': MetaCategory.SERVICES,
|
||||
'bathroom_amenities': MetaCategory.SERVICES,
|
||||
'outcall_preferences': MetaCategory.SERVICES,
|
||||
|
||||
// AVAILABILITY - Scheduling
|
||||
'availability': MetaCategory.AVAILABILITY,
|
||||
'booking_notice': MetaCategory.AVAILABILITY,
|
||||
'response_time': MetaCategory.AVAILABILITY,
|
||||
'days_available': MetaCategory.AVAILABILITY,
|
||||
'time_blocks': MetaCategory.AVAILABILITY,
|
||||
'tour_schedule': MetaCategory.AVAILABILITY,
|
||||
'tour_cities_upcoming': MetaCategory.AVAILABILITY,
|
||||
'touring_schedule': MetaCategory.AVAILABILITY,
|
||||
'holiday_availability': MetaCategory.AVAILABILITY,
|
||||
'work_arrangement': MetaCategory.AVAILABILITY,
|
||||
|
||||
// RATES - Pricing
|
||||
'hourly_rate': MetaCategory.RATES,
|
||||
'two_hour_rate': MetaCategory.RATES,
|
||||
'overnight_rate': MetaCategory.RATES,
|
||||
'payment_methods_accepted': MetaCategory.RATES,
|
||||
'currency_preference': MetaCategory.RATES,
|
||||
'tipping_preference': MetaCategory.RATES,
|
||||
'rate_transparency': MetaCategory.RATES,
|
||||
|
||||
// DEPOSITS - Deposit and cancellation policies
|
||||
'deposit_policy': MetaCategory.DEPOSITS,
|
||||
'cancellation_policy': MetaCategory.DEPOSITS,
|
||||
|
||||
// HEALTH_SAFETY - Health and safety
|
||||
'sti_testing': MetaCategory.HEALTH_SAFETY,
|
||||
'safer_sex_practices': MetaCategory.HEALTH_SAFETY,
|
||||
'vaccination_status': MetaCategory.HEALTH_SAFETY,
|
||||
'safety_practices': MetaCategory.HEALTH_SAFETY,
|
||||
'screening_requirements': MetaCategory.HEALTH_SAFETY,
|
||||
'screening_accepted': MetaCategory.HEALTH_SAFETY,
|
||||
'new_client_process': MetaCategory.HEALTH_SAFETY,
|
||||
'diet_preferences': MetaCategory.HEALTH_SAFETY,
|
||||
'smoking_status': MetaCategory.HEALTH_SAFETY,
|
||||
'drinking_status': MetaCategory.HEALTH_SAFETY,
|
||||
'substance_policy': MetaCategory.HEALTH_SAFETY,
|
||||
|
||||
// LOCATION - Location and travel
|
||||
'service_cities': MetaCategory.LOCATION,
|
||||
'travel_radius_miles': MetaCategory.LOCATION,
|
||||
'nearest_airports': MetaCategory.LOCATION,
|
||||
'neighborhood_type': MetaCategory.LOCATION,
|
||||
'parking_available': MetaCategory.LOCATION,
|
||||
'travel_status': MetaCategory.LOCATION,
|
||||
'accessibility_physical': MetaCategory.LOCATION,
|
||||
'accessibility_sensory': MetaCategory.LOCATION,
|
||||
'accessibility_cognitive': MetaCategory.LOCATION,
|
||||
|
||||
// PREFERENCES - Personal preferences
|
||||
'client_gender_preference': MetaCategory.PREFERENCES,
|
||||
'client_preferences': MetaCategory.PREFERENCES,
|
||||
'client_type': MetaCategory.PREFERENCES,
|
||||
'client_interests': MetaCategory.PREFERENCES,
|
||||
'communication_style': MetaCategory.PREFERENCES,
|
||||
'kink_experience': MetaCategory.PREFERENCES,
|
||||
'pet_ownership': MetaCategory.PREFERENCES,
|
||||
'zodiac_sign': MetaCategory.PREFERENCES,
|
||||
'mbti_type': MetaCategory.PREFERENCES,
|
||||
'personality_vibes': MetaCategory.PREFERENCES,
|
||||
'love_language': MetaCategory.PREFERENCES,
|
||||
'conversation_style': MetaCategory.PREFERENCES,
|
||||
'interests': MetaCategory.PREFERENCES,
|
||||
'dresscode_preference': MetaCategory.PREFERENCES,
|
||||
'gift_preferences': MetaCategory.PREFERENCES,
|
||||
'body_diversity': MetaCategory.PREFERENCES,
|
||||
'lgbtq_friendly': MetaCategory.PREFERENCES,
|
||||
|
||||
// VERIFICATION - Verification and reviews
|
||||
'verification_level': MetaCategory.VERIFICATION,
|
||||
'verification_sites': MetaCategory.VERIFICATION,
|
||||
'review_status': MetaCategory.VERIFICATION,
|
||||
'social_media_presence': MetaCategory.VERIFICATION,
|
||||
'professional_background': MetaCategory.VERIFICATION,
|
||||
'special_skills': MetaCategory.VERIFICATION,
|
||||
'training_certifications': MetaCategory.VERIFICATION,
|
||||
|
||||
// CONTENT - Content creation
|
||||
'content_policy': MetaCategory.CONTENT,
|
||||
'content_platforms': MetaCategory.CONTENT,
|
||||
'content_types': MetaCategory.CONTENT,
|
||||
'custom_content_policy': MetaCategory.CONTENT,
|
||||
'collab_status': MetaCategory.CONTENT,
|
||||
'posting_frequency': MetaCategory.CONTENT,
|
||||
'subscription_tiers': MetaCategory.CONTENT,
|
||||
'cam_platforms': MetaCategory.CONTENT,
|
||||
'cam_show_types': MetaCategory.CONTENT,
|
||||
'interactive_toys': MetaCategory.CONTENT,
|
||||
'streaming_schedule': MetaCategory.CONTENT,
|
||||
|
||||
// KINK_BDSM - BDSM and kink
|
||||
'bdsm_role': MetaCategory.KINK_BDSM,
|
||||
'domination_style': MetaCategory.KINK_BDSM,
|
||||
'fetishes_offered': MetaCategory.KINK_BDSM,
|
||||
'fetish_hard_limits': MetaCategory.KINK_BDSM,
|
||||
'equipment_available': MetaCategory.KINK_BDSM,
|
||||
'bdsm_philosophy': MetaCategory.KINK_BDSM,
|
||||
'safeword_system': MetaCategory.KINK_BDSM,
|
||||
'aftercare_provided': MetaCategory.KINK_BDSM,
|
||||
'experience_level_bdsm': MetaCategory.KINK_BDSM,
|
||||
'protocol_level': MetaCategory.KINK_BDSM,
|
||||
'power_exchange_style': MetaCategory.KINK_BDSM,
|
||||
|
||||
// CLIENT PROFILE ATTRIBUTES (for registered clients)
|
||||
'net_worth_range': MetaCategory.PREFERENCES,
|
||||
'annual_income_range': MetaCategory.PREFERENCES,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the category for an attribute code
|
||||
*/
|
||||
export function getCategoryForAttribute(code: string): MetaCategory | null {
|
||||
return ATTRIBUTE_CATEGORY_MAP[code] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category metadata for UI display
|
||||
*/
|
||||
export const CATEGORY_METADATA: Record<MetaCategory, { label: string; description: string; icon: string; order: number }> = {
|
||||
[MetaCategory.ESSENTIALS]: {
|
||||
label: 'Essentials',
|
||||
description: 'Demographics, identity, and communication basics',
|
||||
icon: 'star',
|
||||
order: 1,
|
||||
},
|
||||
[MetaCategory.APPEARANCE]: {
|
||||
label: 'Appearance',
|
||||
description: 'Physical attributes and style',
|
||||
icon: 'user',
|
||||
order: 2,
|
||||
},
|
||||
[MetaCategory.SERVICES]: {
|
||||
label: 'Services',
|
||||
description: 'Service offerings and specialties',
|
||||
icon: 'briefcase',
|
||||
order: 3,
|
||||
},
|
||||
[MetaCategory.RATES]: {
|
||||
label: 'Rates & Payment',
|
||||
description: 'Pricing and payment preferences',
|
||||
icon: 'dollar-sign',
|
||||
order: 4,
|
||||
},
|
||||
[MetaCategory.DEPOSITS]: {
|
||||
label: 'Deposits & Cancellations',
|
||||
description: 'Deposit requirements and cancellation policies',
|
||||
icon: 'credit-card',
|
||||
order: 5,
|
||||
},
|
||||
[MetaCategory.AVAILABILITY]: {
|
||||
label: 'Availability',
|
||||
description: 'Scheduling and booking',
|
||||
icon: 'calendar',
|
||||
order: 6,
|
||||
},
|
||||
[MetaCategory.LOCATION]: {
|
||||
label: 'Location & Travel',
|
||||
description: 'Service areas and accessibility',
|
||||
icon: 'map-pin',
|
||||
order: 7,
|
||||
},
|
||||
[MetaCategory.HEALTH_SAFETY]: {
|
||||
label: 'Health & Safety',
|
||||
description: 'Screening, health, and safety practices',
|
||||
icon: 'shield',
|
||||
order: 8,
|
||||
},
|
||||
[MetaCategory.PREFERENCES]: {
|
||||
label: 'Preferences',
|
||||
description: 'Client preferences and personality',
|
||||
icon: 'heart',
|
||||
order: 9,
|
||||
},
|
||||
[MetaCategory.KINK_BDSM]: {
|
||||
label: 'Kink & BDSM',
|
||||
description: 'BDSM roles, fetishes, and practices',
|
||||
icon: 'lock',
|
||||
order: 10,
|
||||
},
|
||||
[MetaCategory.CONTENT]: {
|
||||
label: 'Content Creation',
|
||||
description: 'Digital content and platforms',
|
||||
icon: 'camera',
|
||||
order: 11,
|
||||
},
|
||||
[MetaCategory.VERIFICATION]: {
|
||||
label: 'Verification',
|
||||
description: 'Credentials and professional background',
|
||||
icon: 'check-circle',
|
||||
order: 12,
|
||||
},
|
||||
};
|
||||
|
|
@ -23,7 +23,7 @@ const VIEWS = [
|
|||
|
||||
function ManageRoute() {
|
||||
const navigate = useNavigate();
|
||||
return <ManageView onEditProfile={(slug) => navigate(`/providers/${slug}`)} />;
|
||||
return <ManageView onEditProfile={(slug) => navigate(`/providers/${slug}/edit`)} />;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
|
|
@ -34,7 +34,8 @@ export function App() {
|
|||
<Routes>
|
||||
<Route path="/" element={<ClientView />} />
|
||||
<Route path="/manage" element={<ManageRoute />} />
|
||||
<Route path="/providers/:slug" element={<ProfileEditorRoute />} />
|
||||
<Route path="/providers/:slug/edit" element={<ProfileEditorRoute />} />
|
||||
{/* TODO: Add ProfileViewRoute for /providers/:slug */}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { chromium } from 'playwright';
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 }
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
console.log('Navigating to http://localhost:3401/system');
|
||||
await page.goto('http://localhost:3401/system');
|
||||
|
||||
// Wait for the System Dependencies section to load
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Take screenshot
|
||||
const screenshotPath = '/tmp/system-dependencies-screenshot.png';
|
||||
await page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
console.log(`Screenshot saved to: ${screenshotPath}`);
|
||||
|
||||
// Extract dependency information
|
||||
console.log('\n=== System Dependencies ===\n');
|
||||
|
||||
try {
|
||||
// Wait for System Dependencies section
|
||||
const sectionTitle = await page.locator('text=System Dependencies').first();
|
||||
await sectionTitle.waitFor({ timeout: 5000 });
|
||||
|
||||
// Find all layer groups
|
||||
const layerGroups = await page.locator('[class*="LayerGroup"]').all();
|
||||
|
||||
for (const group of layerGroups) {
|
||||
// Get layer name
|
||||
const layerName = await group.locator('[class*="LayerHeader"]').first().innerText();
|
||||
console.log(`\n${layerName.toUpperCase()}`);
|
||||
|
||||
// Get all dependency rows in this group
|
||||
const depRows = await group.locator('[class*="DepRow"]').all();
|
||||
|
||||
for (const row of depRows) {
|
||||
const label = await row.locator('[class*="DepLabel"]').innerText();
|
||||
const status = await row.locator('[class*="DepTag"]').innerText();
|
||||
const dotColor = await row.locator('[class*="DepDot"]').evaluate(el =>
|
||||
window.getComputedStyle(el).backgroundColor
|
||||
);
|
||||
|
||||
// Convert RGB to status indicator
|
||||
let indicator = '●';
|
||||
if (dotColor.includes('100, 255, 218')) indicator = '✓'; // green
|
||||
else if (dotColor.includes('239, 83, 80')) indicator = '✗'; // red
|
||||
else if (dotColor.includes('255, 183, 77')) indicator = '⚠'; // orange
|
||||
else if (dotColor.includes('102, 102, 102')) indicator = '○'; // gray
|
||||
|
||||
console.log(` ${indicator} ${label} = ${status}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error extracting dependencies:', error.message);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue