diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java index b9a705d..2d34111 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/AuthControllerMapper.java @@ -35,7 +35,7 @@ public class AuthControllerMapper { output.expiresIn(), output.issuedAt(), output.organizations().stream() - .map(org -> new LoginResponse.Organization(org.name(), org.roles())) + .map(org -> new LoginResponse.Organization(org.id(), org.name(), org.roles())) .collect(Collectors.toList()) ); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java index 67bc0f4..c2f194a 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/auth/LoginResponse.java @@ -13,6 +13,7 @@ public record LoginResponse( List organizations ) { public record Organization( + Long id, String name, List roles ) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java index 2553434..705f4a3 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginOutput.java @@ -13,6 +13,7 @@ public record LoginOutput( List organizations ) { public record OrganizationOutput( + Long id, String name, List roles ) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java index 1282740..29a32b3 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/auth/login/LoginUseCase.java @@ -53,6 +53,7 @@ public class LoginUseCase { .collect(Collectors.groupingBy(uo -> uo.getOrganization().getName())) .entrySet().stream() .map(entry -> new LoginOutput.OrganizationOutput( + entry.getValue().stream().map(uo -> uo.getOrganization().getId()).findFirst().orElse(null), entry.getKey(), entry.getValue().stream().map(uo -> uo.getRole().name()).collect(Collectors.toList()) )) diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx index 5f52a6f..13578d0 100644 --- a/frontend/src/contexts/AuthContext.tsx +++ b/frontend/src/contexts/AuthContext.tsx @@ -18,6 +18,11 @@ interface AuthContextType { setActiveOrg: (id: number) => void; } +const STORAGE_KEYS = { + USER: "user", + ACTIVE_ORG_ID: "activeOrgId", +} as const; + const AuthContext = createContext(undefined); export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ @@ -26,9 +31,24 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ const [user, setUser] = useState(null); const [activeOrgId, setActiveOrgId] = useState(null); const [isLoading, setIsLoading] = useState(true); + + const setupActiveOrg = useCallback((organizations: LoginResponse["organizations"]) => { + const storedOrgId = localStorage.getItem(STORAGE_KEYS.ACTIVE_ORG_ID); + const parsedStoredId = storedOrgId ? Number(storedOrgId) : null; + + const isValidOrg = organizations.some(org => org.id === parsedStoredId); + + if (isValidOrg && parsedStoredId !== null) { + setActiveOrgId(parsedStoredId); + } else if (organizations.length > 0) { + const firstOrgId = organizations[0].id; + setActiveOrgId(firstOrgId); + localStorage.setItem(STORAGE_KEYS.ACTIVE_ORG_ID, firstOrgId.toString()); + } + }, []); const logout = useCallback(() => { - localStorage.removeItem("user"); + localStorage.removeItem(STORAGE_KEYS.USER); setUser(null); window.location.href = "/login"; }, []); @@ -60,7 +80,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ const renewToken = async (refreshToken: string) => { try { const response = await authService.refresh(refreshToken); - localStorage.setItem("user", JSON.stringify(response)); + localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(response)); setUser(response); } catch (error) { console.error("Failed to renew token", error); @@ -69,24 +89,16 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ }; useEffect(() => { - const storedUser = localStorage.getItem("user"); + const storedUser = localStorage.getItem(STORAGE_KEYS.USER); if (storedUser) { try { const userData: LoginResponse = JSON.parse(storedUser); if (checkTokenExpiration(userData)) { setUser(userData); - - const storedOrgId = localStorage.getItem("activeOrgId"); - if (storedOrgId) { - setActiveOrgId(Number(storedOrgId)); - } else if (userData.organizations.length > 0) { - const firstOrgId = userData.organizations[0].id; - setActiveOrgId(firstOrgId); - localStorage.setItem("activeOrgId", firstOrgId.toString()); - } + setupActiveOrg(userData.organizations); } } catch (e) { - localStorage.removeItem("user"); + localStorage.removeItem(STORAGE_KEYS.USER); } } setIsLoading(false); @@ -105,27 +117,24 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ const login = async (email: string, password: string) => { const response = await authService.login(email, password); - localStorage.setItem("user", JSON.stringify(response)); + localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(response)); setUser(response); + setupActiveOrg(response.organizations); return response; }; const refreshUser = async () => { try { const response = await authService.me(); - const storedUser = localStorage.getItem("user"); + const storedUser = localStorage.getItem(STORAGE_KEYS.USER); if (storedUser) { const userData = JSON.parse(storedUser); const updatedUser = { ...userData, ...response }; - localStorage.setItem("user", JSON.stringify(updatedUser)); + localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(updatedUser)); setUser(updatedUser); // Update active org if not set or if current active org is gone - if (!activeOrgId && updatedUser.organizations.length > 0) { - const firstOrgId = updatedUser.organizations[0].id; - setActiveOrgId(firstOrgId); - localStorage.setItem("activeOrgId", firstOrgId.toString()); - } + setupActiveOrg(updatedUser.organizations); } } catch (error) { console.error("Failed to refresh user profile", error); @@ -134,12 +143,12 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ const setActiveOrg = (id: number) => { setActiveOrgId(id); - localStorage.setItem("activeOrgId", id.toString()); + localStorage.setItem(STORAGE_KEYS.ACTIVE_ORG_ID, id.toString()); }; - const activeOrg = React.useMemo(() => - user?.organizations.find(org => org.id === activeOrgId) || null, - [user, activeOrgId] + const activeOrg = React.useMemo( + () => user?.organizations.find((org) => org.id === activeOrgId) || null, + [user, activeOrgId], ); return (