Dev#38
Conversation
guilds/joining new guilds in the app
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds mobile sidebar infrastructure and mobile-aware UI: a MobileSidebarProvider/context, mobile detection usage, mobile menu buttons, auto-closing mobile sidebar on navigation, mobile right-panel behavior, a responsive Sidebar layout, and a new CreateGuildDialog component. Changes
Sequence DiagramsequenceDiagram
participant User
participant UI
participant MobileSidebar as MobileSidebarContext
participant Router
User->>UI: Tap mobile menu (hamburger)
UI->>MobileSidebar: setOpen(true)
MobileSidebar-->>UI: open = true (sidebar visible)
User->>UI: Tap channel/DM item
UI->>Router: navigate(to channel/DM)
Router-->>UI: route updated
UI->>MobileSidebar: setOpen(false)
MobileSidebar-->>UI: open = false (sidebar hidden)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx (1)
201-210:⚠️ Potential issue | 🟡 MinorInconsistency in mobile handling for
togglePinnedMessages.The
useEffectat line 79 checksisMobile === falsebefore callingsetView, meaning the sidebar view is not set on mobile. However,togglePinnedMessages(line 201) callssetViewunconditionally without checkingisMobile. This creates an inconsistency: if the intent is to prevent right sidebar views from opening automatically on mobile,togglePinnedMessagesshould also checkisMobileto match this pattern. If the current behavior is intentional (allowing pinned messages to open via this callback on mobile), this should be clarified.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authenticated/`$guildSlug/$channelId.tsx around lines 201 - 210, The togglePinnedMessages callback sets the sidebar view unconditionally while the earlier useEffect prevents setting right-sidebar views when isMobile is true; update togglePinnedMessages to respect the same mobile guard by checking isMobile before calling setView (and only toggleCollapsed when appropriate), so replace the unconditional setView calls in togglePinnedMessages with a check like if (!isMobile) then setView({ type: "pinned-messages", guildSlug, channelId }) (and mirror the else branch behavior) to ensure consistent mobile handling with the existing useEffect that prevents opening right-sidebar views on mobile; reference the togglePinnedMessages function and the setView/isCollapsed/toggleCollapsed variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/components/chat/header.tsx`:
- Around line 41-48: The Menu and Members icon-only buttons rendered in the Chat
header (conditional on isMobile and calling openMobileSidebar/openMobileMembers
or similar handlers) lack accessible names; add aria-label attributes (e.g.,
aria-label="Open menu" for the Menu button and aria-label="Members" for the
Members button) to both buttons and, if the Members button is a toggle, also
expose its toggle state with aria-pressed={isMembersOpen} (or the appropriate
state variable) so screen readers receive a meaningful name and state.
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx`:
- Around line 107-119: The success paths only call onOpenChange(false) which
closes the dialog but not the mobile GuildBar sheet; update both success
branches (the blocks containing onOpenChange(false) followed by navigate(...) at
the shown diff and the similar block around lines 147-151) to also close the
mobile left sheet before navigation by invoking the function/prop that controls
the GuildBar mobile sheet (e.g., call closeGuildBarSheet(),
setGuildBarOpen(false), or the existing mobile-sheet close handler used
elsewhere in this component/context) so the sheet is dismissed, then perform
navigate(...) as before.
- Around line 64-75: getFirstChannelId currently only checks
channels.categories[0], so if the first category is empty it misses channels in
later categories; update getFirstChannelId to first check
channels.uncategorized[0]?.id, then iterate over channels.categories (e.g., for
loop or find) and return the first non-empty category.channels[0]?.id, finally
return null if none found, making sure to handle missing/empty categories arrays
from the channels response.
In `@apps/web/src/components/sidebar/guild-bar/guild-bar.tsx`:
- Around line 126-134: The add-guild button that calls setCreateDialogOpen and
renders only the Plus icon is missing an accessible name; update the button
element in guild-bar.tsx to provide an accessible label (e.g., add
aria-label="Create guild" or aria-label="Add guild"), or include visually-hidden
text inside the button while keeping the Plus icon, so assistive tech can
announce it; ensure the attribute is placed on the same button that wraps the
Plus icon and that no duplicate interactive elements are introduced.
In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 23-33: LeftSidebarContent renders GuildBar inside the mobile Sheet
but GuildBar’s navigation handlers don’t close the mobile drawer; update the
flow so GuildBar triggers closing the mobile sidebar after route changes—either
pass a close callback from LeftSidebarContent into GuildBar (e.g., pass a prop
like onNavigate that calls useMobileSidebar().setOpen(false) from
LeftSidebarContent) or add useMobileSidebar().setOpen(false) calls directly
inside GuildBar’s navigation/open-dialog handlers; ensure the same fix also
applies for the alternate rendering paths (ChannelPanel/DMPanel) referenced in
this component so taps on Home or guild icons close the sheet.
- Around line 150-170: MobileRightPanel currently opens solely based on !!view
causing stale guild-scoped content to persist when a user navigates off a guild
route; update MobileRightPanel to read the current guild slug (e.g., from
useGuild or the existing hook that exposes guildSlug), change open to !!view &&
!!guildSlug, and add an effect that calls clearView() whenever guildSlug becomes
falsy so the sheet is closed/cleared when leaving a guild route; reference the
existing symbols MobileRightPanel, useRightSidebar (view, clearView) and the
guildSlug value in your changes.
---
Outside diff comments:
In `@apps/web/src/routes/_authenticated/`$guildSlug/$channelId.tsx:
- Around line 201-210: The togglePinnedMessages callback sets the sidebar view
unconditionally while the earlier useEffect prevents setting right-sidebar views
when isMobile is true; update togglePinnedMessages to respect the same mobile
guard by checking isMobile before calling setView (and only toggleCollapsed when
appropriate), so replace the unconditional setView calls in togglePinnedMessages
with a check like if (!isMobile) then setView({ type: "pinned-messages",
guildSlug, channelId }) (and mirror the else branch behavior) to ensure
consistent mobile handling with the existing useEffect that prevents opening
right-sidebar views on mobile; reference the togglePinnedMessages function and
the setView/isCollapsed/toggleCollapsed variables.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4ade616b-31ca-465b-9f92-4365a0ea3c4b
📒 Files selected for processing (14)
apps/web/src/components/allies/allies-page.tsxapps/web/src/components/chat/header.tsxapps/web/src/components/onboarding/onboarding-dialog.tsxapps/web/src/components/sidebar/channel-panel/channel-list.tsxapps/web/src/components/sidebar/dm-panel/dm-list.tsxapps/web/src/components/sidebar/dm-panel/dm-panel.tsxapps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsxapps/web/src/components/sidebar/guild-bar/guild-bar.tsxapps/web/src/components/sidebar/index.tsxapps/web/src/components/sidebar/right-panel/guild-members-panel.tsxapps/web/src/components/sidebar/right-panel/pinned-messages-panel.tsxapps/web/src/context/mobile-sidebar-context.tsxapps/web/src/routes/_authenticated.tsxapps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx
| {isMobile && ( | ||
| <button | ||
| type="button" | ||
| onClick={() => openMobileSidebar(true)} | ||
| className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground" | ||
| > | ||
| <Menu className="size-5" /> | ||
| </button> |
There was a problem hiding this comment.
Give the new icon-only mobile controls accessible names.
The new Menu and Members buttons render only icons. Tooltips do not provide a reliable accessible name, so screen readers get unlabeled controls for two primary mobile actions. Add aria-labels; if the Members button remains a toggle, expose its state with aria-pressed too.
Suggested fix
<button
type="button"
onClick={() => openMobileSidebar(true)}
+ aria-label="Open navigation menu"
className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
>
@@
<button
type="button"
onClick={() => {
if (view) {
clearView()
} else {
setView({
type: "guild-members",
guildSlug: guildSlug ?? "",
channelId,
})
}
}}
+ aria-label="Toggle members panel"
className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
>Also applies to: 98-119
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/chat/header.tsx` around lines 41 - 48, The Menu and
Members icon-only buttons rendered in the Chat header (conditional on isMobile
and calling openMobileSidebar/openMobileMembers or similar handlers) lack
accessible names; add aria-label attributes (e.g., aria-label="Open menu" for
the Menu button and aria-label="Members" for the Members button) to both buttons
and, if the Members button is a toggle, also expose its toggle state with
aria-pressed={isMembersOpen} (or the appropriate state variable) so screen
readers receive a meaningful name and state.
| onOpenChange(false) | ||
|
|
||
| if (firstChannelId) { | ||
| navigate({ | ||
| to: "/$guildSlug/$channelId", | ||
| params: { guildSlug: createdGuildSlug, channelId: firstChannelId }, | ||
| }) | ||
| } else { | ||
| navigate({ | ||
| to: "/$guildSlug", | ||
| params: { guildSlug: createdGuildSlug }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Close the mobile drawer before redirecting out of this dialog.
This dialog is now reachable from the mobile left sheet via GuildBar, but the success paths only call onOpenChange(false). On mobile that dismisses the dialog, not the sheet, so the destination page stays covered after create/join completes.
Suggested fix
import { ArrowLeft, Loader2, Plus, Users } from "lucide-react"
import { useEffect, useState } from "react"
+import { useMobileSidebar } from "@/context/mobile-sidebar-context"
import { apiClient } from "@/lib/api-client"
@@
const queryClient = useQueryClient()
const navigate = useNavigate()
+ const { setOpen: setMobileSidebarOpen } = useMobileSidebar()
@@
onOpenChange(false)
+ setMobileSidebarOpen(false)
if (firstChannelId) {
@@
onOpenChange(false)
+ setMobileSidebarOpen(false)
await navigate({
to: "/invite/$code",
params: { code: inviteCode },Also applies to: 147-151
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 107 - 119, The success paths only call onOpenChange(false) which closes
the dialog but not the mobile GuildBar sheet; update both success branches (the
blocks containing onOpenChange(false) followed by navigate(...) at the shown
diff and the similar block around lines 147-151) to also close the mobile left
sheet before navigation by invoking the function/prop that controls the GuildBar
mobile sheet (e.g., call closeGuildBarSheet(), setGuildBarOpen(false), or the
existing mobile-sheet close handler used elsewhere in this component/context) so
the sheet is dismissed, then perform navigate(...) as before.
| <button | ||
| type="button" | ||
| onClick={() => setCreateDialogOpen(true)} | ||
| className="group relative flex items-center justify-center px-3 py-1" | ||
| > | ||
| <div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white"> | ||
| <Plus className="size-6" /> | ||
| </div> | ||
| </div> | ||
| </button> |
There was a problem hiding this comment.
Add an accessible name to the new icon-only add-guild button.
This control only renders a Plus icon, so assistive tech will announce it as an unlabeled button. Give it an aria-label at minimum.
Suggested fix
<button
type="button"
onClick={() => setCreateDialogOpen(true)}
+ aria-label="Add guild"
+ aria-haspopup="dialog"
className="group relative flex items-center justify-center px-3 py-1"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| type="button" | |
| onClick={() => setCreateDialogOpen(true)} | |
| className="group relative flex items-center justify-center px-3 py-1" | |
| > | |
| <div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white"> | |
| <Plus className="size-6" /> | |
| </div> | |
| </div> | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => setCreateDialogOpen(true)} | |
| aria-label="Add guild" | |
| aria-haspopup="dialog" | |
| className="group relative flex items-center justify-center px-3 py-1" | |
| > | |
| <div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white"> | |
| <Plus className="size-6" /> | |
| </div> | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/sidebar/guild-bar/guild-bar.tsx` around lines 126 -
134, The add-guild button that calls setCreateDialogOpen and renders only the
Plus icon is missing an accessible name; update the button element in
guild-bar.tsx to provide an accessible label (e.g., add aria-label="Create
guild" or aria-label="Add guild"), or include visually-hidden text inside the
button while keeping the Plus icon, so assistive tech can announce it; ensure
the attribute is placed on the same button that wraps the Plus icon and that no
duplicate interactive elements are introduced.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
apps/web/src/components/sidebar/index.tsx (1)
150-160:⚠️ Potential issue | 🟠 MajorClear the mobile right-sidebar view when the guild context disappears.
Line 153 only hides the sheet when
guildSlugis falsy; it does not resetview. Because the provider keeps that state untilclearView()runs, a stale guild-scoped panel can reopen unexpectedly on the next guild route.Suggested fix
-import { useCallback, useRef, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" @@ function MobileRightPanel() { const { view, clearView } = useRightSidebar() const { guildSlug } = useParams({ strict: false }) const open = !!view && !!guildSlug + + useEffect(() => { + if (!guildSlug && view) { + clearView() + } + }, [guildSlug, view, clearView]) return (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/sidebar/index.tsx` around lines 150 - 160, The MobileRightPanel currently only hides the Sheet when guildSlug is falsy but doesn’t reset the sidebar state; update MobileRightPanel so the provider state is cleared whenever the guild context disappears by invoking clearView() when guildSlug becomes falsy (e.g., add a useEffect watching guildSlug: if (!guildSlug) clearView()). Reference the MobileRightPanel component, useRightSidebar (clearView), and useParams (guildSlug) to locate where to add the effect so stale guild-scoped panels cannot reopen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx`:
- Around line 64-75: getFirstChannelId and the invite-code parsing logic are
duplicated between create-guild-dialog (getFirstChannelId) and
onboarding-dialog; extract both into a shared utility (e.g., export functions
like getFirstChannelId and parseInviteCode from a new onboarding/guild helpers
module), replace the local implementations in create-guild-dialog and
onboarding-dialog with imports from that module, ensure the shared
getFirstChannelId keeps the same API (takes guildSlug and returns channel
id|null) and the parseInviteCode accepts the same input currently used in both
places and returns the same output shape, and update any references to use the
new exported names.
- Around line 77-125: The create flow can complete after the user dismisses the
dialog; fix by blocking closure while the request is in-flight and
aborting/post-checking the async flow: in CreateGuildDialog update handleCreate
to create an AbortController and (if authClient supports it) pass its signal to
authClient.organization.create, and after each await check
controller.signal.aborted (or a local `cancelled` flag) before calling setError,
queryClient.invalidateQueries, getFirstChannelId, onOpenChange, or navigate;
also prevent the UI from closing while loading by guarding onOpenChange so it
ignores attempts to close when `loading` is true (i.e., only allow
onOpenChange(false) when !loading) and disable modal/backdrop/escape-close props
while loading so the Back/dismiss buttons are blocked during the request
(references: handleCreate, authClient.organization.create, getFirstChannelId,
onOpenChange, navigate, setLoading).
In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 174-187: SidebarLayout currently mounts DesktopSidebarLayout
because useIsMobile() defaults to false before it settles, causing a
desktop-to-mobile flash; instead, ensure the component waits for the mobile
detection to settle before choosing a branch: update the hook or guard in
SidebarLayout so useIsMobile() can indicate an "undetermined" state (e.g.,
undefined or isResolved flag) and if undetermined return a neutral placeholder
(or null) rather than rendering DesktopSidebarLayout; reference SidebarLayout,
useIsMobile, MobileSidebar, MobileRightPanel, DesktopSidebarLayout and children
when making the guard so mobile users are routed to the mobile tree on first
render without remounting children.
---
Duplicate comments:
In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 150-160: The MobileRightPanel currently only hides the Sheet when
guildSlug is falsy but doesn’t reset the sidebar state; update MobileRightPanel
so the provider state is cleared whenever the guild context disappears by
invoking clearView() when guildSlug becomes falsy (e.g., add a useEffect
watching guildSlug: if (!guildSlug) clearView()). Reference the MobileRightPanel
component, useRightSidebar (clearView), and useParams (guildSlug) to locate
where to add the effect so stale guild-scoped panels cannot reopen.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 70b9b04f-d5b7-4f4b-ba18-39dad913093d
📒 Files selected for processing (3)
apps/web/src/components/onboarding/onboarding-dialog.tsxapps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsxapps/web/src/components/sidebar/index.tsx
| const getFirstChannelId = async (guildSlug: string) => { | ||
| const channelsRes = await apiClient.v1.guilds[":guildSlug"].channels.$get({ | ||
| param: { guildSlug }, | ||
| }) | ||
| if (!channelsRes.ok) return null | ||
| const channels = await channelsRes.json() | ||
| if (channels.uncategorized[0]?.id) return channels.uncategorized[0].id | ||
| for (const cat of channels.categories) { | ||
| if (cat.channels[0]?.id) return cat.channels[0].id | ||
| } | ||
| return null | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract the shared onboarding helpers.
getFirstChannelId and invite-code parsing now duplicate logic in apps/web/src/components/onboarding/onboarding-dialog.tsx. These rules already had to be re-aligned in this PR, so please move them into a shared utility before the two flows drift again.
Also applies to: 133-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 64 - 75, getFirstChannelId and the invite-code parsing logic are
duplicated between create-guild-dialog (getFirstChannelId) and
onboarding-dialog; extract both into a shared utility (e.g., export functions
like getFirstChannelId and parseInviteCode from a new onboarding/guild helpers
module), replace the local implementations in create-guild-dialog and
onboarding-dialog with imports from that module, ensure the shared
getFirstChannelId keeps the same API (takes guildSlug and returns channel
id|null) and the parseInviteCode accepts the same input currently used in both
places and returns the same output shape, and update any references to use the
new exported names.
| const handleCreate = async (e: React.FormEvent) => { | ||
| e.preventDefault() | ||
| const normalizedSlug = sluggify(slug) | ||
| if (!name.trim() || !normalizedSlug) return | ||
| setError(null) | ||
| setLoading(true) | ||
|
|
||
| try { | ||
| const res = await authClient.organization.create({ | ||
| name: name.trim(), | ||
| slug: normalizedSlug, | ||
| }) | ||
|
|
||
| if (res.error) { | ||
| const message = (res.error.message ?? "Failed to create guild").replace( | ||
| /organization/gi, | ||
| "Guild" | ||
| ) | ||
| setError(message) | ||
| return | ||
| } | ||
|
|
||
| const createdGuildSlug = res.data?.slug ?? normalizedSlug | ||
| await queryClient.invalidateQueries({ queryKey: ["guilds"] }) | ||
|
|
||
| let firstChannelId: string | null = null | ||
| try { | ||
| firstChannelId = await getFirstChannelId(createdGuildSlug) | ||
| } catch {} | ||
|
|
||
| onOpenChange(false) | ||
|
|
||
| if (firstChannelId) { | ||
| navigate({ | ||
| to: "/$guildSlug/$channelId", | ||
| params: { guildSlug: createdGuildSlug, channelId: firstChannelId }, | ||
| }) | ||
| } else { | ||
| navigate({ | ||
| to: "/$guildSlug", | ||
| params: { guildSlug: createdGuildSlug }, | ||
| }) | ||
| } | ||
| } catch { | ||
| setError("Something went wrong. Please try again.") | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
| } |
There was a problem hiding this comment.
Block exit paths while the create request is in flight.
Right now the create call keeps running even if the user clicks Back or dismisses the modal. That makes cancel misleading, because the guild can still be created and the app will redirect there once the promise resolves.
Suggested fix
export function CreateGuildDialog({
open,
onOpenChange,
}: {
open: boolean
onOpenChange: (open: boolean) => void
}) {
+ const handleOpenChange = (nextOpen: boolean) => {
+ if (!loading) onOpenChange(nextOpen)
+ }
+
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="sm:max-w-md">
+ return (
+ <Dialog open={open} onOpenChange={handleOpenChange}>
+ <DialogContent
+ showCloseButton={!loading}
+ onInteractOutside={loading ? (e) => e.preventDefault() : undefined}
+ onEscapeKeyDown={loading ? (e) => e.preventDefault() : undefined}
+ className="sm:max-w-md"
+ > <button
type="button"
+ disabled={loading}
onClick={() => {
setStep("choose")
setError(null)
}}Also applies to: 155-156, 202-212
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 77 - 125, The create flow can complete after the user dismisses the
dialog; fix by blocking closure while the request is in-flight and
aborting/post-checking the async flow: in CreateGuildDialog update handleCreate
to create an AbortController and (if authClient supports it) pass its signal to
authClient.organization.create, and after each await check
controller.signal.aborted (or a local `cancelled` flag) before calling setError,
queryClient.invalidateQueries, getFirstChannelId, onOpenChange, or navigate;
also prevent the UI from closing while loading by guarding onOpenChange so it
ignores attempts to close when `loading` is true (i.e., only allow
onOpenChange(false) when !loading) and disable modal/backdrop/escape-close props
while loading so the Back/dismiss buttons are blocked during the request
(references: handleCreate, authClient.organization.create, getFirstChannelId,
onOpenChange, navigate, setLoading).
| function SidebarLayout({ children }: { children: React.ReactNode }) { | ||
| const isMobile = useIsMobile() | ||
|
|
||
| if (isMobile) { | ||
| return ( | ||
| <div className="flex h-full w-full flex-col"> | ||
| <MobileSidebar /> | ||
| <MobileRightPanel /> | ||
| {children} | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| return <DesktopSidebarLayout>{children}</DesktopSidebarLayout> |
There was a problem hiding this comment.
Don’t route mobile users through the desktop layout on the first render.
useIsMobile() resolves to false before its effect runs, so phones mount DesktopSidebarLayout here and then swap to the mobile tree on the next pass. That causes a desktop-to-mobile flash, remounts children, and can briefly seed desktop right-sidebar state from the channel-route effect that keys on isMobile === false.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/sidebar/index.tsx` around lines 174 - 187,
SidebarLayout currently mounts DesktopSidebarLayout because useIsMobile()
defaults to false before it settles, causing a desktop-to-mobile flash; instead,
ensure the component waits for the mobile detection to settle before choosing a
branch: update the hook or guard in SidebarLayout so useIsMobile() can indicate
an "undetermined" state (e.g., undefined or isResolved flag) and if undetermined
return a neutral placeholder (or null) rather than rendering
DesktopSidebarLayout; reference SidebarLayout, useIsMobile, MobileSidebar,
MobileRightPanel, DesktopSidebarLayout and children when making the guard so
mobile users are routed to the mobile tree on first render without remounting
children.
Pull Request Summary
Overview
This PR implements mobile-responsive sidebar behavior across the app and adds a full guild create/join dialog. It introduces a MobileSidebar context/provider and hooks, converts desktop side panels into mobile Sheets, updates navigation to close the mobile sidebar after navigation, and adds CreateGuildDialog with create and join flows.
Key Changes
Mobile sidebar context & provider
Mobile layout & navigation
viewis set and clearsviewon close.Right sidebar behavior adapted for mobile
Create/join guild dialog
Small UX updates and fixes
guild-membersview to desktop only (effect depends on isMobile).Files of Note
Code Quality & Observations
Potential Concerns / Recommendations
Confidence Score: 4/5