diff --git a/libs/i18n/locales/en/translation.json b/libs/i18n/locales/en/translation.json index 8ddec060d..df8e639dc 100644 --- a/libs/i18n/locales/en/translation.json +++ b/libs/i18n/locales/en/translation.json @@ -225,9 +225,12 @@ "Continue": "Continue", "Select Organization": "Select Organization", "Unable to log in to the application": "Unable to log in to the application", + "You do not have access to any organizations.": "You do not have access to any organizations.", + "Please contact your administrator to be granted access to an organization.": "Please contact your administrator to be granted access to an organization.", "We cannot log you in as we could not determine what organizations you have access to.": "We cannot log you in as we could not determine what organizations you have access to.", "Please try refreshing the page. If the problem persists, contact your administrator.": "Please try refreshing the page. If the problem persists, contact your administrator.", "Error details": "Error details", + "Reload organizations": "Reload organizations", "Change Organization": "Change Organization", "Settings": "Settings", "Fleet not found": "Fleet not found", diff --git a/libs/ui-components/src/components/common/OrganizationGuard.tsx b/libs/ui-components/src/components/common/OrganizationGuard.tsx index 6d222293a..f05805ecc 100644 --- a/libs/ui-components/src/components/common/OrganizationGuard.tsx +++ b/libs/ui-components/src/components/common/OrganizationGuard.tsx @@ -10,6 +10,9 @@ interface OrganizationContextType { mustShowOrganizationSelector: boolean; selectOrganization: (org: Organization) => void; selectionError?: string; + isReloading: boolean; + isEmptyOrganizations: boolean; + refetch: (extraDelay: number) => Promise; } const OrganizationContext = React.createContext(null); @@ -29,6 +32,8 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => { const [availableOrganizations, setAvailableOrganizations] = React.useState([]); const [organizationsLoaded, setOrganizationsLoaded] = React.useState(false); const [selectionError, setSelectionError] = React.useState(); + const [isEmptyOrganizations, setIsEmptyOrganizations] = React.useState(false); + const [isReloading, setIsReloading] = React.useState(false); const initializationStartedRef = React.useRef(false); const selectOrganization = React.useCallback((org: Organization) => { @@ -43,6 +48,63 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => { } }, []); + const fetchOrganizations = React.useCallback(async () => { + try { + const organizations = await fetch.get('organizations'); + setAvailableOrganizations(organizations.items); + + // Treat empty organizations list as an error + if (!organizations.items || organizations.items.length === 0) { + setSelectionError('No organizations available'); + setIsEmptyOrganizations(true); + setOrganizationsLoaded(true); + return; + } + + const currentOrgId = getCurrentOrganizationId(); + + // Validate current organization against available organizations + const currentOrg = currentOrgId + ? organizations.items.find((org) => org.metadata?.name === currentOrgId) + : undefined; + + if (currentOrg) { + // The previously selected organization exists - use it + selectOrganization(currentOrg); + } else { + if (organizations.items?.length === 1) { + // Only one organization available - select it automatically + selectOrganization(organizations.items[0]); + } else if (currentOrgId) { + // Previously set organization does not exist anymore - remove it from localStorage so the user can select a new organization + setCurrentOrganization(undefined); + storeCurrentOrganizationId(''); + } + } + setSelectionError(undefined); + setIsEmptyOrganizations(false); + } catch (error) { + setSelectionError(getErrorMessage(error)); + setAvailableOrganizations([]); + setIsEmptyOrganizations(false); + } finally { + setOrganizationsLoaded(true); + } + }, [fetch, selectOrganization]); + + const refetch = React.useCallback( + async (addDelay: number = 0) => { + setIsReloading(true); + try { + await new Promise((resolve) => setTimeout(resolve, addDelay)); + await fetchOrganizations(); + } finally { + setIsReloading(false); + } + }, + [fetchOrganizations], + ); + // Determine if multi-orgs are enabled. If so, check if an organization is already selected React.useEffect(() => { // Prevent multiple initialization calls - only run once @@ -52,41 +114,7 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => { initializationStartedRef.current = true; - const initializeOrganizations = async () => { - try { - const organizations = await fetch.get('organizations'); - setAvailableOrganizations(organizations.items); - - const currentOrgId = getCurrentOrganizationId(); - - // Validate current organization against available organizations - const currentOrg = currentOrgId - ? organizations.items.find((org) => org.metadata?.name === currentOrgId) - : undefined; - - if (currentOrg) { - // The previously selected organization exists - use it - selectOrganization(currentOrg); - } else { - if (organizations.items?.length === 1) { - // Only one organization available - select it automatically - selectOrganization(organizations.items[0]); - } else if (currentOrgId) { - // Previously set organization does not exist anymore - remove it from localStorage so the user can select a new organization - setCurrentOrganization(undefined); - storeCurrentOrganizationId(''); - } - } - setSelectionError(undefined); - } catch (error) { - setSelectionError(getErrorMessage(error)); - setAvailableOrganizations([]); - } finally { - setOrganizationsLoaded(true); - } - }; - - void initializeOrganizations(); + void fetchOrganizations(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -106,8 +134,20 @@ const OrganizationGuard = ({ children }: React.PropsWithChildren) => { mustShowOrganizationSelector, selectOrganization, selectionError, + isEmptyOrganizations, + isReloading, + refetch, }), - [currentOrganization, availableOrganizations, mustShowOrganizationSelector, selectOrganization, selectionError], + [ + currentOrganization, + availableOrganizations, + mustShowOrganizationSelector, + selectOrganization, + selectionError, + isEmptyOrganizations, + isReloading, + refetch, + ], ); return {children}; diff --git a/libs/ui-components/src/components/common/OrganizationSelector.tsx b/libs/ui-components/src/components/common/OrganizationSelector.tsx index 60a6c4ca2..8d3623254 100644 --- a/libs/ui-components/src/components/common/OrganizationSelector.tsx +++ b/libs/ui-components/src/components/common/OrganizationSelector.tsx @@ -39,6 +39,7 @@ interface OrganizationSelectorContentProps { } const MAX_ORGANIZATIONS_FOR_SCROLL = 4; +const EXTRA_DELAY = 450; const OrganizationSelectorContent = ({ defaultOrganizationId, @@ -141,8 +142,15 @@ interface OrganizationSelectorProps { } const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSelectorProps) => { - const { availableOrganizations, selectOrganization, mustShowOrganizationSelector, selectionError } = - useOrganizationGuardContext(); + const { + availableOrganizations, + selectOrganization, + mustShowOrganizationSelector, + selectionError, + isEmptyOrganizations, + refetch, + isReloading, + } = useOrganizationGuardContext(); const { t } = useTranslation(); const getLastSelectedOrganization = React.useCallback(() => { @@ -172,21 +180,47 @@ const OrganizationSelector = ({ onClose, isFirstLogin = true }: OrganizationSele [availableOrganizations, selectOrganization, onClose], ); + const handleRefetch = React.useCallback(async () => { + await refetch(EXTRA_DELAY); + }, [refetch]); + if (selectionError) { return ( - {t('We cannot log you in as we could not determine what organizations you have access to.')} - {t('Please try refreshing the page. If the problem persists, contact your administrator.')} - -
- {t('Error details')}: - {selectionError} -
-
+ {isEmptyOrganizations ? ( + <> + {t('You do not have access to any organizations.')} + {t('Please contact your administrator to be granted access to an organization.')} + + ) : ( + <> + + {t('We cannot log you in as we could not determine what organizations you have access to.')} + + + {t('Please try refreshing the page. If the problem persists, contact your administrator.')} + + +
+ {t('Error details')}: + {selectionError} +
+
+ + )}
+ + + + + + +