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}