diff --git a/web/src/atoms/AlertState.ts b/web/src/atoms/AlertState.ts deleted file mode 100644 index 95d1bba7b..000000000 --- a/web/src/atoms/AlertState.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {ReactNode} from 'react'; -import {atom} from 'recoil'; - -export enum AlertVariant { - Success = 'success', - Failure = 'danger', -} - -export interface AlertDetails { - variant: AlertVariant; - title: string; - key?: string; - message?: string | ReactNode; -} - -export const alertState = atom({ - key: 'alertState', - default: [], -}); diff --git a/web/src/atoms/SidebarState.ts b/web/src/atoms/SidebarState.ts deleted file mode 100644 index fca32b789..000000000 --- a/web/src/atoms/SidebarState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {atom} from 'recoil'; - -export const SidebarState = atom({ - key: 'sidebarState', - default: { - isOpen: true, - }, -}); diff --git a/web/src/components/header/QuayHeader.tsx b/web/src/components/header/QuayHeader.tsx index 3d04fa6e4..442671aa1 100644 --- a/web/src/components/header/QuayHeader.tsx +++ b/web/src/components/header/QuayHeader.tsx @@ -15,13 +15,12 @@ import logo from 'src/assets/quay.svg'; import rh_logo from 'src/assets/RH_QuayIO2.svg'; import {HeaderToolbar} from './HeaderToolbar'; import {Link} from 'react-router-dom'; -import {SidebarState} from 'src/atoms/SidebarState'; -import {useSetRecoilState} from 'recoil'; +import {useUI} from 'src/contexts/UIContext'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import './QuayHeader.css'; export function QuayHeader({toggleDrawer}: {toggleDrawer: () => void}) { - const setSidebarState = useSetRecoilState(SidebarState); + const {toggleSidebar} = useUI(); const quayConfig = useQuayConfig(); let logoUrl = logo; if (quayConfig && quayConfig.config?.BRANDING?.logo) { @@ -33,17 +32,13 @@ export function QuayHeader({toggleDrawer}: {toggleDrawer: () => void}) { logoUrl = rh_logo; } - const toggleSidebarVisibility = () => { - setSidebarState((oldState) => ({isOpen: !oldState.isOpen})); - }; - return ( diff --git a/web/src/components/labels/LabelsEditable.tsx b/web/src/components/labels/LabelsEditable.tsx index 6866fada6..5ea8873df 100644 --- a/web/src/components/labels/LabelsEditable.tsx +++ b/web/src/components/labels/LabelsEditable.tsx @@ -1,192 +1,281 @@ +import {Label as ImageLabel} from 'src/resources/TagResource'; import { - Label as ImageLabel, -} from 'src/resources/TagResource'; -import { Button, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Label, Skeleton} from '@patternfly/react-core'; + Button, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Label, + Skeleton, +} from '@patternfly/react-core'; import './Labels.css'; -import { useLabels } from 'src/hooks/UseTagLabels'; -import { useEffect, useState } from 'react'; +import {useLabels} from 'src/hooks/UseTagLabels'; +import {useEffect, useState} from 'react'; import Conditional from '../empty/Conditional'; -import { useAlerts } from 'src/hooks/UseAlerts'; -import { AlertVariant } from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import EditableLabel from './EditableLabel'; export default function EditableLabels(props: EditableLabelsProps) { - const { - labels, - setLabels, - initialLabels, - loading, - error, - createLabels, - successCreatingLabels, - errorCreatingLabels, - errorCreatingLabelsDetails, - loadingCreateLabels, - deleteLabels, - successDeletingLabels, - errorDeletingLabels, - errorDeletingLabelsDetails, - loadingDeleteLabels, - } = useLabels(props.org, props.repo, props.digest); - const [newLabel, setNewLabel] = useState(''); - const [invalidNewLabel, setInvalidNewLabel] = useState(null); - const { addAlert } = useAlerts(); - const loadingLabelChanges: boolean = loadingCreateLabels || loadingDeleteLabels; - const readonlyLabels = labels.filter((label: ImageLabel) => label.source_type !== 'api'); - const mutableLabels = labels.filter((label: ImageLabel) => label.source_type === 'api'); + const { + labels, + setLabels, + initialLabels, + loading, + error, + createLabels, + successCreatingLabels, + errorCreatingLabels, + errorCreatingLabelsDetails, + loadingCreateLabels, + deleteLabels, + successDeletingLabels, + errorDeletingLabels, + errorDeletingLabelsDetails, + loadingDeleteLabels, + } = useLabels(props.org, props.repo, props.digest); + const [newLabel, setNewLabel] = useState(''); + const [invalidNewLabel, setInvalidNewLabel] = useState(null); + const {addAlert} = useUI(); + const loadingLabelChanges: boolean = + loadingCreateLabels || loadingDeleteLabels; + const readonlyLabels = labels.filter( + (label: ImageLabel) => label.source_type !== 'api', + ); + const mutableLabels = labels.filter( + (label: ImageLabel) => label.source_type === 'api', + ); - if (error) { - return <>Unable to get labels; + useEffect(() => { + if (successCreatingLabels) { + addAlert({ + variant: AlertVariant.Success, + title: `Created labels successfully`, + }); } - if (loading) { - return ; + if (errorCreatingLabels) { + const errorCreatingLabelsMessage = ( + <> + {Array.from(errorCreatingLabelsDetails.getErrors()).map( + ([label, error]) => ( +

+ Could not create label {label}: {error.error.message} +

+ ), + )} + + ); + addAlert({ + variant: AlertVariant.Failure, + title: `Could not create labels`, + message: errorCreatingLabelsMessage, + }); + } + if (successDeletingLabels) { + addAlert({ + variant: AlertVariant.Success, + title: `Deleted labels successfully`, + }); + } + if (errorDeletingLabels) { + const errorDeletingLabelsMessage = ( + <> + {Array.from(errorDeletingLabelsDetails.getErrors()).map( + ([label, error]) => ( +

+ Could not delete label {label}: {error.error.message} +

+ ), + )} + + ); + addAlert({ + variant: AlertVariant.Failure, + title: `Could not delete labels`, + message: errorDeletingLabelsMessage, + }); + } + if ( + (successCreatingLabels || + errorCreatingLabels || + successDeletingLabels || + errorDeletingLabels) && + !loadingLabelChanges + ) { + props.onComplete(); + } + }, [ + successCreatingLabels, + errorCreatingLabels, + successDeletingLabels, + errorDeletingLabels, + loadingLabelChanges, + addAlert, + props.onComplete, + errorCreatingLabelsDetails, + errorDeletingLabelsDetails, + ]); + + if (error) { + return <>Unable to get labels; + } + if (loading) { + return ; + } + + const onEditComplete = (newLabel: string) => { + if (newLabel === '') { + setNewLabel(''); + setInvalidNewLabel(null); + return; + } + let invalidMessage: string = null; + let key: string = null; + let value: string = null; + const keyValue: string[] = newLabel.split('='); + if (keyValue.length === 2) { + key = keyValue[0].trim(); + value = keyValue[1].trim(); + if (key === '' || !/^[0-9A-Za-z/\-_.]+$/.test(key)) { + invalidMessage = + 'Invalid label format, key must match ^[0-9A-Za-z/\\-_.]+=.+$'; + } + if (value === '' || !/^[0-9A-Za-z/\-_.]+$/.test(value)) { + invalidMessage = + 'Invalid label format, value must match ^[0-9A-Za-z/\\-_.]+=.+$'; + } + } else { + invalidMessage = 'Invalid label format, must be key value separated by ='; } - useEffect(() => { - if (successCreatingLabels) { - addAlert({ variant: AlertVariant.Success, title: `Created labels successfully` }); - } - if (errorCreatingLabels) { - let errorCreatingLabelsMessage = (<>{Array.from(errorCreatingLabelsDetails.getErrors()).map(([label, error]) => (

Could not create label {label}: {error.error.message}

))}) - addAlert({ variant: AlertVariant.Failure, title: `Could not create labels`, message: errorCreatingLabelsMessage }); - } - if (successDeletingLabels) { - addAlert({ variant: AlertVariant.Success, title: `Deleted labels successfully` }); - } - if (errorDeletingLabels) { - let errorDeletingLabelsMessage = (<>{Array.from(errorDeletingLabelsDetails.getErrors()).map(([label, error]) => (

Could not delete label {label}: {error.error.message}

))}) - addAlert({ variant: AlertVariant.Failure, title: `Could not delete labels`, message: errorDeletingLabelsMessage }); - } - if ((successCreatingLabels || errorCreatingLabels || successDeletingLabels || errorDeletingLabels) && !loadingLabelChanges) { - props.onComplete(); - } - }, [successCreatingLabels, errorCreatingLabels, successDeletingLabels, errorDeletingLabels]); - - const onEditComplete = (newLabel: string) => { - if(newLabel === '') { - setNewLabel(''); - setInvalidNewLabel(null); - return; - } - let invalidMessage: string = null; - let key: string = null; - let value: string = null; - const keyValue: string[] = newLabel.split('='); - if (keyValue.length === 2) { - key = keyValue[0].trim(); - value = keyValue[1].trim(); - if (key === "" || !/^[0-9A-Za-z/\-_.]+$/.test(key)) { - invalidMessage = 'Invalid label format, key must match ^[0-9A-Za-z/\\-_.]+=.+$' - } - if (value === "" || !/^[0-9A-Za-z/\-_.]+$/.test(value)) { - invalidMessage = 'Invalid label format, value must match ^[0-9A-Za-z/\\-_.]+=.+$'; - } - } else { - invalidMessage = 'Invalid label format, must be key value separated by =' - } - - if (labels.some(l => l.key === key && l.value === value)) { - invalidMessage = 'Key value already exists' - } - - if (invalidMessage === null) { - setNewLabel(''); - const newLabelObj: ImageLabel = { id: `${key}=${value}`, key, value, source_type: 'api', media_type: null }; - setLabels(prev => [...prev, newLabelObj]) - setInvalidNewLabel(null); - } else { - setInvalidNewLabel(invalidMessage); - setNewLabel(newLabel); - } + if (labels.some((l) => l.key === key && l.value === value)) { + invalidMessage = 'Key value already exists'; } - const removeLabel = (label: ImageLabel) => { - setLabels(prev => prev.filter(l => l.id !== label.id)) + if (invalidMessage === null) { + setNewLabel(''); + const newLabelObj: ImageLabel = { + id: `${key}=${value}`, + key, + value, + source_type: 'api', + media_type: null, + }; + setLabels((prev) => [...prev, newLabelObj]); + setInvalidNewLabel(null); + } else { + setInvalidNewLabel(invalidMessage); + setNewLabel(newLabel); } + }; - const saveLabels = () => { - const [addedLabels, deletedLabels] = filterLabels(initialLabels, labels); - if (addedLabels.length > 0) { - createLabels(addedLabels); - } - if (deletedLabels.length > 0) { - deleteLabels(deletedLabels); - } + const removeLabel = (label: ImageLabel) => { + setLabels((prev) => prev.filter((l) => l.id !== label.id)); + }; + + const saveLabels = () => { + const [addedLabels, deletedLabels] = filterLabels(initialLabels, labels); + if (addedLabels.length > 0) { + createLabels(addedLabels); } - - const isSaveButtonDisabled = () => { - const [addedLabels, deletedLabels] = filterLabels(initialLabels, labels); - return (addedLabels.length === 0 && deletedLabels.length === 0); + if (deletedLabels.length > 0) { + deleteLabels(deletedLabels); } + }; - const filterLabels = (initialLabels: ImageLabel[], updatedLabels: ImageLabel[]) => { - const addedLabels: ImageLabel[] = updatedLabels.filter(updatedLabel => !initialLabels.some(intitialLabel => intitialLabel.key===updatedLabel.key && intitialLabel.value===updatedLabel.value)); - const deletedLabels: ImageLabel[] = initialLabels.filter(intitialLabel => !updatedLabels.some(updatedLabel => intitialLabel.key===updatedLabel.key && intitialLabel.value===updatedLabel.value)); - return [addedLabels, deletedLabels]; - } + const isSaveButtonDisabled = () => { + const [addedLabels, deletedLabels] = filterLabels(initialLabels, labels); + return addedLabels.length === 0 && deletedLabels.length === 0; + }; - return (<> - - - Read-only labels - - {readonlyLabels?.length === 0 ? "No labels found" : readonlyLabels.map((label: ImageLabel) => ( - <> - {' '} - - ))} - - - - Mutable labels - - {mutableLabels?.map((label: ImageLabel) => ( - - ))} - - -
- {invalidNewLabel} -
-
-
-
-
-
- {' '} - - ) + const filterLabels = ( + initialLabels: ImageLabel[], + updatedLabels: ImageLabel[], + ) => { + const addedLabels: ImageLabel[] = updatedLabels.filter( + (updatedLabel) => + !initialLabels.some( + (intitialLabel) => + intitialLabel.key === updatedLabel.key && + intitialLabel.value === updatedLabel.value, + ), + ); + const deletedLabels: ImageLabel[] = initialLabels.filter( + (intitialLabel) => + !updatedLabels.some( + (updatedLabel) => + intitialLabel.key === updatedLabel.key && + intitialLabel.value === updatedLabel.value, + ), + ); + return [addedLabels, deletedLabels]; + }; + + return ( + <> + + + Read-only labels + + {readonlyLabels?.length === 0 + ? 'No labels found' + : readonlyLabels.map((label: ImageLabel) => ( + <> + {' '} + + ))} + + + + Mutable labels + + {mutableLabels?.map((label: ImageLabel) => ( + + ))} + + +
{invalidNewLabel}
+
+
+
+
+
+ {' '} + + + ); } interface EditableLabelsProps { - org: string; - repo: string; - digest: string; - onComplete?: () => void; + org: string; + repo: string; + digest: string; + onComplete?: () => void; } diff --git a/web/src/components/modals/ChangeAccountTypeModal.tsx b/web/src/components/modals/ChangeAccountTypeModal.tsx index 1344d709b..0005ff486 100644 --- a/web/src/components/modals/ChangeAccountTypeModal.tsx +++ b/web/src/components/modals/ChangeAccountTypeModal.tsx @@ -18,8 +18,7 @@ import { import {useCurrentUser} from 'src/hooks/UseCurrentUser'; import {useConvertAccount} from 'src/hooks/UseConvertAccount'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Avatar from 'src/components/Avatar'; interface ChangeAccountTypeModalProps { @@ -33,7 +32,7 @@ export default function ChangeAccountTypeModal({ }: ChangeAccountTypeModalProps) { const {user} = useCurrentUser(); const quayConfig = useQuayConfig(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const canConvert = user?.organizations?.length === 0; const [convertStep, setConvertStep] = useState(canConvert ? 1 : 0); // Start at step 1 if can convert const [accountType, setAccountType] = useState('organization'); // Default to organization diff --git a/web/src/components/modals/CreateApplicationTokenModal.tsx b/web/src/components/modals/CreateApplicationTokenModal.tsx index e0d4814d1..de1708e58 100644 --- a/web/src/components/modals/CreateApplicationTokenModal.tsx +++ b/web/src/components/modals/CreateApplicationTokenModal.tsx @@ -12,6 +12,8 @@ import { import {useCreateApplicationToken} from 'src/hooks/UseApplicationTokens'; import {IApplicationToken} from 'src/resources/UserResource'; import CredentialsModal from './CredentialsModal'; +import {useUI} from 'src/contexts/UIContext'; +import {AlertVariant} from 'src/contexts/UIContext'; interface CreateApplicationTokenModalProps { isOpen: boolean; @@ -27,14 +29,24 @@ export default function CreateApplicationTokenModal({ null, ); const [error, setError] = useState(''); + const {addAlert} = useUI(); const createTokenMutator = useCreateApplicationToken({ onSuccess: (data) => { setCreatedToken(data.token); setError(''); + addAlert({ + variant: AlertVariant.Success, + title: `Successfully created application token "${title}"`, + }); }, onError: (err) => { setError(err.message); + addAlert({ + variant: AlertVariant.Failure, + title: 'Failed to create application token', + message: err.message, + }); }, }); diff --git a/web/src/components/modals/CreateRepoModalTemplate.tsx b/web/src/components/modals/CreateRepoModalTemplate.tsx index 42331e7ef..b63f7f863 100644 --- a/web/src/components/modals/CreateRepoModalTemplate.tsx +++ b/web/src/components/modals/CreateRepoModalTemplate.tsx @@ -25,6 +25,8 @@ import {addDisplayError} from 'src/resources/ErrorHandling'; import {IOrganization} from 'src/resources/OrganizationResource'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import {useCreateRepository} from 'src/hooks/UseCreateRepository'; +import {useUI} from 'src/contexts/UIContext'; +import {AlertVariant} from 'src/contexts/UIContext'; enum visibilityType { PUBLIC = 'PUBLIC', @@ -39,6 +41,7 @@ export default function CreateRepositoryModalTemplate( } const [err, setErr] = useState(); const quayConfig = useQuayConfig(); + const {addAlert} = useUI(); const [currentOrganization, setCurrentOrganization] = useState({ // For org scoped view, the name is set current org and for Repository list view, @@ -53,10 +56,23 @@ export default function CreateRepositoryModalTemplate( const {createRepository} = useCreateRepository({ onSuccess: () => { + addAlert({ + variant: AlertVariant.Success, + title: `Successfully created repository ${currentOrganization.name}/${newRepository.name}`, + }); props.handleModalToggle(); }, onError: (error) => { - setErr(addDisplayError('Unable to create repository', error)); + const errorMessage = addDisplayError( + 'Unable to create repository', + error, + ); + setErr(errorMessage); + addAlert({ + variant: AlertVariant.Failure, + title: 'Unable to create repository', + message: errorMessage, + }); }, }); diff --git a/web/src/components/modals/OrgSubscriptionModal.tsx b/web/src/components/modals/OrgSubscriptionModal.tsx index ca3a855f7..6630ec648 100644 --- a/web/src/components/modals/OrgSubscriptionModal.tsx +++ b/web/src/components/modals/OrgSubscriptionModal.tsx @@ -13,8 +13,7 @@ import { SelectOption, } from '@patternfly/react-core'; import React from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useManageOrgSubscriptions} from 'src/hooks/UseMarketplaceSubscriptions'; interface OrgSubscriptionModalProps { @@ -29,7 +28,7 @@ export default function OrgSubscriptionModal(props: OrgSubscriptionModalProps) { const [selectedSku, setSelectedSku] = React.useState(''); const [menuIsOpen, setMenuIsOpen] = React.useState(false); const [bindingQuantity, setBindingQuantity] = React.useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const onSelect = ( _event: React.MouseEvent | undefined, value: string | number | undefined, diff --git a/web/src/components/modals/RobotFederationModal.tsx b/web/src/components/modals/RobotFederationModal.tsx index 46855beb1..d0760c959 100644 --- a/web/src/components/modals/RobotFederationModal.tsx +++ b/web/src/components/modals/RobotFederationModal.tsx @@ -15,15 +15,14 @@ import {PlusIcon, TrashIcon} from '@patternfly/react-icons'; import React, {useEffect, useState} from 'react'; import DisplayModal from './robotAccountWizard/DisplayModal'; import {useRobotFederation} from 'src/hooks/useRobotFederation'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; function RobotFederationForm(props: RobotFederationFormProps) { const [federationFormState, setFederationFormState] = useState< RobotFederationFormEntryProps[] >([]); - const alerts = useAlerts(); + const {addAlert} = useUI(); const {robotFederationConfig, loading, fetchError, setRobotFederationConfig} = useRobotFederation({ @@ -33,13 +32,13 @@ function RobotFederationForm(props: RobotFederationFormProps) { setFederationFormState( result.map((config) => ({...config, isExpanded: false})), ); - alerts.addAlert({ + addAlert({ title: 'Robot federation config saved', variant: AlertVariant.Success, }); }, onError: (e) => { - alerts.addAlert({ + addAlert({ title: e.error_message || 'Error saving federation config', variant: AlertVariant.Failure, }); diff --git a/web/src/components/modals/robotAccountWizard/AddToTeam.tsx b/web/src/components/modals/robotAccountWizard/AddToTeam.tsx index 31b97ebc7..dc9a04514 100644 --- a/web/src/components/modals/robotAccountWizard/AddToTeam.tsx +++ b/web/src/components/modals/robotAccountWizard/AddToTeam.tsx @@ -12,14 +12,13 @@ import NameAndDescription from 'src/components/modals/robotAccountWizard/NameAnd import {addDisplayError} from 'src/resources/ErrorHandling'; import TeamView from './TeamView'; import {useCreateTeam} from 'src/hooks/UseTeams'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; export default function AddToTeam(props: AddToTeamProps) { const [newTeamName, setNewTeamName] = useState(''); const [newTeamDescription, setNewTeamDescription] = useState(''); const [err, setErr] = useState(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {createNewTeamHook} = useCreateTeam(props.orgName, { onSuccess: () => { diff --git a/web/src/components/sidebar/QuaySidebar.tsx b/web/src/components/sidebar/QuaySidebar.tsx index fa4dd4368..fd5f62e46 100644 --- a/web/src/components/sidebar/QuaySidebar.tsx +++ b/web/src/components/sidebar/QuaySidebar.tsx @@ -7,14 +7,13 @@ import { PageSidebarBody, } from '@patternfly/react-core'; import {Link, useLocation} from 'react-router-dom'; -import {SidebarState} from 'src/atoms/SidebarState'; import {NavigationPath} from 'src/routes/NavigationPath'; import OrganizationsList from 'src/routes/OrganizationsList/OrganizationsList'; import RepositoriesList from 'src/routes/RepositoriesList/RepositoriesList'; -import {useRecoilValue} from 'recoil'; import OverviewList from 'src/routes/OverviewList/OverviewList'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import {useCurrentUser} from 'src/hooks/UseCurrentUser'; +import {useUI} from 'src/contexts/UIContext'; import ServiceKeys from 'src/routes/Superuser/ServiceKeys/ServiceKeys'; import ChangeLog from 'src/routes/Superuser/ChangeLog/ChangeLog'; import UsageLogs from 'src/routes/Superuser/UsageLogs/UsageLogs'; @@ -31,7 +30,7 @@ interface SideNavProps { export function QuaySidebar() { const location = useLocation(); - const sidebarState = useRecoilValue(SidebarState); + const {isSidebarOpen} = useUI(); const quayConfig = useQuayConfig(); const {isSuperUser} = useCurrentUser(); @@ -178,7 +177,7 @@ export function QuaySidebar() { ); - if (sidebarState.isOpen) { + if (isSidebarOpen) { return ( {Navigation} diff --git a/web/src/contexts/UIContext.tsx b/web/src/contexts/UIContext.tsx new file mode 100644 index 000000000..7d650d19e --- /dev/null +++ b/web/src/contexts/UIContext.tsx @@ -0,0 +1,108 @@ +import React, { + createContext, + useContext, + useState, + useEffect, + useCallback, + useMemo, + ReactNode, +} from 'react'; + +export enum AlertVariant { + Success = 'success', + Failure = 'danger', +} + +export interface AlertDetails { + variant: AlertVariant; + title: string; + key?: string; + message?: string | ReactNode; +} + +interface UIContextType { + // Sidebar state + isSidebarOpen: boolean; + toggleSidebar: () => void; + + // Alert state + alerts: AlertDetails[]; + addAlert: (alert: AlertDetails) => void; + removeAlert: (key: string) => void; + clearAllAlerts: () => void; +} + +const UIContext = createContext(undefined); + +interface UIProviderProps { + children: React.ReactNode; +} + +export function UIProvider({children}: UIProviderProps) { + // Sidebar state with localStorage persistence + const [isSidebarOpen, setIsSidebarOpen] = useState(() => { + const stored = localStorage.getItem('quay-sidebar-open'); + return stored !== null ? stored === 'true' : true; // Default to open + }); + + // Alert state + const [alerts, setAlerts] = useState([]); + + // Persist sidebar state to localStorage + useEffect(() => { + localStorage.setItem('quay-sidebar-open', String(isSidebarOpen)); + }, [isSidebarOpen]); + + const toggleSidebar = useCallback(() => { + setIsSidebarOpen((prev) => !prev); + }, []); + + const addAlert = useCallback((alert: AlertDetails) => { + const alertWithKey = { + ...alert, + key: alert.key ?? Math.random().toString(36).substring(7), + }; + setAlerts((prev) => [...prev, alertWithKey]); + }, []); + + const removeAlert = useCallback((key: string) => { + setAlerts((prev) => prev.filter((a) => a.key !== key)); + }, []); + + const clearAllAlerts = useCallback(() => { + setAlerts([]); + }, []); + + const value: UIContextType = useMemo( + () => ({ + isSidebarOpen, + toggleSidebar, + alerts, + addAlert, + removeAlert, + clearAllAlerts, + }), + [ + isSidebarOpen, + toggleSidebar, + alerts, + addAlert, + removeAlert, + clearAllAlerts, + ], + ); + + return {children}; +} + +/** + * Custom hook to access UI context + * @returns UI context with sidebar state and future alert/theme state + */ +export function useUI(): UIContextType { + const context = useContext(UIContext); + if (context === undefined) { + throw new Error('useUI must be used within a UIProvider'); + } + return context; +} diff --git a/web/src/hooks/UseAlerts.ts b/web/src/hooks/UseAlerts.ts deleted file mode 100644 index 68305dae3..000000000 --- a/web/src/hooks/UseAlerts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {useRecoilState} from 'recoil'; -import {AlertDetails, alertState} from 'src/atoms/AlertState'; - -export function useAlerts() { - const [alerts, setAlerts] = useRecoilState(alertState); - const addAlert = (alert: AlertDetails) => { - if (alert.key == null) { - alert.key = Math.random().toString(36).substring(7); - } - setAlerts([...alerts, alert]); - }; - - const clearAllAlerts = () => { - setAlerts([]); - }; - - return { - addAlert, - clearAllAlerts, - }; -} diff --git a/web/src/hooks/UseCreateServiceKey.ts b/web/src/hooks/UseCreateServiceKey.ts index db4038392..04217a961 100644 --- a/web/src/hooks/UseCreateServiceKey.ts +++ b/web/src/hooks/UseCreateServiceKey.ts @@ -1,5 +1,5 @@ import {useForm} from 'react-hook-form'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; import { createServiceKey, CreateServiceKeyRequest, diff --git a/web/src/hooks/UseMirroringForm.ts b/web/src/hooks/UseMirroringForm.ts index 7d3d3e270..3a5424df5 100644 --- a/web/src/hooks/UseMirroringForm.ts +++ b/web/src/hooks/UseMirroringForm.ts @@ -2,7 +2,7 @@ import {useState} from 'react'; import {useForm} from 'react-hook-form'; import {MirroringFormData} from 'src/routes/RepositoryDetails/Mirroring/types'; import {Entity, EntityKind} from 'src/resources/UserResource'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; // Default form values const defaultFormValues: MirroringFormData = { diff --git a/web/src/hooks/UseOAuthApplicationForm.ts b/web/src/hooks/UseOAuthApplicationForm.ts index eb1575bc9..e0fa6335c 100644 --- a/web/src/hooks/UseOAuthApplicationForm.ts +++ b/web/src/hooks/UseOAuthApplicationForm.ts @@ -1,5 +1,5 @@ import {useForm} from 'react-hook-form'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; import { OAuthApplicationFormData, defaultOAuthFormValues, diff --git a/web/src/hooks/UseTeams.ts b/web/src/hooks/UseTeams.ts index 756da3d80..4d0289d1b 100644 --- a/web/src/hooks/UseTeams.ts +++ b/web/src/hooks/UseTeams.ts @@ -18,8 +18,7 @@ import { import {BulkOperationError, ResourceError} from 'src/resources/ErrorHandling'; import {useCurrentUser} from './UseCurrentUser'; import {IAvatar} from 'src/resources/OrganizationResource'; -import {useAlerts} from './UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {useUI, AlertVariant} from 'src/contexts/UIContext'; import {addRepoPermissionToTeam} from 'src/resources/DefaultPermissionResource'; interface createNewTeamForNamespaceParams { @@ -29,7 +28,7 @@ interface createNewTeamForNamespaceParams { export function useCreateTeam(orgName, {onSuccess, onError}) { const queryClient = useQueryClient(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { data: responseData, diff --git a/web/src/index.tsx b/web/src/index.tsx index 1a7c9b138..bceac6d47 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -3,6 +3,7 @@ import {createRoot} from 'react-dom/client'; import reportWebVitals from './reportWebVitals'; import {RecoilRoot} from 'recoil'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; +import {UIProvider} from './contexts/UIContext'; // Load App after patternfly so custom CSS that overrides patternfly doesn't require !important import App from './App'; @@ -22,9 +23,11 @@ const root = createRoot(container); root.render( - - - + + + + + , ); diff --git a/web/src/routes/Alerts.tsx b/web/src/routes/Alerts.tsx index 4cb4c016e..6545fa0d3 100644 --- a/web/src/routes/Alerts.tsx +++ b/web/src/routes/Alerts.tsx @@ -3,11 +3,10 @@ import { AlertActionCloseButton, AlertGroup, } from '@patternfly/react-core'; -import {useRecoilState} from 'recoil'; -import {AlertVariant, alertState} from 'src/atoms/AlertState'; +import {useUI, AlertVariant} from 'src/contexts/UIContext'; export default function Alerts() { - const [alerts, setAlerts] = useRecoilState(alertState); + const {alerts, removeAlert} = useUI(); return ( {alerts.map((alert) => ( @@ -19,7 +18,7 @@ export default function Alerts() { actionClose={ { - setAlerts((prev) => prev.filter((a) => a.key !== alert.key)); + removeAlert(alert.key); }} /> } diff --git a/web/src/routes/Build/Build.tsx b/web/src/routes/Build/Build.tsx index de58db71a..d0be97f1e 100644 --- a/web/src/routes/Build/Build.tsx +++ b/web/src/routes/Build/Build.tsx @@ -38,8 +38,7 @@ import { TriggeredBuildDescription, } from 'src/routes/RepositoryDetails/Builds/BuildHistory'; import Conditional from 'src/components/empty/Conditional'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {getErrorMessageFromUnknown} from 'src/resources/ErrorHandling'; import {useEffect, useState} from 'react'; import { @@ -59,7 +58,7 @@ import {RepositoryBuildPhase} from 'src/resources/BuildResource'; export default function Build() { const location = useLocation(); const navigate = useNavigate(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [poll, setPoll] = useState(true); const [showTimestamps, setShowTimestamps] = useState(false); const [modalOpen, setModalOpen] = useState(false); diff --git a/web/src/routes/OrganizationsList/CreateOrganizationModal.tsx b/web/src/routes/OrganizationsList/CreateOrganizationModal.tsx index eec89ac17..805b7137c 100644 --- a/web/src/routes/OrganizationsList/CreateOrganizationModal.tsx +++ b/web/src/routes/OrganizationsList/CreateOrganizationModal.tsx @@ -16,6 +16,8 @@ import {useState} from 'react'; import FormError from 'src/components/errors/FormError'; import {addDisplayError} from 'src/resources/ErrorHandling'; import {useOrganizations} from 'src/hooks/UseOrganizations'; +import {useUI} from 'src/contexts/UIContext'; +import {AlertVariant} from 'src/contexts/UIContext'; interface Validation { message: string; @@ -40,6 +42,7 @@ export const CreateOrganizationModal = ( const [err, setErr] = useState(); const {createOrganization} = useOrganizations(); + const {addAlert} = useUI(); const handleNameInputChange = (value: string) => { const regex = /^([a-z0-9]+(?:[._-][a-z0-9]+)*)$/; @@ -77,9 +80,22 @@ export const CreateOrganizationModal = ( const createOrganizationHandler = async () => { try { await createOrganization(organizationName, organizationEmail); + addAlert({ + variant: AlertVariant.Success, + title: `Successfully created organization ${organizationName}`, + }); props.handleModalToggle(); } catch (err) { - setErr(addDisplayError('Unable to create organization', err)); + const errorMessage = addDisplayError( + 'Unable to create organization', + err, + ); + setErr(errorMessage); + addAlert({ + variant: AlertVariant.Failure, + title: 'Unable to create organization', + message: errorMessage, + }); } }; diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/AuthorizedApplications/AuthorizedApplicationsList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/AuthorizedApplications/AuthorizedApplicationsList.tsx index 551202c8f..3ea460c63 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/AuthorizedApplications/AuthorizedApplicationsList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/AuthorizedApplications/AuthorizedApplicationsList.tsx @@ -15,8 +15,7 @@ import {CubeIcon} from '@patternfly/react-icons'; import {useAuthorizedApplications} from 'src/hooks/UseAuthorizedApplications'; import GenerateTokenAuthorizationModal from 'src/components/modals/GenerateTokenAuthorizationModal'; import TokenDisplayModal from 'src/components/modals/TokenDisplayModal'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {GlobalAuthState} from 'src/resources/AuthResource'; import {useQueryClient} from '@tanstack/react-query'; @@ -33,7 +32,7 @@ export default function AuthorizedApplicationsList() { isRevoking, isDeletingAssigned, } = useAuthorizedApplications(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const queryClient = useQueryClient(); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsDropDown.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsDropDown.tsx index e71f96166..2eb9477fc 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsDropDown.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsDropDown.tsx @@ -11,15 +11,14 @@ import { useUpdateDefaultPermission, } from 'src/hooks/UseDefaultPermissions'; import {repoPermissions} from './DefaultPermissionsList'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {titleCase} from 'src/libs/utils'; export default function DefaultPermissionsDropDown( props: DefaultPermissionsDropdownProps, ) { const [isOpen, setIsOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { setDefaultPermission, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsList.tsx index 14b0b35e1..819986098 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsList.tsx @@ -25,8 +25,7 @@ import {BulkDeleteModalTemplate} from 'src/components/modals/BulkDeleteModalTemp import Conditional from 'src/components/empty/Conditional'; import {BulkOperationError, addDisplayError} from 'src/resources/ErrorHandling'; import RequestError from 'src/components/errors/RequestError'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import ErrorModal from 'src/components/errors/ErrorModal'; import {usePaginatedSortableTable} from '../../../../../hooks/usePaginatedSortableTable'; @@ -80,7 +79,7 @@ export default function DefaultPermissionsList( >([]); const [bulkDeleteModalIsOpen, setBulkDeleteModalIsOpen] = useState(false); const [err, setError] = useState(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const onSelectPermission = ( permission: IDefaultPermission, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DeleteDefaultPermissionKebab.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DeleteDefaultPermissionKebab.tsx index 50192f75e..6e0cd9135 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DeleteDefaultPermissionKebab.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DeleteDefaultPermissionKebab.tsx @@ -7,8 +7,7 @@ import { MenuToggleElement, } from '@patternfly/react-core'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import { IDefaultPermission, useDeleteDefaultPermission, @@ -18,7 +17,7 @@ export default function DeleteDefaultPermissionKebab( props: DefaultPermissionsDropdownProps, ) { const [isOpen, setIsOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { removeDefaultPermission, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreatePermissionDrawer.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreatePermissionDrawer.tsx index afaa68b39..f6537ddbb 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreatePermissionDrawer.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreatePermissionDrawer.tsx @@ -34,8 +34,7 @@ import Conditional from 'src/components/empty/Conditional'; import {useFetchTeams} from 'src/hooks/UseTeams'; import {repoPermissions} from 'src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/DefaultPermissionsList'; import {RepoPermissionDropdownItems} from 'src/routes/RepositoriesList/RobotAccountsList'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {validateTeamName} from 'src/libs/utils'; export default function CreatePermissionDrawer( @@ -74,7 +73,7 @@ export default function CreatePermissionDrawer( const {robots, isLoadingRobots} = useFetchRobotAccounts(props.orgName); // Get teams const {teams, isLoadingTeams} = useFetchTeams(props.orgName); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const permissionRadioButtons = ( <> diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreateTeamModal.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreateTeamModal.tsx index 6e5e2b060..5ed581ff5 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreateTeamModal.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreateTeamModal.tsx @@ -11,8 +11,7 @@ import { } from '@patternfly/react-core'; import {ExclamationCircleIcon} from '@patternfly/react-icons'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useCreateTeam} from 'src/hooks/UseTeams'; type validate = 'success' | 'error' | 'default'; @@ -23,7 +22,7 @@ export const CreateTeamModal = (props: CreateTeamModalProps): JSX.Element => { const [validatedName, setValidatedName] = useState('default'); const [nameHelperText, setNameHelperText] = useState(props.nameHelperText); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const handleNameChange = ( _event: React.FormEvent, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/AddTeamMember.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/AddTeamMember.tsx index 84542a5ec..d43065de8 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/AddTeamMember.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/AddTeamMember.tsx @@ -16,8 +16,7 @@ import { import {getAccountTypeForMember} from 'src/libs/utils'; import {TrashIcon} from '@patternfly/react-icons'; import NameAndDescription from 'src/components/modals/robotAccountWizard/NameAndDescription'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import ToggleDrawer from 'src/components/ToggleDrawer'; const memberAndRobotColNames = { @@ -30,7 +29,7 @@ export default function AddTeamMember(props: AddTeamMemberProps) { const [perPage, setPerPage] = useState(20); const [newRobotAccntName, setNewRobotAccntName] = useState(''); const [newRobotAccntDescription, setNewRobotAccntDescription] = useState(''); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {error, robots} = useFetchRobotAccounts(props.orgName); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/CreateTeamWizard.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/CreateTeamWizard.tsx index adf6e4e5e..3d63f09b0 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/CreateTeamWizard.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createTeamWizard/CreateTeamWizard.tsx @@ -32,8 +32,7 @@ import AddTeamMember from './AddTeamMember'; import Review from './ReviewTeam'; import ReviewAndFinishFooter from './ReviewAndFinishFooter'; import {useAddRepoPermissionToTeam} from 'src/hooks/UseTeams'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; export const CreateTeamWizard = (props: CreateTeamWizardProps): JSX.Element => { const [selectedRepoPerms, setSelectedRepoPerms] = useRecoilState( @@ -47,7 +46,7 @@ export const CreateTeamWizard = (props: CreateTeamWizardProps): JSX.Element => { const [deletedTeamMembers, setDeletedTeamMembers] = useState( [], ); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); // Fetching repos const {repos} = useRepositories(props.orgName); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/CreateOAuthApplicationModal.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/CreateOAuthApplicationModal.tsx index b189ed3c6..b1371ed5c 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/CreateOAuthApplicationModal.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/CreateOAuthApplicationModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Modal, ModalVariant, Button, Form} from '@patternfly/react-core'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {useUI} from 'src/contexts/UIContext'; import {FormTextInput} from 'src/components/forms/FormTextInput'; import {useOAuthApplicationForm} from 'src/hooks/UseOAuthApplicationForm'; import {OAuthApplicationFormData} from './types'; @@ -8,7 +8,7 @@ import {OAuthApplicationFormData} from './types'; export default function CreateOAuthApplicationModal( props: CreateOAuthApplicationModalProps, ) { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {control, errors, formValues, handleSubmit, isValid} = useOAuthApplicationForm( diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/GenerateTokenTab.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/GenerateTokenTab.tsx index f0d97da73..fb2c47fe6 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/GenerateTokenTab.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/GenerateTokenTab.tsx @@ -23,8 +23,7 @@ import EntitySearch from 'src/components/EntitySearch'; import {Entity} from 'src/resources/UserResource'; import {GlobalAuthState} from 'src/resources/AuthResource'; import {OAUTH_SCOPES, OAuthScope} from '../types'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface GenerateTokenTabProps { application: IOAuthApplication | null; @@ -45,7 +44,7 @@ export default function GenerateTokenTab(props: GenerateTokenTabProps) { const [isTokenDisplayModalOpen, setIsTokenDisplayModalOpen] = useState(false); const {user} = useCurrentUser(); const quayConfig = useQuayConfig(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); // Initialize form with all scopes set to false const defaultValues: GenerateTokenFormData = {}; diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/OAuthInformationTab.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/OAuthInformationTab.tsx index eb62b6700..b11eefdc0 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/OAuthInformationTab.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/OAuthInformationTab.tsx @@ -16,8 +16,7 @@ import { IOAuthApplication, useResetOAuthApplicationClientSecret, } from 'src/hooks/UseOAuthApplications'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {ConfirmationModal} from 'src/components/modals/ConfirmationModal'; interface OAuthInformationTabProps { @@ -29,7 +28,7 @@ interface OAuthInformationTabProps { export default function OAuthInformationTab(props: OAuthInformationTabProps) { const [isResetModalOpen, setIsResetModalOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {resetOAuthApplicationClientSecretMutation} = useResetOAuthApplicationClientSecret( diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/SettingsTab.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/SettingsTab.tsx index aa04b0f1b..817ee2d31 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/SettingsTab.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/ManageOAuthApplicationTabs/SettingsTab.tsx @@ -11,8 +11,7 @@ import { IOAuthApplication, useUpdateOAuthApplication, } from 'src/hooks/UseOAuthApplications'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {FormTextInput} from 'src/components/forms/FormTextInput'; import {OAuthApplicationFormData} from '../types'; @@ -23,7 +22,7 @@ interface SettingsTabProps { } export default function SettingsTab(props: SettingsTabProps) { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { control, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationActionsKebab.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationActionsKebab.tsx index d5e473dc3..6989cf4fe 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationActionsKebab.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationActionsKebab.tsx @@ -7,8 +7,7 @@ import { MenuToggleElement, } from '@patternfly/react-core'; import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import { IOAuthApplication, useDeleteOAuthApplication, @@ -20,7 +19,7 @@ export default function OAuthApplicationActionsKebab( ) { const [isOpen, setIsOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { removeOAuthApplication, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationsList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationsList.tsx index cdf841e35..a50018b6b 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationsList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/OAuthApplications/OAuthApplicationsList.tsx @@ -18,8 +18,7 @@ import ManageOAuthApplicationDrawer from './ManageOAuthApplicationDrawer'; import Conditional from 'src/components/empty/Conditional'; import {BulkOperationError, addDisplayError} from 'src/resources/ErrorHandling'; import RequestError from 'src/components/errors/RequestError'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import ErrorModal from 'src/components/errors/ErrorModal'; import Empty from 'src/components/empty/Empty'; import {KeyIcon} from '@patternfly/react-icons'; @@ -44,7 +43,7 @@ export default function OAuthApplicationsList( IOAuthApplication[] >([]); const [error, setError] = useState([]); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { loading, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/AutoPruning.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/AutoPruning.tsx index 0c1c3be36..e9ef81298 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/AutoPruning.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/AutoPruning.tsx @@ -1,9 +1,8 @@ import {Button, Spinner, Title} from '@patternfly/react-core'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Conditional from 'src/components/empty/Conditional'; import RequestError from 'src/components/errors/RequestError'; -import {useAlerts} from 'src/hooks/UseAlerts'; import { useCreateNamespaceAutoPrunePolicy, useDeleteNamespaceAutoPrunePolicy, @@ -31,7 +30,7 @@ export const shorthandTimeUnits = { export default function AutoPruning(props: AutoPruning) { const [policies, setPolicies] = useState([]); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const config = useQuayConfig(); const { error, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/BillingInformation.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/BillingInformation.tsx index 9a9781be3..c0aaeb0bb 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/BillingInformation.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/BillingInformation.tsx @@ -19,8 +19,7 @@ import {useOrganization} from 'src/hooks/UseOrganization'; import {useUpgradePlan} from 'src/hooks/UseUpgradePlan'; import {AxiosError} from 'axios'; import {useOrganizationSettings} from 'src/hooks/UseOrganizationSettings'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Alerts from 'src/routes/Alerts'; import MarketplaceDetails from './MarketplaceDetails'; @@ -31,7 +30,7 @@ type BillingInformationProps = { export const BillingInformation = (props: BillingInformationProps) => { const organizationName = props.organizationName; const {user} = useCurrentUser(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { updateUser, loading: userUpdateLoading, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/GeneralSettings.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/GeneralSettings.tsx index f339903d8..f92af4814 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/GeneralSettings.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/GeneralSettings.tsx @@ -17,8 +17,7 @@ import { import {useForm, Controller} from 'react-hook-form'; import moment from 'moment'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useCurrentUser, useUpdateUser} from 'src/hooks/UseCurrentUser'; import {useOrganization} from 'src/hooks/UseOrganization'; import {useOrganizationSettings} from 'src/hooks/UseOrganizationSettings'; @@ -65,7 +64,7 @@ export const GeneralSettings = (props: GeneralSettingsProps) => { const {user, loading: isUserLoading} = useCurrentUser(); const {organization, isUserOrganization, loading} = useOrganization(organizationName); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {updateOrgSettings} = useOrganizationSettings({ name: organizationName, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/ProxyCacheConfig.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/ProxyCacheConfig.tsx index 647fec7d9..8dff4998b 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/ProxyCacheConfig.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/ProxyCacheConfig.tsx @@ -12,8 +12,7 @@ import { TextInput, } from '@patternfly/react-core'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import { IProxyCacheConfig, useCreateProxyCacheConfig, @@ -41,7 +40,7 @@ export const ProxyCacheConfig = (props: ProxyCacheConfigProps) => { const [proxyCacheConfig, setProxyCacheConfig] = useState( defaultProxyCacheConfig, ); - const {addAlert, clearAllAlerts} = useAlerts(); + const {addAlert, clearAllAlerts} = useUI(); const {fetchedProxyCacheConfig, isLoadingProxyCacheConfig} = useFetchProxyCacheConfig(props.organizationName); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/QuotaManagement.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/QuotaManagement.tsx index 9f01c8b43..e874000cf 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/Settings/QuotaManagement.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/Settings/QuotaManagement.tsx @@ -23,8 +23,7 @@ import {PlusIcon} from '@patternfly/react-icons'; import {useEffect, useState} from 'react'; import {useForm, Controller} from 'react-hook-form'; import {FormTextInput} from 'src/components/forms/FormTextInput'; -import {AlertVariant as AlertVariantState} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant as AlertVariantState, useUI} from 'src/contexts/UIContext'; import {useCurrentUser} from 'src/hooks/UseCurrentUser'; import {useSuperuserPermissions} from 'src/hooks/UseSuperuserPermissions'; import { @@ -119,7 +118,7 @@ export const QuotaManagement = (props: QuotaManagementProps) => { }>({}); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const {addAlert, clearAllAlerts} = useAlerts(); + const {addAlert, clearAllAlerts} = useUI(); // Check if there's already a "Reject" limit to prevent adding duplicates const hasRejectLimit = limits.some((limit) => limit.type === 'Reject'); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsDeleteModal.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsDeleteModal.tsx index b77eef0b7..6c826b2b6 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsDeleteModal.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsDeleteModal.tsx @@ -1,14 +1,13 @@ import {Alert, Button, Modal, ModalVariant} from '@patternfly/react-core'; import {useEffect} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useDeleteCollaborator} from 'src/hooks/UseMembers'; import {IMembers} from 'src/resources/MembersResource'; export default function CollaboratorsDeleteModal( props: CollaboratorsDeleteModalProps, ) { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const deleteMsg = 'User will be removed from all teams and repositories under this organization in which they are a member or have permissions.'; const deleteAlert = ( @@ -27,6 +26,7 @@ export default function CollaboratorsDeleteModal( variant: AlertVariant.Failure, title: `Error deleting collaborator`, }); + props.toggleModal(); } }, [errorDeleteCollaborator]); @@ -36,6 +36,7 @@ export default function CollaboratorsDeleteModal( variant: AlertVariant.Success, title: `Successfully deleted collaborator`, }); + props.toggleModal(); } }, [successDeleteCollaborator]); @@ -55,7 +56,6 @@ export default function CollaboratorsDeleteModal( removeCollaborator({ collaborator: props.collaborator.name, }); - props.toggleModal; }} data-testid={`${props.collaborator.name}-del-btn`} > diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsViewList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsViewList.tsx index 980f3b499..d0a536794 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsViewList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/CollaboratorsView/CollaboratorsViewList.tsx @@ -11,8 +11,7 @@ import {useFetchCollaborators} from 'src/hooks/UseMembers'; import {useEffect, useState} from 'react'; import {IMembers} from 'src/resources/MembersResource'; import {TrashIcon} from '@patternfly/react-icons'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {ToolbarPagination} from 'src/components/toolbar/ToolbarPagination'; import CollaboratorsDeleteModal from './CollaboratorsDeleteModal'; import Conditional from 'src/components/empty/Conditional'; @@ -55,7 +54,7 @@ export default function CollaboratorsViewList( const [selectedCollaborators, setSelectedCollaborators] = useState< IMembers[] >([]); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [collaboratorToBeDeleted, setCollaboratorToBeDeleted] = useState(); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/MembersView/MembersViewList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/MembersView/MembersViewList.tsx index babdd721e..08c6574a7 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/MembersView/MembersViewList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/MembersView/MembersViewList.tsx @@ -12,8 +12,7 @@ import {useEffect, useState} from 'react'; import MembersViewToolbar from './MembersViewToolbar'; import {useFetchMembers} from 'src/hooks/UseMembers'; import {IMemberTeams, IMembers} from 'src/resources/MembersResource'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {getTeamMemberPath} from 'src/routes/NavigationPath'; import {ToolbarPagination} from 'src/components/toolbar/ToolbarPagination'; import {usePaginatedSortableTable} from '../../../../../../hooks/usePaginatedSortableTable'; @@ -59,7 +58,7 @@ export default function MembersViewList(props: MembersViewListProps) { const [selectedMembers, setSelectedMembers] = useState([]); const [isPopoverOpen, setPopoverOpen] = useState(false); const [searchParams] = useSearchParams(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); useEffect(() => { if (error) { diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/AddNewTeamMemberDrawer.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/AddNewTeamMemberDrawer.tsx index fdfd37f93..e715207ee 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/AddNewTeamMemberDrawer.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/AddNewTeamMemberDrawer.tsx @@ -18,11 +18,10 @@ import {DesktopIcon} from '@patternfly/react-icons'; import React, {useState} from 'react'; import {Ref} from 'react'; import {useParams} from 'react-router-dom'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import EntitySearch from 'src/components/EntitySearch'; import Conditional from 'src/components/empty/Conditional'; import CreateRobotAccountModal from 'src/components/modals/CreateRobotAccountModal'; -import {useAlerts} from 'src/hooks/UseAlerts'; import { useAddMembersToTeam, useFetchTeamMembersForOrg, @@ -50,7 +49,7 @@ export default function AddNewTeamMemberDrawer( const {robots, isLoadingRobots} = useFetchRobotAccounts(props.orgName); // Get teams const {teams} = useFetchTeams(props.orgName); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const creatorDefaultOptions = [ diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/ManageMembersList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/ManageMembersList.tsx index b8379221f..7eee15143 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/ManageMembersList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/ManageMembers/ManageMembersList.tsx @@ -47,8 +47,7 @@ import { } from '@patternfly/react-icons'; import {useParams} from 'react-router-dom'; import Empty from 'src/components/empty/Empty'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import { getAccountTypeForMember, formatDate, @@ -164,7 +163,7 @@ export default function ManageMembersList(props: ManageMembersListProps) { const [selectedTeamMembers, setSelectedTeamMembers] = useState( [], ); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [isEditing, setIsEditing] = useState(false); const [teamDescr, setTeamDescr] = useState(); diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/SetRepoPermissionsModal/SetRepoPermissionForTeamModal.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/SetRepoPermissionsModal/SetRepoPermissionForTeamModal.tsx index 05c0afeb4..5e9e9745e 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/SetRepoPermissionsModal/SetRepoPermissionForTeamModal.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/SetRepoPermissionsModal/SetRepoPermissionForTeamModal.tsx @@ -3,7 +3,7 @@ import {Button, Modal, ModalVariant, Spinner} from '@patternfly/react-core'; import {useEffect, useState} from 'react'; import Empty from 'src/components/empty/Empty'; import {CubesIcon} from '@patternfly/react-icons'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import SetRepoPermissionsToolbar from './SetRepoPermissionsForTeamToolbar'; import { ITeamRepoPerms, @@ -12,7 +12,6 @@ import { } from 'src/hooks/UseTeams'; import {SetRepoPermForTeamRoleDropDown} from './SetRepoPermForTeamRoleDropDown'; import {formatDate} from 'src/libs/utils'; -import {AlertVariant} from 'src/atoms/AlertState'; export const setRepoPermForTeamColumnNames = { repoName: 'Repository', @@ -50,7 +49,7 @@ export default function SetRepoPermissionForTeamModal( [], ); const [isKebabOpen, setKebabOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); useEffect(() => { if (successUpdateRepoPerm) { diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsRoleDropDown.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsRoleDropDown.tsx index a8dd2a4cf..f3c413070 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsRoleDropDown.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsRoleDropDown.tsx @@ -6,8 +6,7 @@ import { MenuToggle, MenuToggleElement, } from '@patternfly/react-core'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useUpdateTeamDetails} from 'src/hooks/UseTeams'; import {titleCase} from 'src/libs/utils'; @@ -19,7 +18,7 @@ export enum teamPermissions { export function TeamsRoleDropDown(props: TeamsRoleDropDownProps) { const [isOpen, setIsOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { updateTeamDetails, diff --git a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsViewList.tsx b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsViewList.tsx index a26ab8558..a61d69abe 100644 --- a/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsViewList.tsx +++ b/web/src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/TeamsViewList.tsx @@ -22,8 +22,7 @@ import {BulkDeleteModalTemplate} from 'src/components/modals/BulkDeleteModalTemp import {BulkOperationError, addDisplayError} from 'src/resources/ErrorHandling'; import ErrorModal from 'src/components/errors/ErrorModal'; import {getTeamMemberPath} from 'src/routes/NavigationPath'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import SetRepoPermissionForTeamModal from 'src/routes/OrganizationsList/Organization/Tabs/TeamsAndMembership/TeamsView/SetRepoPermissionsModal/SetRepoPermissionForTeamModal'; import {ToolbarPagination} from 'src/components/toolbar/ToolbarPagination'; import Conditional from 'src/components/empty/Conditional'; @@ -76,7 +75,7 @@ export default function TeamsViewList(props: TeamsViewListProps) { const [deleteModalIsOpen, setDeleteModalIsOpen] = useState(false); const [err, setIsError] = useState(); const [searchParams] = useSearchParams(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [isSetRepoPermModalOpen, setIsSetRepoPermModalOpen] = useState(false); const [repoPermForTeam, setRepoPermForTeam] = useState(''); const [isDeleteModalForRowOpen, setIsDeleteModalForRowOpen] = useState(false); diff --git a/web/src/routes/OrganizationsList/modals/ChangeEmailModal.tsx b/web/src/routes/OrganizationsList/modals/ChangeEmailModal.tsx index 25959d36f..72eff1b86 100644 --- a/web/src/routes/OrganizationsList/modals/ChangeEmailModal.tsx +++ b/web/src/routes/OrganizationsList/modals/ChangeEmailModal.tsx @@ -9,8 +9,7 @@ import { Alert, } from '@patternfly/react-core'; import {useChangeUserEmail} from 'src/hooks/UseUserActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface ChangeEmailModalProps { isOpen: boolean; @@ -21,7 +20,7 @@ interface ChangeEmailModalProps { export default function ChangeEmailModal(props: ChangeEmailModalProps) { const [newEmail, setNewEmail] = useState(''); const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {changeEmail, isLoading} = useChangeUserEmail({ onSuccess: () => { diff --git a/web/src/routes/OrganizationsList/modals/ChangePasswordModal.tsx b/web/src/routes/OrganizationsList/modals/ChangePasswordModal.tsx index b68ee0bda..c39c1549e 100644 --- a/web/src/routes/OrganizationsList/modals/ChangePasswordModal.tsx +++ b/web/src/routes/OrganizationsList/modals/ChangePasswordModal.tsx @@ -9,8 +9,7 @@ import { Alert, } from '@patternfly/react-core'; import {useChangeUserPassword} from 'src/hooks/UseUserActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface ChangePasswordModalProps { isOpen: boolean; @@ -21,7 +20,7 @@ interface ChangePasswordModalProps { export default function ChangePasswordModal(props: ChangePasswordModalProps) { const [newPassword, setNewPassword] = useState(''); const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {changePassword, isLoading} = useChangeUserPassword({ onSuccess: () => { diff --git a/web/src/routes/OrganizationsList/modals/CreateUserModal.tsx b/web/src/routes/OrganizationsList/modals/CreateUserModal.tsx index 0d2c6248e..f48175dd8 100644 --- a/web/src/routes/OrganizationsList/modals/CreateUserModal.tsx +++ b/web/src/routes/OrganizationsList/modals/CreateUserModal.tsx @@ -10,8 +10,7 @@ import { } from '@patternfly/react-core'; import {useForm} from 'react-hook-form'; import {useCreateUser} from 'src/hooks/UseCreateUser'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface CreateUserModalProps { isOpen: boolean; @@ -28,7 +27,7 @@ interface CreateUserFormData { export function CreateUserModal(props: CreateUserModalProps) { const [errorMessage, setErrorMessage] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { register, diff --git a/web/src/routes/OrganizationsList/modals/DeleteOrganizationModal.tsx b/web/src/routes/OrganizationsList/modals/DeleteOrganizationModal.tsx index 0731cd1f9..ed9bac7dd 100644 --- a/web/src/routes/OrganizationsList/modals/DeleteOrganizationModal.tsx +++ b/web/src/routes/OrganizationsList/modals/DeleteOrganizationModal.tsx @@ -1,8 +1,7 @@ import {useState} from 'react'; import {Modal, ModalVariant, Button, Text, Alert} from '@patternfly/react-core'; import {useDeleteSingleOrganization} from 'src/hooks/UseOrganizationActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface DeleteOrganizationModalProps { isOpen: boolean; @@ -14,7 +13,7 @@ export default function DeleteOrganizationModal( props: DeleteOrganizationModalProps, ) { const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {deleteOrganization, isLoading} = useDeleteSingleOrganization({ onSuccess: () => { diff --git a/web/src/routes/OrganizationsList/modals/DeleteUserModal.tsx b/web/src/routes/OrganizationsList/modals/DeleteUserModal.tsx index fcde9240e..cc10ee961 100644 --- a/web/src/routes/OrganizationsList/modals/DeleteUserModal.tsx +++ b/web/src/routes/OrganizationsList/modals/DeleteUserModal.tsx @@ -1,8 +1,7 @@ import {useState} from 'react'; import {Modal, ModalVariant, Button, Text, Alert} from '@patternfly/react-core'; import {useDeleteUser} from 'src/hooks/UseUserActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface DeleteUserModalProps { isOpen: boolean; @@ -12,7 +11,7 @@ interface DeleteUserModalProps { export default function DeleteUserModal(props: DeleteUserModalProps) { const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {deleteUser, isLoading} = useDeleteUser({ onSuccess: () => { diff --git a/web/src/routes/OrganizationsList/modals/RenameOrganizationModal.tsx b/web/src/routes/OrganizationsList/modals/RenameOrganizationModal.tsx index 1339ebb36..236dac82e 100644 --- a/web/src/routes/OrganizationsList/modals/RenameOrganizationModal.tsx +++ b/web/src/routes/OrganizationsList/modals/RenameOrganizationModal.tsx @@ -9,8 +9,7 @@ import { Alert, } from '@patternfly/react-core'; import {useRenameOrganization} from 'src/hooks/UseOrganizationActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface RenameOrganizationModalProps { isOpen: boolean; @@ -23,7 +22,7 @@ export default function RenameOrganizationModal( ) { const [newName, setNewName] = useState(''); const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {renameOrganization, isLoading} = useRenameOrganization({ onSuccess: (oldName: string, newName: string) => { diff --git a/web/src/routes/OrganizationsList/modals/SendRecoveryEmailModal.tsx b/web/src/routes/OrganizationsList/modals/SendRecoveryEmailModal.tsx index c63f5689f..4ef032ad5 100644 --- a/web/src/routes/OrganizationsList/modals/SendRecoveryEmailModal.tsx +++ b/web/src/routes/OrganizationsList/modals/SendRecoveryEmailModal.tsx @@ -7,8 +7,7 @@ import { AlertVariant as PFAlertVariant, } from '@patternfly/react-core'; import {useSendRecoveryEmail} from 'src/hooks/UseUserActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface SendRecoveryEmailModalProps { isOpen: boolean; @@ -21,7 +20,7 @@ export default function SendRecoveryEmailModal( ) { const [error, setError] = useState(null); const [successEmail, setSuccessEmail] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {sendRecovery, isLoading} = useSendRecoveryEmail({ onSuccess: (data) => { diff --git a/web/src/routes/OrganizationsList/modals/TakeOwnershipModal.tsx b/web/src/routes/OrganizationsList/modals/TakeOwnershipModal.tsx index 9d3526d76..66bac7723 100644 --- a/web/src/routes/OrganizationsList/modals/TakeOwnershipModal.tsx +++ b/web/src/routes/OrganizationsList/modals/TakeOwnershipModal.tsx @@ -1,8 +1,7 @@ import {useState} from 'react'; import {Modal, ModalVariant, Button, Text, Alert} from '@patternfly/react-core'; import {useTakeOwnership} from 'src/hooks/UseOrganizationActions'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; interface TakeOwnershipModalProps { isOpen: boolean; @@ -13,7 +12,7 @@ interface TakeOwnershipModalProps { export default function TakeOwnershipModal(props: TakeOwnershipModalProps) { const [error, setError] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const entityType = props.isUser ? 'user' : 'organization'; const {takeOwnership, isLoading} = useTakeOwnership({ @@ -46,8 +45,6 @@ export default function TakeOwnershipModal(props: TakeOwnershipModalProps) { takeOwnership(props.organizationName); }; - const entityType = props.isUser ? 'user' : 'organization'; - return ( (null); - const {addAlert} = useAlerts(); - const action = props.currentlyEnabled ? 'disabled' : 'enabled'; + const {addAlert} = useUI(); + const action = props.currentlyEnabled ? 'Disable' : 'Enable'; + const actionLower = action.toLowerCase(); const {toggleStatus, isLoading} = useToggleUserStatus({ onSuccess: () => { addAlert({ variant: AlertVariant.Success, - title: `Successfully ${action} user ${props.username}`, + title: `Successfully ${actionLower}d user ${props.username}`, }); handleClose(); }, @@ -32,9 +32,7 @@ export default function ToggleUserStatusModal( setError(errorMessage); addAlert({ variant: AlertVariant.Failure, - title: `Failed to ${ - action === 'disabled' ? 'disable' : 'enable' - } user ${props.username}`, + title: `Failed to ${actionLower} user ${props.username}`, message: errorMessage, }); }, @@ -50,9 +48,6 @@ export default function ToggleUserStatusModal( toggleStatus(props.username, !props.currentlyEnabled); }; - const action = props.currentlyEnabled ? 'Disable' : 'Enable'; - const actionLower = action.toLowerCase(); - return ( (false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {robotAccountsForOrg} = useRobotAccounts({ name: props.organizationName, diff --git a/web/src/routes/RepositoryDetails/Builds/BuildHistoryManuallyStartTriggerModal.tsx b/web/src/routes/RepositoryDetails/Builds/BuildHistoryManuallyStartTriggerModal.tsx index e26ffc913..3b38260f1 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildHistoryManuallyStartTriggerModal.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildHistoryManuallyStartTriggerModal.tsx @@ -14,8 +14,7 @@ import BuildTriggerDescription from './BuildTriggerDescription'; import Conditional from 'src/components/empty/Conditional'; import {useState} from 'react'; import {useStartBuild} from 'src/hooks/UseBuilds'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useSourceRefs} from 'src/hooks/UseBuildTriggers'; import TypeAheadSelect from 'src/components/TypeAheadSelect'; import {isNullOrUndefined} from 'src/libs/utils'; @@ -25,7 +24,7 @@ export default function ManuallyStartTrigger(props: ManuallyStartTriggerProps) { const [commit, setCommit] = useState(''); const [ref, setRef] = useState({name: '', kind: null}); const [isValid, setIsValid] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {refs, isLoading, isError, error} = useSourceRefs( org, repo, diff --git a/web/src/routes/RepositoryDetails/Builds/BuildHistoryStartBuildModalUploadDockerfile.tsx b/web/src/routes/RepositoryDetails/Builds/BuildHistoryStartBuildModalUploadDockerfile.tsx index af69b1afd..c1639c22e 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildHistoryStartBuildModalUploadDockerfile.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildHistoryStartBuildModalUploadDockerfile.tsx @@ -12,12 +12,11 @@ import { import {DesktopIcon} from '@patternfly/react-icons'; import React from 'react'; import {useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import EntitySearch from 'src/components/EntitySearch'; import FileUpload from 'src/components/FileUpload'; import Conditional from 'src/components/empty/Conditional'; import CreateRobotAccountModal from 'src/components/modals/CreateRobotAccountModal'; -import {useAlerts} from 'src/hooks/UseAlerts'; import {useStartDockerfileBuild} from 'src/hooks/UseBuilds'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import {useRepository, useTransitivePermissions} from 'src/hooks/UseRepository'; @@ -37,7 +36,7 @@ export default function DockerfileUploadBuild( const [privateRepo, setPrivatRepo] = useState(); const [selectedRobot, setSelectedRobot] = useState(null); const [isCreateRobotModalOpen, setIsCreateRobotModalOpen] = useState(false); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {teams} = useFetchTeams(props.org); const [org, repo] = privateRepo?.split('/') ?? [null, null]; const {repoDetails} = useRepository(org, repo); diff --git a/web/src/routes/RepositoryDetails/Builds/BuildTriggerDeleteModal.tsx b/web/src/routes/RepositoryDetails/Builds/BuildTriggerDeleteModal.tsx index 44f0807bb..41d7281d3 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildTriggerDeleteModal.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildTriggerDeleteModal.tsx @@ -1,12 +1,11 @@ import {Button, Modal, ModalVariant} from '@patternfly/react-core'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useDeleteBuildTrigger} from 'src/hooks/UseBuildTriggers'; export default function BuildTriggerDeleteModal( props: BuildTriggerDeleteModalProps, ) { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {deleteTrigger} = useDeleteBuildTrigger( props.org, props.repo, diff --git a/web/src/routes/RepositoryDetails/Builds/BuildTriggerSetupWizard.tsx b/web/src/routes/RepositoryDetails/Builds/BuildTriggerSetupWizard.tsx index 9447684df..e1b1d6665 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildTriggerSetupWizard.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildTriggerSetupWizard.tsx @@ -12,8 +12,7 @@ import ContextStep from './BuildTriggerSetupWizardContext'; import RobotAccounts from './BuildTriggerSetupWizardRobotAccounts'; import ReviewAndFinishProps from './BuildTriggerSetupWizardReviewAndFinish'; import {useActivateBuildTrigger} from 'src/hooks/UseBuildTriggers'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import SelectOrganization from './BuildTriggerSetupWizardOrganization'; import {isNullOrUndefined} from 'src/libs/utils'; import HostedRepository from './BuildTriggerSetupWizardHostedRepository'; @@ -45,7 +44,7 @@ export default function BuildTriggerSetupWizard( repoUrl, !isNullOrUndefined(repoUrl) && repoUrl !== '' && !isCustomGit, ); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {activateTrigger} = useActivateBuildTrigger( props.org, props.repo, diff --git a/web/src/routes/RepositoryDetails/Builds/BuildTriggerToggleModal.tsx b/web/src/routes/RepositoryDetails/Builds/BuildTriggerToggleModal.tsx index b82784d9a..3e80f2649 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildTriggerToggleModal.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildTriggerToggleModal.tsx @@ -1,12 +1,11 @@ import {Button, Modal, ModalVariant} from '@patternfly/react-core'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useToggleBuildTrigger} from 'src/hooks/UseBuildTriggers'; export default function BuildTriggerToggleModal( props: BuildTriggerToggleModalProps, ) { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {toggleTrigger} = useToggleBuildTrigger( props.org, props.repo, diff --git a/web/src/routes/RepositoryDetails/Builds/BuildTriggersInactiveTriggerRow.tsx b/web/src/routes/RepositoryDetails/Builds/BuildTriggersInactiveTriggerRow.tsx index c5af2308f..154c4f1a4 100644 --- a/web/src/routes/RepositoryDetails/Builds/BuildTriggersInactiveTriggerRow.tsx +++ b/web/src/routes/RepositoryDetails/Builds/BuildTriggersInactiveTriggerRow.tsx @@ -1,13 +1,12 @@ import {Td, Tr} from '@patternfly/react-table'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Conditional from 'src/components/empty/Conditional'; -import {useAlerts} from 'src/hooks/UseAlerts'; import {useDeleteBuildTrigger} from 'src/hooks/UseBuildTriggers'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; export default function InactiveTrigger(props: InactiveTriggerProps) { const config = useQuayConfig(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {deleteTrigger} = useDeleteBuildTrigger( props.org, props.repo, diff --git a/web/src/routes/RepositoryDetails/Information/Information.tsx b/web/src/routes/RepositoryDetails/Information/Information.tsx index 544949386..d53aeb7ca 100644 --- a/web/src/routes/RepositoryDetails/Information/Information.tsx +++ b/web/src/routes/RepositoryDetails/Information/Information.tsx @@ -24,8 +24,7 @@ import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import {useQuayState} from 'src/hooks/UseQuayState'; import {RepositoryDetails} from 'src/resources/RepositoryResource'; import axios from 'src/libs/axios'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import React from 'react'; @@ -91,7 +90,7 @@ export default function Information(props: InformationProps) { const {organization, repository, repoDetails} = props; const config = useQuayConfig(); const {inReadOnlyMode} = useQuayState(); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const queryClient = useQueryClient(); const [description, setDescription] = useState( diff --git a/web/src/routes/RepositoryDetails/Mirroring/Mirroring.tsx b/web/src/routes/RepositoryDetails/Mirroring/Mirroring.tsx index c0584f3e6..e305515ca 100644 --- a/web/src/routes/RepositoryDetails/Mirroring/Mirroring.tsx +++ b/web/src/routes/RepositoryDetails/Mirroring/Mirroring.tsx @@ -21,7 +21,7 @@ import { } from '@patternfly/react-core'; import {DesktopIcon, UsersIcon} from '@patternfly/react-icons'; import {useRepository} from 'src/hooks/UseRepository'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {useUI} from 'src/contexts/UIContext'; import FormError from 'src/components/errors/FormError'; import {useFetchRobotAccounts} from 'src/hooks/useRobotAccounts'; import {useFetchTeams} from 'src/hooks/UseTeams'; @@ -40,7 +40,7 @@ export const Mirroring: React.FC = ({namespace, repoName}) => { errorLoadingRepoDetails, isLoading: isLoadingRepo, } = useRepository(namespace, repoName); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const queryClient = useQueryClient(); // Initialize form hook diff --git a/web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx b/web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx index 2dbb90f3b..c2e33847e 100644 --- a/web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx +++ b/web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx @@ -18,7 +18,7 @@ import {FormTextInput} from 'src/components/forms/FormTextInput'; import {FormCheckbox} from 'src/components/forms/FormCheckbox'; import EntitySearch from 'src/components/EntitySearch'; import {Entity} from 'src/resources/UserResource'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; import { MirroringConfigResponse, getMirrorConfig, diff --git a/web/src/routes/RepositoryDetails/Mirroring/MirroringModals.tsx b/web/src/routes/RepositoryDetails/Mirroring/MirroringModals.tsx index fbf7c4dcd..90a246a68 100644 --- a/web/src/routes/RepositoryDetails/Mirroring/MirroringModals.tsx +++ b/web/src/routes/RepositoryDetails/Mirroring/MirroringModals.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; import CreateRobotAccountModal from 'src/components/modals/CreateRobotAccountModal'; import {CreateTeamModal} from 'src/routes/OrganizationsList/Organization/Tabs/DefaultPermissions/createPermissionDrawer/CreateTeamModal'; import {Entity} from 'src/resources/UserResource'; diff --git a/web/src/routes/RepositoryDetails/Mirroring/MirroringStatus.tsx b/web/src/routes/RepositoryDetails/Mirroring/MirroringStatus.tsx index e9e4bf807..285a7e994 100644 --- a/web/src/routes/RepositoryDetails/Mirroring/MirroringStatus.tsx +++ b/web/src/routes/RepositoryDetails/Mirroring/MirroringStatus.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Divider, Button} from '@patternfly/react-core'; import {StatusDisplay} from 'src/components/StatusDisplay'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant} from 'src/contexts/UIContext'; import { MirroringConfigResponse, getMirrorConfig, diff --git a/web/src/routes/RepositoryDetails/RepositoryDetails.tsx b/web/src/routes/RepositoryDetails/RepositoryDetails.tsx index 82e5f58ec..0e61257c9 100644 --- a/web/src/routes/RepositoryDetails/RepositoryDetails.tsx +++ b/web/src/routes/RepositoryDetails/RepositoryDetails.tsx @@ -15,13 +15,12 @@ import { } from '@patternfly/react-core'; import {useEffect, useRef, useState} from 'react'; import {useLocation, useNavigate, useSearchParams} from 'react-router-dom'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {QuayBreadcrumb} from 'src/components/breadcrumb/Breadcrumb'; import Conditional from 'src/components/empty/Conditional'; import ErrorBoundary from 'src/components/errors/ErrorBoundary'; import RequestError from 'src/components/errors/RequestError'; import CreateRobotAccountModal from 'src/components/modals/CreateRobotAccountModal'; -import {useAlerts} from 'src/hooks/UseAlerts'; import {useQuayConfig} from 'src/hooks/UseQuayConfig'; import {useRepository} from 'src/hooks/UseRepository'; import {useFetchTeams} from 'src/hooks/UseTeams'; @@ -73,7 +72,7 @@ export default function RepositoryDetails() { ); const [isCreateRobotModalOpen, setIsCreateRobotModalOpen] = useState(false); const [selectedEntity, setSelectedEntity] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [err, setErr] = useState(); const drawerRef = useRef(); diff --git a/web/src/routes/RepositoryDetails/Settings/RepositoryAutoPruning.tsx b/web/src/routes/RepositoryDetails/Settings/RepositoryAutoPruning.tsx index 8c39890b3..bf490355d 100644 --- a/web/src/routes/RepositoryDetails/Settings/RepositoryAutoPruning.tsx +++ b/web/src/routes/RepositoryDetails/Settings/RepositoryAutoPruning.tsx @@ -1,9 +1,8 @@ import {Button, Spinner, Title} from '@patternfly/react-core'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import Conditional from 'src/components/empty/Conditional'; import RequestError from 'src/components/errors/RequestError'; -import {useAlerts} from 'src/hooks/UseAlerts'; import {useNamespaceAutoPrunePolicies} from 'src/hooks/UseNamespaceAutoPrunePolicies'; import {useOrganization} from 'src/hooks/UseOrganization'; import { @@ -23,7 +22,7 @@ import {getErrorMessageFromUnknown} from 'src/resources/ErrorHandling'; export default function RepositoryAutoPruning(props: RepositoryAutoPruning) { const [policies, setPolicies] = useState([]); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {organization} = useOrganization(props.organizationName); const {user} = useCurrentUser(); const config = useQuayConfig(); diff --git a/web/src/routes/RepositoryDetails/TagHistory/TagHistoryPermanentlyDeleteTag.tsx b/web/src/routes/RepositoryDetails/TagHistory/TagHistoryPermanentlyDeleteTag.tsx index 557eda10c..14d0c17de 100644 --- a/web/src/routes/RepositoryDetails/TagHistory/TagHistoryPermanentlyDeleteTag.tsx +++ b/web/src/routes/RepositoryDetails/TagHistory/TagHistoryPermanentlyDeleteTag.tsx @@ -3,13 +3,12 @@ import {TagAction, TagEntry} from './types'; import ManifestDigest from 'src/components/ManifestDigest'; import {useEffect, useState} from 'react'; import {usePermanentlyDeleteTag} from 'src/hooks/UseTags'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {InfoCircleIcon} from '@patternfly/react-icons'; export default function PermanentlyDeleteTag(props: RestoreTagProps) { const {tagEntry} = props; - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [isModalOpen, setIsModalOpen] = useState(false); const {permanentlyDeleteTag, success, error} = usePermanentlyDeleteTag( props.org, diff --git a/web/src/routes/RepositoryDetails/TagHistory/TagHistoryRestoreTag.tsx b/web/src/routes/RepositoryDetails/TagHistory/TagHistoryRestoreTag.tsx index 3ed195d2f..fa101f277 100644 --- a/web/src/routes/RepositoryDetails/TagHistory/TagHistoryRestoreTag.tsx +++ b/web/src/routes/RepositoryDetails/TagHistory/TagHistoryRestoreTag.tsx @@ -3,12 +3,11 @@ import {TagAction, TagEntry} from './types'; import {ReactElement, useEffect, useState} from 'react'; import {Alert, Button, Label, Modal} from '@patternfly/react-core'; import {useRestoreTag} from 'src/hooks/UseTags'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; export default function RestoreTag(props: RestoreTagProps) { const {tagEntry, org, repo} = props; - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [isModalOpen, setIsModalOpen] = useState(false); const {restoreTag, success, error} = useRestoreTag(org, repo); diff --git a/web/src/routes/RepositoryDetails/Tags/DeleteModal.tsx b/web/src/routes/RepositoryDetails/Tags/DeleteModal.tsx index 2e7c5d250..a28333191 100644 --- a/web/src/routes/RepositoryDetails/Tags/DeleteModal.tsx +++ b/web/src/routes/RepositoryDetails/Tags/DeleteModal.tsx @@ -11,8 +11,7 @@ import './Tags.css'; import {isNullOrUndefined} from 'src/libs/utils'; import Conditional from 'src/components/empty/Conditional'; import {useDeleteTag} from 'src/hooks/UseTags'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertDetails, AlertVariant} from 'src/atoms/AlertState'; +import {AlertDetails, AlertVariant, useUI} from 'src/contexts/UIContext'; export interface ModalOptions { isOpen: boolean; @@ -26,7 +25,7 @@ export function DeleteModal(props: ModalProps) { errorDeleteTags, errorDeleteTagDetails, } = useDeleteTag(props.org, props.repo); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const isReadonly: boolean = props.repoDetails?.state !== 'NORMAL'; useEffect(() => { diff --git a/web/src/routes/RepositoryDetails/Tags/TagsActionsAddTagModal.tsx b/web/src/routes/RepositoryDetails/Tags/TagsActionsAddTagModal.tsx index 29e1f8161..c0e912be3 100644 --- a/web/src/routes/RepositoryDetails/Tags/TagsActionsAddTagModal.tsx +++ b/web/src/routes/RepositoryDetails/Tags/TagsActionsAddTagModal.tsx @@ -6,14 +6,13 @@ import { Title, } from '@patternfly/react-core'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useCreateTag} from 'src/hooks/UseTags'; import {isNullOrUndefined} from 'src/libs/utils'; export default function AddTagModal(props: AddTagModalProps) { const [value, setValue] = useState(''); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const {createTag, successCreateTag, errorCreateTag} = useCreateTag( props.org, props.repo, diff --git a/web/src/routes/RepositoryDetails/Tags/TagsActionsEditExpirationModal.tsx b/web/src/routes/RepositoryDetails/Tags/TagsActionsEditExpirationModal.tsx index b0a67a46f..de8ac8659 100644 --- a/web/src/routes/RepositoryDetails/Tags/TagsActionsEditExpirationModal.tsx +++ b/web/src/routes/RepositoryDetails/Tags/TagsActionsEditExpirationModal.tsx @@ -14,14 +14,13 @@ import { TimePicker, } from '@patternfly/react-core'; import {useEffect, useState} from 'react'; -import {AlertVariant} from 'src/atoms/AlertState'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; import {useSetExpiration} from 'src/hooks/UseTags'; import {formatDate, isNullOrUndefined} from 'src/libs/utils'; export default function EditExpirationModal(props: EditExpirationModalProps) { const [date, setDate] = useState(null); - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const { setExpiration, successSetExpiration, diff --git a/web/src/routes/StandaloneMain.tsx b/web/src/routes/StandaloneMain.tsx index c85813984..32d16f8a3 100644 --- a/web/src/routes/StandaloneMain.tsx +++ b/web/src/routes/StandaloneMain.tsx @@ -39,6 +39,7 @@ import {OAuthError} from 'src/routes/OAuthCallback/OAuthError'; import SystemStatusBanner from 'src/components/SystemStatusBanner'; import {GlobalMessages} from 'src/components/GlobalMessages'; import {LoadingPage} from 'src/components/LoadingPage'; +import {useUI} from 'src/contexts/UIContext'; // Lazy load route components for better performance const OrganizationsList = lazy( @@ -212,9 +213,16 @@ export function StandaloneMain() { const quayConfig = useQuayConfig(); const {loading, error} = useCurrentUser(); + const location = useLocation(); + const {clearAllAlerts} = useUI(); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + // Clear alerts when navigating to a different route + useEffect(() => { + clearAllAlerts(); + }, [location.pathname, clearAllAlerts]); + const toggleDrawer = () => { setIsDrawerOpen((prev) => !prev); }; diff --git a/web/src/routes/Superuser/ServiceKeys/CreateServiceKeyForm.tsx b/web/src/routes/Superuser/ServiceKeys/CreateServiceKeyForm.tsx index f67d867ec..e1b7ee345 100644 --- a/web/src/routes/Superuser/ServiceKeys/CreateServiceKeyForm.tsx +++ b/web/src/routes/Superuser/ServiceKeys/CreateServiceKeyForm.tsx @@ -12,7 +12,7 @@ import { HelperTextItem, } from '@patternfly/react-core'; import {useCreateServiceKey} from 'src/hooks/UseCreateServiceKey'; -import {useAlerts} from 'src/hooks/UseAlerts'; +import {useUI} from 'src/contexts/UIContext'; import FormError from 'src/components/errors/FormError'; interface CreateServiceKeyFormProps { @@ -26,7 +26,7 @@ export const CreateServiceKeyForm: React.FC = ({ onClose, onSuccess, }) => { - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const [error, setError] = React.useState(null); const formHook = useCreateServiceKey(addAlert, setError, () => { diff --git a/web/src/routes/UsageLogs/UsageLogsExportModal.tsx b/web/src/routes/UsageLogs/UsageLogsExportModal.tsx index 6c0958cce..c1dda4e39 100644 --- a/web/src/routes/UsageLogs/UsageLogsExportModal.tsx +++ b/web/src/routes/UsageLogs/UsageLogsExportModal.tsx @@ -10,8 +10,7 @@ import { } from '@patternfly/react-core'; import {exportLogs} from 'src/hooks/UseUsageLogs'; -import {useAlerts} from 'src/hooks/UseAlerts'; -import {AlertVariant} from 'src/atoms/AlertState'; +import {AlertVariant, useUI} from 'src/contexts/UIContext'; export default function ExportLogsModal(props: ExportLogsModalProps) { const [isModalOpen, setIsModalOpen] = React.useState(false); @@ -22,7 +21,7 @@ export default function ExportLogsModal(props: ExportLogsModalProps) { const handleModalToggle = (_event: KeyboardEvent | React.MouseEvent) => { setIsModalOpen(!isModalOpen); }; - const {addAlert} = useAlerts(); + const {addAlert} = useUI(); const exportLogsClick = (callback: string) => { return exportLogs(