diff --git a/apps/portal/components/AppLauncher.vue b/apps/portal/components/AppLauncher.vue index b322b1f..8127203 100644 --- a/apps/portal/components/AppLauncher.vue +++ b/apps/portal/components/AppLauncher.vue @@ -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(() => { 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. (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(() => { // 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(() => {