feat: improve organization switching experience with a branded loading overlay and session management for smoother transitions

This commit is contained in:
Usman Baig
2026-02-22 20:48:09 +01:00
parent 8007900940
commit c73c300620
4 changed files with 37 additions and 11 deletions

View File

@@ -8,26 +8,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [0.11.0-alpha] - 2026-02-22
### Changed
### Added
- **Smoother loading experience.** Pages now show a subtle preview of the layout while data loads instead of a blank screen or spinner. This applies everywhere — dashboards, settings, uptime, funnels, notifications, billing, and detail modals.
- **No more loading flicker.** Fast-loading pages no longer flash a loading state for a split second before showing content.
- **Clearer error messages.** When something goes wrong, the error message now tells you what failed (e.g. "Failed to load uptime monitors") instead of a generic "Failed to load data".
- **Faster favicon loading.** Site icons in the dashboard, referrers, and campaigns now use Next.js image optimization for better caching and lazy loading.
- **Better page titles.** Browser tabs now show which site and page you're on (e.g. "Uptime · example.com | Pulse") instead of the same generic title everywhere.
- **Link previews for public dashboards.** Sharing a public dashboard link on social media now shows a proper preview with the site name and description.
- **Faster login redirects.** If you're not signed in and try to open a dashboard or settings page, you're redirected to login immediately instead of seeing a blank page first. Already-signed-in users who visit the login page are sent straight to the dashboard.
- **Graceful error recovery.** If a page crashes, you now see a friendly error screen with a "Try again" button instead of a blank white page. Each section of the app has its own error message so you know exactly what went wrong.
- **Security headers.** All pages now include clickjacking protection, MIME-sniffing prevention, a strict referrer policy, and HSTS. Browser APIs like camera and microphone are explicitly disabled.
- **Better form experience.** Forms now auto-focus the first field when they open, text inputs enforce character limits with a visible counter when you're close, and the settings page warns you before navigating away with unsaved changes.
- **Accessibility improvements.** The notification bell, workspace switcher, and all dashboard tabs are now fully keyboard-navigable. Screen readers announce unread counts, active organizations, and tab changes correctly. Decorative icons are hidden from assistive technology.
- **Smooth organization switching.** Switching between organizations now shows a branded loading screen instead of a blank flash while the page reloads.
### Changed
- **Smoother loading experience.** Pages now show a subtle preview of the layout while data loads instead of a blank screen or spinner. This applies everywhere — dashboards, settings, uptime, funnels, notifications, billing, and detail modals.
- **Clearer error messages.** When something goes wrong, the error message now tells you what failed (e.g. "Failed to load uptime monitors") instead of a generic "Failed to load data".
- **Faster favicon loading.** Site icons in the dashboard, referrers, and campaigns now use Next.js image optimization for better caching and lazy loading.
- **Tighter name limits.** Site, funnel, and monitor names are now capped at 100 characters instead of 255 — long enough for any real name, short enough to not break the UI.
- **Stricter type safety.** Eliminated all `any` types and `@ts-ignore` suppressions across the codebase, so the TypeScript compiler catches more bugs at build time.
### Fixed
- **No more loading flicker.** Fast-loading pages no longer flash a loading state for a split second before showing content.
- **Organization context switch.** Switching away from a deleted organization now stores the session correctly instead of using an insecure fallback.
- **Removed debug logs.** Auth and organization-switching details no longer leak into the browser console in production.
- **Stricter type safety.** Eliminated all `any` types and `@ts-ignore` suppressions across the codebase, so the TypeScript compiler catches more bugs at build time.
- **Accessibility improvements.** The notification bell, workspace switcher, and all dashboard tabs are now fully keyboard-navigable. Screen readers announce unread counts, active organizations, and tab changes correctly. Decorative icons are hidden from assistive technology.
## [0.10.0-alpha] - 2026-02-21

View File

@@ -10,14 +10,30 @@ import Link from 'next/link'
import { useEffect, useState } from 'react'
import { getUserOrganizations, switchContext } from '@/lib/api/organization'
import { setSessionAction } from '@/app/actions/auth'
import { LoadingOverlay } from '@ciphera-net/ui'
import { useRouter } from 'next/navigation'
const ORG_SWITCH_KEY = 'pulse_switching_org'
export default function LayoutContent({ children }: { children: React.ReactNode }) {
const auth = useAuth()
const router = useRouter()
const isOnline = useOnlineStatus()
const [orgs, setOrgs] = useState<any[]>([])
const [isSwitchingOrg, setIsSwitchingOrg] = useState(() => {
if (typeof window === 'undefined') return false
return sessionStorage.getItem(ORG_SWITCH_KEY) === 'true'
})
// * Clear the switching flag once the page has settled after reload
useEffect(() => {
if (isSwitchingOrg) {
sessionStorage.removeItem(ORG_SWITCH_KEY)
const timer = setTimeout(() => setIsSwitchingOrg(false), 600)
return () => clearTimeout(timer)
}
}, [isSwitchingOrg])
// * Fetch organizations for the header organization switcher
useEffect(() => {
if (auth.user) {
@@ -32,6 +48,7 @@ export default function LayoutContent({ children }: { children: React.ReactNode
try {
const { access_token } = await switchContext(orgId)
await setSessionAction(access_token)
sessionStorage.setItem(ORG_SWITCH_KEY, 'true')
window.location.reload()
} catch (err) {
console.error('Failed to switch organization', err)
@@ -47,6 +64,10 @@ export default function LayoutContent({ children }: { children: React.ReactNode
const headerHeightRem = 6;
const mainTopPaddingRem = barHeightRem + headerHeightRem;
if (isSwitchingOrg) {
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" portal={false} />
}
return (
<>
{auth.user && <OfflineBanner isOnline={isOnline} />}

View File

@@ -33,8 +33,8 @@ export default function OrganizationSwitcher({ orgs, activeOrgId }: { orgs: Orga
// * Note: switchContext only returns access_token, we keep existing refresh token
await setSessionAction(access_token)
// Force reload to pick up new permissions
window.location.reload()
sessionStorage.setItem('pulse_switching_org', 'true')
window.location.reload()
} catch (err) {
console.error('Failed to switch organization', err)

View File

@@ -419,10 +419,11 @@ export default function OrganizationSettings() {
try {
const { access_token } = await switchContext(null)
await setSessionAction(access_token)
sessionStorage.setItem('pulse_switching_org', 'true')
window.location.href = '/'
} catch (switchErr) {
console.error('Failed to switch to personal context after delete:', switchErr)
// Fallback: reload and let backend handle invalid token if any
sessionStorage.setItem('pulse_switching_org', 'true')
window.location.href = '/'
}