From 32602f5c32ff183e615ba65c968f39ea8df09344 Mon Sep 17 00:00:00 2001 From: OpenShift Cherrypick Robot Date: Fri, 7 Nov 2025 00:00:08 +0100 Subject: [PATCH] [redhat-3.16] chore: migrate SidebarState and AlertState from Recoil to React Context (#4481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: migrate SidebarState and AlertState from Recoil to React Context Migrates UI state management from Recoil atoms to a centralized UIContext using pure React Context API. This is part of the broader effort to simplify state management and reduce dependencies. Changes: - Create UIContext with sidebar and alert state management - Migrate SidebarState: isSidebarOpen with localStorage persistence - Migrate AlertState: alerts array with add/remove/clear operations - Move AlertVariant enum and AlertDetails interface to UIContext - Remove UseAlerts hook (now redundant - consumers use useUI directly) - Update Alerts component to use removeAlert from context - Update QuayHeader and QuaySidebar to use useUI hook - Update 128 files to import types/hooks from UIContext - Delete AlertState.ts, SidebarState.ts, and UseAlerts.ts Benefits: - Zero runtime logic changes for consumers - Centralized UI state in single context - Reduced Recoil surface area (2 fewer atoms) - Simpler architecture (removed unnecessary hook wrapper) - Future-proof for additional UI state (theme, plugin mode) - Pure React with no external dependencies for UI state Co-authored-by: Claude Signed-off-by: Brady Pratt * fix(ui): add toast alerts for create organization, repository, and application token operations Adds success and failure toast alerts to create operations that previously only showed inline error messages. This provides consistent feedback across all CRUD operations and ensures users receive confirmation after modals close. Also fixes browser compatibility issue in UIContext by replacing crypto.randomUUID() with Math.random().toString(36) for alert key generation, maintaining compatibility with the original UseAlerts implementation. Changes: - Add toast alerts to CreateOrganizationModal for create org operations - Add toast alerts to CreateRepoModalTemplate for create repo operations - Add toast alerts to CreateApplicationTokenModal for token creation - Fix UIContext to use Math.random() instead of crypto.randomUUID() - Maintain existing inline error displays for immediate validation feedback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude * fix(ui): clear alerts on route navigation to prevent stale alerts from persisting Adds route-level alert cleanup to StandaloneMain to clear all alerts when navigating between routes. This prevents old alerts from accumulating and reappearing when users return to previously visited pages. Previously, alerts (especially failure alerts which don't auto-dismiss) would persist in UIContext state and re-render when navigating back to a page. Now alerts are automatically cleared whenever the route pathname changes. Changes: - Add useLocation hook to track route changes - Add clearAllAlerts from UIContext - Add useEffect to clear alerts on location.pathname change - Ensures fresh alert state for each route 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Signed-off-by: Brady Pratt * chore: address coderabbit feedback - fix early return, initialize the useEffect first - call props.toggleModal() - fix entityType shadowing - improve grammar in alerts for ToggleUserStatusModal Signed-off-by: Brady Pratt * fix(ui): wait for collaborator deletion before closing modal The CollaboratorsDeleteModal was closing immediately after clicking delete, causing it to unmount before the async deletion completed. This prevented the success alert from appearing because the useEffect hook that adds the alert never fired after component unmount. Fixed by moving the toggleModal() call from the delete button's onClick handler into the success and error useEffect hooks, ensuring the modal stays open until the mutation completes and the alert is displayed. Co-authored-by: Claude --------- Signed-off-by: Brady Pratt Co-authored-by: Brady Pratt Co-authored-by: Claude --- web/src/atoms/AlertState.ts | 19 - web/src/atoms/SidebarState.ts | 8 - web/src/components/header/QuayHeader.tsx | 11 +- web/src/components/labels/LabelsEditable.tsx | 429 +++++++++++------- .../modals/ChangeAccountTypeModal.tsx | 5 +- .../modals/CreateApplicationTokenModal.tsx | 12 + .../modals/CreateRepoModalTemplate.tsx | 18 +- .../modals/OrgSubscriptionModal.tsx | 5 +- .../modals/RobotFederationModal.tsx | 9 +- .../modals/robotAccountWizard/AddToTeam.tsx | 5 +- web/src/components/sidebar/QuaySidebar.tsx | 7 +- web/src/contexts/UIContext.tsx | 108 +++++ web/src/hooks/UseAlerts.ts | 21 - web/src/hooks/UseCreateServiceKey.ts | 2 +- web/src/hooks/UseMirroringForm.ts | 2 +- web/src/hooks/UseOAuthApplicationForm.ts | 2 +- web/src/hooks/UseTeams.ts | 5 +- web/src/index.tsx | 9 +- web/src/routes/Alerts.tsx | 7 +- web/src/routes/Build/Build.tsx | 5 +- .../CreateOrganizationModal.tsx | 18 +- .../AuthorizedApplicationsList.tsx | 5 +- .../DefaultPermissionsDropDown.tsx | 5 +- .../DefaultPermissionsList.tsx | 5 +- .../DeleteDefaultPermissionKebab.tsx | 5 +- .../CreatePermissionDrawer.tsx | 5 +- .../CreateTeamModal.tsx | 5 +- .../createTeamWizard/AddTeamMember.tsx | 5 +- .../createTeamWizard/CreateTeamWizard.tsx | 5 +- .../CreateOAuthApplicationModal.tsx | 4 +- .../GenerateTokenTab.tsx | 5 +- .../OAuthInformationTab.tsx | 5 +- .../SettingsTab.tsx | 5 +- .../OAuthApplicationActionsKebab.tsx | 5 +- .../OAuthApplicationsList.tsx | 5 +- .../Tabs/Settings/AutoPruning.tsx | 5 +- .../Tabs/Settings/BillingInformation.tsx | 5 +- .../Tabs/Settings/GeneralSettings.tsx | 5 +- .../Tabs/Settings/ProxyCacheConfig.tsx | 5 +- .../Tabs/Settings/QuotaManagement.tsx | 5 +- .../CollaboratorsDeleteModal.tsx | 8 +- .../CollaboratorsViewList.tsx | 5 +- .../MembersView/MembersViewList.tsx | 5 +- .../ManageMembers/AddNewTeamMemberDrawer.tsx | 5 +- .../ManageMembers/ManageMembersList.tsx | 5 +- .../SetRepoPermissionForTeamModal.tsx | 5 +- .../TeamsView/TeamsRoleDropDown.tsx | 5 +- .../TeamsView/TeamsViewList.tsx | 5 +- .../modals/ChangeEmailModal.tsx | 5 +- .../modals/ChangePasswordModal.tsx | 5 +- .../modals/CreateUserModal.tsx | 5 +- .../modals/DeleteOrganizationModal.tsx | 5 +- .../modals/DeleteUserModal.tsx | 5 +- .../modals/RenameOrganizationModal.tsx | 5 +- .../modals/SendRecoveryEmailModal.tsx | 5 +- .../modals/TakeOwnershipModal.tsx | 7 +- .../modals/ToggleUserStatusModal.tsx | 17 +- .../RepositoriesList/RobotAccountsList.tsx | 5 +- .../BuildHistoryManuallyStartTriggerModal.tsx | 5 +- ...HistoryStartBuildModalUploadDockerfile.tsx | 5 +- .../Builds/BuildTriggerDeleteModal.tsx | 5 +- .../Builds/BuildTriggerSetupWizard.tsx | 5 +- .../Builds/BuildTriggerToggleModal.tsx | 5 +- .../BuildTriggersInactiveTriggerRow.tsx | 5 +- .../Information/Information.tsx | 5 +- .../RepositoryDetails/Mirroring/Mirroring.tsx | 4 +- .../Mirroring/MirroringConfiguration.tsx | 2 +- .../Mirroring/MirroringModals.tsx | 2 +- .../Mirroring/MirroringStatus.tsx | 2 +- .../RepositoryDetails/RepositoryDetails.tsx | 5 +- .../Settings/RepositoryAutoPruning.tsx | 5 +- .../TagHistoryPermanentlyDeleteTag.tsx | 5 +- .../TagHistory/TagHistoryRestoreTag.tsx | 5 +- .../RepositoryDetails/Tags/DeleteModal.tsx | 5 +- .../Tags/TagsActionsAddTagModal.tsx | 5 +- .../Tags/TagsActionsEditExpirationModal.tsx | 5 +- web/src/routes/StandaloneMain.tsx | 8 + .../ServiceKeys/CreateServiceKeyForm.tsx | 4 +- .../routes/UsageLogs/UsageLogsExportModal.tsx | 5 +- 79 files changed, 570 insertions(+), 435 deletions(-) delete mode 100644 web/src/atoms/AlertState.ts delete mode 100644 web/src/atoms/SidebarState.ts create mode 100644 web/src/contexts/UIContext.tsx delete mode 100644 web/src/hooks/UseAlerts.ts 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(