This commit is contained in:
2026-08-14 00:06:43 +02:00
parent 841a05572e
commit a9395585f8
22 changed files with 326 additions and 22 deletions
+4 -1
View File
@@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Descrybe">
<rect width="32" height="32" rx="8" fill="#634FFC"/>
<path fill="#FFFFFF" d="M10 7.5h7.1c5.05 0 8.9 3.55 8.9 8.5s-3.85 8.5-8.9 8.5H10V7.5zm4.1 3.35v10.3h3c2.95 0 5.05-2.15 5.05-5.15S20.05 10.85 17.1 10.85h-3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 317 B

@@ -1,7 +1,7 @@
<script lang="ts">
import { i18n } from "$lib/i18n";
import { Building2, Check, ChevronDown } from "@lucide/svelte";
import { Building2, Check, ChevronDown, Undo2 } from "@lucide/svelte";
import { api } from "$lib/api";
import { notifyApiError } from "$lib/notify";
import type { Company } from "$lib/types";
@@ -15,11 +15,17 @@
let {
companies = [],
activeCompanyId = "",
activeCompanyName = ""
activeCompanyName = "",
homeCompanyId = "",
homeCompanyName = "",
staffTenantActing = false
}: {
companies?: Company[];
activeCompanyId?: string;
activeCompanyName?: string;
homeCompanyId?: string;
homeCompanyName?: string;
staffTenantActing?: boolean;
} = $props();
let switching = $state(false);
@@ -35,7 +41,13 @@
return match?.name?.trim() || i18n.t("companySwitcher.select");
});
const canSwitch = $derived(list.length > 1);
const homeId = $derived(homeCompanyId.trim());
const canRevert = $derived(Boolean(homeId && homeId !== activeCompanyId));
const canSwitch = $derived(list.length > 1 || canRevert);
const revertLabel = $derived.by(() => {
const name = homeCompanyName.trim() || list.find((c) => c.id === homeId)?.name?.trim() || "";
return name ? i18n.t("companySwitcher.returnTo", { name }) : i18n.t("companySwitcher.returnHome");
});
async function selectCompany(companyId: string) {
if (!companyId || companyId === activeCompanyId || switching) return;
@@ -54,7 +66,7 @@
}
</script>
{#if list.length > 0}
{#if list.length > 0 || canRevert}
{#if canSwitch}
<DropdownMenu bind:open={menuOpen} align="end">
{#snippet trigger({ open, toggle })}
@@ -69,11 +81,22 @@
data-tour="company-switcher"
title={label}
>
<Building2 class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
{#if staffTenantActing || canRevert}
<Building2 class="h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400" />
{:else}
<Building2 class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
{/if}
<span class="min-w-0 truncate">{switching ? i18n.t("switcher.switching") : label}</span>
<ChevronDown class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
</button>
{/snippet}
{#if canRevert}
<DropdownMenuItem onclick={() => void selectCompany(homeId)}>
<Undo2 class="h-3.5 w-3.5 text-primary" />
<span class="truncate">{revertLabel}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{/if}
<DropdownMenuLabel>{i18n.t("companySwitcher.companies")}</DropdownMenuLabel>
<DropdownMenuSeparator />
{#each list as company (company.id)}
@@ -93,7 +116,7 @@
{:else}
<div
class="inline-flex h-8 max-w-[12rem] items-center gap-1.5 rounded-md border border-transparent px-2 text-xs font-medium text-muted-foreground sm:max-w-[18rem] sm:px-2.5"
data-tour="company-switcher"
data-testid="company-switcher"
title={label}
>
<Building2 class="h-3.5 w-3.5 shrink-0" />
+2
View File
@@ -5501,6 +5501,8 @@ export const de: MessageDict = {
"companySwitcher.select": "Unternehmen auswählen",
"companySwitcher.switchAria": "Unternehmen wechseln: {label}",
"companySwitcher.companies": "Unternehmen",
"companySwitcher.returnTo": "Zurück zu {name}",
"companySwitcher.returnHome": "Zurück zum Heimatunternehmen",
"storeReconnect.primaryLabel": "Shop erneut verbinden",
"storeReconnect.titlePlural": "Shops nach Migration erneut verbinden",
"storeReconnect.titleChannel": "{channel} nach Migration erneut verbinden",
+2
View File
@@ -5536,6 +5536,8 @@ export const en: MessageDict = {
"companySwitcher.select": "Select company",
"companySwitcher.switchAria": "Switch company: {label}",
"companySwitcher.companies": "Companies",
"companySwitcher.returnTo": "Return to {name}",
"companySwitcher.returnHome": "Return to home company",
"storeReconnect.primaryLabel": "Reconnect store",
"storeReconnect.titlePlural": "Reconnect stores after migration",
"storeReconnect.titleChannel": "Reconnect {channel} after migration",
+2
View File
@@ -5501,6 +5501,8 @@ export const es: MessageDict = {
"companySwitcher.select": "Seleccionar empresa",
"companySwitcher.switchAria": "Cambiar de empresa: {label}",
"companySwitcher.companies": "Empresas",
"companySwitcher.returnTo": "Volver a {name}",
"companySwitcher.returnHome": "Volver a la empresa de origen",
"storeReconnect.primaryLabel": "Reconectar tienda",
"storeReconnect.titlePlural": "Reconectar tiendas tras la migración",
"storeReconnect.titleChannel": "Reconectar {channel} tras la migración",
+2
View File
@@ -5501,6 +5501,8 @@ export const fr: MessageDict = {
"companySwitcher.select": "Sélectionner une entreprise",
"companySwitcher.switchAria": "Changer dentreprise : {label}",
"companySwitcher.companies": "Entreprises",
"companySwitcher.returnTo": "Revenir à {name}",
"companySwitcher.returnHome": "Revenir à lentreprise dorigine",
"storeReconnect.primaryLabel": "Reconnecter la boutique",
"storeReconnect.titlePlural": "Reconnecter les boutiques après la migration",
"storeReconnect.titleChannel": "Reconnecter {channel} après la migration",
+2
View File
@@ -5501,6 +5501,8 @@ export const it: MessageDict = {
"companySwitcher.select": "Seleziona azienda",
"companySwitcher.switchAria": "Cambia azienda: {label}",
"companySwitcher.companies": "Aziende",
"companySwitcher.returnTo": "Torna a {name}",
"companySwitcher.returnHome": "Torna allazienda di origine",
"storeReconnect.primaryLabel": "Riconnetti negozio",
"storeReconnect.titlePlural": "Riconnetti i negozi dopo la migrazione",
"storeReconnect.titleChannel": "Riconnetti {channel} dopo la migrazione",
+2
View File
@@ -5501,6 +5501,8 @@ export const ja: MessageDict = {
"companySwitcher.select": "会社を選択",
"companySwitcher.switchAria": "会社を切替: {label}",
"companySwitcher.companies": "会社",
"companySwitcher.returnTo": "{name} に戻る",
"companySwitcher.returnHome": "ホーム会社に戻る",
"storeReconnect.primaryLabel": "ストアを再接続",
"storeReconnect.titlePlural": "移行後にストアを再接続",
"storeReconnect.titleChannel": "移行後に {channel} を再接続",
+2
View File
@@ -5501,6 +5501,8 @@ export const nl: MessageDict = {
"companySwitcher.select": "Bedrijf selecteren",
"companySwitcher.switchAria": "Bedrijf wisselen: {label}",
"companySwitcher.companies": "Bedrijven",
"companySwitcher.returnTo": "Terug naar {name}",
"companySwitcher.returnHome": "Terug naar thuisbedrijf",
"storeReconnect.primaryLabel": "Winkel opnieuw verbinden",
"storeReconnect.titlePlural": "Winkels opnieuw verbinden na migratie",
"storeReconnect.titleChannel": "{channel} opnieuw verbinden na migratie",
+2
View File
@@ -5501,6 +5501,8 @@ export const pl: MessageDict = {
"companySwitcher.select": "Wybierz firmę",
"companySwitcher.switchAria": "Przełącz firmę: {label}",
"companySwitcher.companies": "Firmy",
"companySwitcher.returnTo": "Wróć do {name}",
"companySwitcher.returnHome": "Wróć do firmy domowej",
"storeReconnect.primaryLabel": "Połącz sklep ponownie",
"storeReconnect.titlePlural": "Połącz sklepy ponownie po migracji",
"storeReconnect.titleChannel": "Połącz {channel} ponownie po migracji",
+2
View File
@@ -5501,6 +5501,8 @@ export const pt: MessageDict = {
"companySwitcher.select": "Selecionar empresa",
"companySwitcher.switchAria": "Mudar de empresa: {label}",
"companySwitcher.companies": "Empresas",
"companySwitcher.returnTo": "Voltar para {name}",
"companySwitcher.returnHome": "Voltar para a empresa de origem",
"storeReconnect.primaryLabel": "Reconectar loja",
"storeReconnect.titlePlural": "Reconectar lojas após a migração",
"storeReconnect.titleChannel": "Reconectar {channel} após a migração",
+8 -1
View File
@@ -93,10 +93,17 @@ export type MeResponse = {
company?: Company | null;
companies?: Company[];
active_company_id?: string;
membership?: { role: string; status: string } | null;
membership?: { role: string; status: string; staff_override?: boolean } | null;
credits?: CreditBalance | null;
staff_access?: StaffAccess | null;
staff_capabilities?: string[];
/** Platform staff_role=admin only: company switcher lists all tenants (no membership required). */
staff_tenant_switch?: boolean;
/** True while admin is acting in a company without membership. */
staff_tenant_acting?: boolean;
/** Membership company to restore via company switcher revert. */
staff_home_company_id?: string;
staff_home_company?: Company | null;
/** Non-prod only: show header user-switch for platform admin/demo or while impersonating. */
dev_user_switch?: boolean;
impersonating?: boolean;