ソースを参照

Move authentication into the main app shell and gate API-backed views for guests.

Replace the old login page with a header Zoom sign-in/profile flow, add profile-menu logout, and keep post-login users in-app while refreshing protected content states.

Made-with: Cursor
huzaifahayat12 3 ヶ月 前
コミット
9173bb6613
1 ファイル変更176 行追加200 行削除
  1. 176 200
      zoom_app/ViewController.swift

+ 176 - 200
zoom_app/ViewController.swift

@@ -186,11 +186,8 @@ class ViewController: NSViewController {
     private var mutedText: NSColor { palette.mutedText }
 
     private let rootContainer = NSView()
-    private var loginView: NSView?
     private var homeView: NSView?
     private weak var googleButton: NSButton?
-    private weak var nextSignInButton: NSButton?
-    private weak var zoomSocialButton: NSButton?
     private weak var timeLabel: NSTextField?
     private weak var dateLabel: NSTextField?
     private weak var meetingsDayHeaderLabel: NSTextField?
@@ -242,6 +239,8 @@ class ViewController: NSViewController {
     private weak var settingsRestoreButton: NSButton?
     private weak var settingsGoogleActionButton: NSButton?
     private weak var topBarPremiumButton: NSButton?
+    private weak var topBarAuthButton: NSButton?
+    private var topBarAuthButtonWidthConstraint: NSLayoutConstraint?
     private var paywallWindow: NSWindow?
     private var joinMeetingWindow: NSWindow?
     private var scheduleMeetingWindow: NSWindow?
@@ -285,6 +284,7 @@ class ViewController: NSViewController {
     private var searchOutsideClickMonitor: Any?
     private var meetingSearchTextObserver: NSObjectProtocol?
     private var globalSearchResults: [ScheduledMeeting] = []
+    private var topBarProfileInitial = "H"
     private var clockTimer: Timer?
     private var meetingsRefreshTimer: Timer?
     private var isSigningIn = false
@@ -308,7 +308,6 @@ class ViewController: NSViewController {
     private let meetingsPageSize: Int = 8
     
     private enum SidebarStyle {
-        case login
         case home
     }
 
@@ -477,11 +476,7 @@ class ViewController: NSViewController {
             window.styleMask.insert(.fullSizeContentView)
         }
         alignNativeTrafficLights()
-        if isUserLoggedIn() {
-            showHomeView(profile: nil)
-        } else {
-            showLoginView()
-        }
+        showHomeView(profile: nil)
     }
 
     override func viewDidLayout() {
@@ -503,54 +498,7 @@ class ViewController: NSViewController {
         ])
     }
 
-    private func showLoginView() {
-        clockTimer?.invalidate()
-        meetingsRefreshTimer?.invalidate()
-        meetingsRefreshTimer = nil
-        clearMeetingsScrollObserver()
-        clearMeetingsPageScrollObserver()
-        removeSearchFieldObserver()
-        removeMeetingSearchObserver()
-        removeSearchShortcutMonitor()
-        homeSearchField = nil
-        homeSearchPill = nil
-        meetingSearchOverlayView = nil
-        meetingSearchPanelView = nil
-        meetingSearchField = nil
-        meetingSearchResultsStack = nil
-        meetingSearchEmptyLabel = nil
-        meetingsPageRootView = nil
-        meetingsPageCategoryFilter = nil
-        meetingsPageFromDatePicker = nil
-        meetingsPageToDatePicker = nil
-        meetingsPageRefreshButton = nil
-        meetingsPageCountLabel = nil
-        meetingsPageStatusLabel = nil
-        meetingsPageListStack = nil
-        meetingsPageEmptyLabel = nil
-        meetingsPageLoadMoreButton = nil
-        meetingsPageFilteredMeetings = []
-        meetingsPageSelectedRangePreset = "All"
-        meetingsPageFromDateFilter = nil
-        meetingsPageToDateFilter = nil
-        meetingsPageVisibleCount = 0
-        allScheduledMeetings = []
-        homeView?.removeFromSuperview()
-        homeView = nil
-        isSigningIn = false
-        nextSignInButton?.title = "Next"
-        nextSignInButton?.isEnabled = true
-        zoomSocialButton?.isEnabled = true
-
-        if loginView == nil {
-            loginView = makeLoginView()
-        }
-        guard let loginView else { return }
-        attachToRoot(loginView)
-    }
-
     private func showHomeView(profile: GoogleUserProfile?) {
-        loginView?.removeFromSuperview()
         clearMeetingsScrollObserver()
         clearMeetingsPageScrollObserver()
         removeSearchFieldObserver()
@@ -565,14 +513,15 @@ class ViewController: NSViewController {
         meetingSearchEmptyLabel = nil
         meetingsPageRootView = nil
         homeView?.removeFromSuperview()
+        if let profileName = profile?.name?.trimmingCharacters(in: .whitespacesAndNewlines), profileName.isEmpty == false {
+            topBarProfileInitial = String(profileName.prefix(1)).uppercased()
+        }
         selectedMeetingsDayStart = Calendar.current.startOfDay(for: Date())
         homeView = makeHomeView(profile: profile)
         if let homeView { attachToRoot(homeView) }
         installSearchShortcutMonitor()
-        persistLoggedInState(true)
         startClock()
-        startMeetingsAutoRefresh()
-        triggerMeetingsRefresh(force: true)
+        applyAuthenticationUIState(triggerInitialRefresh: true)
         updateHomeSidebarHighlight()
         updateSelectedHomeSectionUI()
     }
@@ -609,10 +558,9 @@ class ViewController: NSViewController {
                 let token = try await googleOAuth.validAccessToken(presentingWindow: view.window)
                 let profile = try? await googleOAuth.fetchUserProfile(accessToken: token)
                 await MainActor.run {
-                    self.isSigningIn = false
                     self.googleButton?.title = "G"
-                    self.googleButton?.isEnabled = true
-                    self.showHomeView(profile: profile)
+                    self.resetLoginSigningInState()
+                    self.completeLoginAndRestart(profile: profile)
                 }
             } catch {
                 await MainActor.run {
@@ -629,9 +577,8 @@ class ViewController: NSViewController {
     @objc private func zoomPrimarySignInTapped() {
         guard isSigningIn == false else { return }
         isSigningIn = true
-        nextSignInButton?.title = "Signing in…"
-        nextSignInButton?.isEnabled = false
-        zoomSocialButton?.isEnabled = false
+        topBarAuthButton?.title = "Signing in..."
+        topBarAuthButton?.isEnabled = false
         googleButton?.isEnabled = false
 
         Task {
@@ -648,7 +595,7 @@ class ViewController: NSViewController {
 
                 await MainActor.run {
                     self.resetLoginSigningInState()
-                    self.showHomeView(profile: profile)
+                    self.completeLoginAndRestart(profile: profile)
                 }
             } catch {
                 await MainActor.run {
@@ -660,6 +607,10 @@ class ViewController: NSViewController {
     }
 
     @objc private func scheduleMeetingTapped() {
+        guard isUserLoggedIn() else {
+            showSimpleAlert(title: "Sign in required", message: "Please sign in with Zoom to schedule a meeting.")
+            return
+        }
         if let existing = scheduleMeetingWindow {
             existing.makeKeyAndOrderFront(nil)
             NSApp.activate(ignoringOtherApps: true)
@@ -1385,7 +1336,33 @@ class ViewController: NSViewController {
         googleOAuth.clearSavedTokens()
         zoomOAuth.clearSavedTokens()
         persistLoggedInState(false)
-        showLoginView()
+        allScheduledMeetings = []
+        applyAuthenticationUIState(triggerInitialRefresh: false)
+        Task { @MainActor in
+            self.applyMeetings([])
+        }
+    }
+
+    @objc private func topBarAuthButtonTapped() {
+        if isUserLoggedIn() {
+            showProfileMenu()
+        } else {
+            zoomPrimarySignInTapped()
+        }
+    }
+
+    @objc private func profileMenuLogoutTapped() {
+        logoutTapped()
+    }
+
+    private func showProfileMenu() {
+        guard let button = topBarAuthButton else { return }
+        let menu = NSMenu()
+        let logoutItem = NSMenuItem(title: "Logout", action: #selector(profileMenuLogoutTapped), keyEquivalent: "")
+        logoutItem.target = self
+        menu.addItem(logoutItem)
+        let menuOrigin = NSPoint(x: button.bounds.minX, y: button.bounds.minY - 6)
+        menu.popUp(positioning: nil, at: menuOrigin, in: button)
     }
     
     @objc private func topBarPlaceholderTapped() {
@@ -1407,6 +1384,10 @@ class ViewController: NSViewController {
     }
 
     @objc private func refreshMeetingsTapped() {
+        guard isUserLoggedIn() else {
+            applyGuestLoginMessages()
+            return
+        }
         Task { @MainActor in
             self.animateMeetingsRefresh()
             self.setMeetingsLoadingUI(true)
@@ -1443,6 +1424,7 @@ class ViewController: NSViewController {
     }
 
     private func startMeetingsAutoRefresh() {
+        guard isUserLoggedIn() else { return }
         meetingsRefreshTimer?.invalidate()
         // Poll Zoom meetings periodically so newly scheduled meetings appear automatically.
         meetingsRefreshTimer = Timer.scheduledTimer(withTimeInterval: meetingsRefreshInterval, repeats: true) { [weak self] _ in
@@ -1452,6 +1434,7 @@ class ViewController: NSViewController {
     }
 
     private func triggerMeetingsRefresh(force: Bool = false) {
+        guard isUserLoggedIn() else { return }
         let now = Date()
         if let until = meetingsRateLimitedUntil, now < until {
             return
@@ -1782,12 +1765,77 @@ class ViewController: NSViewController {
     @MainActor
     private func resetLoginSigningInState() {
         isSigningIn = false
-        nextSignInButton?.title = "Next"
-        nextSignInButton?.isEnabled = true
-        zoomSocialButton?.isEnabled = true
+        updateTopBarAuthButton()
         googleButton?.isEnabled = true
     }
 
+    @MainActor
+    private func completeLoginAndRestart(profile: GoogleUserProfile?) {
+        persistLoggedInState(true)
+        showHomeView(profile: profile)
+        if let window = view.window {
+            window.makeKeyAndOrderFront(nil)
+        }
+        NSApp.activate(ignoringOtherApps: true)
+    }
+
+    @MainActor
+    private func updateTopBarAuthButton() {
+        guard let button = topBarAuthButton else { return }
+        let loggedIn = isUserLoggedIn()
+        if loggedIn {
+            button.title = topBarProfileInitial
+            button.toolTip = "Profile"
+            button.font = .systemFont(ofSize: 14, weight: .bold)
+            button.contentTintColor = primaryText
+            button.layer?.backgroundColor = accentBlue.withAlphaComponent(0.75).cgColor
+            button.layer?.cornerRadius = 10
+            topBarAuthButtonWidthConstraint?.constant = 34
+        } else {
+            button.title = "Sign in with Zoom"
+            button.toolTip = "Sign in with Zoom"
+            button.font = .systemFont(ofSize: 12, weight: .semibold)
+            button.contentTintColor = .white
+            button.layer?.backgroundColor = accentBlue.cgColor
+            button.layer?.cornerRadius = 11
+            topBarAuthButtonWidthConstraint?.constant = 120
+        }
+        button.isEnabled = isSigningIn == false
+    }
+
+    @MainActor
+    private func applyGuestLoginMessages() {
+        meetingsStatusLabel?.stringValue = "Sign in with Zoom to load meetings."
+        emptyMeetingLabel?.isHidden = false
+        emptyMeetingLabel?.stringValue = "Please sign in with Zoom to view your scheduled meetings."
+        meetingsPageStatusLabel?.stringValue = "Sign in with Zoom to view meetings."
+        meetingsPageEmptyLabel?.isHidden = false
+        meetingsPageEmptyLabel?.stringValue = "Please sign in with Zoom to view your meetings."
+        meetingsPageCountLabel?.stringValue = "0 scheduled"
+        schedulerMeetingsEmptyLabel?.isHidden = false
+        schedulerMeetingsEmptyLabel?.stringValue = "Please sign in with Zoom to use Scheduler."
+    }
+
+    @MainActor
+    private func applyAuthenticationUIState(triggerInitialRefresh: Bool) {
+        let loggedIn = isUserLoggedIn()
+        updateTopBarAuthButton()
+        settingsGoogleActionButton?.title = loggedIn ? "Sign Out" : "Sign in with Zoom"
+        settingsGoogleActionButton?.isEnabled = true
+
+        meetingsRefreshTimer?.invalidate()
+        meetingsRefreshTimer = nil
+        meetingsRateLimitedUntil = nil
+        if loggedIn {
+            startMeetingsAutoRefresh()
+            if triggerInitialRefresh {
+                triggerMeetingsRefresh(force: true)
+            }
+        } else {
+            applyGuestLoginMessages()
+        }
+    }
+
     /// Returns false if the user cancelled or left credentials empty.
     @MainActor
     private func ensureZoomOAuthClientConfigured() -> Bool {
@@ -1840,6 +1888,15 @@ class ViewController: NSViewController {
             view.removeFromSuperview()
         }
 
+        guard isUserLoggedIn() else {
+            meetingsStatusLabel?.stringValue = "Sign in with Zoom to load meetings."
+            emptyMeetingLabel?.isHidden = false
+            emptyMeetingLabel?.stringValue = "Please sign in with Zoom to view your scheduled meetings."
+            applySchedulerMeetingsForSelectedDate()
+            renderSchedulerCalendarGrid()
+            return
+        }
+
         let query = (homeSearchField?.stringValue ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
         let calendar = Calendar.current
         let dayStart = selectedMeetingsDayStart
@@ -1889,6 +1946,12 @@ class ViewController: NSViewController {
 
     @MainActor
     private func applyMeetingsPageFilters(resetVisible: Bool) {
+        guard isUserLoggedIn() else {
+            meetingsPageFilteredMeetings = []
+            meetingsPageVisibleCount = 0
+            renderMeetingsPageList()
+            return
+        }
         let calendar = Calendar.current
         let now = Date()
         let activePreset = meetingsPageCategoryFilter?.titleOfSelectedItem ?? meetingsPageSelectedRangePreset
@@ -1965,6 +2028,15 @@ class ViewController: NSViewController {
             view.removeFromSuperview()
         }
 
+        guard isUserLoggedIn() else {
+            meetingsPageCountLabel?.stringValue = "0 scheduled"
+            meetingsPageStatusLabel?.stringValue = "Sign in with Zoom to view meetings."
+            meetingsPageEmptyLabel?.isHidden = false
+            meetingsPageEmptyLabel?.stringValue = "Please sign in with Zoom to view your meetings."
+            meetingsPageLoadMoreButton?.isHidden = true
+            return
+        }
+
         let totalCount = meetingsPageFilteredMeetings.count
         let visibleCount = min(meetingsPageVisibleCount, totalCount)
         meetingsPageCountLabel?.stringValue = "\(totalCount) scheduled"
@@ -2133,6 +2205,12 @@ class ViewController: NSViewController {
             view.removeFromSuperview()
         }
 
+        guard isUserLoggedIn() else {
+            schedulerMeetingsEmptyLabel?.isHidden = false
+            schedulerMeetingsEmptyLabel?.stringValue = "Please sign in with Zoom to use Scheduler."
+            return
+        }
+
         let meetings = meetingsForDay(selectedMeetingsDayStart)
         if meetings.isEmpty {
             schedulerMeetingsEmptyLabel?.isHidden = false
@@ -2183,7 +2261,7 @@ class ViewController: NSViewController {
                 let dayDate = calendar.date(byAdding: .day, value: dayOffset, to: monthStart) ?? monthStart
                 let dayNumber = calendar.component(.day, from: dayDate)
                 let inCurrentMonth = monthRange.contains(dayNumber) && calendar.isDate(dayDate, equalTo: monthStart, toGranularity: .month)
-                let hasMeetings = meetingsForDay(dayDate).isEmpty == false
+                let hasMeetings = isUserLoggedIn() && meetingsForDay(dayDate).isEmpty == false
                 let button = makeSchedulerDayCell(
                     day: dayNumber,
                     date: dayDate,
@@ -2301,6 +2379,13 @@ class ViewController: NSViewController {
     }
 
     private func loadScheduledMeetings() async {
+        guard isUserLoggedIn() else {
+            await MainActor.run {
+                self.applyMeetings([])
+                self.applyGuestLoginMessages()
+            }
+            return
+        }
         if isLoadingMeetings { return }
         isLoadingMeetings = true
         defer {
@@ -2596,111 +2681,6 @@ class ViewController: NSViewController {
         return mapMeetings(allMeetings)
     }
 
-    // MARK: - Login UI
-
-    private func makeLoginView() -> NSView {
-        let root = NSView()
-        let sidebar = makeSidebar(items: ["Home", "Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"], selected: "Home", style: .login)
-        let content = NSView()
-
-        root.addSubview(sidebar)
-        root.addSubview(content)
-        sidebar.translatesAutoresizingMaskIntoConstraints = false
-        content.translatesAutoresizingMaskIntoConstraints = false
-
-        NSLayoutConstraint.activate([
-            sidebar.leadingAnchor.constraint(equalTo: root.leadingAnchor),
-            sidebar.topAnchor.constraint(equalTo: root.topAnchor),
-            sidebar.bottomAnchor.constraint(equalTo: root.bottomAnchor),
-            sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
-            content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
-            content.trailingAnchor.constraint(equalTo: root.trailingAnchor),
-            content.topAnchor.constraint(equalTo: root.topAnchor),
-            content.bottomAnchor.constraint(equalTo: root.bottomAnchor)
-        ])
-
-        let back = makeLabel("‹  Back", size: 32, color: accentBlue, weight: .regular, centered: false)
-        let logo = makeLabel("zoom\nWorkplace", size: 24, color: primaryText, weight: .bold, centered: true)
-        logo.maximumNumberOfLines = 2
-        let domain = makeLabel("us05web.zoom.us", size: 16, color: primaryText, weight: .semibold, centered: true)
-
-        let emailField = NSTextField()
-        emailField.placeholderString = "Email or phone number"
-        emailField.font = .systemFont(ofSize: 20, weight: .regular)
-        emailField.textColor = .white
-        emailField.wantsLayer = true
-        emailField.layer?.cornerRadius = 10
-        emailField.layer?.borderWidth = 1.5
-        emailField.layer?.borderColor = accentBlue.cgColor
-        emailField.layer?.backgroundColor = cardBackground.cgColor
-        emailField.focusRingType = .none
-
-        let nextButton = NSButton(title: "Next", target: self, action: #selector(zoomPrimarySignInTapped))
-        nextButton.font = .systemFont(ofSize: 20, weight: .semibold)
-        nextButton.isBordered = false
-        nextButton.wantsLayer = true
-        nextButton.layer?.cornerRadius = 10
-        nextButton.layer?.backgroundColor = cardBackground.cgColor
-        nextButton.contentTintColor = primaryText
-
-        let divider = NSBox()
-        divider.boxType = .separator
-        let socialText = makeLabel("or sign in with", size: 14, color: secondaryText, weight: .regular, centered: true)
-
-        let sso = makeSocialButton(icon: "🔑", text: "SSO")
-        let google = makeSocialButton(icon: "G", text: "Google", action: #selector(googleLoginTapped))
-        let apple = makeSocialButton(icon: "", text: "Apple")
-        let facebook = makeSocialButton(icon: "f", text: "Facebook")
-        let zoomSocial = makeSocialButton(icon: "Z", text: "Zoom", action: #selector(zoomPrimarySignInTapped))
-        self.googleButton = google.button
-        self.nextSignInButton = nextButton
-        self.zoomSocialButton = zoomSocial.button
-
-        let social = NSStackView(views: [sso.container, google.container, apple.container, facebook.container, zoomSocial.container])
-        social.orientation = .horizontal
-        social.spacing = 14
-        social.distribution = .fillEqually
-
-        let signup = makeLabel("Don't have an account? Sign up", size: 15, color: primaryText, weight: .regular, centered: true)
-        let footer = makeLabel("Help    Terms    Privacy", size: 14, color: accentBlue, weight: .regular, centered: true)
-
-        [back, logo, domain, emailField, nextButton, divider, socialText, social, signup, footer].forEach {
-            $0.translatesAutoresizingMaskIntoConstraints = false
-            content.addSubview($0)
-        }
-
-        NSLayoutConstraint.activate([
-            back.topAnchor.constraint(equalTo: content.topAnchor, constant: 24),
-            back.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 34),
-            logo.topAnchor.constraint(equalTo: content.topAnchor, constant: 118),
-            logo.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            domain.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: 12),
-            domain.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            emailField.topAnchor.constraint(equalTo: domain.bottomAnchor, constant: 30),
-            emailField.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            emailField.widthAnchor.constraint(equalToConstant: 520),
-            emailField.heightAnchor.constraint(equalToConstant: 52),
-            nextButton.topAnchor.constraint(equalTo: emailField.bottomAnchor, constant: 20),
-            nextButton.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            nextButton.widthAnchor.constraint(equalTo: emailField.widthAnchor),
-            nextButton.heightAnchor.constraint(equalToConstant: 52),
-            divider.topAnchor.constraint(equalTo: nextButton.bottomAnchor, constant: 28),
-            divider.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            divider.widthAnchor.constraint(equalTo: emailField.widthAnchor),
-            socialText.centerYAnchor.constraint(equalTo: divider.centerYAnchor),
-            socialText.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            social.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 18),
-            social.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            social.widthAnchor.constraint(equalTo: emailField.widthAnchor),
-            signup.topAnchor.constraint(equalTo: social.bottomAnchor, constant: 14),
-            signup.centerXAnchor.constraint(equalTo: content.centerXAnchor),
-            footer.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -16),
-            footer.centerXAnchor.constraint(equalTo: content.centerXAnchor)
-        ])
-
-        return root
-    }
-
     private func makeSettingsView() -> NSView {
         let panel = NSView()
         panel.translatesAutoresizingMaskIntoConstraints = false
@@ -2937,7 +2917,7 @@ class ViewController: NSViewController {
             logoutTapped()
             return
         }
-        showSimpleAlert(title: "Sign in", message: "Please sign in from the login screen to connect your account.")
+        zoomPrimarySignInTapped()
     }
 
     private func roundedContainer(cornerRadius: CGFloat, color: NSColor) -> NSView {
@@ -3056,13 +3036,9 @@ class ViewController: NSViewController {
             applyWindowBackgroundForCurrentTheme(window)
         }
 
-        if isUserLoggedIn() {
-            let keepSelected = selectedHomeSidebarItem
-            selectedHomeSidebarItem = keepSelected
-            showHomeView(profile: nil)
-        } else {
-            showLoginView()
-        }
+        let keepSelected = selectedHomeSidebarItem
+        selectedHomeSidebarItem = keepSelected
+        showHomeView(profile: nil)
     }
 
     private func applyWindowBackgroundForCurrentTheme(_ window: NSWindow) {
@@ -3737,15 +3713,15 @@ class ViewController: NSViewController {
         topBarPremiumButton = upgradeToProButton
         updateTopBarPremiumButton()
 
-        let profileChip = NSButton(title: String((profile?.name ?? "H").prefix(1)).uppercased(), target: self, action: #selector(logoutTapped))
-        profileChip.isBordered = false
-        profileChip.wantsLayer = true
-        profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.75).cgColor
-        profileChip.layer?.cornerRadius = 10
-        profileChip.contentTintColor = primaryText
-        profileChip.font = .systemFont(ofSize: 14, weight: .bold)
-        profileChip.toolTip = "Profile (click to logout)"
-        [upgradeToProButton, profileChip].forEach { rightTopBarCluster.addArrangedSubview($0) }
+        let authButton = NSButton(title: "", target: self, action: #selector(topBarAuthButtonTapped))
+        authButton.isBordered = false
+        authButton.wantsLayer = true
+        authButton.layer?.cornerRadius = 11
+        authButton.contentTintColor = .white
+        authButton.font = .systemFont(ofSize: 12, weight: .semibold)
+        authButton.bezelStyle = .shadowlessSquare
+        self.topBarAuthButton = authButton
+        [upgradeToProButton, authButton].forEach { rightTopBarCluster.addArrangedSubview($0) }
 
         let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
         let timeTitle = makeLabel("--:--", size: 56, color: primaryText, weight: .bold, centered: true)
@@ -4088,8 +4064,7 @@ class ViewController: NSViewController {
             searchField.leadingAnchor.constraint(equalTo: searchIcon.trailingAnchor, constant: 8),
             searchField.trailingAnchor.constraint(equalTo: searchPill.trailingAnchor, constant: -10),
             searchField.centerYAnchor.constraint(equalTo: searchPill.centerYAnchor),
-            profileChip.widthAnchor.constraint(equalToConstant: 34),
-            profileChip.heightAnchor.constraint(equalToConstant: 34),
+            authButton.heightAnchor.constraint(equalToConstant: 30),
 
             welcome.topAnchor.constraint(equalTo: contentColumn.topAnchor, constant: 18),
             welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
@@ -4300,6 +4275,10 @@ class ViewController: NSViewController {
             self.applyGlobalMeetingSearch(query: popupSearchField.stringValue)
         }
         applyGlobalMeetingSearch(query: "")
+        topBarAuthButtonWidthConstraint = authButton.widthAnchor.constraint(equalToConstant: 120)
+        topBarAuthButtonWidthConstraint?.isActive = true
+        updateTopBarAuthButton()
+        applyAuthenticationUIState(triggerInitialRefresh: false)
 
         return root
     }
@@ -4522,7 +4501,7 @@ class ViewController: NSViewController {
 
     // MARK: - Shared UI
 
-    private func makeSidebar(items: [String], selected: String, style: SidebarStyle = .login) -> NSView {
+    private func makeSidebar(items: [String], selected: String, style: SidebarStyle = .home) -> NSView {
         let sidebar = NSView()
         sidebar.wantsLayer = true
         sidebar.layer?.backgroundColor = (style == .home ? chromeUnifiedBackground : sidebarBackground).cgColor
@@ -4724,11 +4703,8 @@ class ViewController: NSViewController {
             NSLayoutConstraint.activate([
                 stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 10)
             ])
-            stack.bottomAnchor.constraint(lessThanOrEqualTo: sidebar.bottomAnchor, constant: -18).isActive = true
-        } else {
-            stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 18).isActive = true
-            stack.bottomAnchor.constraint(lessThanOrEqualTo: sidebar.bottomAnchor, constant: -18).isActive = true
         }
+        stack.bottomAnchor.constraint(lessThanOrEqualTo: sidebar.bottomAnchor, constant: -18).isActive = true
 
         return sidebar
     }