forked from wrenn/wrenn
Fix team name blink on navigation by lifting teams into a singleton store
Teams list was fetched on every Sidebar mount (each page navigation), causing a flash from '…' to the real name on every tab switch. Move teams into a module-level reactive store (teams.svelte.ts) that fetches once per session and is shared between Sidebar and the team page.
This commit is contained in:
@ -3,7 +3,8 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Popover } from 'bits-ui';
|
import { Popover } from 'bits-ui';
|
||||||
import { auth } from '$lib/auth.svelte';
|
import { auth } from '$lib/auth.svelte';
|
||||||
import { listTeams, createTeam, switchTeam, type TeamWithRole } from '$lib/api/team';
|
import { teams as teamsStore } from '$lib/teams.svelte';
|
||||||
|
import { createTeam, switchTeam } from '$lib/api/team';
|
||||||
import {
|
import {
|
||||||
IconMonitor,
|
IconMonitor,
|
||||||
IconBox,
|
IconBox,
|
||||||
@ -25,9 +26,7 @@
|
|||||||
|
|
||||||
let teamPopoverOpen = $state(false);
|
let teamPopoverOpen = $state(false);
|
||||||
|
|
||||||
// Real teams from API
|
let currentTeamName = $derived(teamsStore.list.find((t) => t.id === auth.teamId)?.name ?? '');
|
||||||
let teams = $state<TeamWithRole[]>([]);
|
|
||||||
let currentTeamName = $derived(teams.find((t) => t.id === auth.teamId)?.name ?? '');
|
|
||||||
let userName = $derived(auth.email ?? '');
|
let userName = $derived(auth.email ?? '');
|
||||||
|
|
||||||
// Create team dialog
|
// Create team dialog
|
||||||
@ -69,10 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTeams() {
|
async function fetchTeams() {
|
||||||
const result = await listTeams();
|
await teamsStore.fetch();
|
||||||
if (result.ok) {
|
|
||||||
teams = result.data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSwitchTeam(teamId: string) {
|
async function handleSwitchTeam(teamId: string) {
|
||||||
@ -180,7 +176,7 @@
|
|||||||
>
|
>
|
||||||
Teams
|
Teams
|
||||||
</div>
|
</div>
|
||||||
{#each teams as team (team.id)}
|
{#each teamsStore.list as team (team.id)}
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center gap-2.5 rounded-[var(--radius-input)] px-2.5 py-2 text-ui transition-colors duration-150 hover:bg-[var(--color-bg-3)] {team.id ===
|
class="flex w-full items-center gap-2.5 rounded-[var(--radius-input)] px-2.5 py-2 text-ui transition-colors duration-150 hover:bg-[var(--color-bg-3)] {team.id ===
|
||||||
auth.teamId
|
auth.teamId
|
||||||
|
|||||||
31
frontend/src/lib/teams.svelte.ts
Normal file
31
frontend/src/lib/teams.svelte.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { listTeams, type TeamWithRole } from '$lib/api/team';
|
||||||
|
|
||||||
|
function createTeamsStore() {
|
||||||
|
let teams = $state<TeamWithRole[]>([]);
|
||||||
|
let loaded = $state(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
get list() {
|
||||||
|
return teams;
|
||||||
|
},
|
||||||
|
get loaded() {
|
||||||
|
return loaded;
|
||||||
|
},
|
||||||
|
async fetch() {
|
||||||
|
if (loaded) return;
|
||||||
|
const result = await listTeams();
|
||||||
|
if (result.ok) {
|
||||||
|
teams = result.data;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Call after mutating teams (create/switch triggers a full reload, but
|
||||||
|
// adding a team locally avoids a flicker in the popover list).
|
||||||
|
set(newTeams: TeamWithRole[]) {
|
||||||
|
teams = newTeams;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const teams = createTeamsStore();
|
||||||
@ -17,9 +17,9 @@
|
|||||||
searchUsers,
|
searchUsers,
|
||||||
type TeamInfo,
|
type TeamInfo,
|
||||||
type TeamMember,
|
type TeamMember,
|
||||||
type TeamWithRole,
|
|
||||||
type UserSearchResult
|
type UserSearchResult
|
||||||
} from '$lib/api/team';
|
} from '$lib/api/team';
|
||||||
|
import { teams as teamsStore } from '$lib/teams.svelte';
|
||||||
|
|
||||||
let collapsed = $state(
|
let collapsed = $state(
|
||||||
typeof window !== 'undefined'
|
typeof window !== 'undefined'
|
||||||
@ -30,12 +30,11 @@
|
|||||||
// Page data
|
// Page data
|
||||||
let team = $state<TeamInfo | null>(null);
|
let team = $state<TeamInfo | null>(null);
|
||||||
let members = $state<TeamMember[]>([]);
|
let members = $state<TeamMember[]>([]);
|
||||||
let allTeams = $state<TeamWithRole[]>([]);
|
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
|
|
||||||
// True when this is the user's only team — deleting/leaving would leave them teamless
|
// True when this is the user's only team — deleting/leaving would leave them teamless
|
||||||
let isLastTeam = $derived(allTeams.length <= 1);
|
let isLastTeam = $derived(teamsStore.list.length <= 1);
|
||||||
|
|
||||||
// Current user's role — derived from members list
|
// Current user's role — derived from members list
|
||||||
let myRole = $derived(members.find((m) => m.user_id === auth.userId)?.role ?? 'member');
|
let myRole = $derived(members.find((m) => m.user_id === auth.userId)?.role ?? 'member');
|
||||||
@ -86,9 +85,9 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [teamResult, teamsResult] = await Promise.all([
|
const [teamResult] = await Promise.all([
|
||||||
getTeam(auth.teamId),
|
getTeam(auth.teamId),
|
||||||
listTeams()
|
teamsStore.fetch()
|
||||||
]);
|
]);
|
||||||
if (teamResult.ok) {
|
if (teamResult.ok) {
|
||||||
team = teamResult.data.team;
|
team = teamResult.data.team;
|
||||||
@ -96,9 +95,6 @@
|
|||||||
} else {
|
} else {
|
||||||
error = teamResult.error;
|
error = teamResult.error;
|
||||||
}
|
}
|
||||||
if (teamsResult.ok) {
|
|
||||||
allTeams = teamsResult.data;
|
|
||||||
}
|
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user