From 80e44ec5cde57f888a4c3f60415632f9d4284247 Mon Sep 17 00:00:00 2001 From: Chris Myers Date: Fri, 2 Oct 2026 10:27:36 -0600 Subject: [PATCH] Unify login methods and add register --- .../src/modules/CreateCollectionModal.jsx | 33 ++- .../src/modules/modular_login/FJLogin.jsx | 193 ++++++++++++--- .../modular_login/SBHInstanceSelector.jsx | 219 ++++++++++++++---- .../src/modules/modular_login/SBHLogin.jsx | 219 +++++++++++++++--- .../src/modules/modular_login/SBHOnly.jsx | 154 ------------ .../unified_modal/RepositorySelectorModal.jsx | 199 ---------------- .../modules/unified_modal/unifiedModal.jsx | 30 +-- frontend/src/pages/CloudHome.jsx | 8 +- frontend/src/pages/LocalHome.jsx | 8 +- frontend/src/redux/hooks/useUnifiedModal.js | 58 +---- frontend/src/redux/slices/modalSlice.js | 2 +- 11 files changed, 567 insertions(+), 556 deletions(-) delete mode 100644 frontend/src/modules/modular_login/SBHOnly.jsx delete mode 100644 frontend/src/modules/unified_modal/RepositorySelectorModal.jsx diff --git a/frontend/src/modules/CreateCollectionModal.jsx b/frontend/src/modules/CreateCollectionModal.jsx index b3e5c9c1..294541f6 100644 --- a/frontend/src/modules/CreateCollectionModal.jsx +++ b/frontend/src/modules/CreateCollectionModal.jsx @@ -6,13 +6,22 @@ import { showNotification } from '@mantine/notifications'; import { createStudySBH, createStudyFJ } from '../API'; import { useState } from 'react'; -function CreateCollectionModal({ opened, onClose, studyName, studyDescription, goBack }) { +function CreateCollectionModal({ + opened, + onClose, + studyName, + studyDescription, + goBack, + createFlapjackStudy = false, + modalData = {}, +}) { const [instanceData, setInstanceData] = useLocalStorage({ key: "SynbioHub", defaultValue: [] }); const [instanceDataFJ, setInstanceDataFJ] = useLocalStorage({ key: "Flapjack", defaultValue: [] }); const selected = useSelector(state => state.primaryRepository.sbhPrimary); const selectedFJ = useSelector(state => state.primaryRepository.fjPrimary); const [overwrite, setOverwrite] = useState(false); const studyId = makeIdentifier(studyName || ""); + const shouldCreateFlapjackStudy = Boolean(modalData?.createFlapjackStudy ?? createFlapjackStudy); function makeIdentifier(text) { let id = text.trim() @@ -60,12 +69,12 @@ function CreateCollectionModal({ opened, onClose, studyName, studyDescription, g const registryURL = instance?.registryURL || url; const registryPrefix = instance?.registryPrefix || url; - const urlFJ = selectedFJ && selectedFJ.trim() !== "" ? selectedFJ : null; - const instanceFJ = instanceDataFJ.find((inst) => inst.registryURL === urlFJ); - const authFJ = instanceFJ ? instanceFJ.authtoken : null; - const registryAPIFJ = instanceFJ?.registryAPI || urlFJ; - const registryURLFJ = instanceFJ?.registryURL || urlFJ; - const registryPrefixFJ = instanceFJ?.registryPrefix || urlFJ; + const urlFJ = shouldCreateFlapjackStudy && selectedFJ && selectedFJ.trim() !== "" ? selectedFJ : null; + const instanceFJ = shouldCreateFlapjackStudy ? instanceDataFJ.find((inst) => inst.registryURL === urlFJ) : null; + const authFJ = shouldCreateFlapjackStudy ? (instanceFJ ? instanceFJ.authtoken : null) : null; + const registryAPIFJ = shouldCreateFlapjackStudy ? (instanceFJ?.registryAPI || urlFJ) : null; + const registryURLFJ = shouldCreateFlapjackStudy ? (instanceFJ?.registryURL || urlFJ) : null; + const registryPrefixFJ = shouldCreateFlapjackStudy ? (instanceFJ?.registryPrefix || urlFJ) : null; if (!url) { showNotification({ @@ -81,7 +90,7 @@ function CreateCollectionModal({ opened, onClose, studyName, studyDescription, g await createStudySBH(id, version, name, description, citations, auth, registryAPI, overwrite); let FJid = null; - if (urlFJ) { + if (shouldCreateFlapjackStudy && urlFJ) { FJid = await createStudyFJ(id, version, name, description, citations, authFJ, registryAPIFJ, overwrite); } @@ -104,10 +113,10 @@ function CreateCollectionModal({ opened, onClose, studyName, studyDescription, g registryURL, registryAPI, registryPrefix, - FJid, - registryURLFJ, - registryAPIFJ, - registryPrefixFJ, + FJid: shouldCreateFlapjackStudy ? FJid : null, + registryURLFJ: shouldCreateFlapjackStudy ? registryURLFJ : null, + registryAPIFJ: shouldCreateFlapjackStudy ? registryAPIFJ : null, + registryPrefixFJ: shouldCreateFlapjackStudy ? registryPrefixFJ : null, }; onClose(payload); diff --git a/frontend/src/modules/modular_login/FJLogin.jsx b/frontend/src/modules/modular_login/FJLogin.jsx index 3aa30e82..7cd93133 100644 --- a/frontend/src/modules/modular_login/FJLogin.jsx +++ b/frontend/src/modules/modular_login/FJLogin.jsx @@ -1,6 +1,6 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useForm } from '@mantine/form'; -import { TextInput, PasswordInput, Button, Modal } from '@mantine/core'; +import { TextInput, PasswordInput, Button, Modal, Group } from '@mantine/core'; import { useLocalStorage } from '@mantine/hooks'; import axios from 'axios'; import { showNotification, cleanNotifications } from '@mantine/notifications'; @@ -37,8 +37,38 @@ const login = async (instance, username, password) => { } }; +const register = async (instance, payload) => { + const registrationPayload = JSON.stringify({ + username: payload.username, + password: payload.password, + password2: payload.password, + email: payload.email, + }); + + const response = await fetch(`${instance}/api/auth/register/`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: registrationPayload, + }); + + const responseText = await response.text(); + if (!response.ok) { + throw new Error(responseText || 'Registration failed'); + } + + try { + return JSON.parse(responseText) ?? { success: true }; + } catch { + return responseText ? { success: true, message: responseText } : { success: true }; + } +}; + const FJInstanceLogin = ({ opened, onClose, goBack, setRepoSelection, selectedRepo, selectedFJRepo, onLoginSuccess }) => { const [instanceData, setInstanceData] = useLocalStorage({ key: "Flapjack", defaultValue: [] }); + const [registerOpened, setRegisterOpened] = useState(false); const dispatch = useDispatch(); const selected = useSelector(state => state.primaryRepository.fjPrimary); const setSelected = (value) => dispatch(setFJPrimary(typeof value === 'function' ? value(selected) : value)); @@ -63,7 +93,21 @@ const FJInstanceLogin = ({ opened, onClose, goBack, setRepoSelection, selectedRe }, }); - // Reset form when modal is opened + const registerForm = useForm({ + initialValues: { + username: '', + email: '', + password: '', + confirmPassword: '', + }, + validate: { + username: (value) => (value ? null : 'Username is required'), + email: (value) => (value ? null : 'Email is required'), + password: (value) => (value ? null : 'Password is required'), + confirmPassword: (value, values) => (value === values.password ? null : 'Passwords do not match'), + }, + }); + useEffect(() => { if (opened) { form.reset(); @@ -134,35 +178,124 @@ const FJInstanceLogin = ({ opened, onClose, goBack, setRepoSelection, selectedRe } }; + const handleRegisterSubmit = async (values) => { + if (!repoToUse) { + showNotification({ + title: 'Registration failed', + message: 'Please select a Flapjack repository before registering.', + color: 'red', + }); + return; + } + + try { + const existing = instanceData.find(item => item.registryURL === repoToUse) || {}; + const registryAPI = existing.registryAPI || repoToUse; + await register(registryAPI, { + username: values.username, + email: values.email, + password: values.password, + }); + + cleanNotifications(); + showNotification({ + title: 'Registration successful', + message: 'Your Flapjack account has been created. You can now log in.', + color: 'green', + }); + setRegisterOpened(false); + registerForm.reset(); + form.setValues({ username: values.username, password: '' }); + } catch (error) { + cleanNotifications(); + const backendMessage = error.response?.data?.error || error.response?.data?.message || error.message || 'Unable to register your account.'; + showNotification({ + title: 'Registration failed', + message: typeof backendMessage === 'string' ? backendMessage : 'Unable to register your account.', + color: 'red', + }); + } + }; + return ( - -
{handleSubmit(values)})} + <> + + {handleSubmit(values)})} + > + + + + + + + + + + +
+ + setRegisterOpened(false)} + title="Register to Flapjack" > - - - - - - +
{ handleRegisterSubmit(values); })}> + + + + + + + + + +
+ ); }; diff --git a/frontend/src/modules/modular_login/SBHInstanceSelector.jsx b/frontend/src/modules/modular_login/SBHInstanceSelector.jsx index ac7e481b..5a7b6ae1 100644 --- a/frontend/src/modules/modular_login/SBHInstanceSelector.jsx +++ b/frontend/src/modules/modular_login/SBHInstanceSelector.jsx @@ -7,40 +7,54 @@ import { cleanNotifications, showNotification } from '@mantine/notifications'; import axios from 'axios'; import { useDispatch, useSelector } from 'react-redux'; import { setSBHPrimary } from '../../redux/slices/primaryRepositorySlice'; +import { CheckLogin, clearInvalidCredentials } from '../../API'; +import { MODAL_TYPES } from '../unified_modal/unifiedModal'; -const SBHInstanceSelector = ({onClose, setRepoSelection }) => { +const SBHInstanceSelector = ({ + onClose, + setRepoSelection, + navigateTo, + completeWorkflow, + modalData = {}, + setModalData, +}) => { const [showLogin, setShowLogin] = useState(false); const [addRegistryOpen, setAddRegistryOpen] = useState(false); const [instanceData, setInstanceData] = useLocalStorage({ key: "SynbioHub", defaultValue: [] }); const [nullSelected, setNullSelected] = useState(false); const dispatch = useDispatch(); const selected = useSelector(state => state.primaryRepository.sbhPrimary); - const setSelected = (value) => dispatch(setSBHPrimary(typeof value === 'function' ? value(selected) : value)); - - const findInstance = (uri) => { - return instanceData.find((element) => element.registryURL === uri); - } + const selectedRepo = modalData.selectedRepo || selected; + const setSelected = (value) => { + const nextValue = typeof value === 'function' ? value(selected) : value; + dispatch(setSBHPrimary(nextValue)); + setModalData?.(prev => ({ ...prev, selectedRepo: nextValue })); + }; + + const unifiedNextModal = modalData.nextModal || MODAL_TYPES.FLAPJACK_OPTIONS; + const isUnifiedFlow = typeof navigateTo === 'function' || typeof completeWorkflow === 'function'; + + const findInstance = (uri) => instanceData.find((element) => element.registryURL === uri); const handleRemoveInstance = () => { - setInstanceData(instanceData.filter(instance => instance.registryURL !== selected)); + setInstanceData(instanceData.filter(instance => instance.registryURL !== selectedRepo)); setSelected(null); }; const stripData = (uri, showNotificationFlag = false) => { const updatedInstanceData = instanceData.map((item) => - item.registryURL === uri? { + item.registryURL === uri ? { ...item, email: '', authtoken: '', name: '', username: '', affiliation: '', - } - : item + } : item ); setInstanceData(updatedInstanceData); - + if (!showNotificationFlag) { showNotification({ title: 'Logout Successful', @@ -48,7 +62,7 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { color: 'green', }); } - } + }; const login = async (uri, auth) => { const instance = findInstance(uri); @@ -57,11 +71,11 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { const response = await axios.get(`${registryAPI}/profile`, { headers: { 'Accept': 'text/plain; charset=UTF-8', - "X-authorization" : `${auth}` + 'X-authorization': `${auth}` } }); - if(response.data){ - setRepoSelection(""); + if (response.data) { + setRepoSelection?.(''); return; } } catch (error) { @@ -87,15 +101,14 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { color: 'blue', loading: true, }); - const response = await axios.post(`${registryAPI}/logout`, null, { + await axios.post(`${registryAPI}/logout`, null, { headers: { 'Accept': 'text/plain; charset=UTF-8', - "X-authorization" : `${auth}` + 'X-authorization': `${auth}` } }); cleanNotifications(); stripData(uri); - } catch (error) { cleanNotifications(); stripData(uri, true); @@ -134,6 +147,105 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { setSelected(registryURL); }; + const handleUnifiedSelection = async () => { + if (!selectedRepo || selectedRepo.startsWith('Select')) { + setNullSelected(true); + return; + } + + setSelected(selectedRepo); + setModalData?.(prev => ({ ...prev, selectedRepo: selectedRepo, skipRepositorySelection: true })); + + const repoInfo = (instanceData || []).find(r => r.registryURL === selectedRepo); + if (!repoInfo || !repoInfo.authtoken) { + if (typeof navigateTo === 'function') { + navigateTo(MODAL_TYPES.SBH_LOGIN, { + selectedRepo: selectedRepo, + returnTo: unifiedNextModal, + }); + return; + } + if (typeof onClose === 'function') { + onClose({ selectedRepo: selectedRepo }); + } + return; + } + + try { + const loginResult = await CheckLogin(repoInfo.registryAPI || selectedRepo, repoInfo.authtoken); + if (!loginResult.valid) { + clearInvalidCredentials(selectedRepo); + showNotification({ + title: 'Invalid Credentials', + message: 'Stored credentials are invalid or expired. Please log in.', + color: 'orange', + }); + if (typeof navigateTo === 'function') { + navigateTo(MODAL_TYPES.SBH_LOGIN, { + selectedRepo: selectedRepo, + returnTo: unifiedNextModal, + }); + return; + } + if (typeof onClose === 'function') { + onClose({ selectedRepo: selectedRepo }); + } + return; + } + + const profileEmail = loginResult.profile?.email || ''; + const userInfo = { + name: loginResult.profile?.name || repoInfo.name || 'Unknown', + username: loginResult.profile?.username || repoInfo.username || 'Unknown', + email: profileEmail, + affiliation: loginResult.profile?.affiliation || repoInfo.affiliation || 'N/A', + }; + + setModalData?.(prev => ({ + ...prev, + selectedRepo: selectedRepo, + userInfo, + authToken: repoInfo.authtoken, + validated: true, + })); + + if (typeof navigateTo === 'function') { + const moved = navigateTo(unifiedNextModal, { + selectedRepo: selectedRepo, + authToken: repoInfo.authtoken, + validated: true, + userInfo, + }); + if (!moved && typeof completeWorkflow === 'function') { + completeWorkflow({ selectedRepo: selectedRepo, authToken: repoInfo.authtoken, validated: true, userInfo }); + } + return; + } + + if (typeof completeWorkflow === 'function') { + completeWorkflow({ selectedRepo: selectedRepo, authToken: repoInfo.authtoken, validated: true, userInfo }); + } + } catch (err) { + console.error('Credential check error:', err); + clearInvalidCredentials(selectedRepo); + showNotification({ + title: 'Credential Check Failed', + message: err.message || 'Failed to verify credentials. Please log in.', + color: 'red', + }); + if (typeof navigateTo === 'function') { + navigateTo(MODAL_TYPES.SBH_LOGIN, { + selectedRepo: selectedRepo, + returnTo: unifiedNextModal, + }); + return; + } + if (typeof onClose === 'function') { + onClose({ selectedRepo: selectedRepo }); + } + } + }; + const selectData = instanceData.map(inst => ({ value: inst.registryURL, label: inst.registryURL, @@ -141,9 +253,20 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { return ( <> - {showLogin ? - - : ( <> + {showLogin ? ( + setShowLogin(false)} + goBack={setShowLogin} + setRepoSelection={setRepoSelection} + selectedRepo={selectedRepo} + onLoginSuccess={() => { + setShowLogin(false); + setRepoSelection?.(''); + }} + /> + ) : ( + <> setAddRegistryOpen(false)} @@ -152,31 +275,45 @@ const SBHInstanceSelector = ({onClose, setRepoSelection }) => { existingRegistries={instanceData.map(inst => inst.registryURL)} /> - - - - - {selectedRepo && selectedRepo !== 'add-repository' && !selectedRepo.startsWith('Select') && ( - - )} - - - - - ); -} diff --git a/frontend/src/modules/unified_modal/unifiedModal.jsx b/frontend/src/modules/unified_modal/unifiedModal.jsx index e025446d..d5409ed4 100644 --- a/frontend/src/modules/unified_modal/unifiedModal.jsx +++ b/frontend/src/modules/unified_modal/unifiedModal.jsx @@ -5,12 +5,11 @@ import { closeUnifiedModal } from '../../redux/slices/modalSlice'; import { setSBHPrimary } from '../../redux/slices/primaryRepositorySlice'; // Todo: Modernize the following legacy modals -import SBHLogin from '../modular_login/SBHOnly'; +import SBHLogin from '../modular_login/SBHLogin'; import FJLogin from '../modular_login/FJLogin'; import CreateCollectionModal from '../CreateCollectionModal'; import SBHInstanceSelector from '../modular_login/SBHInstanceSelector'; import FJInstanceSelector from '../modular_login/FJInstanceSelector'; -import RepositorySelectorModal from './RepositorySelectorModal'; import FlapjackOptionsModal from './FlapjackOptionsModal'; import CollectionBrowserModal from './CollectionBrowserModal'; import AddRegistryModal from './AddRegistryModal'; @@ -25,7 +24,6 @@ export const MODAL_TYPES = { SBH_INSTANCE_SELECTOR: 'sbh_instance_selector', FJ_INSTANCE_SELECTOR: 'fj_instance_selector', DIRECTORY_SELECT: 'directory_select', - REPOSITORY_SELECTOR: 'repository_selector', SBH_CREDENTIAL_CHECK: 'sbh_credential_check', COLLECTION_BROWSER: 'collection_browser', WELL_LOCATIONS_CONFIG: 'well_locations_config', @@ -38,13 +36,12 @@ const MODAL_FLOWS = { [MODAL_TYPES.ADD_SBH_REPO]: [MODAL_TYPES.SBH_LOGIN], [MODAL_TYPES.ADD_FJ_REPO]: [MODAL_TYPES.FJ_LOGIN], [MODAL_TYPES.CREATE_COLLECTION]: [MODAL_TYPES.SBH_LOGIN], - [MODAL_TYPES.SBH_INSTANCE_SELECTOR]: [MODAL_TYPES.SBH_LOGIN, MODAL_TYPES.ADD_SBH_REPO], + [MODAL_TYPES.SBH_INSTANCE_SELECTOR]: [MODAL_TYPES.SBH_LOGIN, MODAL_TYPES.ADD_SBH_REPO, MODAL_TYPES.FLAPJACK_OPTIONS], [MODAL_TYPES.FJ_INSTANCE_SELECTOR]: [MODAL_TYPES.FJ_LOGIN, MODAL_TYPES.ADD_FJ_REPO, MODAL_TYPES.CREATE_COLLECTION], [MODAL_TYPES.DIRECTORY_SELECT]: [], - [MODAL_TYPES.REPOSITORY_SELECTOR]: [MODAL_TYPES.ADD_SBH_REPO, MODAL_TYPES.FLAPJACK_OPTIONS, MODAL_TYPES.SBH_LOGIN], [MODAL_TYPES.WELL_LOCATIONS_CONFIG]: [], [MODAL_TYPES.COLLECTION_BROWSER]: [MODAL_TYPES.SBH_LOGIN, MODAL_TYPES.CREATE_COLLECTION], - [MODAL_TYPES.FLAPJACK_OPTIONS]: [MODAL_TYPES.FJ_INSTANCE_SELECTOR,MODAL_TYPES.CREATE_COLLECTION], + [MODAL_TYPES.FLAPJACK_OPTIONS]: [MODAL_TYPES.FJ_INSTANCE_SELECTOR, MODAL_TYPES.CREATE_COLLECTION], }; const titles = { @@ -56,7 +53,6 @@ const titles = { [MODAL_TYPES.SBH_INSTANCE_SELECTOR]: 'Select SynBioHub Instance', [MODAL_TYPES.FJ_INSTANCE_SELECTOR]: 'Select Flapjack Instance', [MODAL_TYPES.DIRECTORY_SELECT]: 'Select Directory', - [MODAL_TYPES.REPOSITORY_SELECTOR]: 'Select Repository', [MODAL_TYPES.SBH_CREDENTIAL_CHECK]: 'Verify Credentials', [MODAL_TYPES.COLLECTION_BROWSER]: 'Browse Collections', [MODAL_TYPES.WELL_LOCATIONS_CONFIG]: 'Well Locations & Advanced Configurations', @@ -70,7 +66,6 @@ const sizes = { [MODAL_TYPES.SBH_INSTANCE_SELECTOR]: 'xl', [MODAL_TYPES.FJ_INSTANCE_SELECTOR]: 'xl', [MODAL_TYPES.COLLECTION_BROWSER]: 1200, - [MODAL_TYPES.REPOSITORY_SELECTOR]: 'lg', [MODAL_TYPES.SBH_CREDENTIAL_CHECK]: 'lg', [MODAL_TYPES.WELL_LOCATIONS_CONFIG]: 'lg', }; @@ -320,8 +315,7 @@ function UnifiedModal({ case MODAL_TYPES.ADD_SBH_REPO: const hasCredentialCheckInHistory = modalHistory.includes(MODAL_TYPES.FLAPJACK_OPTIONS); - const hasRepoSelectorInHistory = modalHistory.includes(MODAL_TYPES.REPOSITORY_SELECTOR); - const shouldReturnToValidator = hasCredentialCheckInHistory || hasRepoSelectorInHistory; + const shouldReturnToValidator = hasCredentialCheckInHistory; return ( - ); - - case MODAL_TYPES.SBH_INSTANCE_SELECTOR: - return ( - setModalData(prev => ({ ...prev, selectedRepo: selection }))} + createFlapjackStudy={modalData[currentModal]?.createFlapjackStudy ?? modalProps.createFlapjackStudy ?? false} {...commonProps} /> ); @@ -400,9 +386,9 @@ function UnifiedModal({ ); - case MODAL_TYPES.REPOSITORY_SELECTOR: + case MODAL_TYPES.SBH_INSTANCE_SELECTOR: return ( - ); - /* SBH_CREDENTIAL_CHECK removed: flows now go directly to FLAPJACK_OPTIONS */ - case MODAL_TYPES.COLLECTION_BROWSER: return ( state.modal.addCollections) - const SBHOnlyOpened = useSelector((state) => state.modal.sbhLoginOpen) + const sbhLoginOpened = useSelector((state) => state.modal.sbhLoginOpen) const libraryName = useSelector((state) => state.modal.libraryName) const libraryDescription = useSelector((state) => state.modal.libraryDescription) @@ -53,8 +53,8 @@ export default function CloudHome() { libraryDescription={libraryDescription} onClose={() => dispatch(closeCreateCollection())} /> - dispatch(closeSBHLogin())} /> diff --git a/frontend/src/pages/LocalHome.jsx b/frontend/src/pages/LocalHome.jsx index 586d481c..49f283f3 100644 --- a/frontend/src/pages/LocalHome.jsx +++ b/frontend/src/pages/LocalHome.jsx @@ -7,7 +7,7 @@ import { closeModal, closeSBH, closeFJ, closeCreateCollection, closeSBHLogin } f import FJModal from '../modules/modular_login/FJModal'; import SBHModal from '../modules/modular_login/SBHModal'; import CreateCollectionModal from '../modules/CreateCollectionModal'; -import SBHOnly from '../modules/modular_login/SBHOnly'; +import SBHLogin from '../modules/modular_login/SBHLogin'; import UnifiedModal from '../modules/unified_modal/unifiedModal'; @@ -18,7 +18,7 @@ export default function LocalHome() { const collectionModalOpened = useSelector((state) => state.modal.addCollections) - const SBHOnlyOpened = useSelector((state) => state.modal.sbhLoginOpen) + const sbhLoginOpened = useSelector((state) => state.modal.sbhLoginOpen) const libraryName = useSelector((state) => state.modal.libraryName) const libraryDescription = useSelector((state) => state.modal.libraryDescription) @@ -64,8 +64,8 @@ export default function LocalHome() { onComplete={unifiedModalCallback} modalProps={unifiedModalProps} /> - dispatch(closeSBHLogin())} /> diff --git a/frontend/src/redux/hooks/useUnifiedModal.js b/frontend/src/redux/hooks/useUnifiedModal.js index 14b4f135..8099cabf 100644 --- a/frontend/src/redux/hooks/useUnifiedModal.js +++ b/frontend/src/redux/hooks/useUnifiedModal.js @@ -179,9 +179,9 @@ export function useUnifiedModal() { studyDirectory, onComplete ) => { - open(MODAL_TYPES.REPOSITORY_SELECTOR, { + open(MODAL_TYPES.SBH_INSTANCE_SELECTOR, { allowedModals: [ - MODAL_TYPES.REPOSITORY_SELECTOR, + MODAL_TYPES.SBH_INSTANCE_SELECTOR, MODAL_TYPES.FLAPJACK_OPTIONS, MODAL_TYPES.SBH_LOGIN, MODAL_TYPES.FJ_LOGIN, @@ -235,7 +235,7 @@ export function useUnifiedModal() { /** * Open collection browser workflow - * Steps: REPOSITORY_SELECTOR -> SBH_CREDENTIAL_CHECK -> COLLECTION_BROWSER + * Steps: SBH_INSTANCE_SELECTOR -> SBH_LOGIN -> COLLECTION_BROWSER * * @param {function} onComplete - Callback function that receives selected collections * @param {object} props - Optional props for initial configuration @@ -284,9 +284,9 @@ export function useUnifiedModal() { return; } - open(MODAL_TYPES.REPOSITORY_SELECTOR, { + open(MODAL_TYPES.SBH_INSTANCE_SELECTOR, { allowedModals: [ - MODAL_TYPES.REPOSITORY_SELECTOR, + MODAL_TYPES.SBH_INSTANCE_SELECTOR, MODAL_TYPES.FLAPJACK_OPTIONS, MODAL_TYPES.COLLECTION_BROWSER, MODAL_TYPES.ADD_SBH_REPO, @@ -358,54 +358,6 @@ export function useUnifiedModal() { executeImport(); }, [open]), - /** - * Open collection browser workflow for resource selection (plasmids, backbones, etc.) - * This workflow skips repository selection and uses the provided repository. - * Silently validates credentials first - only shows UI if there's a problem. - * - * @param {string} repositoryUrl - The repository URL to use (from previous selection) - * @param {string} expectedEmail - The email address to validate against (for cross-reference) - * @param {function} onComplete - Callback function that receives selected collections - * @param {object} props - Optional props for initial configuration - * @param {boolean} props.multiSelect - Whether to allow multiple selections (default: true) - * - * The callback receives data only when the entire workflow completes: - * { - * selectedRepo: string, - * userInfo: { email, username, name, ... }, - * collections: [{ uri, name, displayId, ... }], - * count: number, - * completed: true, - * validated: true // indicates email was validated - * } - * - * If credentials don't match, the workflow aborts and returns: - * { - * error: 'Email mismatch', - * expectedEmail: string, - * actualEmail: string, - * aborted: true - * } - */ - browseCollectionsForResource: useCallback((repositoryUrl, expectedEmail, onComplete, props = {}) => { - open(MODAL_TYPES.COLLECTION_BROWSER, { - allowedModals: [ - MODAL_TYPES.SBH_CREDENTIAL_CHECK, - MODAL_TYPES.COLLECTION_BROWSER, - MODAL_TYPES.SBH_LOGIN, - MODAL_TYPES.CREATE_COLLECTION - ], - props: { - ...props, - selectedRepo: repositoryUrl, - expectedEmail: expectedEmail, - skipRepositorySelection: true, - silentCredentialCheck: true, - multiSelect: props.multiSelect !== undefined ? props.multiSelect : true, // Default to multi-select for backward compatibility - }, - onComplete, - }); - }, [open]), }; return { diff --git a/frontend/src/redux/slices/modalSlice.js b/frontend/src/redux/slices/modalSlice.js index ae5df116..6a8e9695 100644 --- a/frontend/src/redux/slices/modalSlice.js +++ b/frontend/src/redux/slices/modalSlice.js @@ -5,7 +5,7 @@ import { createSlice } from '@reduxjs/toolkit'; const initialState = { bothOpen: false, fjOpen: false, - sbHOpen: false, + sbhOpen: false, directoryOpen: false, addSBHrepository: false, addFJrepository: false,