Skip to content

[fix] sidebar:組織切換即時回饋、user menu 改成選單並可接受邀請 - #65

Merged
YJack0000 merged 2 commits into
mainfrom
fix/sidebar-org-switcher-user-menu
Sep 7, 2026
Merged

[fix] sidebar:組織切換即時回饋、user menu 改成選單並可接受邀請#65
YJack0000 merged 2 commits into
mainfrom
fix/sidebar-org-switcher-user-menu

Conversation

@YJack0000

Copy link
Copy Markdown
Contributor

側邊欄三個一直被抱怨的地方,一次修掉。

1. 組織切換器常常是空的、選完要等很久

問題OrgSwitcher 完全靠 client hook(useListOrganizations() / useActiveOrganization())。第一次繪製時兩個都還沒回來,所以看到的是一個空白而且 disabled 的下拉選單;選完之後 await setActiverouter.refresh(),期間每一頁 force-dynamic 的 server component 都要重跑,畫面卻完全沒有「正在換」的跡象,而且顯示的值要等 hook 重抓才會更新。另外還有一段 localStorage「還原上次組織」的 effect,會在 mount 時再觸發一次切換 + refresh。

改法

  • src/app/dashboard/layout.tsx(server component)一次撈好 session 與 auth.api.listOrganizations(),把 initialOrganizations / initialActiveOrgId 傳給 AppSidebarOrgSwitcher。第一次繪製就有組織名稱可顯示。
  • client hook 降級成「首次繪製之後保持清單新鮮」(organizations ?? initialOrganizations)。
  • 本地 selectedIdinitialActiveOrgId 起始,選擇當下就樂觀更新,下拉立刻顯示新組織;失敗才回捲並跳 toast。
  • 真的什麼都沒有時才顯示 Skeleton,不再出現空白 disabled 的下拉。
  • 新增 src/components/refresh-pending.tsxRefreshPendingProvider / useRefreshPending / RefreshPendingOverlay。切換期間主內容區壓暗(opacity + pointer-events-none)並在最上緣顯示一條不定量進度條,切換器本身換成 spinner。
  • 移除 localStorage 的 lastOrgId 還原邏輯 —— session 本來就記得 active org,那段自動切換只會造成莫名其妙的重新載入。

2. 使用者按鈕一按就登出

問題UserMenu 的整顆按鈕就是 signOut,沒有選單、沒有確認,手滑一次就掉出去。

改法:改成 DropdownMenu,圖示從 LogOut 換成 ChevronsUpDown。選單內容由上而下:

  1. 名稱 / email / 目前組織的唯讀標題區。
  2. 「邀請」項目,有待處理邀請時帶數字 badge;trigger 上的頭像也會出現一個小圓點,關著選單也看得到。
  3. 分隔線。
  4. 「登出」(destructive 樣式),行為與原本相同。選單本身就是一層刻意動作,不再另外加確認框。

3. 邀請只有 /onboarding 看得到

問題:接受邀請的 UI 只在 /onboarding,而那頁只有「還沒有任何組織」的人進得去。已經在某個組織、又被邀請去另一個組織的人,永遠看不到那張邀請(目前也沒有邀請信,sendInvitationEmail 沒設定)。

改法

  • 新增 src/components/pending-invitations.tsx,把 onboarding 的邀請清單與接受邏輯抽出來共用。導出 useUserInvitations()(回傳 { invites, acceptable, expired, loading, refresh })與 PendingInvitations 元件。過期/未過期的切分沿用 onboarding 原本的判斷(listUserInvitations 只濾 status === "pending",過期的仍會回來,得在前端自己分)。
  • 每張邀請顯示組織名稱、身分、到期日,加上「接受」與「婉拒」(acceptInvitation / rejectInvitation);過期的一樣只顯示不給按鈕。
  • user menu 的「邀請」開一個 Dialog 裝這個元件,沒有邀請時顯示空狀態。
  • onboarding 行為不變,改用同一個元件,不再重複一份邏輯。
  • 不做持續輪詢:user menu mount 時取一次,選單打開時再取一次。
  • i18n:邀請字串從 auth.onboarding.invites 提到 auth.invites(兩邊共用同一份),userMenu.* 補在 common.ts

Render 策略

初始資料由 dashboard 的 server layout 一次撈好往下傳,client 端只負責保持新鮮;互動先樂觀更新本地 state,真正的 server 往返用共用的 transition 包起來,pending 狀態透過 context 傳到主內容區顯示。transition 開在 RefreshPendingProvider 而不是各自在呼叫端,因為 router.refresh() 要在 transition 裡呼叫,isPending 才會一路撐到 server 回來 —— 呼叫端自己開的話只能在送出 refresh() 的當下關掉旗標,而那正好是等待開始的時間點。

沒有 schema 變更。

驗證

  • bun run lint 通過
  • bunx tsc --noEmit 零錯誤
  • bun run build 成功
  • built server 起來 smoke test:/login 200、/onboarding/dashboard 未登入正常 307,無 runtime error

@YJack0000
YJack0000 requested a review from yui0303 September 7, 2026 16:03
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

✅ SonarQube Quality Gate passed — pathorsAI_internal

0 open issues on this PR.

@yui0303 yui0303 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved per request.

@YJack0000
YJack0000 merged commit be4462d into main Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants