Psyter Web Portal - Detailed Structure (Part 2 of 2)

Project: Web Portal (.NET Framework MVC)
Technology: ASP.NET MVC 5, .NET Framework 4.7.2, jQuery, Bootstrap
Purpose: Controllers, Views, and Frontend Components
Last Updated: November 5, 2025


📂 Controllers Overview

Total Controllers: 15+ controllers across 5 modules

Module Breakdown:
1. Public/Web (1 controller) - Public website
2. Patient (2 controllers) - Patient portal
3. Physician (3 controllers) - Provider portal
4. Admin (5+ controllers) - Administration
5. Common (4 controllers) - Shared functionality


🌐 WebController (Public Website)

Route: /Web/*
Authorization: [AllowAnonymous] - No login required
Purpose: Public-facing website pages

Total Actions: 20+ public pages

Key Actions

Home()

[CustomRouting]
public async Task<ActionResult> Home()
{
    string viewPath = "";

    // Select language-specific view
    if (Session[SessionVariables.CurrentLanguage] == AppConstants.Languages.English)
    {
        viewPath = "~/Views/Web/English/Home.cshtml";
    }
    else
    {
        viewPath = "~/Views/Web/Arabic/Home.cshtml";
    }

    // Get SEO page details
    var pageDetails = await Common.Common.GetPageDetailForSEOFromDB("Home");

    return View(viewPath, pageDetails);
}

Features:
- Landing page with hero section
- Featured therapists
- Service highlights
- Call-to-action buttons
- SEO-optimized meta tags

PsychologicalTest()

[CustomRouting]
public async Task<ActionResult> PsychologicalTest()
{
    // Redirect logged-in users to their dashboard
    if (Session[SessionVariables.UserInfo] != null)
    {
        return RedirectToAction("ScreeningQuestions", "Client");
    }

    // Get screening questions
    DALManager api = new DALManager(ApiType.psyter);
    APIAuthTokensResponse auth = 
        (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject response = await api.GetData(
        "", 
        PsyterApiMethodNames.PScreeningQuestions, 
        auth.APIAuthToken.access_token
    );

    ScreenigQuestionOptionResponse questions = 
        response.ToObject<ScreenigQuestionOptionResponse>();

    if (questions.Status == 1)
    {
        ViewBag.QuestionOne = questions.ScreeningQuestions[0];
        ViewBag.QuestionData = JsonConvert.SerializeObject(questions);
    }

    var pageDetails = await Common.Common.GetPageDetailForSEOFromDB("PsychologicalTest");

    return View(viewPath, pageDetails);
}

Features:
- Free psychological screening test
- Multi-step questionnaire
- Result analysis
- Therapist recommendations based on score

PhysicianList()

[CustomRouting]
public async Task<ActionResult> PhysicianList()
{
    // Therapist listing page
    var pageDetails = await Common.Common.GetPageDetailForSEOFromDB("PhysicianList");

    return View(viewPath, pageDetails);
}

Features:
- Search and filter therapists
- View profiles
- Check availability
- Book appointments (requires login)

Login()

[HttpGet]
public ActionResult Login(string returnUrl)
{
    ViewBag.ReturnUrl = returnUrl;
    return View();
}

[HttpPost]
public async Task<ActionResult> Login(LoginRequest request)
{
    ApiDataAccess api = new ApiDataAccess();
    JObject userDetails = await api.GetUserDetails(
        request.Username, 
        request.Password
    );

    if (userDetails["access_token"] != null)
    {
        // Store user info in session
        Session[SessionVariables.UserInfo] = userDetails;

        // Get user profile status
        JObject profileStatus = await api.GetUserProfileStatus(
            userDetails["UserLoginInfoId"].ToString()
        );
        Session[SessionVariables.UserProfileStatus] = profileStatus;

        // Redirect based on user type
        int userType = (int)userDetails["UserTypeId"];

        switch (userType)
        {
            case 1: // Patient
                return RedirectToAction("Index", "Client");
            case 2: // Provider
                return RedirectToAction("Index", "ServiceProvider");
            case 3: // Admin
                return RedirectToAction("Index", "Admin");
            case 4: // Accountant
                return RedirectToAction("Index", "Accountant");
            case 5: // Content Manager
                return RedirectToAction("Index", "ContentManager");
            case 8: // Organization
                return RedirectToAction("Dashboard", "Organization");
            default:
                return RedirectToAction("Home");
        }
    }

    ViewBag.Error = "Invalid username or password";
    return View(request);
}

Other Public Pages

FAQ() - Frequently asked questions
LearnMore() - About mental health
TermsCondition() - Terms of service
PrivacyPolicy() - Privacy policy
ContactUs() - Contact form
AboutUs() - About Psyter
HowItWorks() - Platform guide


👤 Patient Controllers

1. ClientController (Patient Portal)

Route: /Client/*
Authorization: [IsClient] - Patients only
Purpose: Patient dashboard and profile management

Total Actions: 40+ patient-specific actions

Index()

public ActionResult Index()
{
    // Check profile completion
    var profileComponents = 
        Session[SessionVariables.UserProfileStatus] as List<CatProfileComponent>;

    var requiredComponents = profileComponents
        .Where(x => x.Id == 1 || x.Id == 2) // Personal + Contact
        .Where(x => x.IsComplete == true)
        .ToList();

    ViewBag.IsProfileCompleted = (requiredComponents.Count >= 2);

    return View("~/Views/Patient/English/Index.cshtml");
}

Dashboard Features:
- Upcoming appointments
- Recent therapists
- Profile completion status
- Quick booking
- Screening results
- Payment history

ClientProfile()

public ActionResult ClientProfile()
{
    // Patient profile management
    return PartialView("~/Views/Patient/English/PatientProfile.cshtml");
}

Profile Sections:
- Personal information
- Contact information
- Emergency contacts
- Health information
- Insurance details (if applicable)

Appointments()

public ActionResult Appointments()
{
    // Show payment status if returning from payment gateway
    if (TempData["responseParameters"] != null)
    {
        var parameters = (SortedDictionary<string, string>)TempData["responseParameters"];
        ViewBag.StatusCode = parameters["Response.StatusCode"];
        ViewBag.StatusDescription = parameters["Response.StatusDescription"];
    }

    return PartialView("~/Views/Common/English/MyAppointments.cshtml");
}

Appointment Management:
- View upcoming appointments
- View past appointments
- Cancel appointments
- Reschedule appointments
- Join video sessions
- Rate completed sessions

TherapistList()

public ActionResult TherapistList()
{
    // Browse and book therapists
    return PartialView("~/Views/Web/English/PhysicianList.cshtml");
}

Therapist Discovery:
- Search by name, specialty, language
- Filter by gender, experience, fees
- View profiles and ratings
- Check real-time availability
- Book appointments

BookAppointment()

[HttpPost]
public async Task<ActionResult> BookAppointment(BookingRequest request)
{
    var userInfo = Session[SessionVariables.UserInfo];
    request.ClientId = userInfo["UserLoginInfoId"];

    ApiDataAccess api = new ApiDataAccess();
    var json = JsonConvert.SerializeObject(request);

    JObject response = await api.BookAppointment(
        json, 
        userInfo["access_token"]
    );

    if (response["Status"].ToString() == "1")
    {
        // Booking successful
        TempData["BookingSuccess"] = true;
        TempData["BookingId"] = response["Data"]["SlotBookingId"];

        // Redirect to payment
        return RedirectToAction("PaymentGateway", new { 
            bookingId = response["Data"]["SlotBookingId"] 
        });
    }

    TempData["Error"] = response["Message"];
    return RedirectToAction("TherapistList");
}

MyWallet()

public ActionResult MyWallet()
{
    // Wallet management
    return PartialView("~/Views/Patient/English/MyWallet.cshtml");
}

Wallet Features:
- View balance
- Add funds
- Transaction history
- Withdraw funds
- Payment methods

ScreeningQuestions()

public async Task<ActionResult> ScreeningQuestions()
{
    // Get screening questions
    DALManager api = new DALManager(ApiType.psyter);
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject response = await api.GetData(
        "", 
        PsyterApiMethodNames.PScreeningQuestions, 
        auth.APIAuthToken.access_token
    );

    return View("~/Views/Patient/English/ScreeningQuestions.cshtml", response);
}

[HttpPost]
public async Task<ActionResult> SubmitScreening(ScreeningSubmissionRequest request)
{
    var userInfo = Session[SessionVariables.UserInfo];
    request.UserId = userInfo["UserLoginInfoId"];

    ApiDataAccess api = new ApiDataAccess();
    var json = JsonConvert.SerializeObject(request);

    JObject response = await api.SubmitScreening(
        json, 
        userInfo["access_token"]
    );

    return Json(response, JsonRequestBehavior.AllowGet);
}

2. ScreeningController (Assessment)

Route: /Client/Screening/*
Authorization: [IsClient]
Purpose: Psychological screening and assessment

TakeAssessment()

public async Task<ActionResult> TakeAssessment()
{
    // Load questionnaire
    var questions = await GetScreeningQuestions();
    return View(questions);
}

GetResults()

public async Task<ActionResult> GetResults(long assessmentId)
{
    // Get assessment results and recommendations
    var results = await GetAssessmentResults(assessmentId);
    return View(results);
}

🩺 Physician Controllers

1. ServiceProviderController (Provider Portal)

Route: /ServiceProvider/*
Authorization: [IsServiceProvider] - Providers only
Purpose: Provider dashboard and profile management

Total Actions: 50+ provider-specific actions

Index()

public ActionResult Index()
{
    // Check profile completion
    var profileComponents = 
        Session[SessionVariables.UserProfileStatus] as List<CatProfileComponent>;

    // Providers need: Personal, Contact, Education, Experience
    var requiredComponents = profileComponents
        .Where(x => x.Id == 1 || x.Id == 2 || x.Id == 5 || x.Id == 6)
        .Where(x => x.IsComplete == true)
        .ToList();

    ViewBag.IsProfileCompleted = (requiredComponents.Count >= 4);

    return View("~/Views/Physician/English/Index.cshtml");
}

Provider Dashboard:
- Today’s appointments
- Upcoming schedule
- Earnings summary
- Patient requests
- Messages
- Profile completion

ProviderProfile()

public ActionResult ProviderProfile()
{
    return PartialView("~/Views/Physician/English/ProviderProfile.cshtml");
}

Profile Components:
- Personal information
- Contact information
- Education history
- Work experience
- Specialties
- Languages spoken
- Consultation fees
- Short bio
- CV upload
- License/certificate upload

Appointments()

public ActionResult Appointments()
{
    return PartialView("~/Views/Patient/English/MyAppointments.cshtml");
}

Appointment Management:
- View scheduled appointments
- View patient details
- Start video session
- Cancel appointments
- Mark no-show
- View appointment history

MyClients()

public ActionResult MyClients()
{
    return PartialView("~/Views/Physician/English/MyClientsListing.cshtml");
}

Client Management:
- View patient list
- Search patients
- View patient history
- Access session notes
- View assessments

MyHistory()

public ActionResult MyHistory()
{
    return PartialView("~/Views/Physician/English/MyHistory.cshtml");
}

Session History:
- Completed sessions
- Session notes
- Prescriptions issued
- Homework assigned
- Patient progress

MyEarnings()

public async Task<ActionResult> MyEarnings()
{
    var userInfo = Session[SessionVariables.UserInfo];

    ApiDataAccess api = new ApiDataAccess();
    JObject earnings = await api.GetProviderEarnings(
        userInfo["UserLoginInfoId"],
        userInfo["access_token"]
    );

    return PartialView("~/Views/Physician/English/MyEarnings.cshtml", earnings);
}

Earnings Management:
- Total earnings
- Pending payments
- Completed payments
- Payment history
- Withdraw funds
- Bank account setup

2. PhysicianAvailabilityController

Route: /PhysicianAvailability/*
Authorization: [IsServiceProvider]
Purpose: Schedule and availability management

ManageAvailability()

public ActionResult ManageAvailability()
{
    return View("~/Views/Physician/English/ManageAvailability.cshtml");
}

Availability Features:
- Set weekly schedule
- Block time slots
- Set vacation dates
- Configure slot duration (30/45/60 min)
- Emergency unavailability

SaveAvailability()

[HttpPost]
public async Task<ActionResult> SaveAvailability(AvailabilityRequest request)
{
    var userInfo = Session[SessionVariables.UserInfo];
    request.ServiceProviderId = userInfo["UserLoginInfoId"];

    // Call scheduling API
    string schedulingBaseUrl = ConfigurationManager.AppSettings["SchedulingApiBasePath"];
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri(schedulingBaseUrl);

    var json = JsonConvert.SerializeObject(request);
    var content = new StringContent(json, Encoding.UTF8, "application/json");

    HttpResponseMessage response = await client.PostAsync(
        "availability/save", 
        content
    );

    var responseJson = await response.Content.ReadAsStringAsync();
    return Json(JObject.Parse(responseJson), JsonRequestBehavior.AllowGet);
}

3. PrescriptionController

Route: /Prescription/*
Authorization: [IsServiceProvider]
Purpose: Prescription management

CreatePrescription()

[HttpPost]
public async Task<ActionResult> CreatePrescription(PrescriptionRequest request)
{
    var userInfo = Session[SessionVariables.UserInfo];
    request.ProviderId = userInfo["UserLoginInfoId"];

    ApiDataAccess api = new ApiDataAccess();
    var json = JsonConvert.SerializeObject(request);

    JObject response = await api.SavePrescription(
        json, 
        userInfo["access_token"]
    );

    return Json(response, JsonRequestBehavior.AllowGet);
}

Prescription Features:
- Create prescriptions
- Medication details
- Dosage instructions
- Duration
- Notes
- Send to patient
- PDF generation


👨‍💼 Admin Controllers

1. Admin/Catalogue/CatalogueController

Route: /Admin/Catalogue/*
Authorization: [IsAdmin]
Purpose: System configuration and catalogues

ManageSystemPercentage()

public ActionResult ManageSystemPercentage()
{
    // Manage platform fees
    return PartialView("~/Views/Admin/Catalogue/English/ManageSystemCharges.cshtml");
}

System Configuration:
- Platform fee percentage
- VAT percentage
- Minimum booking amount
- Maximum booking amount
- Refund policies

ViewMonthlyReport()

public ActionResult ViewMonthlyReport()
{
    return PartialView("~/Views/Admin/Catalogue/English/ViewMonthlyReport.cshtml");
}

Reports:
- Monthly revenue
- Bookings by status
- Provider earnings
- Patient registrations
- Payment methods used

ViewMonthlyStatistics()

public ActionResult ViewMonthlyStatistics()
{
    return PartialView("~/Views/Admin/Catalogue/English/ViewMonthlyStatistics.cshtml");
}

Statistics:
- User registrations (trend)
- Bookings (trend)
- Revenue (trend)
- Active users
- Top providers
- Popular specialties

GetGoogleAnalyticsReportData()

[HttpPost]
public async Task<ActionResult> GetGoogleAnalyticsReportData(
    GetGoogleAnalyticsRequest request
)
{
    request.DimensionName = "platform";
    var response = await Common.Common.GetGoogleAnalyticsReportData(request);

    return Json(response, JsonRequestBehavior.AllowGet);
}

Google Analytics Integration:
- Page views
- User sessions
- Bounce rate
- Conversion rate
- Traffic sources
- Device breakdown

ManageOrganizations()

public ActionResult ManageOrganizations()
{
    return View("~/Views/Admin/Catalogue/English/ManageOrganizations.cshtml");
}

Organization Management:
- Create organizations
- Assign API keys
- Set booking limits
- View organization bookings
- Manage credits

SendNotifications()

public ActionResult SendNotifications()
{
    return PartialView("~/Views/Admin/Catalogue/English/SendNotifications.cshtml");
}

Push Notifications:
- Send to all users
- Send to specific user types
- Send to specific users
- Schedule notifications
- View notification history

2. UserManagerController

Route: /UserManager/*
Authorization: [IsAdmin]
Purpose: User management

ManageUsers()

public async Task<ActionResult> ManageUsers(int userType)
{
    ApiDataAccess api = new ApiDataAccess();
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject users = await api.GetUsersByType(
        userType, 
        auth.APIAuthToken.access_token
    );

    ViewBag.UserType = userType;
    return View("~/Views/Admin/English/ManageUsers.cshtml", users);
}

User Management:
- View all users (patients/providers/admins)
- Search users
- Filter by status
- Activate/deactivate users
- View user details
- Edit user information

ApproveProvider()

[HttpPost]
public async Task<ActionResult> ApproveProvider(long providerId)
{
    ApiDataAccess api = new ApiDataAccess();
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject response = await api.ApproveProvider(
        providerId, 
        auth.APIAuthToken.access_token
    );

    return Json(response, JsonRequestBehavior.AllowGet);
}

Provider Approval:
- Review provider applications
- Verify credentials
- Approve/reject
- Request more information

3. AccountantController

Route: /Accountant/*
Authorization: [IsAccountant]
Purpose: Financial management

ManagePayments()

public ActionResult ManagePayments()
{
    return View("~/Views/Accountant/English/ManagePayments.cshtml");
}

Payment Management:
- View all transactions
- Filter by date/status/user
- Process refunds
- Generate invoices
- Export to Excel
- Payment reconciliation

ProcessWithdrawal()

[HttpPost]
public async Task<ActionResult> ProcessWithdrawal(WithdrawalRequest request)
{
    ApiDataAccess api = new ApiDataAccess();
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject response = await api.ProcessWithdrawal(
        request, 
        auth.APIAuthToken.access_token
    );

    return Json(response, JsonRequestBehavior.AllowGet);
}

Withdrawal Management:
- View pending withdrawals
- Verify bank details
- Approve/reject
- Upload payment proof
- Mark as paid

4. ContentManagerController

Route: /ContentManager/*
Authorization: [IsContentManager]
Purpose: Content management

ManageBlogs()

public ActionResult ManageBlogs()
{
    return View("~/Views/ContentManager/English/ManageBlogs.cshtml");
}

Blog Management:
- Create blog posts
- Edit posts
- Delete posts
- Publish/unpublish
- Add images
- SEO settings

5. SEOManagerController

Route: /SEOManager/*
Authorization: [IsSeoManager]
Purpose: SEO optimization

ManageSEO()

public ActionResult ManageSEO()
{
    return View("~/Views/SEOManager/English/ManageSEO.cshtml");
}

SEO Management:
- Edit page titles
- Edit meta descriptions
- Edit meta keywords
- Manage redirects
- Generate sitemaps


🔄 CommonController (Shared)

Route: /Common/*
Authorization: [AllowAnonymous]
Purpose: Shared API endpoints for all pages

Catalogue APIs

GetCountries()

public async Task<ActionResult> GetCountries()
{
    DALManager api = new DALManager(ApiType.psyter);
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];

    JObject response = await api.GetData(
        "", 
        PsyterApiMethodNames.CmnGetcountries, 
        auth.APIAuthToken.access_token
    );

    CountryResponse countries = response.ToObject<CountryResponse>();
    return Json(countries, JsonRequestBehavior.AllowGet);
}

GetCitiesByCountry()

public async Task<ActionResult> GetCitiesByCountry(long countryId)
{
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];
    DALManager api = new DALManager(ApiType.psyter);

    JObject response = await api.GetData(
        countryId.ToString(), 
        PsyterApiMethodNames.CmnGetCitiesByCountry, 
        auth.APIAuthToken.access_token
    );

    CityResponse cities = response.ToObject<CityResponse>();
    return Json(cities, JsonRequestBehavior.AllowGet);
}

GetPhysicianSpecialities()

public async Task<ActionResult> GetPhysicianSpecialities()
{
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];
    DALManager api = new DALManager(ApiType.psyter);

    JObject response = await api.GetData(
        "", 
        PsyterApiMethodNames.CmnGetPhyGetSpecialities, 
        auth.APIAuthToken.access_token
    );

    CatSpecialitiesResponse specialities = 
        response.ToObject<CatSpecialitiesResponse>();

    return Json(specialities, JsonRequestBehavior.AllowGet);
}

GetLanguages()

public async Task<ActionResult> GetLanguages()
{
    var auth = (APIAuthTokensResponse)Session[SessionVariables.APIAuthTokenList];
    DALManager api = new DALManager(ApiType.psyter);

    JObject response = await api.GetData(
        "", 
        PsyterApiMethodNames.CmnGetLanguages, 
        auth.APIAuthToken.access_token
    );

    LanguageResponse languages = response.ToObject<LanguageResponse>();
    return Json(languages, JsonRequestBehavior.AllowGet);
}

🎨 Views Structure

Total Views: 200+ Razor views

View Organization

Views/
├── Web/
│   ├── Arabic/
│   │   ├── Home.cshtml
│   │   ├── PhysicianList.cshtml
│   │   ├── FAQ.cshtml
│   │   ├── TermCondition.cshtml
│   │   └── ...
│   ├── English/
│   │   ├── Home.cshtml
│   │   ├── PhysicianList.cshtml
│   │   ├── FAQ.cshtml
│   │   └── ...
├── Patient/
│   ├── Arabic/
│   │   ├── Index.cshtml
│   │   ├── PatientProfile.cshtml
│   │   ├── MyAppointments.cshtml
│   │   ├── ScreeningQuestions.cshtml
│   │   └── ...
│   ├── English/
│   │   ├── Index.cshtml
│   │   ├── PatientProfile.cshtml
│   │   └── ...
├── Physician/
│   ├── English/
│   │   ├── Index.cshtml
│   │   ├── ProviderProfile.cshtml
│   │   ├── ManageAvailability.cshtml
│   │   ├── MyClientsListing.cshtml
│   │   └── ...
├── Admin/
│   ├── English/
│   │   ├── Index.cshtml
│   │   ├── ManageUsers.cshtml
│   │   └── ...
│   ├── Catalogue/
│   │   ├── English/
│   │   │   ├── ManageSystemCharges.cshtml
│   │   │   ├── ViewMonthlyReport.cshtml
│   │   │   └── ...
├── Shared/
│   ├── _Layout.cshtml
│   ├── _LayoutGuest.cshtml
│   ├── Layout_ValidUser.cshtml
│   ├── Error.cshtml
│   └── ...
└── Common/
    ├── English/
    │   ├── MyAppointments.cshtml
    │   └── ...
    ├── Arabic/
    │   ├── MyAppointments.cshtml
    │   └── ...

Shared Layouts

_Layout.cshtml (Main Layout)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@Model.PageTitleTagPLang</title>
    <meta name="description" content="@Model.PageMetaTagDescriptionPLang" />
    <meta name="keywords" content="@Model.PageMetaTagKeywordsPLang" />

    <!-- CSS Bundles -->
    @Styles.Render("~/Content/css/bootstrapcss_eng")
    @Styles.Render("~/Content/css/commoncss")

    <!-- JS Bundles -->
    @Scripts.Render("~/bundles/js/jquery")
    @Scripts.Render("~/bundles/js/bootstrap")
    @Scripts.Render("~/bundles/js/common")

    <!-- Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=AW-747458204"></script>

    <!-- Matomo Analytics -->
    <script>
        var _paq = window._paq = window._paq || [];
        _paq.push(['trackPageView']);
        _paq.push(['enableLinkTracking']);
    </script>
</head>
<body>
    <!-- Page Loader -->
    <div id="DivPageLoading" class="loader">
        <img src="~/Content/images/img/psyter-loader.gif">
    </div>

    <!-- Top Bar -->
    <div class="TopBar orangeClr clearfix">
        <div class="container-fluid">
            <div class="row">
                <div class="col-sm-4 logo">
                    <a href="~/en/home-page">
                        <img src="~/Content/images/img/logo.png" width="135px" />
                    </a>
                </div>
                <div class="col-sm-8 Language_Links">
                    <span>
                        <a id="lnk-en" class="LanguageActive" 
                           onclick="Psyter.Common.setLanguageCulture('en-US');">
                            <i class="fas fa-globe-americas"></i> English
                        </a>
                    </span>
                    <span>
                        <a id="lnk-ar" class="ar-link" 
                           onclick="Psyter.Common.setLanguageCulture('ar-SA');">
                            العربية
                        </a>
                    </span>

                    <!-- Social Links -->
                    <span><a href="https://www.facebook.com/Psyter.Official" target="_blank">
                        <i class="fab fa-facebook-f"></i>
                    </a></span>
                    <span><a href="https://www.twitter.com/Psyter_official" target="_blank">
                        <i class="fab fa-twitter"></i>
                    </a></span>
                    <span><a href="https://www.instagram.com/psyter.official" target="_blank">
                        <i class="fab fa-instagram"></i>
                    </a></span>
                </div>
            </div>
        </div>
    </div>

    <!-- Navigation Bar -->
    <nav class="navbar navbar-expand-lg">
        <!-- Navigation items -->
    </nav>

    <!-- Main Content -->
    <div class="container">
        @RenderBody()
    </div>

    <!-- Footer -->
    <footer>
        <div class="container">
            <p>&copy; 2025 Psyter. All rights reserved.</p>
        </div>
    </footer>
</body>
</html>

Layout_ValidUser.cshtml (Logged-in Layout)

<!DOCTYPE html>
<html>
<head>
    <!-- Similar head section -->
</head>
<body>
    <!-- User Dashboard Layout -->
    <div class="dashboard-wrapper">
        <!-- Sidebar -->
        <aside class="sidebar">
            <div class="user-profile">
                <img src="@ViewBag.ProfileImage" />
                <h4>@ViewBag.UserName</h4>
                <p>@ViewBag.UserType</p>
            </div>

            <ul class="nav-menu">
                <li><a href="/Client/Index">Dashboard</a></li>
                <li><a href="/Client/Appointments">Appointments</a></li>
                <li><a href="/Client/ClientProfile">Profile</a></li>
                <li><a href="/Client/MyWallet">Wallet</a></li>
                <li><a href="/Client/Messages">Messages</a></li>
                <li><a href="/Web/Logout">Logout</a></li>
            </ul>
        </aside>

        <!-- Main Content Area -->
        <main class="main-content">
            @RenderBody()
        </main>
    </div>
</body>
</html>

📜 JavaScript Architecture

Total JavaScript Files: 70+ files

Core JavaScript Files

common-text.js (Localization)

var Psyter = Psyter || {};

Psyter.LocalizedText = {
    // English
    "en-US": {
        "LOGIN": "Login",
        "REGISTER": "Register",
        "BOOK_NOW": "Book Now",
        "CANCEL": "Cancel",
        "CONFIRM": "Confirm",
        "SAVE": "Save",
        "DELETE": "Delete",
        "EDIT": "Edit",
        "SEARCH": "Search",
        "ERROR_OCCURRED": "An error occurred. Please try again.",
        "SUCCESS": "Operation completed successfully",
        // ... 200+ translations
    },

    // Arabic
    "ar-SA": {
        "LOGIN": "تسجيل الدخول",
        "REGISTER": "التسجيل",
        "BOOK_NOW": "احجز الآن",
        "CANCEL": "إلغاء",
        "CONFIRM": "تأكيد",
        // ... 200+ translations
    }
};

Layout.js (Common Functions)

var Psyter = Psyter || {};

Psyter.Common = {
    rootURL: "",
    psyterApiToken: "",
    currentLanguage: "en-US",

    // Initialize
    loadData: function(apiBasePath, language) {
        this.apiBasePath = apiBasePath;
        this.currentLanguage = language;
        this.initializeEvents();
    },

    // Language Switch
    setLanguageCulture: function(culture) {
        $.ajax({
            url: this.rootURL + "Web/SetLanguage",
            type: "POST",
            data: { culture: culture },
            success: function() {
                location.reload();
            }
        });
    },

    // API Call Wrapper
    callAPI: function(method, endpoint, data, successCallback, errorCallback) {
        $.ajax({
            url: this.apiBasePath + endpoint,
            type: method,
            headers: {
                "Authorization": "Bearer " + this.psyterApiToken
            },
            data: JSON.stringify(data),
            contentType: "application/json",
            success: successCallback,
            error: errorCallback
        });
    },

    // Show Progress
    ShowProgress: function() {
        $('#DivPageLoading').show();
    },

    // Hide Progress
    HideProgress: function() {
        $('#DivPageLoading').hide();
    },

    // Format Date
    formatDate: function(date, format) {
        return moment(date).format(format);
    },

    // Show Alert
    showAlert: function(message, type) {
        swal({
            title: type === "success" ? "Success" : "Error",
            text: message,
            type: type,
            confirmButtonText: "OK"
        });
    }
};

Login.js

$(document).ready(function() {
    // Login Form Validation
    $("#frmLogin").validate({
        rules: {
            Username: { required: true, email: true },
            Password: { required: true, minlength: 6 }
        },
        messages: {
            Username: {
                required: Psyter.LocalizedText[Psyter.Common.currentLanguage].USERNAME_REQUIRED,
                email: Psyter.LocalizedText[Psyter.Common.currentLanguage].INVALID_EMAIL
            },
            Password: {
                required: Psyter.LocalizedText[Psyter.Common.currentLanguage].PASSWORD_REQUIRED,
                minlength: Psyter.LocalizedText[Psyter.Common.currentLanguage].PASSWORD_MIN_LENGTH
            }
        },
        submitHandler: function(form) {
            Psyter.Common.ShowProgress();
            form.submit();
        }
    });

    // Forgot Password
    $("#lnkForgotPassword").click(function() {
        window.location.href = Psyter.Common.rootURL + "Web/ForgotPassword";
    });
});
var PhysicianList = {
    filters: {
        specialityId: null,
        genderType: null,
        languageId: null,
        minExperience: null,
        maxFees: null,
        searchText: ""
    },

    // Load Therapists
    loadTherapists: function() {
        Psyter.Common.ShowProgress();

        Psyter.Common.callAPI(
            "POST",
            "ServiceProvider/GetCareProvidersList",
            this.filters,
            function(response) {
                if (response.Status === 1) {
                    PhysicianList.renderTherapists(response.Data);
                } else {
                    Psyter.Common.showAlert(response.Message, "error");
                }
                Psyter.Common.HideProgress();
            },
            function() {
                Psyter.Common.showAlert("Error loading therapists", "error");
                Psyter.Common.HideProgress();
            }
        );
    },

    // Render Therapists
    renderTherapists: function(therapists) {
        var html = "";

        $.each(therapists, function(index, therapist) {
            html += '<div class="therapist-card">';
            html += '  <img src="' + therapist.ProfileImagePath + '" />';
            html += '  <h4>' + therapist.FullName + '</h4>';
            html += '  <p>' + therapist.Speciality + '</p>';
            html += '  <p>Experience: ' + therapist.Experience + ' years</p>';
            html += '  <p>Fee: SAR ' + therapist.ConsultationFee + '</p>';
            html += '  <div class="rating">';
            html += '    <span>' + therapist.Rating + ' ★</span>';
            html += '  </div>';
            html += '  <button onclick="PhysicianList.viewProfile(' + therapist.UserLoginInfoId + ')">View Profile</button>';
            html += '  <button onclick="PhysicianList.bookAppointment(' + therapist.UserLoginInfoId + ')">Book Now</button>';
            html += '</div>';
        });

        $("#therapistList").html(html);
    },

    // Apply Filters
    applyFilters: function() {
        this.filters.specialityId = $("#ddlSpeciality").val();
        this.filters.genderType = $("#ddlGender").val();
        this.filters.languageId = $("#ddlLanguage").val();
        this.filters.minExperience = $("#txtMinExperience").val();
        this.filters.maxFees = $("#txtMaxFees").val();

        this.loadTherapists();
    },

    // View Profile
    viewProfile: function(providerId) {
        window.location.href = Psyter.Common.rootURL + 
            "Web/TherapistProfile?id=" + providerId;
    },

    // Book Appointment
    bookAppointment: function(providerId) {
        window.location.href = Psyter.Common.rootURL + 
            "Client/BookAppointment?providerId=" + providerId;
    }
};

$(document).ready(function() {
    PhysicianList.loadTherapists();

    $("#btnApplyFilters").click(function() {
        PhysicianList.applyFilters();
    });
});

BookSlot.js (Appointment Booking)

var BookSlot = {
    selectedProviderId: null,
    selectedDate: null,
    selectedSlot: null,

    // Initialize
    init: function(providerId) {
        this.selectedProviderId = providerId;
        this.loadProviderSchedule();
        this.initializeDatePicker();
    },

    // Initialize Date Picker
    initializeDatePicker: function() {
        $("#txtBookingDate").datepicker({
            minDate: 0,
            maxDate: "+3M",
            dateFormat: "dd/mm/yy",
            onSelect: function(date) {
                BookSlot.selectedDate = date;
                BookSlot.loadAvailableSlots();
            }
        });
    },

    // Load Provider Schedule
    loadProviderSchedule: function() {
        Psyter.Common.callAPI(
            "GET",
            "ServiceProvider/GetSchedule/" + this.selectedProviderId,
            null,
            function(response) {
                if (response.Status === 1) {
                    BookSlot.renderSchedule(response.Data);
                }
            }
        );
    },

    // Load Available Slots
    loadAvailableSlots: function() {
        Psyter.Common.ShowProgress();

        var data = {
            ServiceProviderId: this.selectedProviderId,
            Date: this.selectedDate
        };

        Psyter.Common.callAPI(
            "POST",
            "Booking/GetAvailableSlots",
            data,
            function(response) {
                if (response.Status === 1) {
                    BookSlot.renderSlots(response.Data);
                } else {
                    Psyter.Common.showAlert("No slots available", "info");
                }
                Psyter.Common.HideProgress();
            }
        );
    },

    // Render Slots
    renderSlots: function(slots) {
        var html = "";

        $.each(slots, function(index, slot) {
            if (slot.IsAvailable) {
                html += '<div class="time-slot" data-slot-id="' + slot.SlotAvailabilityId + '">';
                html += '  <span>' + slot.StartTime + ' - ' + slot.EndTime + '</span>';
                html += '</div>';
            }
        });

        $("#availableSlots").html(html);

        // Click handler
        $(".time-slot").click(function() {
            $(".time-slot").removeClass("selected");
            $(this).addClass("selected");
            BookSlot.selectedSlot = $(this).data("slot-id");
        });
    },

    // Confirm Booking
    confirmBooking: function() {
        if (!this.selectedSlot) {
            Psyter.Common.showAlert("Please select a time slot", "warning");
            return;
        }

        var data = {
            ServiceProviderId: this.selectedProviderId,
            SlotAvailabilityId: this.selectedSlot,
            CatCommunicationTypeId: $("#ddlCommunicationType").val(),
            BookingNotes: $("#txtNotes").val()
        };

        Psyter.Common.ShowProgress();

        Psyter.Common.callAPI(
            "POST",
            "Booking/BookSlot",
            data,
            function(response) {
                if (response.Status === 1) {
                    // Redirect to payment
                    window.location.href = Psyter.Common.rootURL + 
                        "Payment/Gateway?bookingId=" + response.Data.SlotBookingId;
                } else {
                    Psyter.Common.showAlert(response.Message, "error");
                }
                Psyter.Common.HideProgress();
            }
        );
    }
};

MyAppointments.js

var MyAppointments = {
    // Load Appointments
    loadAppointments: function(status) {
        Psyter.Common.ShowProgress();

        Psyter.Common.callAPI(
            "GET",
            "Booking/GetMyBookings?status=" + status,
            null,
            function(response) {
                if (response.Status === 1) {
                    MyAppointments.renderAppointments(response.Data);
                }
                Psyter.Common.HideProgress();
            }
        );
    },

    // Render Appointments
    renderAppointments: function(appointments) {
        var html = "";

        $.each(appointments, function(index, appt) {
            html += '<div class="appointment-card">';
            html += '  <div class="appointment-header">';
            html += '    <h5>' + appt.ServiceProviderName + '</h5>';
            html += '    <span class="badge badge-' + appt.StatusClass + '">' + 
                         appt.BookingStatus + '</span>';
            html += '  </div>';
            html += '  <div class="appointment-body">';
            html += '    <p><i class="far fa-calendar"></i> ' + appt.SlotDate + '</p>';
            html += '    <p><i class="far fa-clock"></i> ' + appt.SlotStartTime + 
                         ' - ' + appt.SlotEndTime + '</p>';
            html += '    <p><i class="fas fa-video"></i> ' + appt.CommunicationType + '</p>';
            html += '  </div>';
            html += '  <div class="appointment-actions">';

            if (appt.CanJoin) {
                html += '<button onclick="MyAppointments.joinSession(' + 
                         appt.SlotBookingId + ')">Join Session</button>';
            }

            if (appt.CanCancel) {
                html += '<button onclick="MyAppointments.cancelAppointment(' + 
                         appt.SlotBookingId + ')">Cancel</button>';
            }

            if (appt.CanRate) {
                html += '<button onclick="MyAppointments.rateSession(' + 
                         appt.SlotBookingId + ')">Rate</button>';
            }

            html += '  </div>';
            html += '</div>';
        });

        $("#appointmentsList").html(html);
    },

    // Join Video Session
    joinSession: function(bookingId) {
        window.location.href = Psyter.Common.rootURL + 
            "VideoCall/Join?bookingId=" + bookingId;
    },

    // Cancel Appointment
    cancelAppointment: function(bookingId) {
        swal({
            title: "Cancel Appointment",
            text: "Are you sure you want to cancel this appointment?",
            type: "warning",
            showCancelButton: true,
            confirmButtonText: "Yes, cancel it"
        }).then(function(result) {
            if (result.value) {
                Psyter.Common.callAPI(
                    "POST",
                    "Booking/CancelBooking/" + bookingId,
                    null,
                    function(response) {
                        if (response.Status === 1) {
                            Psyter.Common.showAlert("Appointment cancelled", "success");
                            MyAppointments.loadAppointments("all");
                        } else {
                            Psyter.Common.showAlert(response.Message, "error");
                        }
                    }
                );
            }
        });
    }
};

video-sdk-config.js (VideoSDK Integration)

var VideoSDK = {
    meetingId: null,
    token: null,
    participantName: null,

    // Initialize Meeting
    initMeeting: function(meetingId, token, name) {
        this.meetingId = meetingId;
        this.token = token;
        this.participantName = name;

        // Initialize VideoSDK
        window.VideoSDK.config(token);

        const meeting = window.VideoSDK.initMeeting({
            meetingId: meetingId,
            name: name,
            micEnabled: true,
            webcamEnabled: true,
            maxResolution: "hd"
        });

        this.setupEventListeners(meeting);
        meeting.join();
    },

    // Setup Event Listeners
    setupEventListeners: function(meeting) {
        meeting.on("meeting-joined", function() {
            console.log("Meeting joined");
        });

        meeting.on("participant-joined", function(participant) {
            console.log("Participant joined:", participant.name);
        });

        meeting.on("participant-left", function(participant) {
            console.log("Participant left:", participant.name);
        });
    }
};

🎨 Frontend Libraries

CSS Frameworks

Bootstrap 4.6.0:
- Responsive grid system
- UI components
- Utilities

Custom CSS:
- site.css - Global styles
- custom.css - Page-specific styles
- RTL support for Arabic

JavaScript Libraries

jQuery 3.6.0:
- DOM manipulation
- AJAX calls
- Event handling

jQuery UI 1.13.1:
- Datepicker
- Dialog
- Autocomplete

Moment.js:
- Date formatting
- Timezone handling
- Relative time

SweetAlert2:
- Beautiful alerts
- Confirmations
- Notifications

Select2:
- Enhanced dropdowns
- Search functionality
- Multi-select

Owl Carousel:
- Image sliders
- Testimonials
- Featured therapists

Chart.js / Highcharts:
- Analytics charts
- Revenue graphs
- Statistics visualization

Bootstrap Slider:
- Price range filter
- Experience filter

Croppie.js:
- Image cropping
- Profile picture upload

TinyMCE:
- Rich text editor
- Blog post editing
- Content management

VideoSDK:
- Video conferencing
- Screen sharing
- Recording


🚀 Deployment

Azure DevOps Pipeline

File: azure-pipelines.yml

# ASP.NET
# Build and deploy ASP.NET MVC project

trigger:
- master

pool: 'DevWebServerAgentPool'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
# Install NuGet
- task: NuGetToolInstaller@1

# Restore NuGet packages
- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

# Build solution
- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

# Publish artifacts
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

# Copy files to deployment directory
- task: CopyFiles@2
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)\Psyter'
    Contents: '**'
    TargetFolder: 'D:\ROOT\Development\Psyter\Master\Web'
    OverWrite: true

Deployment Target

IIS Configuration:
- Application Pool: Psyter_Web
- .NET Version: 4.7.2
- Pipeline Mode: Integrated
- URL: https://dvx.innotech-sa.com/Psyter/Master/Web


🔐 Security Features

Request Validation

Web.config:

<system.web>
    <httpRuntime 
        enableVersionHeader="false" 
        requestValidationMode="2.0" />
    <httpCookies 
        httpOnlyCookies="true" 
        requireSSL="true" />
</system.web>

HTTPS Enforcement

Global.asax.cs:

protected void Application_BeginRequest()
{
    if (!Request.IsSecureConnection && !Request.IsLocal)
    {
        Response.Redirect(
            "https://" + Request.ServerVariables["HTTP_HOST"] + 
            Request.RawUrl
        );
    }
}

Anti-Forgery Tokens

Views:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <!-- Form fields -->
}

Controllers:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SubmitForm(FormModel model)
{
    // Process form
}


🌍 Multilingual Support

Language Structure

Supported Languages:
- English (en-US)
- Arabic (ar-SA)

View Organization

Views/
├── Web/
│   ├── English/
│   │   └── Home.cshtml
│   └── Arabic/
│       └── Home.cshtml

Language Selection

Layout.cshtml:

<a onclick="Psyter.Common.setLanguageCulture('en-US');">English</a>
<a onclick="Psyter.Common.setLanguageCulture('ar-SA');">العربية</a>

WebController.cs:

public ActionResult SetLanguage(string culture)
{
    Session[SessionVariables.CurrentLanguage] = culture;

    if (culture == "ar-SA")
    {
        Thread.CurrentThread.CurrentCulture = new CultureInfo("ar-SA");
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("ar-SA");
    }
    else
    {
        Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
    }

    return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}


📊 Analytics Integration

Google Analytics

_Layout.cshtml:

<!-- Global site tag (gtag.js) - Google Ads -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-747458204"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'AW-747458204');
</script>

Matomo Analytics

<!-- Matomo -->
<script>
    var _paq = window._paq = window._paq || [];
    _paq.push(['trackPageView']);
    _paq.push(['enableLinkTracking']);
    (function() {
        var u="//analyse.innotech-sa.com/";
        _paq.push(['setTrackerUrl', u+'matomo.php']);
        _paq.push(['setSiteId', '2']);
        var d=document, g=d.createElement('script'), 
            s=d.getElementsByTagName('script')[0];
        g.async=true; g.src=u+'matomo.js'; 
        s.parentNode.insertBefore(g,s);
    })();
</script>

🎯 Key Features Summary

For Patients

✅ Free psychological screening
✅ Search & filter therapists
✅ Real-time availability
✅ Online booking
✅ Video sessions
✅ Secure payments
✅ Wallet management
✅ Appointment history
✅ Rate & review

For Providers

✅ Complete profile management
✅ Schedule & availability
✅ Patient management
✅ Session notes
✅ Prescriptions
✅ Earnings dashboard
✅ Withdrawal requests
✅ Analytics

For Admins

✅ User management
✅ Provider approval
✅ Payment management
✅ System configuration
✅ Reports & analytics
✅ Content management
✅ SEO optimization
✅ Organization management


END OF PART 2


Web Portal Documentation Complete:

Part 1 Covered:
- Project architecture
- Configuration
- Data access layer
- Models & enumerations
- Common utilities
- Authentication & filters

Part 2 Covered:
- All controllers (Web, Patient, Physician, Admin, Common)
- Views & layouts
- JavaScript architecture
- Frontend libraries
- Multilingual support
- Deployment
- Security features
- Analytics integration

Total Documentation: ~55,000 words across both parts

Progress: 8 of 9 repositories complete

Remaining: WindowsService, IOSCareProvider