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>© 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";
});
});
PhysicianList.js (Therapist Search)¶
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