# Contact Create Flow (Frontend Guide) This document explains the full flow to create a contact, send an automatic invite email, and let the invited user set their first password. ## 1) Prerequisites 1. Admin user is already logged in and has an `access token`. 2. Admin user has already configured a Security PIN. 3. Frontend knows the target `role_id` (fetch from `GET /api/v1/roles/get-all`). ## 2) Endpoints Used 1. `POST /api/v1/auth/pin/verify` to get a PIN `action_token`. 2. `POST /api/v1/contacts/create` to create the contact (auto invite enabled). 3. `POST /api/v1/auth/set-password` to set the first password from invite token. ## 3) Detailed Flow ### Step A - Verify PIN for contact create action Action ID for contact create: - `2a59e8b4-5a72-4be3-b8fc-4f49941181c3` Request: ```json { "data": { "type": "auth_pin_verify", "attributes": { "pin": "123456", "action": "2a59e8b4-5a72-4be3-b8fc-4f49941181c3" } } } ``` Success response: ```json { "data": { "type": "auth_pin_verify", "attributes": { "verified": true, "action_token": "", "action_token_ttl_ms": 300000 } } } ``` Store `action_token`, then pass it to contact create in header: - `X-PIN-Verification-Token: ` ### Step B - Create Contact (auto invite) Endpoint: - `POST /api/v1/contacts/create` Required headers: - `Authorization: Bearer ` - `X-PIN-Verification-Token: ` - `Content-Type: application/json` Minimum required body: - `data.type = contact__create` - `attributes.role_id` - `attributes.email` - `attributes.first_name` - `attributes.last_name` Allowed create types: - `contact_pilot_create` - `contact_doctor_create` - `contact_air_rescuer_create` - `contact_technician_create` - `contact_flight_assistant_create` - `contact_staff_create` Rules: - `data.type` must match the role resolved from `attributes.role_id`. - Only one role-specific profile object is allowed, and it must match `data.type`. > Note: backend now **automatically sends an invite email** after successful contact creation. If PIN token is missing/invalid, API returns `202` with PIN verification required error. ### Step C - User clicks invite link from email Invite email contains a link like: - `https://frontend-domain/set-password?token=` This token already carries user identity (email + user id claims) and is single-use. ### Step D - Frontend submits set password Endpoint: - `POST /api/v1/auth/set-password` Request: ```json { "data": { "type": "auth_set_password", "attributes": { "token": "", "password": "StrongP@ssw0rd123" } } } ``` If successful, user can directly log in using `POST /api/v1/auth/login`. ## 4) Contact Create Payloads by Role ## Pilot ```json { "data": { "type": "contact_pilot_create", "attributes": { "role_id": "", "email": "pilot1@example.com", "username": "pilot1", "first_name": "Marco", "last_name": "Melandri", "mobile_phone": "+628123456789", "timezone": "Asia/Jakarta", "is_active": true, "pilot": { "pilot_category": "regular", "short_name": "Marco", "license_no": "PIL-001", "license_issued_at": "2025-01-01T00:00:00Z", "license_expired_at": "2028-01-01T00:00:00Z", "technician_license_no": "TECH-001", "has_ifr_qualification": true, "is_chief_pilot": false, "is_dul": false, "total_flight_minutes": 12000, "responsible_pilot_minutes": 5000, "second_pilot_minutes": 3000, "double_command_minutes": 1000, "flight_instructor_minutes": 500, "night_flight_minutes": 1200, "ifr_flight_minutes": 800, "location": "Jakarta", "postcode": "12950", "street_line": "Jl. Sudirman No. 1", "weight_kg": 72.5, "photo_file_id": "/uploads/contacts/pilot1.jpg" } } } } ``` ## Doctor ```json { "data": { "type": "contact_doctor_create", "attributes": { "role_id": "", "email": "doctor1@example.com", "username": "doctor1", "first_name": "Alicia", "last_name": "Pratama", "mobile_phone": "+628123450001", "timezone": "Asia/Jakarta", "is_active": true, "doctor": { "short_name": "Dr. Alicia", "medical_license_no": "MED-DOCTOR-001", "license_issued_at": "2024-01-01T00:00:00Z", "license_expired_at": "2029-01-01T00:00:00Z", "specialization": "Emergency Medicine", "sub_specialization": "Trauma", "is_chief_physician": false, "location": "Jakarta", "postcode": "12950", "street_line": "Jl. Jenderal Sudirman No. 10", "photo_file_id": "/uploads/contacts/doctor1.jpg" } } } } ``` ## Air Rescuer ```json { "data": { "type": "contact_air_rescuer_create", "attributes": { "role_id": "", "email": "airrescuer1@example.com", "username": "airrescuer1", "first_name": "Raka", "last_name": "Wijaya", "mobile_phone": "+628123450002", "timezone": "Asia/Jakarta", "is_active": true, "air_rescuer": { "short_name": "Raka", "is_chief_physician_in_charge": false, "responsible_flight_rescuer": false, "location": "Bandung", "postcode": "40115", "street_line": "Jl. Merdeka No. 20", "photo_file_id": "/uploads/contacts/airrescuer1.jpg" } } } } ``` ## Technician ```json { "data": { "type": "contact_technician_create", "attributes": { "role_id": "", "email": "tech1@example.com", "username": "tech1", "first_name": "Bima", "last_name": "Nugraha", "mobile_phone": "+628123450003", "timezone": "Asia/Jakarta", "is_active": true, "technician": { "short_name": "Bima", "license_no": "TECH-7788", "is_chief_technician": false, "is_responsible_complaints": true, "location": "Surabaya", "postcode": "60231", "street_line": "Jl. Diponegoro No. 30", "photo_file_id": "/uploads/contacts/tech1.jpg" } } } } ``` ## Flight Assistant ```json { "data": { "type": "contact_flight_assistant_create", "attributes": { "role_id": "", "email": "fa1@example.com", "username": "fa1", "first_name": "Nadia", "last_name": "Putri", "mobile_phone": "+628123450004", "timezone": "Asia/Jakarta", "is_active": true, "flight_assistant": { "short_name": "Nadia", "location": "Yogyakarta", "postcode": "55281", "street_line": "Jl. Kaliurang No. 15", "photo_file_id": "/uploads/contacts/fa1.jpg" } } } } ``` ## Staff ```json { "data": { "type": "contact_staff_create", "attributes": { "role_id": "", "email": "staff1@example.com", "username": "staff1", "first_name": "Dina", "last_name": "Maharani", "mobile_phone": "+628123450005", "timezone": "Asia/Jakarta", "is_active": true, "staff": { "short_name": "Dina", "role_label": "Operations Staff", "location": "Semarang", "postcode": "50132", "street_line": "Jl. Pandanaran No. 8", "photo_file_id": "/uploads/contacts/staff1.jpg" } } } } ``` ## 5) Example Create Contact Response ```json { "data": { "type": "contact", "id": "019ce67e-9289-731d-ae4c-0156902aabbc", "attributes": { "role_code": "doctor", "role_name": "doctor", "email": "doctor1@example.com", "first_name": "Alicia", "last_name": "Pratama", "is_active": true, "doctor": { "short_name": "Dr. Alicia", "medical_license_no": "MED-DOCTOR-001" }, "invite_sent": true, "invite_error": "" } } } ``` If invite sending fails (for example SES/SQS config issue): - contact create is still successful, - `invite_sent = false`, - `invite_error` contains the error message. ## 6) Error Cases Frontend Should Handle 1. `202 PIN verification required` on contact create: - call `/auth/pin/verify` again (Step A), then retry create with `X-PIN-Verification-Token`. 2. `422 Validation error`: - check payload fields (invalid role_id, invalid email, etc). 3. `400` on create: - typically business/constraint conflict. 4. `400` on set-password: - token invalid/expired/used or password does not meet rules. ## 7) Frontend Integration Summary 1. Admin logs in. 2. Fetch `role_id` from roles list. 3. Verify PIN (`/auth/pin/verify` with contact-create action ID). 4. Create contact (`/contacts/create` + `X-PIN-Verification-Token`). 5. Wait for invited user to open invite email. 6. Set-password page reads `token` from query param. 7. Submit `/auth/set-password` with `{ token, password }`. 8. Redirect user to login page.