fix(portal): app launcher opens real per-service hosts

The "Jump to" launcher only navigated for the internal tiles (Personal /
Admin / Partner); every external app (Mail, Drev, Møder, …) just fired a
toast and never opened. Hosts were also hardcoded to *.dezky.com, with Drev
pointing at a vanity drev. subdomain instead of the real OCIS host.

- Open external apps in a new tab at https://<host>.<baseDomain>
- Derive the base domain from the portal's own hostname so links resolve in
  every environment (app.dezky.local → dezky.local, app.dezky.com → dezky.com)
- Map Drev → files (OCIS); mail/meet/chat/cal/contacts/docs use their service
  subdomain
This commit is contained in:
Ronni Baslund
2026-06-07 12:13:59 +02:00
parent 98e49bfe34
commit b7f10eb092
+33 -12
View File
@@ -15,7 +15,10 @@ interface Tile {
key: string
name: string
icon: IconName
ext: string
// Real per-service subdomain behind Traefik (e.g. 'files' for OCIS). Combined
// with the live base domain at click time to build the app URL. Omitted for
// internal tiles (Personal / Admin / Partner) which navigate in-app instead.
host?: string
current?: boolean
}
@@ -32,12 +35,13 @@ const tiles = computed<Tile[]>(() => {
const isAdmin = section.value === 'admin'
const isPartner = section.value === 'partner'
const base: Tile[] = [
{ key: 'mail', name: 'Mail', icon: 'mail', ext: 'mail.dezky.com' },
{ key: 'drev', name: 'Drev', icon: 'folder', ext: 'drev.dezky.com' },
{ key: 'moder', name: 'Møder', icon: 'video', ext: 'meet.dezky.com' },
{ key: 'chat', name: 'Chat', icon: 'chat', ext: 'chat.dezky.com' },
{ key: 'cal', name: 'Kalender', icon: 'calendar', ext: 'cal.dezky.com' },
{ key: 'contacts', name: 'Kontakter', icon: 'users', ext: 'contacts.dezky.com' },
{ key: 'mail', name: 'Mail', icon: 'mail', host: 'mail' },
// Drev = OCIS files, served at files.<domain> (not a vanity 'drev' host).
{ key: 'drev', name: 'Drev', icon: 'folder', host: 'files' },
{ key: 'moder', name: 'Møder', icon: 'video', host: 'meet' },
{ key: 'chat', name: 'Chat', icon: 'chat', host: 'chat' },
{ key: 'cal', name: 'Kalender', icon: 'calendar', host: 'cal' },
{ key: 'contacts', name: 'Kontakter', icon: 'users', host: 'contacts' },
]
// Admin tile is the entry point to the workspace-admin surface. Show it to any
// tenant admin/owner (so they can get TO /admin from the personal shell), not
@@ -46,23 +50,40 @@ const tiles = computed<Tile[]>(() => {
// admin and personal surfaces — clicking either crosses over, "HERE" shows
// which side you're on.
if (isAdmin || isTenantAdmin.value) {
base.push({ key: 'home', name: 'Personal', icon: 'home', ext: 'app.dezky.com', current: section.value === 'user' })
base.push({ key: 'admin', name: 'Admin', icon: 'shield', ext: 'admin.dezky.com', current: isAdmin && !isPartner })
base.push({ key: 'home', name: 'Personal', icon: 'home', current: section.value === 'user' })
base.push({ key: 'admin', name: 'Admin', icon: 'shield', current: isAdmin && !isPartner })
}
if (isPartner) {
base.push({ key: 'partner', name: 'Partner', icon: 'briefcase', ext: 'partner.nordicmsp.dk', current: true })
base.push({ key: 'partner', name: 'Partner', icon: 'briefcase', current: true })
}
base.push({ key: 'docs', name: 'Docs', icon: 'file', ext: 'docs.dezky.com' })
base.push({ key: 'docs', name: 'Docs', icon: 'file', host: 'docs' })
return base
})
// The base domain the portal itself is served on, so app links resolve in every
// environment: app.dezky.local → dezky.local, app.dezky.com → dezky.com. Falls
// back to the dev domain during SSR (open() only runs client-side on click).
function appBaseDomain(): string {
if (import.meta.client) {
const parts = window.location.hostname.split('.')
return parts.length > 2 ? parts.slice(1).join('.') : window.location.hostname
}
return 'dezky.local'
}
const toast = useToast()
function open(t: Tile) {
launcher.hide()
if (t.key === 'home') return navigateTo('/')
if (t.key === 'admin') return navigateTo('/admin')
if (t.key === 'partner') return navigateTo('/partner')
toast.info(`Opening ${t.name}`, t.ext)
if (t.host) {
const url = `https://${t.host}.${appBaseDomain()}`
window.open(url, '_blank', 'noopener')
toast.info(`Opening ${t.name}`, url.replace(/^https:\/\//, ''))
return
}
toast.info(`Opening ${t.name}`)
}
onMounted(() => {