@page "/visa" @using System.Collections.Generic @using System.Linq
@* Page Header Area *@

Visa Services

Start, track and complete visa applications for SAQA travellers

@if (!string.IsNullOrEmpty(AlertFeedback)) {
@AlertFeedback
} @* KPI Grid *@
Active Applications
@ActiveAppsCount
3 in progress, 2 awaiting consulate
Approved This Month
@ApprovedThisMonth
100% success rate
Average Turnaround
@AverageTurnaround
Schengen and UK
Documents Pending
@DocumentsPending
Action required from traveller
@* Grid 2: Core Workflow and Table Panels *@
@* Visa Application Workflow Card *@

Visa Application Workflow

Start a new application or manage selected traveller's steps

@* Steps Progress Checklist *@ @if (ActiveWorkflowApp != null) {

Workflow Status: @ActiveWorkflowApp.Traveller (@ActiveWorkflowApp.Country)

Travel Date: @ActiveWorkflowApp.TravelDate
@foreach (var step in ActiveWorkflowApp.Steps.Select((value, i) => new { value, i })) {
@(step.i + 1)
@step.value.Label
@step.value.Sub
@step.value.Status
}
}
@* Application Tracker Card *@

Application Tracker

Live status across all travellers and destinations

Active Tracker
@foreach (var app in VisaApplications) { }
Traveller Country Type Stage Action
@app.Avatar
@app.Traveller
@app.Country @app.Type @app.Stage @if (app.Stage == "Docs required") { } else { }
@* Visa Information Library Card *@

Visa Information Library

Live entry rules database for 195 countries · Updated daily

Updated daily
@* Tile 1: Entry Requirements *@

Entry Requirements

Rules by passport nationality & destination country

@* Tile 2: Processing Times *@

Processing Times

Live consulate turnaround estimation tracker

@* Tile 3: Fee Calculator *@

Fee Calculator

Local embassy fees plus handling & VFS charges

@* Tile 4: Embassy Locator *@

Embassy Locator

Nearest consulates, hours, booking portal links

