diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bbdd766a..e84c360a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 callback state can be validated from cookie or query for split-origin setups - Autoplay Last.fm scrobbling now falls back to stored requester metadata so recommended tracks keep the original requester attribution +- Frontend shell now initializes guild selection on all authenticated routes, + so the server selector is populated right after login instead of only after + visiting pages that manually triggered guild loading (PR #162) ### Changed diff --git a/README.md b/README.md index d1a3250d3..a8cec09d9 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,9 @@ For hosted frontend deployments, set `VITE_API_BASE_URL` to your backend API ori (example: `https://api.yourdomain.com/api`) to avoid auth/API loop misrouting. Last.fm dashboard connect links use this same API base, so split-origin deployments should keep `VITE_API_BASE_URL` aligned with the public backend. +Authenticated frontend shell routes now bootstrap guild selection globally, so +the server selector is populated immediately after login without requiring a +visit to `/servers` first. Without `VITE_API_BASE_URL`, frontend uses same-origin `/api` for `*.lucassantana.tech` hosts and `api.luk-homeserver.com.br` for `*.luk-homeserver.com.br`. diff --git a/packages/frontend/src/components/Layout/Layout.test.tsx b/packages/frontend/src/components/Layout/Layout.test.tsx index a663d9dc1..09399c520 100644 --- a/packages/frontend/src/components/Layout/Layout.test.tsx +++ b/packages/frontend/src/components/Layout/Layout.test.tsx @@ -2,10 +2,10 @@ import { describe, test, expect, vi, beforeEach } from 'vitest' import { render, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import Layout from './Layout' -import { useGuildStore } from '@/stores/guildStore' +import { useGuildSelection } from '@/hooks/useGuildSelection' import type { Guild } from '@/types' -vi.mock('@/stores/guildStore') +vi.mock('@/hooks/useGuildSelection') vi.mock('./Sidebar', () => ({ default: () => , })) @@ -23,18 +23,10 @@ const mockGuild: Guild = { describe('Layout', () => { beforeEach(() => { vi.clearAllMocks() - vi.mocked(useGuildStore).mockReturnValue({ + vi.mocked(useGuildSelection).mockReturnValue({ guilds: [mockGuild], selectedGuild: mockGuild, - selectedGuildId: mockGuild.id, - isLoading: false, - serverSettings: null, - serverListing: null, - fetchGuilds: vi.fn(), selectGuild: vi.fn(), - setSelectedGuild: vi.fn(), - updateServerSettings: vi.fn(), - updateServerListing: vi.fn(), }) }) @@ -75,23 +67,17 @@ describe('Layout', () => { expect(screen.getByText('Music Player')).toBeInTheDocument() expect( - screen.getByText('Manage queue, autoplay, and real-time playback controls.'), + screen.getByText( + 'Manage queue, autoplay, and real-time playback controls.', + ), ).toBeInTheDocument() }) test('renders fallback route copy without selected guild', () => { - vi.mocked(useGuildStore).mockReturnValue({ + vi.mocked(useGuildSelection).mockReturnValue({ guilds: [], selectedGuild: null, - selectedGuildId: null, - isLoading: false, - serverSettings: null, - serverListing: null, - fetchGuilds: vi.fn(), selectGuild: vi.fn(), - setSelectedGuild: vi.fn(), - updateServerSettings: vi.fn(), - updateServerListing: vi.fn(), }) renderLayout('/unknown') diff --git a/packages/frontend/src/components/Layout/Layout.tsx b/packages/frontend/src/components/Layout/Layout.tsx index 13adb928d..7b83ec5bc 100644 --- a/packages/frontend/src/components/Layout/Layout.tsx +++ b/packages/frontend/src/components/Layout/Layout.tsx @@ -1,7 +1,7 @@ import { type ReactNode } from 'react' import { useLocation } from 'react-router-dom' import Sidebar from './Sidebar' -import { useGuildStore } from '@/stores/guildStore' +import { useGuildSelection } from '@/hooks/useGuildSelection' interface LayoutProps { children: ReactNode @@ -15,11 +15,13 @@ interface RouteCopy { const ROUTE_COPY: Record = { '/': { title: 'Dashboard', - subtitle: 'Operational overview and key status signals for your server.', + subtitle: + 'Operational overview and key status signals for your server.', }, '/servers': { title: 'Servers', - subtitle: 'Review installation status and manage your eligible communities.', + subtitle: + 'Review installation status and manage your eligible communities.', }, '/lastfm': { title: 'Last.fm', @@ -38,19 +40,23 @@ function getRouteCopy(pathname: string): RouteCopy { if (pathname.startsWith('/music')) { return { title: 'Music Player', - subtitle: 'Manage queue, autoplay, and real-time playback controls.', + subtitle: + 'Manage queue, autoplay, and real-time playback controls.', } } - return ROUTE_COPY[pathname] ?? { - title: 'Lucky Dashboard', - subtitle: 'Configure modules, moderation, and engagement workflows.', - } + return ( + ROUTE_COPY[pathname] ?? { + title: 'Lucky Dashboard', + subtitle: + 'Configure modules, moderation, and engagement workflows.', + } + ) } function Layout({ children }: LayoutProps) { const location = useLocation() - const { selectedGuild } = useGuildStore() + const { selectedGuild } = useGuildSelection() const routeCopy = getRouteCopy(location.pathname) return ( @@ -60,13 +66,21 @@ function Layout({ children }: LayoutProps) {
-

Lucky control center

-

{routeCopy.title}

-

{routeCopy.subtitle}

+

+ Lucky control center +

+

+ {routeCopy.title} +

+

+ {routeCopy.subtitle} +

{selectedGuild && (
-

Active server

+

+ Active server +

{selectedGuild.name}