diff --git a/apps/users/src/handlers/config.rs b/apps/users/src/handlers/config.rs index 4e40076..6d37606 100644 --- a/apps/users/src/handlers/config.rs +++ b/apps/users/src/handlers/config.rs @@ -486,9 +486,13 @@ mod tests { async fn create_onboarding_config( + auth: AuthUser, State(state): State, Json(payload): Json, ) -> Result { + if let Err(_e) = require_admin(&auth) { + return Err((StatusCode::FORBIDDEN, "Forbidden".to_string())); + } match ConfigRepository::create_onboarding_config(&state.pool, payload).await { Ok(config) => Ok((StatusCode::CREATED, Json(config))), Err(e) => Err(( @@ -528,9 +532,13 @@ async fn list_onboarding_configs( } async fn create_dashboard_config( + auth: AuthUser, State(state): State, Json(payload): Json, ) -> Result { + if let Err(_e) = require_admin(&auth) { + return Err((StatusCode::FORBIDDEN, "Forbidden".to_string())); + } match ConfigRepository::create_dashboard_config(&state.pool, payload).await { Ok(config) => Ok((StatusCode::CREATED, Json(config))), Err(e) => Err(( diff --git a/apps/users/src/handlers/profile.rs b/apps/users/src/handlers/profile.rs index c3f8bfc..4f699ff 100644 --- a/apps/users/src/handlers/profile.rs +++ b/apps/users/src/handlers/profile.rs @@ -7,6 +7,7 @@ use axum::{ Json, Router, }; use contracts::auth_middleware::AuthUser; +use db::models::config::ConfigRepository; use db::models::{role::RoleRepository, user::UserRepository, verification::VerificationRepository}; use serde::Deserialize; use uuid::Uuid; @@ -99,6 +100,136 @@ fn resolve_role_key(auth_role: &str, query_role: Option) -> String { .to_uppercase() } +/// Field ids marked `lockAfterApproval: true` anywhere in a role's active +/// onboarding schema — these become permanently read-only once the profile +/// is APPROVED, per the "name/GST/Aadhar stay locked, phone/address stay +/// editable" verification design. +fn locked_field_ids(schema_json: &serde_json::Value) -> Vec { + schema_json + .get("steps") + .and_then(|s| s.as_array()) + .into_iter() + .flatten() + .filter_map(|step| step.get("fields")?.as_array()) + .flatten() + .filter(|field| field.get("lockAfterApproval").and_then(|v| v.as_bool()).unwrap_or(false)) + .filter_map(|field| field.get("id")?.as_str().map(String::from)) + .collect() +} + +/// Current verification status + a flat view of currently-stored profile +/// fields for this user/role, used only to check locked-field values before +/// a save is persisted. Best-effort: any lookup failure yields an +/// "unapproved" default, since there is nothing to protect if no profile row +/// exists yet. +async fn fetch_current_profile_state( + pool: &sqlx::PgPool, + user_id: Uuid, + role_key: &str, +) -> (String, serde_json::Value) { + if role_key == "COMPANY" { + let row = sqlx::query( + r#"SELECT company_name, gst_number, status FROM company_profiles WHERE user_id = $1"#, + ) + .bind(user_id) + .fetch_optional(pool) + .await; + return match row { + Ok(Some(r)) => { + use sqlx::Row; + let company_name: Option = r.try_get("company_name").ok(); + let gst_number: Option = r.try_get("gst_number").ok(); + let status: String = r.try_get("status").unwrap_or_default(); + (status, serde_json::json!({ "company_name": company_name, "gst_number": gst_number })) + } + _ => ("NOT_STARTED".to_string(), serde_json::Value::Null), + }; + } + + if role_key == "JOB_SEEKER" || role_key == "CUSTOMER" { + let table = if role_key == "JOB_SEEKER" { "job_seeker_profiles" } else { "customer_profiles" }; + let query = format!(r#"SELECT custom_data, status FROM {} WHERE user_id = $1"#, table); + let row = sqlx::query(&query).bind(user_id).fetch_optional(pool).await; + return match row { + Ok(Some(r)) => { + use sqlx::Row; + let custom_data: serde_json::Value = + r.try_get("custom_data").unwrap_or(serde_json::Value::Null); + let status: String = r.try_get("status").unwrap_or_default(); + let profile_data = custom_data.get("basic_info").cloned().unwrap_or(serde_json::Value::Null); + (status, profile_data) + } + _ => ("NOT_STARTED".to_string(), serde_json::Value::Null), + }; + } + + let Some(table) = role_to_table(role_key) else { + return ("NOT_STARTED".to_string(), serde_json::Value::Null); + }; + let Ok(Some(user_role_profile_id)) = get_user_role_profile_id(pool, user_id, role_key).await else { + return ("NOT_STARTED".to_string(), serde_json::Value::Null); + }; + let query = format!(r#"SELECT custom_data, status FROM {} WHERE user_role_profile_id = $1"#, table); + match sqlx::query(&query).bind(user_role_profile_id).fetch_optional(pool).await { + Ok(Some(row)) => { + use sqlx::Row; + let profile_data: serde_json::Value = + row.try_get("custom_data").unwrap_or(serde_json::Value::Null); + let status: String = row.try_get("status").unwrap_or_default(); + (status, profile_data) + } + _ => ("NOT_STARTED".to_string(), serde_json::Value::Null), + } +} + +/// Rejects a save that changes any field marked `lockAfterApproval` once the +/// profile has already been APPROVED — the server-side backstop for the +/// "verified fields stay locked" guarantee (the UI already disables these +/// inputs, but this stops a direct API call from bypassing that). +async fn reject_locked_field_changes( + pool: &sqlx::PgPool, + user_id: Uuid, + role_key: &str, + incoming: &serde_json::Value, +) -> Result<(), (StatusCode, Json)> { + let schema = match ConfigRepository::get_active_onboarding_by_role_key(pool, role_key).await { + Ok(config) => config.schema_json, + Err(_) => return Ok(()), // no schema configured for this role - nothing to enforce + }; + let locked = locked_field_ids(&schema); + if locked.is_empty() { + return Ok(()); + } + + let (status, current) = fetch_current_profile_state(pool, user_id, role_key).await; + if status != "APPROVED" { + return Ok(()); + } + + let mut violations = vec![]; + for field_id in &locked { + if let Some(new_val) = incoming.get(field_id) { + let current_val = current.get(field_id).cloned().unwrap_or(serde_json::Value::Null); + if !current_val.is_null() && new_val != ¤t_val { + violations.push(field_id.clone()); + } + } + } + + if violations.is_empty() { + Ok(()) + } else { + Err(( + StatusCode::CONFLICT, + Json(serde_json::json!({ + "error": format!("These verified fields can no longer be changed: {}", violations.join(", ")), + "code": "FIELD_LOCKED", + "locked_fields": violations, + })), + )) + } +} + // ── Handlers ────────────────────────────────────────────────────────────────── /// GET /api/profile?roleKey=PHOTOGRAPHER @@ -260,6 +391,12 @@ async fn save_profile( ) -> impl IntoResponse { let role_key = input.role_key.to_uppercase(); + if let Err((status, body)) = + reject_locked_field_changes(&state.pool, auth.user_id, &role_key, &input.profile_data).await + { + return (status, body).into_response(); + } + if role_key == "COMPANY" { let name = input .profile_data diff --git a/scripts/seed.sql b/scripts/seed.sql index d540642..99f6cc1 100644 --- a/scripts/seed.sql +++ b/scripts/seed.sql @@ -81,906 +81,191 @@ END $$; -- City fields are readOnly and default to "Chennai, India". -- version = 2 to force updates on re-run. --- CUSTOMER (14 steps: 1 service select + 9 profession-specific + 4 shared) --- The frontend normalizeSchemaPayload() auto-adds visibleWhen to steps --- matching /^customer_(requirements|budget)_([a-z_]+)$/. +-- CUSTOMER (basic + documents; no portfolio) +-- Field ids match src/lib/profile-fields-config.ts's `default` BASIC_FIELDS/DOC_FIELDS +-- (CUSTOMER has no dedicated entry there, so it falls back to `default`). INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) SELECT id, $json${ + "portfolioModel": "none", "steps": [ { - "id": "step_1_service", - "title": "Select Service Category", + "id": "step_1_basic", + "title": "Basic Information", + "type": "basic", "fields": [ - { - "id": "profession", - "label": "Service Category", - "type": "select", - "required": true, - "options": [ - {"label": "Photographer", "value": "photographer"}, - {"label": "Makeup Artist", "value": "makeup_artist"}, - {"label": "Tutor", "value": "tutor"}, - {"label": "Developer", "value": "developer"}, - {"label": "Video Editor", "value": "video_editor"}, - {"label": "Graphic Designer", "value": "graphic_designer"}, - {"label": "Social Media Manager", "value": "social_media_manager"}, - {"label": "Fitness Trainer", "value": "fitness_trainer"}, - {"label": "Catering Services", "value": "catering_services"} - ] - } + {"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, + {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, + {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, + {"id": "gender", "label": "Gender", "type": "select", "required": false, + "options": [{"label":"Male","value":"Male"},{"label":"Female","value":"Female"},{"label":"Other","value":"Other"},{"label":"Prefer not to say","value":"Prefer not to say"}]}, + {"id": "location", "label": "City", "type": "text", "required": true}, + {"id": "state", "label": "State", "type": "text", "required": true}, + {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}, + {"id": "address", "label": "Address", "type": "textarea", "required": false} ] }, { - "id": "customer_requirements_photographer", - "title": "Photography Requirements", + "id": "step_2_documents", + "title": "Documents", + "type": "documents", "fields": [ - {"id": "event_type", "label": "Event Type", "type": "select", "required": true, - "options": [{"label":"Wedding","value":"Wedding"},{"label":"Corporate Event","value":"Corporate Event"},{"label":"Birthday","value":"Birthday"},{"label":"Product Shoot","value":"Product Shoot"},{"label":"Portrait","value":"Portrait"}]}, - {"id": "coverage_hours", "label": "Coverage Hours", "type": "number", "required": true, "placeholder": "e.g., 4", "validation": {"min": 1}}, - {"id": "photo_style", "label": "Photo Style", "type": "select", "required": true, - "options": [{"label":"Traditional","value":"Traditional"},{"label":"Candid","value":"Candid"},{"label":"Cinematic","value":"Cinematic"},{"label":"Documentary","value":"Documentary"}]} + {"id": "aadhar_doc", "label": "Aadhar / Government ID", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"} ] }, { - "id": "customer_requirements_makeup_artist", - "title": "Makeup Requirements", - "fields": [ - {"id": "occasion_type", "label": "Occasion Type", "type": "select", "required": true, - "options": [{"label":"Bridal","value":"Bridal"},{"label":"Party/Guest","value":"Party/Guest"},{"label":"Photoshoot","value":"Photoshoot"},{"label":"Editorial","value":"Editorial"}]}, - {"id": "people_count", "label": "Number of People", "type": "number", "required": true, "placeholder": "e.g., 2", "validation": {"min": 1}}, - {"id": "skin_preferences", "label": "Skin Preferences", "type": "textarea", "placeholder": "Any allergies or specific product requests?"} - ] - }, - { - "id": "customer_requirements_tutor", - "title": "Tutoring Requirements", - "fields": [ - {"id": "subject", "label": "Subject", "type": "text", "required": true, "placeholder": "e.g., Mathematics, Spoken English"}, - {"id": "grade_level", "label": "Grade Level", "type": "select", "required": true, - "options": [{"label":"Primary","value":"Primary"},{"label":"Middle School","value":"Middle School"},{"label":"High School","value":"High School"},{"label":"College","value":"College"},{"label":"Professional","value":"Professional"}]}, - {"id": "sessions_per_week", "label": "Sessions Per Week", "type": "number", "required": true, "placeholder": "e.g., 3", "validation": {"min": 1}} - ] - }, - { - "id": "customer_requirements_developer", - "title": "Development Requirements", - "fields": [ - {"id": "project_type", "label": "Project Type", "type": "select", "required": true, - "options": [{"label":"Website","value":"Website"},{"label":"Mobile App","value":"Mobile App"},{"label":"E-commerce","value":"E-commerce"},{"label":"Custom Software","value":"Custom Software"}]}, - {"id": "platform", "label": "Platform", "type": "select", "required": true, - "options": [{"label":"iOS","value":"iOS"},{"label":"Android","value":"Android"},{"label":"Web","value":"Web"},{"label":"Cross-platform","value":"Cross-platform"}]}, - {"id": "feature_summary", "label": "Feature Summary", "type": "textarea", "required": true, "placeholder": "Briefly describe what the app/website should do"} - ] - }, - { - "id": "customer_requirements_video_editor", - "title": "Video Editing Requirements", - "fields": [ - {"id": "video_type", "label": "Video Type", "type": "select", "required": true, - "options": [{"label":"YouTube Video","value":"YouTube Video"},{"label":"Instagram Reel/Shorts","value":"Instagram Reel/Shorts"},{"label":"Wedding Highlights","value":"Wedding Highlights"},{"label":"Corporate Promo","value":"Corporate Promo"}]}, - {"id": "video_duration", "label": "Video Duration", "type": "select", "required": true, - "options": [{"label":"Under 1 min","value":"Under 1 min"},{"label":"1-5 mins","value":"1-5 mins"},{"label":"5-15 mins","value":"5-15 mins"},{"label":"Over 15 mins","value":"Over 15 mins"}]}, - {"id": "editing_style", "label": "Editing Style", "type": "text", "required": true, "placeholder": "e.g., Fast-paced, Cinematic, Vlog style"} - ] - }, - { - "id": "customer_requirements_graphic_designer", - "title": "Design Requirements", - "fields": [ - {"id": "design_type", "label": "Design Type", "type": "select", "required": true, - "options": [{"label":"Logo/Branding","value":"Logo/Branding"},{"label":"Social Media Posts","value":"Social Media Posts"},{"label":"UI/UX","value":"UI/UX"},{"label":"Print Media","value":"Print Media"}]}, - {"id": "brand_guidelines", "label": "Brand Guidelines", "type": "select", "required": true, - "options": [{"label":"Yes - I have them","value":"Yes - I have them"},{"label":"No - Need to create them","value":"No - Need to create them"}]}, - {"id": "asset_count", "label": "Asset Count", "type": "number", "required": true, "placeholder": "How many images/screens?", "validation": {"min": 1}} - ] - }, - { - "id": "customer_requirements_social_media_manager", - "title": "Social Media Requirements", - "fields": [ - {"id": "platforms", "label": "Platforms", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Instagram","value":"Instagram"},{"label":"LinkedIn","value":"LinkedIn"},{"label":"Facebook","value":"Facebook"},{"label":"X/Twitter","value":"X/Twitter"},{"label":"YouTube","value":"YouTube"}]}, - {"id": "posting_frequency", "label": "Posting Frequency", "type": "select", "required": true, - "options": [{"label":"1-2 times/week","value":"1-2 times/week"},{"label":"3-4 times/week","value":"3-4 times/week"},{"label":"Daily","value":"Daily"}]}, - {"id": "goal", "label": "Goal", "type": "select", "required": true, - "options": [{"label":"Brand Awareness","value":"Brand Awareness"},{"label":"Lead Generation","value":"Lead Generation"},{"label":"Sales/Conversions","value":"Sales/Conversions"},{"label":"Community Building","value":"Community Building"}]} - ] - }, - { - "id": "customer_requirements_fitness_trainer", - "title": "Fitness Training Requirements", - "fields": [ - {"id": "fitness_goal", "label": "Fitness Goal", "type": "select", "required": true, - "options": [{"label":"Weight Loss","value":"Weight Loss"},{"label":"Muscle Gain","value":"Muscle Gain"},{"label":"Flexibility/Yoga","value":"Flexibility/Yoga"},{"label":"General Fitness","value":"General Fitness"}]}, - {"id": "sessions_per_week", "label": "Sessions Per Week", "type": "number", "required": true, "placeholder": "e.g., 5", "validation": {"min": 1}}, - {"id": "training_mode", "label": "Training Mode", "type": "select", "required": true, - "options": [{"label":"Online/Virtual","value":"Online/Virtual"},{"label":"In-person","value":"In-person"}]} - ] - }, - { - "id": "customer_requirements_catering_services", - "title": "Catering Requirements", - "fields": [ - {"id": "event_size", "label": "Event Size (Guests)", "type": "number", "required": true, "placeholder": "Number of guests/plates", "validation": {"min": 1}}, - {"id": "menu_preference", "label": "Menu Preference", "type": "select", "required": true, - "options": [{"label":"Pure Veg","value":"Pure Veg"},{"label":"Non-Veg","value":"Non-Veg"},{"label":"Mixed","value":"Mixed"}]}, - {"id": "cuisine_type", "label": "Cuisine Type", "type": "text", "required": true, "placeholder": "e.g., South Indian, North Indian, Continental"} - ] - }, - { - "id": "step_3_budget_timeline", - "title": "Budget and Timeline", - "fields": [ - {"id": "budget_range", "label": "Budget Range", "type": "select", "required": true, - "options": [{"label":"Under \u20b95,000","value":"Under \u20b95,000"},{"label":"\u20b95,000 - \u20b915,000","value":"\u20b95,000 - \u20b915,000"},{"label":"\u20b915,000 - \u20b950,000","value":"\u20b915,000 - \u20b950,000"},{"label":"\u20b950,000 - \u20b91,00,000","value":"\u20b950,000 - \u20b91,00,000"},{"label":"\u20b91,00,000+","value":"\u20b91,00,000+"}]}, - {"id": "expected_start", "label": "Expected Start", "type": "date", "required": true}, - {"id": "urgency", "label": "Urgency", "type": "select", "required": true, - "options": [{"label":"Relaxed (No strict deadline)","value":"Relaxed (No strict deadline)"},{"label":"Standard (Within a few weeks)","value":"Standard (Within a few weeks)"},{"label":"ASAP (Urgent)","value":"ASAP (Urgent)"}]} - ] - }, - { - "id": "step_4_location", - "title": "Location and Preference", - "fields": [ - {"id": "service_mode", "label": "Service Mode", "type": "select", "required": true, - "options": [{"label":"Onsite (In-person)","value":"Onsite (In-person)"},{"label":"Remote (Online)","value":"Remote (Online)"},{"label":"Hybrid (Mix of both)","value":"Hybrid (Mix of both)"}]}, - {"id": "address_line", "label": "Address Line", "type": "text", "required": true, "placeholder": "Street address, Landmark"}, - {"id": "service_city", "label": "Service City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"}, - {"id": "pin_code", "label": "PIN Code", "type": "text", "required": true, "placeholder": "e.g., 600001", "validation": {"pattern": "^[0-9]{6}$", "minLength": 6, "maxLength": 6}} - ] - }, - { - "id": "step_5_review", - "title": "Final Review", - "fields": [ - {"id": "summary_note", "label": "Additional Instructions", "type": "textarea", "placeholder": "Any additional instructions or context?"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] + "id": "step_3_review", + "title": "Review and Submit", + "type": "review", + "fields": [] } ] }$json$::jsonb, 2, true FROM roles WHERE key = 'CUSTOMER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- COMPANY (6 steps) +-- COMPANY (basic + documents; no portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) SELECT id, $json${ + "portfolioModel": "none", "steps": [ { - "id": "step_1_identity", - "title": "Company Identity", + "id": "step_1_basic", + "title": "Basic Information", + "type": "basic", "fields": [ - {"id": "company_name", "label": "Company Name", "type": "text", "required": true, "placeholder": "Your registered company name"}, - {"id": "legal_name", "label": "Legal Name", "type": "text", "required": true, "placeholder": "As per registration documents"}, - {"id": "industry", "label": "Industry", "type": "select", "required": true, - "options": [{"label":"IT/Software","value":"IT/Software"},{"label":"Marketing/Advertising","value":"Marketing/Advertising"},{"label":"EdTech","value":"EdTech"},{"label":"Media/Entertainment","value":"Media/Entertainment"},{"label":"Health/Wellness","value":"Health/Wellness"},{"label":"Food/Beverage","value":"Food/Beverage"},{"label":"Other","value":"Other"}]} + {"id": "company_name", "label": "Company Name", "type": "text", "required": true, "lockAfterApproval": true}, + {"id": "company_email", "label": "Company Email", "type": "email", "required": true}, + {"id": "company_phone", "label": "Company Phone", "type": "tel", "required": false}, + {"id": "website", "label": "Website URL", "type": "url", "required": false}, + {"id": "location", "label": "City", "type": "text", "required": true}, + {"id": "state", "label": "State", "type": "text", "required": true}, + {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}, + {"id": "address", "label": "Registered Address", "type": "textarea", "required": false}, + {"id": "gst_number", "label": "GST Number (optional)", "type": "text", "required": false, "lockAfterApproval": true} ] }, { - "id": "step_2_contact", - "title": "Contact Details", + "id": "step_2_documents", + "title": "Documents", + "type": "documents", "fields": [ - {"id": "contact_name", "label": "Contact Person Name", "type": "text", "required": true, "placeholder": "HR Manager or Founder"}, - {"id": "contact_email", "label": "Contact Email", "type": "email", "required": true, "placeholder": "hr@yourcompany.com"}, - {"id": "contact_phone", "label": "Contact Phone", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}} + {"id": "registration_doc", "label": "Company Registration Certificate", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, + {"id": "gst_doc", "label": "GST Certificate (optional)", "type": "file", "required": false, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"} ] }, { - "id": "step_3_presence", - "title": "Company Presence", - "fields": [ - {"id": "website", "label": "Website URL", "type": "url", "required": false, "placeholder": "https://yourcompany.com"}, - {"id": "hq_city", "label": "HQ City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"}, - {"id": "team_size", "label": "Team Size", "type": "select", "required": true, - "options": [{"label":"1-10","value":"1-10"},{"label":"11-50","value":"11-50"},{"label":"51-200","value":"51-200"},{"label":"200+","value":"200+"}]} - ] - }, - { - "id": "step_4_hiring", - "title": "Hiring Preferences", - "fields": [ - {"id": "hiring_for", "label": "Currently Hiring For", "type": "text", "required": true, "placeholder": "e.g., Frontend Developer, Sales Executive"}, - {"id": "work_mode", "label": "Work Mode", "type": "select", "required": true, - "options": [{"label":"Onsite (Work from office)","value":"Onsite"},{"label":"Remote (Work from home)","value":"Remote"},{"label":"Hybrid","value":"Hybrid"}]}, - {"id": "monthly_openings", "label": "Monthly Openings", "type": "number", "required": true, "placeholder": "Expected number of hires/month", "validation": {"min": 1}} - ] - }, - { - "id": "step_5_compliance", - "title": "Verification and Compliance", - "fields": [ - {"id": "registration_number", "label": "Company Registration Number", "type": "text", "required": true, "placeholder": "CIN / MSME / GST Number"}, - {"id": "official_email", "label": "Official Email", "type": "email", "required": true, "placeholder": "official@yourcompany.com"} - ] - }, - { - "id": "step_6_business_verification", - "title": "Business Verification", - "fields": [ - {"id": "company_doc_type", "label": "Document Type", "type": "select", "required": true, - "options": [{"label":"GST Certificate","value":"GST Certificate"},{"label":"Certificate of Incorporation","value":"Certificate of Incorporation"},{"label":"MSME/Udyam Registration","value":"MSME/Udyam Registration"},{"label":"Company PAN Card","value":"Company PAN Card"}]}, - {"id": "company_doc_upload", "label": "Upload Company Document", "type": "file", "required": true, "multiple": false, "maxFiles": 1, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] + "id": "step_3_review", + "title": "Review and Submit", + "type": "review", + "fields": [] } ] }$json$::jsonb, 2, true FROM roles WHERE key = 'COMPANY' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- JOB_SEEKER (5 steps — NO resume upload to prevent phone number exposure) +-- JOB_SEEKER (basic + documents + portfolio, portfolio saved into custom_data.job_seeker_portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) SELECT id, $json${ + "portfolioModel": "custom_data", "steps": [ { "id": "step_1_basic", - "title": "Basic Profile", + "title": "Basic Information", + "type": "basic", "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"}, - {"id": "skills", "label": "Key Skills", "type": "text", "required": true, "placeholder": "e.g., JavaScript, React, Node.js (comma-separated)"} + {"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, + {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, + {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, + {"id": "gender", "label": "Gender", "type": "select", "required": false, + "options": [{"label":"Male","value":"Male"},{"label":"Female","value":"Female"},{"label":"Other","value":"Other"},{"label":"Prefer not to say","value":"Prefer not to say"}]}, + {"id": "location", "label": "City", "type": "text", "required": true}, + {"id": "state", "label": "State", "type": "text", "required": true}, + {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}, + {"id": "address", "label": "Address", "type": "textarea", "required": false} ] }, { - "id": "step_2_preferences", - "title": "Job Preferences", + "id": "step_2_documents", + "title": "Documents", + "type": "documents", "fields": [ - {"id": "preferred_role", "label": "Preferred Role", "type": "text", "required": true, "placeholder": "e.g., Frontend Developer"}, - {"id": "expected_salary", "label": "Expected Salary (LPA)", "type": "number", "required": true, "placeholder": "Annual salary in Lakhs", "validation": {"min": 0}}, - {"id": "work_mode", "label": "Work Mode", "type": "select", "required": true, - "options": [{"label":"Onsite","value":"Onsite"},{"label":"Remote","value":"Remote"},{"label":"Hybrid","value":"Hybrid"}]} + {"id": "aadhar_doc", "label": "Aadhar / Government ID", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"} ] }, { - "id": "step_3_experience", - "title": "Experience Details", + "id": "step_3_portfolio", + "title": "My Portfolio", + "type": "portfolio", "fields": [ - {"id": "experience_years", "label": "Years of Experience", "type": "number", "required": true, "placeholder": "0 for freshers", "validation": {"min": 0}}, - {"id": "latest_company", "label": "Latest/Current Company", "type": "text", "required": false, "placeholder": "Company name or 'Fresher'"}, - {"id": "notice_period", "label": "Notice Period", "type": "select", "required": true, - "options": [{"label":"Immediate","value":"Immediate"},{"label":"15 Days","value":"15 Days"},{"label":"30 Days","value":"30 Days"},{"label":"60 Days","value":"60 Days"},{"label":"90 Days","value":"90 Days"}]} + {"id": "headline", "label": "Professional Headline", "type": "text", "required": true}, + {"id": "summary", "label": "Career Summary", "type": "textarea", "required": true}, + {"id": "education", "label": "Education", "type": "textarea", "required": true}, + {"id": "workExperience", "label": "Work Experience", "type": "textarea", "required": true}, + {"id": "skills", "label": "Skills", "type": "textarea", "required": true} ] }, { "id": "step_4_review", - "title": "About Me", - "fields": [ - {"id": "about_me", "label": "About Me", "type": "textarea", "required": true, "placeholder": "Tell companies about yourself, your strengths, and career goals"}, - {"id": "linkedin_url","label": "LinkedIn URL", "type": "url", "required": false, "placeholder": "https://linkedin.com/in/yourprofile"} - ] - }, - { - "id": "step_5_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] + "title": "Review and Submit", + "type": "review", + "fields": [] } ] }$json$::jsonb, 2, true FROM roles WHERE key = 'JOB_SEEKER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; - --- PHOTOGRAPHER (6 steps) +-- PHOTOGRAPHER (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)","type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a photographer specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Photography Specialization", - "fields": [ - {"id": "specialty", "label": "Specialty", "type": "select", "required": true, - "options": [{"label":"Wedding","value":"Wedding"},{"label":"Portrait","value":"Portrait"},{"label":"Product/Commercial","value":"Product/Commercial"},{"label":"Events","value":"Events"},{"label":"Real Estate","value":"Real Estate"},{"label":"Fashion/Editorial","value":"Fashion/Editorial"}]}, - {"id": "camera_equipment", "label": "Camera Equipment", "type": "text", "required": true, "placeholder": "e.g., Canon EOS R5, Sony A7 III"}, - {"id": "editing_software", "label": "Editing Software", "type": "select", "required": true, - "options": [{"label":"Adobe Lightroom","value":"Adobe Lightroom"},{"label":"Adobe Photoshop","value":"Adobe Photoshop"},{"label":"Capture One","value":"Capture One"},{"label":"Other","value":"Other"}]} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Hourly","value":"Hourly"},{"label":"Per Session/Event","value":"Per Session/Event"},{"label":"Per Project","value":"Per Project"},{"label":"Custom Package","value":"Custom Package"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)","type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Weekdays","value":"Weekdays"},{"label":"Weekends","value":"Weekends"},{"label":"All Days","value":"All Days"},{"label":"By Appointment","value":"By Appointment"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Portfolio Images (up to 6)", "type": "file", "required": true, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload up to 6 images, max 2MB each. Displayed in 3\u00d72 grid."}, - {"id": "portfolio_url", "label": "External Portfolio URL", "type": "url", "required": false, "placeholder": "Instagram, website, or Behance link"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your style and best work"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "portfolio_ownership_proof", "label": "Portfolio Ownership Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'PHOTOGRAPHER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- MAKEUP_ARTIST (6 steps) +-- MAKEUP_ARTIST (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a makeup artist specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Makeup Specialization", - "fields": [ - {"id": "makeup_specialty", "label": "Makeup Specialty", "type": "select", "required": true, - "options": [{"label":"Bridal","value":"Bridal"},{"label":"Editorial/Fashion","value":"Editorial/Fashion"},{"label":"Film/TV","value":"Film/TV"},{"label":"Special Effects","value":"Special Effects"},{"label":"Party/Events","value":"Party/Events"}]}, - {"id": "preferred_brands", "label": "Preferred Brands", "type": "text", "required": true, "placeholder": "e.g., MAC, Huda Beauty, Kryolan"}, - {"id": "services_offered", "label": "Services Offered", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Bridal Makeup","value":"Bridal Makeup"},{"label":"Party Makeup","value":"Party Makeup"},{"label":"Photoshoot Makeup","value":"Photoshoot Makeup"},{"label":"Grooming","value":"Grooming"},{"label":"Airbrush Makeup","value":"Airbrush Makeup"}]} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Session","value":"Per Session"},{"label":"Per Person","value":"Per Person"},{"label":"Package-based","value":"Package-based"},{"label":"Custom","value":"Custom"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Weekdays","value":"Weekdays"},{"label":"Weekends","value":"Weekends"},{"label":"All Days","value":"All Days"},{"label":"By Appointment","value":"By Appointment"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Portfolio Images (up to 6)", "type": "file", "required": true, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload up to 6 images, max 2MB each. Displayed in 3\u00d72 grid."}, - {"id": "portfolio_url", "label": "External Portfolio URL", "type": "url", "required": false, "placeholder": "Instagram or website link"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your style and best work"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "professional_certifications", "label": "Professional Certifications", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'MAKEUP_ARTIST' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- TUTOR (6 steps) +-- DEVELOPER (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a tutor specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Teaching Specialization", - "fields": [ - {"id": "subjects", "label": "Subjects", "type": "text", "required": true, "placeholder": "e.g., Mathematics, Physics, Spoken English"}, - {"id": "grade_levels", "label": "Grade Levels", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Primary (1-5)","value":"Primary"},{"label":"Middle School (6-8)","value":"Middle School"},{"label":"High School (9-12)","value":"High School"},{"label":"College","value":"College"},{"label":"Professional/Adult","value":"Professional"}]}, - {"id": "teaching_mode", "label": "Teaching Mode", "type": "select", "required": true, - "options": [{"label":"Online","value":"Online"},{"label":"Offline (Home visits)","value":"Offline"},{"label":"Hybrid","value":"Hybrid"}]} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Hour","value":"Per Hour"},{"label":"Per Session","value":"Per Session"},{"label":"Monthly Package","value":"Monthly Package"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Weekdays","value":"Weekdays"},{"label":"Weekends","value":"Weekends"},{"label":"All Days","value":"All Days"},{"label":"By Appointment","value":"By Appointment"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Credentials and Work Samples", - "fields": [ - {"id": "portfolio_images", "label": "Certificates / Work Samples (up to 6)", "type": "file", "required": false, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload certificates or student work samples, max 2MB each."}, - {"id": "portfolio_url", "label": "Online Profile or Course Link", "type": "url", "required": false, "placeholder": "LinkedIn, Vedantu, or website link"}, - {"id": "portfolio_note", "label": "Teaching Approach", "type": "textarea", "placeholder": "Describe your teaching style and methodology"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true -FROM roles WHERE key = 'TUTOR' -ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; - --- DEVELOPER (6 steps) -INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a developer specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Development Specialization", - "fields": [ - {"id": "developer_type", "label": "Developer Type", "type": "select", "required": true, - "options": [{"label":"Frontend","value":"Frontend"},{"label":"Backend","value":"Backend"},{"label":"Full-stack","value":"Full-stack"},{"label":"Mobile (iOS/Android)","value":"Mobile"},{"label":"DevOps/Cloud","value":"DevOps"}]}, - {"id": "tech_stack", "label": "Tech Stack", "type": "text", "required": true, "placeholder": "e.g., React, Node.js, PostgreSQL, Docker"}, - {"id": "open_source_profile", "label": "GitHub / Portfolio URL", "type": "url", "required": false, "placeholder": "https://github.com/yourusername"} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Hourly","value":"Hourly"},{"label":"Per Project","value":"Per Project"},{"label":"Monthly Retainer","value":"Monthly Retainer"},{"label":"Custom","value":"Custom"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Full-time","value":"Full-time"},{"label":"Part-time","value":"Part-time"},{"label":"Weekends Only","value":"Weekends Only"},{"label":"Flexible","value":"Flexible"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Screenshots / Work Samples (up to 6)", "type": "file", "required": false, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload app/website screenshots, max 2MB each."}, - {"id": "portfolio_url", "label": "Live Project / Portfolio URL", "type": "url", "required": false, "placeholder": "Deployed project, Behance, or GitHub Pages"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your most impactful projects"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "tax_document", "label": "Tax Document", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'DEVELOPER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- VIDEO_EDITOR (6 steps) +-- VIDEO_EDITOR (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a video editor specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Video Editing Specialization", - "fields": [ - {"id": "editing_software", "label": "Primary Editing Software", "type": "select", "required": true, - "options": [{"label":"Adobe Premiere Pro","value":"Adobe Premiere Pro"},{"label":"Final Cut Pro","value":"Final Cut Pro"},{"label":"DaVinci Resolve","value":"DaVinci Resolve"},{"label":"After Effects","value":"After Effects"},{"label":"CapCut","value":"CapCut"}]}, - {"id": "video_specialties", "label": "Video Specialties", "type": "select", "required": true, "multiple": true, - "options": [{"label":"YouTube Videos","value":"YouTube Videos"},{"label":"Instagram Reels/Shorts","value":"Reels/Shorts"},{"label":"Wedding Highlights","value":"Wedding Highlights"},{"label":"Corporate Promo","value":"Corporate Promo"},{"label":"Explainer/Animation","value":"Explainer"}]}, - {"id": "turnaround_time", "label": "Typical Turnaround", "type": "select", "required": true, - "options": [{"label":"24-48 hours","value":"24-48 hours"},{"label":"3-5 days","value":"3-5 days"},{"label":"1-2 weeks","value":"1-2 weeks"},{"label":"Depends on project","value":"Depends"}]} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Video","value":"Per Video"},{"label":"Per Minute of Output","value":"Per Minute"},{"label":"Hourly","value":"Hourly"},{"label":"Monthly Retainer","value":"Monthly Retainer"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Full-time","value":"Full-time"},{"label":"Part-time","value":"Part-time"},{"label":"Weekends Only","value":"Weekends Only"},{"label":"Flexible","value":"Flexible"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Work Thumbnails / Stills (up to 6)", "type": "file", "required": false, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload video thumbnails or stills, max 2MB each."}, - {"id": "portfolio_url", "label": "YouTube / Vimeo Portfolio Link", "type": "url", "required": true, "placeholder": "Link to your best video work"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your editing style and best projects"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "tax_document", "label": "Tax Document", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'VIDEO_EDITOR' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- GRAPHIC_DESIGNER (6 steps) +-- GRAPHIC_DESIGNER (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a graphic designer specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Design Specialization", - "fields": [ - {"id": "design_tools", "label": "Design Tools", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Figma","value":"Figma"},{"label":"Adobe Illustrator","value":"Adobe Illustrator"},{"label":"Adobe Photoshop","value":"Adobe Photoshop"},{"label":"Canva Pro","value":"Canva Pro"},{"label":"Adobe InDesign","value":"Adobe InDesign"}]}, - {"id": "design_specialties","label": "Design Specialties","type": "select", "required": true, "multiple": true, - "options": [{"label":"Branding/Logo","value":"Branding/Logo"},{"label":"UI/UX Design","value":"UI/UX"},{"label":"Print Media","value":"Print Media"},{"label":"Social Media Graphics","value":"Social Media"},{"label":"Motion Graphics","value":"Motion Graphics"}]}, - {"id": "style_note", "label": "Design Style", "type": "text", "required": false, "placeholder": "e.g., Minimalist, Bold, Illustrative"} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Design/Asset","value":"Per Design"},{"label":"Per Project","value":"Per Project"},{"label":"Hourly","value":"Hourly"},{"label":"Monthly Retainer","value":"Monthly Retainer"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Full-time","value":"Full-time"},{"label":"Part-time","value":"Part-time"},{"label":"Weekends Only","value":"Weekends Only"},{"label":"Flexible","value":"Flexible"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Design Samples (up to 6)", "type": "file", "required": true, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload your best design work, max 2MB each. Displayed in 3\u00d72 grid."}, - {"id": "portfolio_url", "label": "Behance / Dribbble / Portfolio URL", "type": "url", "required": false, "placeholder": "https://behance.net/yourprofile"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your design philosophy and best projects"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "tax_document", "label": "Tax Document", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'GRAPHIC_DESIGNER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- SOCIAL_MEDIA_MANAGER (6 steps) +-- SOCIAL_MEDIA_MANAGER (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a social media manager specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Social Media Specialization", - "fields": [ - {"id": "platforms_managed", "label": "Platforms Managed", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Instagram","value":"Instagram"},{"label":"LinkedIn","value":"LinkedIn"},{"label":"Facebook","value":"Facebook"},{"label":"X/Twitter","value":"X/Twitter"},{"label":"YouTube","value":"YouTube"},{"label":"Pinterest","value":"Pinterest"}]}, - {"id": "content_types", "label": "Content Types", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Graphics/Creatives","value":"Graphics"},{"label":"Reels/Short Videos","value":"Reels"},{"label":"Copywriting/Captions","value":"Copywriting"},{"label":"Stories","value":"Stories"},{"label":"Analytics & Reports","value":"Analytics"}]}, - {"id": "tools_used", "label": "Tools Used", "type": "text", "required": true, "placeholder": "e.g., Canva, Hootsuite, Buffer, Meta Business Suite"} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Monthly Retainer","value":"Monthly Retainer"},{"label":"Per Platform","value":"Per Platform"},{"label":"Per Post","value":"Per Post"},{"label":"Custom Package","value":"Custom Package"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Full-time","value":"Full-time"},{"label":"Part-time","value":"Part-time"},{"label":"Flexible","value":"Flexible"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Content Samples (up to 6)", "type": "file", "required": true, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload posts, stories, or analytics screenshots, max 2MB each."}, - {"id": "portfolio_url", "label": "Sample Brand Account URL", "type": "url", "required": false, "placeholder": "Instagram/LinkedIn page you manage"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe brands you have worked with and results achieved"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "email", "label": "Email Address", "type": "email", "required": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "gender", "label": "Gender", "type": "select", "required": false, "options": [{"label": "Male", "value": "Male"}, {"label": "Female", "value": "Female"}, {"label": "Other", "value": "Other"}, {"label": "Prefer not to say", "value": "Prefer not to say"}]}, {"id": "address_line_1", "label": "Address Line 1", "type": "text", "required": true}, {"id": "address_line_2", "label": "Address Line 2 (Optional)", "type": "text", "required": false}, {"id": "city", "label": "City", "type": "text", "required": true}, {"id": "area", "label": "Area", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "pin_code", "label": "PIN Code", "type": "text", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "tax_document", "label": "Tax Document", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'SOCIAL_MEDIA_MANAGER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- FITNESS_TRAINER (6 steps) +-- TUTOR (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name", "type": "text", "required": true, "placeholder": "Your full name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Hi, I am a fitness trainer specializing in..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Training Specialization", - "fields": [ - {"id": "training_specialties", "label": "Training Specialties", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Weight Loss","value":"Weight Loss"},{"label":"Muscle Gain","value":"Muscle Gain"},{"label":"HIIT","value":"HIIT"},{"label":"Yoga/Flexibility","value":"Yoga/Flexibility"},{"label":"CrossFit","value":"CrossFit"},{"label":"Sports-specific","value":"Sports-specific"},{"label":"Rehabilitation","value":"Rehabilitation"}]}, - {"id": "certifications", "label": "Certifications", "type": "text", "required": false, "placeholder": "e.g., ACE CPT, NASM CPT, RYT-200"}, - {"id": "training_mode", "label": "Training Mode", "type": "select", "required": true, - "options": [{"label":"Online/Virtual","value":"Online"},{"label":"In-person (Client's location)","value":"In-person"},{"label":"Both Online and In-person","value":"Both"}]} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Session","value":"Per Session"},{"label":"Monthly Package","value":"Monthly Package"},{"label":"Quarterly Package","value":"Quarterly Package"},{"label":"Custom","value":"Custom"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price in INR", "validation": {"min": 0}}, - {"id": "availability", "label": "Availability", "type": "select", "required": true, - "options": [{"label":"Early Morning (5-8 AM)","value":"Early Morning"},{"label":"Morning (8-12 PM)","value":"Morning"},{"label":"Evening (5-9 PM)","value":"Evening"},{"label":"Flexible","value":"Flexible"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Transformation Photos / Certificates (up to 6)", "type": "file", "required": false, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload client transformation photos or certification images, max 2MB each."}, - {"id": "portfolio_url", "label": "Instagram / YouTube Channel", "type": "url", "required": false, "placeholder": "Your fitness social media or YouTube link"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your training philosophy and client success stories"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "location", "label": "City", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "subjects", "label": "Subjects Taught (comma separated)", "type": "text", "required": false}, {"id": "experience_years", "label": "Years of Experience", "type": "number", "required": false}, {"id": "bio", "label": "Short Bio", "type": "textarea", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Identity Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "address_proof", "label": "Address Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "qualification_proof", "label": "Qualification Proof", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true +FROM roles WHERE key = 'TUTOR' +ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; + +-- FITNESS_TRAINER (basic + documents + portfolio) +INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "first_name", "label": "First Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "last_name", "label": "Last Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "phone", "label": "Mobile Number", "type": "tel", "required": true}, {"id": "location", "label": "City", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "training_type", "label": "Training Type", "type": "select", "required": false, "options": [{"label": "Personal Training", "value": "Personal Training"}, {"label": "Group Fitness", "value": "Group Fitness"}, {"label": "Yoga", "value": "Yoga"}, {"label": "CrossFit", "value": "CrossFit"}, {"label": "Zumba", "value": "Zumba"}, {"label": "Pilates", "value": "Pilates"}, {"label": "Other", "value": "Other"}]}, {"id": "experience_years", "label": "Years of Experience", "type": "number", "required": false}, {"id": "bio", "label": "Short Bio", "type": "textarea", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Aadhar / Government ID", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "certification_doc", "label": "Fitness Certification", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'FITNESS_TRAINER' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version; --- CATERING_SERVICES (6 steps) +-- CATERING_SERVICES (basic + documents + portfolio) INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) -SELECT id, $json${ - "steps": [ - { - "id": "step_1_profile", - "title": "Profile Details", - "fields": [ - {"id": "full_name", "label": "Full Name / Business Name", "type": "text", "required": true, "placeholder": "Your name or catering business name"}, - {"id": "experience", "label": "Experience (Years)", "type": "number", "required": true, "validation": {"min": 0}}, - {"id": "bio", "label": "Bio", "type": "textarea", "required": true, "placeholder": "Tell us about your catering business and specialties..."} - ] - }, - { - "id": "step_2_contact", - "title": "Contact and Location", - "fields": [ - {"id": "email", "label": "Email", "type": "email", "required": true}, - {"id": "phone", "label": "Phone Number", "type": "tel", "required": true, "placeholder": "10-digit mobile number", "validation": {"pattern": "^[0-9]{10}$", "minLength": 10, "maxLength": 10}}, - {"id": "city", "label": "City", "type": "text", "required": true, "readOnly": true, "defaultValue": "Chennai, India"} - ] - }, - { - "id": "step_3_specialization", - "title": "Catering Specialization", - "fields": [ - {"id": "cuisine_types", "label": "Cuisine Types", "type": "select", "required": true, "multiple": true, - "options": [{"label":"South Indian","value":"South Indian"},{"label":"North Indian","value":"North Indian"},{"label":"Continental","value":"Continental"},{"label":"Chinese","value":"Chinese"},{"label":"Fusion","value":"Fusion"},{"label":"Biryani/Mughlai","value":"Biryani/Mughlai"}]}, - {"id": "dietary_options", "label": "Dietary Options", "type": "select", "required": true, "multiple": true, - "options": [{"label":"Pure Veg","value":"Pure Veg"},{"label":"Non-Veg","value":"Non-Veg"},{"label":"Vegan","value":"Vegan"},{"label":"Jain","value":"Jain"}]}, - {"id": "max_capacity", "label": "Max Capacity (Plates/Guests)", "type": "number", "required": true, "placeholder": "Maximum plates per event", "validation": {"min": 1}} - ] - }, - { - "id": "step_4_pricing", - "title": "Pricing and Availability", - "fields": [ - {"id": "pricing_model", "label": "Pricing Model", "type": "select", "required": true, - "options": [{"label":"Per Plate","value":"Per Plate"},{"label":"Per Event","value":"Per Event"},{"label":"Package-based","value":"Package-based"},{"label":"Custom Quote","value":"Custom Quote"}]}, - {"id": "base_rate", "label": "Base Rate (\u20b9)", "type": "number", "required": true, "placeholder": "Starting price (e.g., per plate or per event)", "validation": {"min": 0}}, - {"id": "advance_notice","label": "Advance Notice Required", "type": "select", "required": true, - "options": [{"label":"24 hours","value":"24 hours"},{"label":"2-3 days","value":"2-3 days"},{"label":"1 week","value":"1 week"},{"label":"2+ weeks","value":"2+ weeks"}]} - ] - }, - { - "id": "step_5_portfolio", - "title": "Portfolio", - "fields": [ - {"id": "portfolio_images", "label": "Food / Event Photos (up to 6)", "type": "file", "required": true, "multiple": true, "maxFiles": 6, "accept": "image/jpeg,image/jpg,image/png,image/webp", "maxSizeMB": 2, "helperText": "Upload your best food and event photos, max 2MB each. Displayed in 3\u00d72 grid."}, - {"id": "portfolio_url", "label": "Instagram / Google Business URL", "type": "url", "required": false, "placeholder": "Your food page or Google Business listing"}, - {"id": "portfolio_note", "label": "Portfolio Note", "type": "textarea", "placeholder": "Describe your specialty dishes and memorable events you have catered"} - ] - }, - { - "id": "step_6_verification", - "title": "Identity Verification", - "fields": [ - {"id": "id_type", "label": "ID Type", "type": "select", "required": true, - "options": [{"label":"Aadhaar Card","value":"Aadhaar Card"},{"label":"PAN Card","value":"PAN Card"},{"label":"Driving License","value":"Driving License"},{"label":"Voter ID","value":"Voter ID"},{"label":"Passport","value":"Passport"}]}, - {"id": "id_number", "label": "ID Number", "type": "text", "required": true, "placeholder": "Enter ID Number"}, - {"id": "id_document_upload", "label": "Upload ID Document", "type": "file", "required": true, "multiple": true, "maxFiles": 2, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 2} - ] - } - ] -}$json$::jsonb, 2, true +SELECT id, $json${"portfolioModel": "professional", "steps": [{"id": "step_1_basic", "title": "Basic Information", "type": "basic", "fields": [{"id": "business_name", "label": "Business Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "owner_name", "label": "Owner Name", "type": "text", "required": true, "lockAfterApproval": true}, {"id": "phone", "label": "Contact Number", "type": "tel", "required": true}, {"id": "location", "label": "City", "type": "text", "required": true}, {"id": "state", "label": "State", "type": "text", "required": true}, {"id": "cuisine_types", "label": "Cuisine Types (comma separated)", "type": "text", "required": false}, {"id": "bio", "label": "About Your Service", "type": "textarea", "required": false}]}, {"id": "step_2_documents", "title": "Documents", "type": "documents", "fields": [{"id": "aadhar_doc", "label": "Aadhar / Government ID", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}, {"id": "fssai_license", "label": "FSSAI License", "type": "file", "required": true, "lockAfterApproval": true, "accept": "application/pdf,image/jpeg,image/jpg,image/png", "maxSizeMB": 10, "helperText": "JPG, PNG or PDF - Max 10MB"}]}, {"id": "step_3_portfolio", "title": "My Portfolio", "type": "portfolio", "fields": [{"id": "about", "label": "About", "type": "textarea", "required": true}, {"id": "services_offered", "label": "Services & Pricing", "type": "textarea", "required": true}, {"id": "experience", "label": "Experience / Tools", "type": "textarea", "required": true}, {"id": "faqs", "label": "FAQs", "type": "textarea", "required": false}]}, {"id": "step_4_review", "title": "Review and Submit", "type": "review", "fields": []}]}$json$::jsonb, 2, true FROM roles WHERE key = 'CATERING_SERVICES' ON CONFLICT (role_id) WHERE is_active DO UPDATE SET schema_json = EXCLUDED.schema_json, version = EXCLUDED.version;