From 1d72cdb95c43b85c18e25d8f366c8b1b98e65ec6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 05:16:12 +0000 Subject: [PATCH 1/6] Initial plan From da951ec0df66037c4ae6dd3ba56419e576d7fb56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 05:24:24 +0000 Subject: [PATCH 2/6] Implement multiple accounts support with account switcher Co-authored-by: friuns <7095563+friuns@users.noreply.github.com> --- App.tsx | 103 +++++++++++++---- components/AccountSwitcher.tsx | 137 +++++++++++++++++++++++ services/accountService.ts | 198 +++++++++++++++++++++++++++++++++ services/cacheService.ts | 28 +++-- views/Dashboard.tsx | 49 ++++++-- views/IssueDetail.tsx | 11 +- views/RepoDetail.tsx | 9 +- views/TokenGate.tsx | 33 +++++- 8 files changed, 516 insertions(+), 52 deletions(-) create mode 100644 components/AccountSwitcher.tsx create mode 100644 services/accountService.ts diff --git a/App.tsx b/App.tsx index 0d43b03..54a4081 100644 --- a/App.tsx +++ b/App.tsx @@ -7,16 +7,30 @@ import { IssueDetail } from './views/IssueDetail'; import { signOutFromFirebase, handleRedirectResult } from './services/firebaseService'; import { validateToken } from './services/githubService'; import { ThemeProvider } from './contexts/ThemeContext'; +import { + getActiveAccount, + getAccounts, + addAccount, + removeAccount, + setActiveAccount, + migrateOldAccountData, + Account, + clearAllAccounts +} from './services/accountService'; const App: React.FC = () => { - const [token, setToken] = useState(localStorage.getItem('gh_token')); - const [user, setUser] = useState( - localStorage.getItem('gh_user') ? JSON.parse(localStorage.getItem('gh_user')!) : null - ); + // Migrate old single-account data on first load + React.useEffect(() => { + migrateOldAccountData(); + }, []); + + const [currentAccount, setCurrentAccount] = useState(() => getActiveAccount()); + const [accounts, setAccounts] = useState(() => getAccounts()); const [checkingRedirect, setCheckingRedirect] = useState(true); + const [addingAccount, setAddingAccount] = useState(false); const [currentRoute, setCurrentRoute] = useState( - token && user ? AppRoute.REPO_LIST : AppRoute.TOKEN_INPUT + currentAccount ? AppRoute.REPO_LIST : AppRoute.TOKEN_INPUT ); const [selectedRepo, setSelectedRepo] = useState(null); const [selectedIssue, setSelectedIssue] = useState(null); @@ -42,11 +56,11 @@ const App: React.FC = () => { }, []); const handleLogin = (newToken: string, newUser: GitHubUser) => { - setToken(newToken); - setUser(newUser); - localStorage.setItem('gh_token', newToken); - localStorage.setItem('gh_user', JSON.stringify(newUser)); + const account = addAccount(newToken, newUser); + setCurrentAccount(account); + setAccounts(getAccounts()); setCurrentRoute(AppRoute.REPO_LIST); + setAddingAccount(false); }; const handleLogout = async () => { @@ -57,12 +71,53 @@ const App: React.FC = () => { console.error('Firebase sign out error:', err); } - setToken(null); - setUser(null); - localStorage.removeItem('gh_token'); - localStorage.removeItem('gh_user'); + // Clear all accounts + clearAllAccounts(); + setCurrentAccount(null); + setAccounts([]); setCurrentRoute(AppRoute.TOKEN_INPUT); setSelectedRepo(null); + setAddingAccount(false); + }; + + const handleSwitchAccount = (accountId: string) => { + if (setActiveAccount(accountId)) { + const account = getActiveAccount(); + setCurrentAccount(account); + // Reset navigation to repo list when switching accounts + setCurrentRoute(AppRoute.REPO_LIST); + setSelectedRepo(null); + setSelectedIssue(null); + } + }; + + const handleRemoveAccount = (accountId: string) => { + removeAccount(accountId); + const updatedAccounts = getAccounts(); + setAccounts(updatedAccounts); + + // If we removed the current account, update to the new active one + if (currentAccount?.id === accountId) { + const newActiveAccount = getActiveAccount(); + setCurrentAccount(newActiveAccount); + + // If no accounts left, go to login + if (!newActiveAccount) { + setCurrentRoute(AppRoute.TOKEN_INPUT); + setSelectedRepo(null); + setSelectedIssue(null); + } else { + // Reset to repo list + setCurrentRoute(AppRoute.REPO_LIST); + setSelectedRepo(null); + setSelectedIssue(null); + } + } + }; + + const handleAddAccount = () => { + setAddingAccount(true); + setCurrentRoute(AppRoute.TOKEN_INPUT); }; const navigateToRepo = (repo: Repository) => { @@ -95,17 +150,21 @@ const App: React.FC = () => { ); } - if (currentRoute === AppRoute.TOKEN_INPUT || !token || !user) { - return ; + if (currentRoute === AppRoute.TOKEN_INPUT || !currentAccount) { + return { + setAddingAccount(false); + setCurrentRoute(AppRoute.REPO_LIST); + } : undefined} />; } if (currentRoute === AppRoute.ISSUE_DETAIL && selectedRepo && selectedIssue) { return ( ); } @@ -113,20 +172,26 @@ const App: React.FC = () => { if (currentRoute === AppRoute.REPO_DETAIL && selectedRepo) { return ( ); } return ( ); }; diff --git a/components/AccountSwitcher.tsx b/components/AccountSwitcher.tsx new file mode 100644 index 0000000..3810bc1 --- /dev/null +++ b/components/AccountSwitcher.tsx @@ -0,0 +1,137 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { ChevronDown, Plus, Check, X } from 'lucide-react'; +import { Account } from '../services/accountService'; + +interface AccountSwitcherProps { + accounts: Account[]; + currentAccount: Account; + onSwitch: (accountId: string) => void; + onAddAccount: () => void; + onRemoveAccount: (accountId: string) => void; +} + +export const AccountSwitcher: React.FC = ({ + accounts, + currentAccount, + onSwitch, + onAddAccount, + onRemoveAccount, +}) => { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen]); + + const handleRemoveAccount = (e: React.MouseEvent, accountId: string) => { + e.stopPropagation(); + onRemoveAccount(accountId); + }; + + return ( +
+ {/* Current Account Button */} + + + {/* Dropdown Menu */} + {isOpen && ( +
+ {/* Accounts List */} +
+
+ ACCOUNTS +
+ {accounts.map(account => ( +
{ + if (account.id !== currentAccount.id) { + onSwitch(account.id); + setIsOpen(false); + } + }} + > + {account.user.login} +
+
+ {account.user.login} +
+ {account.user.name && ( +
+ {account.user.name} +
+ )} +
+ {account.id === currentAccount.id ? ( + + ) : ( + accounts.length > 1 && ( + + ) + )} +
+ ))} +
+ + {/* Add Account Button */} +
+ +
+
+ )} +
+ ); +}; diff --git a/services/accountService.ts b/services/accountService.ts new file mode 100644 index 0000000..81cab01 --- /dev/null +++ b/services/accountService.ts @@ -0,0 +1,198 @@ +// Account management service for handling multiple GitHub accounts + +import { GitHubUser } from '../types'; + +const ACCOUNTS_KEY = 'gh_accounts'; +const ACTIVE_ACCOUNT_KEY = 'gh_active_account'; + +export interface Account { + id: string; // Unique identifier (GitHub user login) + token: string; + user: GitHubUser; + addedAt: number; +} + +interface AccountsData { + accounts: Account[]; + activeAccountId: string | null; +} + +/** + * Get all stored accounts + */ +export function getAccounts(): Account[] { + try { + const data = localStorage.getItem(ACCOUNTS_KEY); + if (!data) return []; + const parsed: AccountsData = JSON.parse(data); + return parsed.accounts || []; + } catch { + return []; + } +} + +/** + * Get the currently active account ID + */ +export function getActiveAccountId(): string | null { + try { + return localStorage.getItem(ACTIVE_ACCOUNT_KEY); + } catch { + return null; + } +} + +/** + * Get the currently active account + */ +export function getActiveAccount(): Account | null { + const accounts = getAccounts(); + const activeId = getActiveAccountId(); + + if (!activeId) { + // If no active account set but we have accounts, return the first one + return accounts.length > 0 ? accounts[0] : null; + } + + return accounts.find(acc => acc.id === activeId) || null; +} + +/** + * Add or update an account + */ +export function addAccount(token: string, user: GitHubUser): Account { + const accounts = getAccounts(); + const accountId = user.login; + + // Check if account already exists + const existingIndex = accounts.findIndex(acc => acc.id === accountId); + + const account: Account = { + id: accountId, + token, + user, + addedAt: Date.now(), + }; + + if (existingIndex >= 0) { + // Update existing account + accounts[existingIndex] = account; + } else { + // Add new account + accounts.push(account); + } + + // Save accounts + const data: AccountsData = { + accounts, + activeAccountId: getActiveAccountId() || accountId, + }; + localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(data)); + + // If this is the first account or no active account, make it active + if (!getActiveAccountId()) { + setActiveAccount(accountId); + } + + return account; +} + +/** + * Remove an account + */ +export function removeAccount(accountId: string): void { + const accounts = getAccounts(); + const filtered = accounts.filter(acc => acc.id !== accountId); + + const data: AccountsData = { + accounts: filtered, + activeAccountId: getActiveAccountId(), + }; + + // If we removed the active account, switch to another one + if (getActiveAccountId() === accountId) { + const newActiveId = filtered.length > 0 ? filtered[0].id : null; + data.activeAccountId = newActiveId; + if (newActiveId) { + localStorage.setItem(ACTIVE_ACCOUNT_KEY, newActiveId); + } else { + localStorage.removeItem(ACTIVE_ACCOUNT_KEY); + } + } + + localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(data)); + + // Clear cache for this account + clearAccountCache(accountId); +} + +/** + * Set the active account + */ +export function setActiveAccount(accountId: string): boolean { + const accounts = getAccounts(); + const account = accounts.find(acc => acc.id === accountId); + + if (!account) { + return false; + } + + localStorage.setItem(ACTIVE_ACCOUNT_KEY, accountId); + return true; +} + +/** + * Clear all accounts + */ +export function clearAllAccounts(): void { + localStorage.removeItem(ACCOUNTS_KEY); + localStorage.removeItem(ACTIVE_ACCOUNT_KEY); + + // Clear all account caches + const accounts = getAccounts(); + accounts.forEach(acc => clearAccountCache(acc.id)); +} + +/** + * Clear cache for a specific account + */ +function clearAccountCache(accountId: string): void { + const prefix = `vibe_github_cache_${accountId}_`; + const keysToRemove: string[] = []; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key?.startsWith(prefix)) { + keysToRemove.push(key); + } + } + + keysToRemove.forEach(key => localStorage.removeItem(key)); +} + +/** + * Migrate old single-account data to new multi-account structure + */ +export function migrateOldAccountData(): void { + // Check if we already have accounts + if (getAccounts().length > 0) { + return; // Already migrated + } + + // Check for old single-account data + const oldToken = localStorage.getItem('gh_token'); + const oldUserData = localStorage.getItem('gh_user'); + + if (oldToken && oldUserData) { + try { + const user: GitHubUser = JSON.parse(oldUserData); + addAccount(oldToken, user); + + // Remove old data + localStorage.removeItem('gh_token'); + localStorage.removeItem('gh_user'); + } catch (err) { + console.error('Failed to migrate old account data:', err); + } + } +} diff --git a/services/cacheService.ts b/services/cacheService.ts index d862ba9..1ae31a9 100644 --- a/services/cacheService.ts +++ b/services/cacheService.ts @@ -9,9 +9,18 @@ interface CacheEntry { timestamp: number; } -export function getCached(key: string): T | null { +// Get account-specific cache key +function getAccountCacheKey(key: string, accountId?: string): string { + if (accountId) { + return `${CACHE_PREFIX}${accountId}_${key}`; + } + return CACHE_PREFIX + key; +} + +export function getCached(key: string, accountId?: string): T | null { try { - const raw = localStorage.getItem(CACHE_PREFIX + key); + const cacheKey = getAccountCacheKey(key, accountId); + const raw = localStorage.getItem(cacheKey); if (!raw) return null; const entry: CacheEntry = JSON.parse(raw); return entry.data; @@ -20,21 +29,23 @@ export function getCached(key: string): T | null { } } -export function setCache(key: string, data: T): void { +export function setCache(key: string, data: T, accountId?: string): void { try { const entry: CacheEntry = { data, timestamp: Date.now(), }; - localStorage.setItem(CACHE_PREFIX + key, JSON.stringify(entry)); + const cacheKey = getAccountCacheKey(key, accountId); + localStorage.setItem(cacheKey, JSON.stringify(entry)); } catch { // Ignore storage errors (quota exceeded, etc.) } } -export function isCacheFresh(key: string, ttl = DEFAULT_TTL): boolean { +export function isCacheFresh(key: string, ttl = DEFAULT_TTL, accountId?: string): boolean { try { - const raw = localStorage.getItem(CACHE_PREFIX + key); + const cacheKey = getAccountCacheKey(key, accountId); + const raw = localStorage.getItem(cacheKey); if (!raw) return false; const entry = JSON.parse(raw); return Date.now() - entry.timestamp < ttl; @@ -43,9 +54,10 @@ export function isCacheFresh(key: string, ttl = DEFAULT_TTL): boolean { } } -export function clearCache(key?: string): void { +export function clearCache(key?: string, accountId?: string): void { if (key) { - localStorage.removeItem(CACHE_PREFIX + key); + const cacheKey = getAccountCacheKey(key, accountId); + localStorage.removeItem(cacheKey); } else { // Clear all cache entries const keysToRemove: string[] = []; diff --git a/views/Dashboard.tsx b/views/Dashboard.tsx index 253013c..452c3d6 100644 --- a/views/Dashboard.tsx +++ b/views/Dashboard.tsx @@ -5,26 +5,46 @@ import { RepoCard } from '../components/RepoCard'; import { Button } from '../components/Button'; import { ToastContainer, useToast } from '../components/Toast'; import { ThemeToggle } from '../components/ThemeToggle'; +import { AccountSwitcher } from '../components/AccountSwitcher'; import { LogOut, RefreshCw, Plus, X, Lock, Globe, AlertTriangle } from 'lucide-react'; import { getCached, setCache, CacheKeys } from '../services/cacheService'; +import { Account } from '../services/accountService'; interface DashboardProps { token: string; user: GitHubUser; onRepoSelect: (repo: Repository) => void; onLogout: () => void | Promise; + accounts: Account[]; + currentAccount: Account; + onSwitchAccount: (accountId: string) => void; + onAddAccount: () => void; + onRemoveAccount: (accountId: string) => void; } -export const Dashboard: React.FC = ({ token, user, onRepoSelect, onLogout }) => { +export const Dashboard: React.FC = ({ + token, + user, + onRepoSelect, + onLogout, + accounts, + currentAccount, + onSwitchAccount, + onAddAccount, + onRemoveAccount +}) => { const { toasts, dismissToast, showError } = useToast(); + // Get account ID for cache scoping + const accountId = currentAccount.id; + // Initialize from cache for instant display const [repos, setRepos] = useState(() => { - return getCached(CacheKeys.repos()) || []; + return getCached(CacheKeys.repos(), accountId) || []; }); const [loading, setLoading] = useState(() => { // Only show loading if no cached data - return !getCached(CacheKeys.repos()); + return !getCached(CacheKeys.repos(), accountId); }); const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(''); @@ -34,12 +54,12 @@ export const Dashboard: React.FC = ({ token, user, onRepoSelect, }); // Initialize issues from cache for instant display const [repoIssues, setRepoIssues] = useState>(() => { - const cachedRepos = getCached(CacheKeys.repos()); + const cachedRepos = getCached(CacheKeys.repos(), accountId); if (!cachedRepos) return {}; const issuesMap: Record = {}; for (const repo of cachedRepos.slice(0, 4)) { - const cachedIssues = getCached(CacheKeys.repoIssues(repo.owner.login, repo.name)); + const cachedIssues = getCached(CacheKeys.repoIssues(repo.owner.login, repo.name), accountId); if (cachedIssues) { issuesMap[repo.id] = cachedIssues.filter(issue => !issue.pull_request).slice(0, 3); } @@ -91,7 +111,7 @@ export const Dashboard: React.FC = ({ token, user, onRepoSelect, const data = await fetchRepositories(token); setRepos(data); // Cache the repos for instant display on next visit - setCache(CacheKeys.repos(), data); + setCache(CacheKeys.repos(), data, accountId); // Load issues for first 4 repos - reuse cache when available const reposToShow = data.slice(0, 4); @@ -99,7 +119,7 @@ export const Dashboard: React.FC = ({ token, user, onRepoSelect, for (const repo of reposToShow) { const cacheKey = CacheKeys.repoIssues(repo.owner.login, repo.name); - const cachedIssues = getCached(cacheKey); + const cachedIssues = getCached(cacheKey, accountId); if (cachedIssues) { // Reuse cached issues - filter to actual issues (not PRs) and take first 3 @@ -119,13 +139,13 @@ export const Dashboard: React.FC = ({ token, user, onRepoSelect, setLoading(false); setIsRefreshing(false); } - }, [token, repos.length]); + }, [token, repos.length, accountId]); useEffect(() => { - // Always fetch fresh data on mount, but show cached immediately + // Always fetch fresh data on mount or account change, but show cached immediately loadRepos(false); isInitialMount.current = false; - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, [accountId]); // eslint-disable-line react-hooks/exhaustive-deps const handleCreateRepo = async (e: React.FormEvent) => { e.preventDefault(); @@ -208,8 +228,13 @@ export const Dashboard: React.FC = ({ token, user, onRepoSelect,
- {user.login} - {user.login} +
diff --git a/views/IssueDetail.tsx b/views/IssueDetail.tsx index 7048292..196073a 100644 --- a/views/IssueDetail.tsx +++ b/views/IssueDetail.tsx @@ -12,9 +12,10 @@ interface IssueDetailProps { repo: Repository; issue: Issue; onBack: () => void; + accountId: string; } -export const IssueDetail: React.FC = ({ token, repo, issue, onBack }) => { +export const IssueDetail: React.FC = ({ token, repo, issue, onBack, accountId }) => { const { toasts, dismissToast, showSuccess, showError, showInfo } = useToast(); // Cache keys @@ -22,7 +23,7 @@ export const IssueDetail: React.FC = ({ token, repo, issue, on const expandedCacheKey = CacheKeys.issueExpandedData(repo.owner.login, repo.name, issue.number); // Get cached data for instant display - const cachedData = getCached(expandedCacheKey); + const cachedData = getCached(expandedCacheKey, accountId); // State - initialize from cache if available const [comments, setComments] = useState(() => cachedData?.comments || []); @@ -68,7 +69,7 @@ export const IssueDetail: React.FC = ({ token, repo, issue, on // All issues (needed to find related PRs) const [allIssues, setAllIssues] = useState(() => { - return getCached(issuesCacheKey) || []; + return getCached(issuesCacheKey, accountId) || []; }); // Filter out pull requests @@ -91,7 +92,7 @@ export const IssueDetail: React.FC = ({ token, repo, issue, on ]); setAllIssues(issuesList); - setCache(issuesCacheKey, issuesList); + setCache(issuesCacheKey, issuesList, accountId); setComments(issueComments); setWorkflowRuns(runs); @@ -195,7 +196,7 @@ export const IssueDetail: React.FC = ({ token, repo, issue, on artifacts: Object.fromEntries(artifactMap), prComments: Object.fromEntries(prCommentsMap), }; - setCache(expandedCacheKey, dataToCache); + setCache(expandedCacheKey, dataToCache, accountId); } catch (err) { console.error('Failed to fetch data:', err); setComments([]); diff --git a/views/RepoDetail.tsx b/views/RepoDetail.tsx index 110eb7a..c15cbfc 100644 --- a/views/RepoDetail.tsx +++ b/views/RepoDetail.tsx @@ -11,19 +11,20 @@ interface RepoDetailProps { repo: Repository; onBack: () => void; onIssueSelect: (issue: Issue) => void; + accountId: string; } -export const RepoDetail: React.FC = ({ token, repo, onBack, onIssueSelect }) => { +export const RepoDetail: React.FC = ({ token, repo, onBack, onIssueSelect, accountId }) => { const { toasts, dismissToast, showError } = useToast(); const cacheKey = CacheKeys.repoIssues(repo.owner.login, repo.name); // Initialize from cache for instant display const [issues, setIssues] = useState(() => { - return getCached(cacheKey) || []; + return getCached(cacheKey, accountId) || []; }); const [loading, setLoading] = useState(() => { // Only show loading if no cached data - return !getCached(cacheKey); + return !getCached(cacheKey, accountId); }); const [isRefreshing, setIsRefreshing] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); @@ -58,7 +59,7 @@ export const RepoDetail: React.FC = ({ token, repo, onBack, onI const data = await fetchIssues(token, repo.owner.login, repo.name); setIssues(data); // Cache the issues for instant display on next visit - setCache(cacheKey, data); + setCache(cacheKey, data, accountId); } catch (err) { console.error(err); } finally { diff --git a/views/TokenGate.tsx b/views/TokenGate.tsx index f5e251f..8af94b2 100644 --- a/views/TokenGate.tsx +++ b/views/TokenGate.tsx @@ -3,13 +3,15 @@ import { validateToken } from '../services/githubService'; import { signInWithGitHub } from '../services/firebaseService'; import { GitHubUser } from '../types'; import { Button } from '../components/Button'; -import { Github } from 'lucide-react'; +import { Github, X } from 'lucide-react'; interface TokenGateProps { onSuccess: (token: string, user: GitHubUser) => void; + isAddingAccount?: boolean; + onCancel?: () => void; } -export const TokenGate: React.FC = ({ onSuccess }) => { +export const TokenGate: React.FC = ({ onSuccess, isAddingAccount = false, onCancel }) => { const [error, setError] = useState(''); const [loading, setLoading] = useState(false); @@ -50,13 +52,26 @@ export const TokenGate: React.FC = ({ onSuccess }) => { return (
+ {isAddingAccount && onCancel && ( + + )} +
-

Welcome to GitGenius

+

+ {isAddingAccount ? 'Add GitHub Account' : 'Welcome to GitGenius'} +

- Sign in with your GitHub account to manage your repositories with AI assistance. + {isAddingAccount + ? 'Sign in with another GitHub account to add it to your workspace.' + : 'Sign in with your GitHub account to manage your repositories with AI assistance.'}

@@ -77,6 +92,16 @@ export const TokenGate: React.FC = ({ onSuccess }) => { Sign in with GitHub + {isAddingAccount && onCancel && ( + + )} +

By signing in, you grant access to your public and private repositories.

From b346683feab972ff289df67924035e93ea556e64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 05:27:45 +0000 Subject: [PATCH 3/6] Address code review feedback - improve accessibility and fix state consistency Co-authored-by: friuns <7095563+friuns@users.noreply.github.com> --- App.tsx | 14 ++++++++++---- components/AccountSwitcher.tsx | 1 + services/accountService.ts | 13 ++++++++++++- views/TokenGate.tsx | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/App.tsx b/App.tsx index 54a4081..c7287f3 100644 --- a/App.tsx +++ b/App.tsx @@ -120,6 +120,11 @@ const App: React.FC = () => { setCurrentRoute(AppRoute.TOKEN_INPUT); }; + const handleCancelAddAccount = () => { + setAddingAccount(false); + setCurrentRoute(AppRoute.REPO_LIST); + }; + const navigateToRepo = (repo: Repository) => { setSelectedRepo(repo); setCurrentRoute(AppRoute.REPO_DETAIL); @@ -151,10 +156,11 @@ const App: React.FC = () => { } if (currentRoute === AppRoute.TOKEN_INPUT || !currentAccount) { - return { - setAddingAccount(false); - setCurrentRoute(AppRoute.REPO_LIST); - } : undefined} />; + return ; } if (currentRoute === AppRoute.ISSUE_DETAIL && selectedRepo && selectedIssue) { diff --git a/components/AccountSwitcher.tsx b/components/AccountSwitcher.tsx index 3810bc1..909517b 100644 --- a/components/AccountSwitcher.tsx +++ b/components/AccountSwitcher.tsx @@ -108,6 +108,7 @@ export const AccountSwitcher: React.FC = ({ onClick={(e) => handleRemoveAccount(e, account.id)} className="opacity-0 group-hover:opacity-100 p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-opacity" title="Remove account" + aria-label={`Remove account ${account.user.login}`} > diff --git a/services/accountService.ts b/services/accountService.ts index 81cab01..85f7f5b 100644 --- a/services/accountService.ts +++ b/services/accountService.ts @@ -137,7 +137,15 @@ export function setActiveAccount(accountId: string): boolean { return false; } + // Update both the separate active account key and the accounts data structure localStorage.setItem(ACTIVE_ACCOUNT_KEY, accountId); + + const data: AccountsData = { + accounts, + activeAccountId: accountId, + }; + localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(data)); + return true; } @@ -145,11 +153,14 @@ export function setActiveAccount(accountId: string): boolean { * Clear all accounts */ export function clearAllAccounts(): void { + // Get accounts before clearing to properly clear caches + const accounts = getAccounts(); + + // Clear storage localStorage.removeItem(ACCOUNTS_KEY); localStorage.removeItem(ACTIVE_ACCOUNT_KEY); // Clear all account caches - const accounts = getAccounts(); accounts.forEach(acc => clearAccountCache(acc.id)); } diff --git a/views/TokenGate.tsx b/views/TokenGate.tsx index 8af94b2..6109a55 100644 --- a/views/TokenGate.tsx +++ b/views/TokenGate.tsx @@ -56,6 +56,7 @@ export const TokenGate: React.FC = ({ onSuccess, isAddingAccount From cc803f9cfcc9a05fff0ce1fdeeadc55844023318 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 05:29:55 +0000 Subject: [PATCH 4/6] Fix remaining code review issues - positioning, consistency, and logic Co-authored-by: friuns <7095563+friuns@users.noreply.github.com> --- App.tsx | 2 +- services/accountService.ts | 5 +++-- views/TokenGate.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/App.tsx b/App.tsx index c7287f3..024d914 100644 --- a/App.tsx +++ b/App.tsx @@ -20,7 +20,7 @@ import { const App: React.FC = () => { // Migrate old single-account data on first load - React.useEffect(() => { + useEffect(() => { migrateOldAccountData(); }, []); diff --git a/services/accountService.ts b/services/accountService.ts index 85f7f5b..62c8f0d 100644 --- a/services/accountService.ts +++ b/services/accountService.ts @@ -83,14 +83,15 @@ export function addAccount(token: string, user: GitHubUser): Account { } // Save accounts + const activeId = getActiveAccountId(); const data: AccountsData = { accounts, - activeAccountId: getActiveAccountId() || accountId, + activeAccountId: activeId || accountId, }; localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(data)); // If this is the first account or no active account, make it active - if (!getActiveAccountId()) { + if (!activeId) { setActiveAccount(accountId); } diff --git a/views/TokenGate.tsx b/views/TokenGate.tsx index 6109a55..b9e8a0f 100644 --- a/views/TokenGate.tsx +++ b/views/TokenGate.tsx @@ -51,7 +51,7 @@ export const TokenGate: React.FC = ({ onSuccess, isAddingAccount return (
-
+
{isAddingAccount && onCancel && ( - {isAddingAccount && onCancel && ( + {isAddingAccount && ( - {isAddingAccount && ( + {isAddingAccount && onCancel && (