@* ===================== MODAL DIALOGS ===================== *@ @* 1. Visa Rules Database Modal *@ @if (ShowVisaRulesModal) {

Visa Rules Database

SAQA official compliance & reference database

@* Tabs bar *@
@if (ActiveLibraryTab == "requirements") {
Holders of South African passports require standard visas for UK, Schengen, and USA. eVise solutions are available for Kenya and UAE.
United Kingdom (Standard Visitor Visa)

Required documents: Valid passport (6 months validity), detailed travel itinerary, 3 months payslips, 3 months bank statements, employment verification letter from SAQA Human Resources.

Schengen Area (Short-stay Business Visa)

Required documents: Schengen application form, travel insurance coverage of min €30,000, flight itinerary with reserved return tickets, hotel bookings, official SAQA invitation/support letter, 3-months stamped bank statements.

USA (B1/B2 Visitor Visa)

Required: Completed online DS-160 form, payment confirmation slip, interview scheduled at US Embassy (Pretoria/Johannesburg), physical photo (5x5cm), SAQA letter affirming funding responsibility.

} @if (ActiveLibraryTab == "processing") {

Approximate turnaround times based on VFS Global and consulate metrics for South African applicants:

United Kingdom 10 to 15 working days
Schengen Area (Germany / France / Italy) 5 to 10 working days
USA (B1/B2) 30+ working days (Appointment backlog)
Kenya (eVisa) 1 to 3 working days
} @if (ActiveLibraryTab == "fees") {

Live Interactive Visa Fee Calculator

@{ var fees = GetCalculatedFees(CalcCountry, CalcVisaType); }
Embassy consulate fee (converted): R @fees.embassyFee.ToString("N0")
VFS Global/TLS handling fee: R @fees.serviceFee.ToString("N0")
Total Corporate Lodge Cost: R @fees.total.ToString("N0")
} @if (ActiveLibraryTab == "locator") {
VFS Global (Schengen & UAE Processing)

Address: 1st Floor, Riviera Road Office Park, 66 Riviera Rd, Johannesburg

Hours: Mon-Fri 08:00 - 15:00 · Appointment Required

TLScontact (United Kingdom Visa Center)

Address: 3rd Floor, 24 Central, 6 Fort St, Sandton, Johannesburg

Hours: Mon-Fri 08:30 - 16:00 · Priority service slots available

United States Consulate General

Address: 1 Sandton Dr, Sandhurst, Sandton

Hours: By strict scheduled appointment slip only

}
} @* 2. Start New Visa Application Modal *@ @if (ShowNewAppModal) {

Start New Visa Application

Initiate visa record for tracking & automated checklist support

@if (NewAppTraveller == "Custom") {
}
SAQA Corporate Compliance

Creating this record auto-populates pre-verified profile files, schedules background lodge card authorizations, and sends VFS checklists to the traveller.

} @* 3. Document Submission Portal Modal *@ @if (ShowUploadDocsModal) {

Document Submission Portal

Submit missing requirements to proceed with application

Awaiting: Signed visa declaration form & 3 months stamped bank statements.
@* Mock Drag and drop zone *@
Click here to simulate file selection
Simulates uploading PDF, JPG, PNG (Max 10MB per file)
@if (UploadSelectedFiles.Count > 0) {
Selected Files (@UploadSelectedFiles.Count):
@foreach (var file in UploadSelectedFiles) {
@file Ready
}
}
} @code { // Model Definitions public class VisaStep { public string Label { get; set; } = string.Empty; public string Sub { get; set; } = string.Empty; public string Status { get; set; } = string.Empty; public string StatusClass { get; set; } = string.Empty; } public class VisaApplication { public int Id { get; set; } public string Traveller { get; set; } = string.Empty; public string Avatar { get; set; } = string.Empty; public string Country { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public string Stage { get; set; } = string.Empty; public string TravelDate { get; set; } = string.Empty; public List Steps { get; set; } = new(); } // Core States private int ActiveAppsCount { get; set; } = 5; private int ApprovedThisMonth { get; set; } = 8; private string AverageTurnaround { get; set; } = "9 days"; private int DocumentsPending { get; set; } = 3; private string AlertFeedback { get; set; } = string.Empty; // Interactive Selectors private string VisaSelectedTraveller { get; set; } = "T. Maluleke"; private string VisaSelectedDestination { get; set; } = "United Kingdom"; private DateTime VisaTravelDate { get; set; } = new DateTime(2026, 6, 14); private int ActiveWorkflowId { get; set; } = 1; // Library interactive fee calculator state private string CalcCountry { get; set; } = "United Kingdom"; private string CalcVisaType { get; set; } = "Business"; // Modals visibility toggles private bool ShowVisaRulesModal { get; set; } = false; private bool ShowNewAppModal { get; set; } = false; private bool ShowUploadDocsModal { get; set; } = false; private string ActiveLibraryTab { get; set; } = "requirements"; // Modals data input forms private string NewAppTraveller { get; set; } = "T. Maluleke"; private string NewAppCustomTraveller { get; set; } = string.Empty; private string NewAppDestination { get; set; } = "Schengen Area"; private string NewAppType { get; set; } = "Business"; private DateTime NewAppDate { get; set; } = new DateTime(2026, 7, 15); // Document submissions portal tracker private int? SelectedUploadAppId { get; set; } = null; private List UploadSelectedFiles { get; set; } = new(); // Data Source List private List VisaApplications { get; set; } = new(); // Derived active app in workflow detail panel private VisaApplication? ActiveWorkflowApp => VisaApplications.FirstOrDefault(a => a.Id == ActiveWorkflowId) ?? VisaApplications.FirstOrDefault(); protected override void OnInitialized() { // Populate standard mock data matching React App's seed dataset VisaApplications = new List { new VisaApplication { Id = 1, Traveller = "T. Maluleke", Avatar = "TM", Country = "United Kingdom", Type = "Business", Stage = "At consulate", TravelDate = "2026-06-14", Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 2, document checklist", Sub = "Passport, photo, invitation letter, bank statement", Status = "4 of 4 uploaded", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = "Paid R 2,840", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "VFS Sandton, 28 May, 10:30", Status = "Booked", StatusClass = "bg-[#e2eefb] text-[#0066b3] border border-[#b4d3f5]" }, new VisaStep { Label = "Step 5, decision", Sub = "Visa sticker delivery to office", Status = "Awaiting", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" } } }, new VisaApplication { Id = 2, Traveller = "A. Dlamini", Avatar = "AD", Country = "Kenya", Type = "eVisa", Stage = "Approved", TravelDate = "2026-06-20", Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step-2, document checklist", Sub = "Passport copy, digital photo, itinerary", Status = "2 of 2 uploaded", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = "Paid R 1,250", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "Not Required (eVisa system)", Status = "Not Required", StatusClass = "bg-[#f4f7fb] text-[#5a6577] border border-[#e4eaf2]" }, new VisaStep { Label = "Step 5, decision", Sub = "Approved eVisa copy sent to traveller email", Status = "Approved", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" } } }, new VisaApplication { Id = 3, Traveller = "J. Mokoena", Avatar = "JM", Country = "Schengen Area", Type = "Business", Stage = "Docs uploaded", TravelDate = "2026-07-01", Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 2, document checklist", Sub = "Passport, photo, Schengen insurance, flights", Status = "4 of 4 uploaded", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = "Paid R 2,200", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "VFS Global Cherry Lane, 12 June, 09:15", Status = "Booked", StatusClass = "bg-[#e2eefb] text-[#0066b3] border border-[#b4d3f5]" }, new VisaStep { Label = "Step 5, decision", Sub = "Passport pending collection", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" } } }, new VisaApplication { Id = 4, Traveller = "N. Ndlovu", Avatar = "NN", Country = "USA", Type = "B1 / B2", Stage = "Docs required", TravelDate = "2026-08-15", Steps = new List { new VisaStep { Label = "Step 1, DS-160 application form", Sub = "Partially filled by traveller", Status = "In progress", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" }, new VisaStep { Label = "Step 2, document checklist", Sub = "Passport, US-spec photos, bank statements required", Status = "2 of 4 uploaded", StatusClass = "bg-[#fde5e8] text-[#dc2626] border border-[#f8b4b4]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Pending approval from travel authority", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "Awaiting DS-160 completion & payment", Status = "Not booked", StatusClass = "bg-[#f4f7fb] text-[#5a6577] border border-[#e4eaf2]" }, new VisaStep { Label = "Step 5, decision", Sub = "US Embassy Pretoria interview", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" } } }, new VisaApplication { Id = 5, Traveller = "S. Pillay", Avatar = "SP", Country = "UAE", Type = "eVisa", Stage = "Approved", TravelDate = "2026-06-05", Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 2, document checklist", Sub = "Passport copy, photo, hotel booking", Status = "2 of 2 uploaded", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = "Paid R 1,850", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "Not Required (eVisa system)", Status = "Not Required", StatusClass = "bg-[#f4f7fb] text-[#5a6577] border border-[#e4eaf2]" }, new VisaStep { Label = "Step 5, decision", Sub = "Approved eVisa copy sent to traveller email", Status = "Approved", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" } } } }; } // Workflows and Tracker actions private void OnWorkflowTravellerChanged() { var matched = VisaApplications.FirstOrDefault(a => a.Traveller.Equals(VisaSelectedTraveller, System.StringComparison.OrdinalIgnoreCase)); if (matched != null) { ActiveWorkflowId = matched.Id; } } private void SetActiveWorkflow(int id) { ActiveWorkflowId = id; var app = VisaApplications.FirstOrDefault(a => a.Id == id); if (app != null) { VisaSelectedTraveller = app.Traveller; VisaSelectedDestination = app.Country; } } private void HandleStartWorkflow() { var existing = VisaApplications.FirstOrDefault(app => app.Traveller.Equals(VisaSelectedTraveller, System.StringComparison.OrdinalIgnoreCase)); if (existing != null) { ActiveWorkflowId = existing.Id; TriggerFeedback($"Application for {VisaSelectedTraveller} already exists. Showing current progress below."); } else { var initials = string.Concat(VisaSelectedTraveller.Split(' ').Select(n => n.Length > 0 ? n[0].ToString() : string.Empty)).ToUpper(); if (string.IsNullOrEmpty(initials)) initials = "TR"; var newId = (VisaApplications.Any() ? VisaApplications.Max(a => a.Id) : 0) + 1; var newApp = new VisaApplication { Id = newId, Traveller = VisaSelectedTraveller, Avatar = initials, Country = VisaSelectedDestination, Type = "Business", Stage = "Docs uploaded", TravelDate = VisaTravelDate.ToString("yyyy-MM-dd"), Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 2, document checklist", Sub = "Passport copy, digital photos, supporting letters", Status = "4 of 4 uploaded", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = "Paid R 2,150", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = "Awaiting appointment confirmation", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" }, new VisaStep { Label = "Step 5, decision", Sub = "Embassy review in progress", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" } } }; VisaApplications.Insert(0, newApp); ActiveWorkflowId = newId; ActiveAppsCount++; TriggerFeedback($"Successfully initialized a new {VisaSelectedDestination} visa application workflow for {VisaSelectedTraveller}."); } } private void HandleCreateNewAppFromModal() { var name = NewAppTraveller == "Custom" ? NewAppCustomTraveller : NewAppTraveller; if (string.IsNullOrWhiteSpace(name)) { TriggerFeedback("Please enter a traveller name."); return; } var initials = string.Concat(name.Split(' ').Select(n => n.Length > 0 ? n[0].ToString() : string.Empty)).ToUpper(); if (string.IsNullOrEmpty(initials)) initials = "TR"; var newId = (VisaApplications.Any() ? VisaApplications.Max(a => a.Id) : 0) + 1; bool isUS = NewAppDestination == "USA"; var newApp = new VisaApplication { Id = newId, Traveller = name, Avatar = initials, Country = NewAppDestination, Type = NewAppType, Stage = isUS ? "Docs required" : "Docs uploaded", TravelDate = NewAppDate.ToString("yyyy-MM-dd"), Steps = new List { new VisaStep { Label = "Step 1, application form", Sub = "Auto populated from traveller profile", Status = "Auto filled", StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 2, document checklist", Sub = isUS ? "Passport, photo, US visa fee, 3-months bank statement" : "Passport copy, photos, travel insurance, hotel bookings", Status = isUS ? "2 of 4 uploaded" : "4 of 4 uploaded", StatusClass = isUS ? "bg-[#fde5e8] text-[#dc2626] border border-[#f8b4b4]" : "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 3, fee payment", Sub = "Settled via SAQA lodge card", Status = isUS ? "Pending" : "Paid R 2,250", StatusClass = isUS ? "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" : "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]" }, new VisaStep { Label = "Step 4, consulate appointment", Sub = isUS ? "Awaiting DS-160 and payment" : "VFS Global appointment confirmed", Status = isUS ? "Not booked" : "Booked", StatusClass = isUS ? "bg-[#f4f7fb] text-[#5a6577] border border-[#e4eaf2]" : "bg-[#e2eefb] text-[#0066b3] border border-[#b4d3f5]" }, new VisaStep { Label = "Step 5, decision", Sub = "Visa sticker or eVisa approval pending review", Status = "Pending", StatusClass = "bg-[#fff4dd] text-[#b06b00] border border-[#fbd897]" } } }; VisaApplications.Insert(0, newApp); ActiveWorkflowId = newId; ActiveAppsCount++; if (isUS) { DocumentsPending++; } TriggerFeedback($"New application created successfully for {name} ({NewAppDestination})."); ShowNewAppModal = false; NewAppCustomTraveller = string.Empty; } private void HandleOpenUploadDocs(int appId) { SelectedUploadAppId = appId; UploadSelectedFiles.Clear(); ShowUploadDocsModal = true; } private void TriggerMockFileUpload() { // Simulates double-clicking or uploading typical checklist files in Blazor page UploadSelectedFiles = new List { "signed_visa_form.pdf", "stamped_bank_statements_3_months.pdf" }; StateHasChanged(); } private void HandleConfirmUploadDocs() { if (SelectedUploadAppId == null) return; var targetId = SelectedUploadAppId.Value; var app = VisaApplications.FirstOrDefault(a => a.Id == targetId); if (app != null) { app.Stage = "Docs uploaded"; if (app.Steps.Count >= 3) { app.Steps[1].Status = "4 of 4 uploaded"; app.Steps[1].StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]"; app.Steps[2].Status = "Paid R 2,840"; app.Steps[2].StatusClass = "bg-[#e3f6ed] text-[#22a06b] border border-[#a1dfc2]"; } DocumentsPending = System.Math.Max(0, DocumentsPending - 1); TriggerFeedback($"Documents uploaded successfully for {app.Traveller}. Application is now complete for submission!"); } ShowUploadDocsModal = false; SelectedUploadAppId = null; } // Modal Helpers private void OpenLibraryTab(string tabName) { ActiveLibraryTab = tabName; ShowVisaRulesModal = true; } private void SetActiveLibraryTab(string tabName) { ActiveLibraryTab = tabName; } private void CloseVisaRulesModal() { ShowVisaRulesModal = false; } private void OpenNewAppModal() { ShowNewAppModal = true; } private void CloseNewAppModal() { ShowNewAppModal = false; } private void CloseUploadDocsModal() { ShowUploadDocsModal = false; } // Fee calculations private (int embassyFee, int serviceFee, int total) GetCalculatedFees(string country, string type) { int embassyFee = 2450; int serviceFee = 450; if (country == "United Kingdom") { embassyFee = type == "Transit" ? 950 : type == "Tourist" ? 2150 : 2840; } else if (country == "Schengen Area") { embassyFee = type == "Transit" ? 820 : type == "Tourist" ? 1820 : 2200; } else if (country == "USA") { embassyFee = type == "Transit" ? 1950 : type == "Tourist" ? 3420 : 3650; } else if (country == "Kenya") { embassyFee = 540; serviceFee = 150; } else if (country == "UAE") { embassyFee = type == "Transit" ? 1150 : type == "Tourist" ? 1650 : 1850; } return (embassyFee, serviceFee, embassyFee + serviceFee); } private void HandleAuthorizeLodgeCardFee(int totalFee) { TriggerFeedback($"Authorized R {totalFee.ToString("N0")} on Lodge Card for {CalcCountry} ({CalcVisaType} Visa)."); } // Badge CSS Mapper helper private string GetStageBadgeClass(string stage) { return stage switch { "Approved" => "bg-[#e3f6ed] text-[#22a06b]", "Docs required" => "bg-[#fde5e8] text-[#dc2626]", "Docs uploaded" => "bg-[#e2eefb] text-[#0066b3]", _ => "bg-[#fff4dd] text-[#b06b00]" }; } // Toast/Feedback Trigger private void TriggerFeedback(string message) { AlertFeedback = message; // Simulate simple asynchronous auto-dismiss in C# Blazor _ = DismissAlertAfterDelay(); } private async System.Threading.Tasks.Task DismissAlertAfterDelay() { await System.Threading.Tasks.Task.Delay(5000); AlertFeedback = string.Empty; StateHasChanged(); } }