Customer Portal & My Home — Common cases
403 when entering Customer Portal at bootstrap
Only app_type ∈ {aoadmin, onsite} calls staff endpoints; customer mode must not call Manage/CurrentUser / Employee/LinkOrg.
Real-world example: A user without a staff profile opens the portal and gets a 403. → Diagnose an endpoint called out of the wrong mode. →
app_typeis customer mode butManage/CurrentUseris still called → drop that call from bootstrap; no more 403.
Displayed time drifts by browser
ServiceAreaId = Local → no conversion; the time must match the wall-clock source in every browser. MultiZone → resolve the effective timezone from User.TimezoneIdentifier, null → browser.
Real-world example: Staff open Calendar on a laptop in another timezone at a
Localenterprise. → The time must not change per machine. → CheckEnterpriseSetting.ServiceAreaId; ifLocal, the frontend does not convert and SyncfusionScheduleComponentdoes not reinterpret through the user-local timezone.
Timezone prompt/popup not showing or showing at the wrong time
Gating in order: authenticated (no guest/unresolved) → MultiZone → User.TimezoneIdentifier null → not dismissed this session. Both save and dismiss set a flag; the two surfaces use two separate keys.
Real-world example: A MultiZone user who already has a timezone still sees the popup. → The popup must not auto-trigger when a timezone exists. →
User.TimezoneIdentifiernot null → skip the popup; the user can still open the modal themselves via theTimezoneIndicatorbadge if they want.
My Subscriptions shows another account's data
The backend must hard-scope to the authenticated account; client filters (if sent) are ignored/rejected. Loading/error states do not render stale rows from another account.
Real-world example: A My Subscriptions request carries another person's account identifier. → No cross-account data may be returned. → The backend ignores the client filter and scopes the response to the signed-in account; no other account's enrollments show.
My Subscriptions section missing or on the wrong tab
Tab ↔ status mapping: Pending → Submitted, Confirmed → Approved, Cancelled → Canceled; Waiting/Cart → hide the section. Switching tabs filters the already-fetched data, no re-fetch.
Real-world example: A customer opens the Confirmed tab but sees no section. → Check whether any
Approvedenrollment exists. → The Confirmed tab renders the section only when anApprovedenrollment exists; otherwise → drop the section header/divider entirely.
A cancelled period still appears in Booking Range
The booked-period filter is IsActive = true and StatusId != Canceled; the cancel workflow leaves IsActive = true, so filtering by IsActive alone is not enough.
Real-world example: An enrollment has a cancelled period (
StatusId = Canceled,IsActive = true). → It must not show in the list or the calendar. → Booking Range and the View Calendar drawer both drop that period because theStatusId != Canceledcondition is missing.
Share link pointing to Admin Portal or leaking a token
Every share action composes from the Customer Portal base URL, not the browser origin (INV-CP-SHARELINK-001); no admin prefix, no bearer-token query param; the displayed URL = the copied URL.
Real-world example: Staff click share from the Admin Portal and the customer receives a link with a token/session artifact. → Admin context must not leak. → Re-compose from the Customer Portal origin, keeping only the program/term/site/category/TPS params; the clipboard receives exactly the displayed string.
Card missing contact still renders an empty row
The site card / Program Details only render a row when a value exists; missing email/phone/address → drop the row, keep the order (name → phone → email → address).
Real-world example: A site has no email. → No empty row cluttering the view. → Booking UI Program Details only shows the mobile row; the landing card drops the email row, keeping phone then address.
Sidebar timezone not showing or not saving
The sidebar Select only shows when MultiZone (enSetting.serviceAreaId === 1) and the user is logged in; options come from the frontend catalog (getSystemTimezoneOptions()), independent of API hydration. Save failure → re-enable + revert to the old value.
Real-world example: A user changes the sidebar timezone but the request fails midway. → Never show an unsaved value. →
updateUserTimezonethrows → the Select re-enables and shows the previoustimezoneIdentifier; no further call until the user picks again.