@@ -10,6 +10,7 @@ import { refreshMeMutationKey, useMe } from '@/api/routes/auth';
1010import { EmptyState } from '@/components/common/EmptyState' ;
1111import { Skeleton } from '@/components/common/Skeleton' ;
1212import { Tooltip } from '@/components/common/Tooltip' ;
13+ import { resolveGuildAccess } from '@/hooks/useGuildAccess' ;
1314import { cn , sortGuilds } from '@/utils/util' ;
1415
1516function GuildListSkeleton ( ) {
@@ -31,17 +32,40 @@ export function GuildList() {
3132
3233 const searchQuery = searchParams . get ( 'search' ) ?? '' ;
3334
34- const manageable = useMemo ( ( ) => me ?. guilds . filter ( ( g ) => g . meCanManage ) ?? [ ] , [ me ] ) ;
35+ // AMA guests belong here too, not just managers: a guest-only guild always has `meCanManage: false` (see
36+ // `fetchMe`'s guest-guild synthesis), so filtering on that alone left a guest who manages nothing staring at
37+ // the "no servers" empty state below -- with no way to reach the AMA they were invited to answer, even
38+ // though every gate under `/dashboard/[id]/ama` already grants them access. Tiers come from the shared
39+ // `resolveGuildAccess` rather than a local `meCanManage` read, so this list agrees with what `NavGateCheck`
40+ // will actually let the viewer do when they click through (global admins included).
41+ const visible = useMemo (
42+ ( ) =>
43+ ( me ?. guilds ?? [ ] )
44+ . map ( ( guild ) => {
45+ const { canManage, isAmaGuestOnly } = resolveGuildAccess ( me , guild . id ) ;
46+ return { guild, canManage, isAmaGuestOnly } ;
47+ } )
48+ . filter ( ( entry ) => entry . canManage || entry . isAmaGuestOnly ) ,
49+ [ me ] ,
50+ ) ;
51+ const hasGuestGuilds = useMemo ( ( ) => visible . some ( ( entry ) => entry . isAmaGuestOnly ) , [ visible ] ) ;
3552 const sorted = useMemo ( ( ) => {
3653 const lower = searchQuery . toLowerCase ( ) ;
3754
38- if ( ! manageable . length ) {
55+ if ( ! visible . length ) {
3956 return [ ] ;
4057 }
4158
42- const filtered = manageable . filter ( ( guild ) => guild . name . toLowerCase ( ) . includes ( lower ) ) ;
43- return sortGuilds ( filtered ) ;
44- } , [ manageable , searchQuery ] ) ;
59+ const filtered = visible . filter ( ( entry ) => entry . guild . name . toLowerCase ( ) . includes ( lower ) ) ;
60+ // Managed servers first, then guest-only ones -- guest access is a much narrower thing (one or two
61+ // sessions someone added you to) and shouldn't outrank a server you actually run. `sortGuilds` works on
62+ // bare `MeGuild`s, so the tier flags are re-attached by id afterwards.
63+ const byId = new Map ( filtered . map ( ( entry ) => [ entry . guild . id , entry ] ) ) ;
64+ return [
65+ ...sortGuilds ( filtered . filter ( ( entry ) => entry . canManage ) . map ( ( entry ) => entry . guild ) ) ,
66+ ...sortGuilds ( filtered . filter ( ( entry ) => ! entry . canManage ) . map ( ( entry ) => entry . guild ) ) ,
67+ ] . map ( ( guild ) => ( { guild, isAmaGuestOnly : byId . get ( guild . id ) ! . isAmaGuestOnly } ) ) ;
68+ } , [ visible , searchQuery ] ) ;
4569
4670 // `me` is only `undefined` while the query is still in flight — a resolved-but-logged-out `me` never reaches
4771 // this component (`NavGateProvider` gates the whole `/dashboard` tree on it), but guarding here too keeps
@@ -50,11 +74,11 @@ export function GuildList() {
5074 return < GuildListSkeleton /> ;
5175 }
5276
53- if ( manageable . length === 0 ) {
77+ if ( visible . length === 0 ) {
5478 return (
5579 < EmptyState
5680 icon = { < FaServer className = "h-8 w-8 text-secondary dark:text-secondary-dark" /> }
57- subtitle = "You need Manage Server permissions on a Discord server to configure it here. Just got promoted? Hit Refresh above."
81+ subtitle = "You need Manage Server permissions on a Discord server to configure it here, or an invite to answer questions in someone's AMA . Just got promoted? Hit Refresh above."
5882 title = "No servers to manage yet"
5983 />
6084 ) ;
@@ -86,15 +110,21 @@ export function GuildList() {
86110 </ Tooltip >
87111 </ div >
88112 ) }
113+ { hasGuestGuilds && (
114+ < p className = "mb-4 text-sm text-secondary dark:text-secondary-dark" >
115+ Servers marked < span className = "font-medium text-primary dark:text-primary-dark" > Guest</ span > are ones you
116+ were invited to answer an AMA in — you'll only see those sessions there, nothing else.
117+ </ p >
118+ ) }
89119 < ul
90120 className = { cn (
91121 'grid grid-cols-1 gap-4 transition-opacity md:grid-cols-3 lg:grid-cols-4' ,
92122 isRefreshing && 'opacity-50' ,
93123 ) }
94124 >
95- { sorted . map ( ( guild ) => (
125+ { sorted . map ( ( { guild, isAmaGuestOnly } ) => (
96126 < li className = "min-w-0" key = { guild . id } >
97- < GuildCard data = { guild } />
127+ < GuildCard data = { guild } isGuest = { isAmaGuestOnly } />
98128 </ li >
99129 ) ) }
100130 </ ul >
0 commit comments