diff --git a/src/application/roles/RolesApi.ts b/src/application/roles/RolesApi.ts index ec32b22..5777d5b 100644 --- a/src/application/roles/RolesApi.ts +++ b/src/application/roles/RolesApi.ts @@ -16,7 +16,8 @@ export const rolesApi = { getById: (roleId: string) => apiClient.get(`/api/role/get/${roleId}`), - getAccesses: () => apiClient.get("/api/access/get"), + getAccesses: (tenantId?: string) => + apiClient.get(`/api/access/get${tenantId ? `?tenant_id=${encodeURIComponent(tenantId)}` : ""}`), create: (payload: RoleCreateRequest) => apiClient.post("/api/role/create", payload, { successMessage: "Role created successfully", errorMessage: "Failed to create role" }), diff --git a/src/application/roles/components/AddRoles.tsx b/src/application/roles/components/AddRoles.tsx index 7c98779..bb8f712 100644 --- a/src/application/roles/components/AddRoles.tsx +++ b/src/application/roles/components/AddRoles.tsx @@ -40,12 +40,17 @@ const AddRoles = () => { } let isMounted = true; - const loadAccesses = async () => { + const loadAccesses = async (tenantId?: string) => { setIsAccessLoading(true); try { - const data = await rolesApi.getAccesses(); + const data = await rolesApi.getAccesses(tenantId || undefined); if (isMounted) { setAccessOptions(data); + const validIds = new Set(data.map((d) => d.id)); + setFormData((prev) => ({ + ...prev, + access_ids: (prev.access_ids || []).filter((id) => validIds.has(id)), + })); } } catch (error) { if (isMounted) { @@ -128,44 +133,55 @@ const AddRoles = () => { } }; - loadAccesses(); loadTenants(); return () => { isMounted = false; }; }, [canReadAllTenants, isAuthLoading, user?.tenant_id, user?.tenant_name]); + useEffect(() => { + if (isAuthLoading) return; + let isMounted = true; + const loadAccesses = async () => { + setIsAccessLoading(true); + try { + const data = await rolesApi.getAccesses(formData.tenant_id || undefined); + if (isMounted) { + setAccessOptions(data); + const validIds = new Set(data.map((d) => d.id)); + setFormData((prev) => ({ + ...prev, + access_ids: (prev.access_ids || []).filter((id) => validIds.has(id)), + })); + } + } catch (error) { + if (isMounted) { + const message = + error instanceof Error + ? error.message + : "Unable to load access options."; + setErrorMessage(message); + } + } finally { + if (isMounted) { + setIsAccessLoading(false); + } + } + }; + loadAccesses(); + return () => { + isMounted = false; + }; + }, [formData.tenant_id, isAuthLoading]); + const handleChange = (event: React.ChangeEvent) => { const { name, value } = event.target; setFormData((prev) => ({ ...prev, [name]: value })); }; const availableAccessOptions = useMemo(() => { - if (canReadAllTenants) return accessOptions; - - if (!user?.role?.accesses) return []; - - const accessMap = new Map(accessOptions.map((a) => [a.id, a])); - const includedIds = new Set(); - - accessOptions.forEach((option) => { - if (user.role?.accesses.includes(option.access_code)) { - let current: RoleAccess | undefined = option; - while (current) { - if (includedIds.has(current.id)) break; - includedIds.add(current.id); - - if (current.parent_id) { - current = accessMap.get(current.parent_id); - } else { - current = undefined; - } - } - } - }); - - return accessOptions.filter((option) => includedIds.has(option.id)); - }, [accessOptions, user, canReadAllTenants]); + return accessOptions; + }, [accessOptions]); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); diff --git a/src/application/roles/components/AllRoles.tsx b/src/application/roles/components/AllRoles.tsx index 8b271f1..c785d91 100644 --- a/src/application/roles/components/AllRoles.tsx +++ b/src/application/roles/components/AllRoles.tsx @@ -94,31 +94,8 @@ const AllRoles = () => { const [isDeleting, setIsDeleting] = useState(false); const availableAccessOptions = useMemo(() => { - if (canReadAllTenants) return accessOptions; - - if (!currentUser?.role?.accesses) return []; - - const accessMap = new Map(accessOptions.map((a) => [a.id, a])); - const includedIds = new Set(); - - accessOptions.forEach((option) => { - if (currentUser.role?.accesses.includes(option.access_code)) { - let current: RoleAccess | undefined = option; - while (current) { - if (includedIds.has(current.id)) break; - includedIds.add(current.id); - - if (current.parent_id) { - current = accessMap.get(current.parent_id); - } else { - current = undefined; - } - } - } - }); - - return accessOptions.filter((option) => includedIds.has(option.id)); - }, [accessOptions, currentUser, canReadAllTenants]); + return accessOptions; + }, [accessOptions]); useEffect(() => { let isMounted = true; @@ -267,7 +244,11 @@ const AllRoles = () => { setEditError(""); setIsEditOpen(true); try { - const details = await rolesApi.getById(role.id); + const [details, tenantAccesses] = await Promise.all([ + rolesApi.getById(role.id), + rolesApi.getAccesses(role.tenant_id || undefined), + ]); + setAccessOptions(tenantAccesses); setEditAccessIds(details.accesses?.map((a) => a.id) || []); } catch (err) { setEditError(err instanceof Error ? err.message : t('messages.error')); diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index f997299..d5601d9 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -33,15 +33,10 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ useEffect(() => { const bootstrapAuth = async () => { - const token = getAccessToken(); - if (!token) { - setIsLoading(false); - return; - } - try { const userData = await authApi.me(); setUser(userData); + setAuthCookies(null, true); const preferred = userData.preferred_language; if (preferred) { diff --git a/src/lib/apiClient.ts b/src/lib/apiClient.ts index 159a3e8..8a1966a 100644 --- a/src/lib/apiClient.ts +++ b/src/lib/apiClient.ts @@ -17,11 +17,13 @@ const API_TOAST_OPTIONS = { autoClose: 2000 }; const hardLogout = () => { clearAuthCookies(); - toast.error( - "Your session has expired. Please sign in again.", - API_TOAST_OPTIONS - ); - window.location.href = "/signin"; + if (typeof window !== "undefined" && !window.location.pathname.startsWith("/signin")) { + toast.error( + "Your session has expired. Please sign in again.", + API_TOAST_OPTIONS + ); + window.location.href = "/signin"; + } }; const refreshAccessToken = async () => { diff --git a/src/lib/authCookies.ts b/src/lib/authCookies.ts index 2d9a1f2..89973d4 100644 --- a/src/lib/authCookies.ts +++ b/src/lib/authCookies.ts @@ -1,5 +1,5 @@ const LOGGED_IN_KEY = "logged_in"; -const LOGGED_IN_MAX_AGE_SECONDS = 60 * 15; +const LOGGED_IN_MAX_AGE_SECONDS = 60 * 60 * 24 * 30; const buildCookieOptions = (maxAgeSeconds?: number) => { const options = ["path=/", "samesite=lax"];