diff --git a/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts index eecd6a71c2..5fd66310be 100644 --- a/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts @@ -34,10 +34,7 @@ test.describe('Dashboard', async () => { // Delete project await page.getByRole('row', { name: 'My Project' }).getByRole('button', { name: 'Project Actions' }).click(); - await page.getByRole('menuitemradio', { name: 'Settings' }).click(); - - await page.getByRole('button', { name: 'Delete' }).click(); - await page.getByRole('button', { name: 'Click to confirm' }).click(); + await page.getByRole('menuitemradio', { name: 'Delete' }).click(); // After deleting project, return to default Insomnia Dashboard await expect(page.locator('.app')).toContainText('Personal Workspace'); diff --git a/packages/insomnia/src/account/session.ts b/packages/insomnia/src/account/session.ts index 15c24bcd85..9f49f0f82a 100644 --- a/packages/insomnia/src/account/session.ts +++ b/packages/insomnia/src/account/session.ts @@ -154,7 +154,6 @@ export function getCurrentSessionId() { try { const { sessionExpiry } = JSON.parse(window.localStorage.getItem(_getSessionKey(sessionId)) || '{}'); if (typeof sessionExpiry !== 'string' || !sessionExpiry) { - console.log('No session expiry found', sessionExpiry); return ''; } @@ -257,13 +256,16 @@ function _getSymmetricKey() { } async function _whoami(sessionId: string | null = null): Promise { - const response = await window.main.insomniaFetch({ + const response = await window.main.insomniaFetch({ method: 'GET', path: '/auth/whoami', sessionId: sessionId || getCurrentSessionId(), }); if (typeof response === 'string') { - throw new Error('Unexpected plaintext response'); + throw new Error('Unexpected plaintext response: ' + response); + } + if (response && !response?.encSymmetricKey) { + throw new Error('Unexpected response: ' + JSON.stringify(response)); } return response; } diff --git a/packages/insomnia/src/common/export.tsx b/packages/insomnia/src/common/export.tsx index 727fef639a..1ad1225f25 100644 --- a/packages/insomnia/src/common/export.tsx +++ b/packages/insomnia/src/common/export.tsx @@ -364,7 +364,7 @@ const writeExportedFileToFileSystem = (filename: string, jsonData: string, onDon fs.writeFile(filename, jsonData, {}, onDone); }; -export const exportAllToFile = (activeProjectName: string, workspacesForActiveProject: Workspace[]) => { +export const exportProjectToFile = (activeProjectName: string, workspacesForActiveProject: Workspace[]) => { if (!workspacesForActiveProject.length) { showAlert({ title: 'Cannot export', diff --git a/packages/insomnia/src/ui/components/dropdowns/project-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/project-dropdown.tsx index 1e6750fff7..9526b7da6d 100644 --- a/packages/insomnia/src/ui/components/dropdowns/project-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/project-dropdown.tsx @@ -2,10 +2,19 @@ import { IconName } from '@fortawesome/fontawesome-svg-core'; import React, { FC, Fragment, useEffect, useState } from 'react'; import { Button, + Dialog, + Heading, + Input, Item, + Label, Menu, MenuTrigger, + Modal, + ModalOverlay, Popover, + Radio, + RadioGroup, + TextField, } from 'react-aria-components'; import { useFetcher } from 'react-router-dom'; @@ -15,7 +24,6 @@ import { } from '../../../models/project'; import { Icon } from '../icon'; import { showAlert } from '../modals'; -import ProjectSettingsModal from '../modals/project-settings-modal'; interface Props { project: Project; @@ -33,6 +41,7 @@ export const ProjectDropdown: FC = ({ project, organizationId }) => { const [isProjectSettingsModalOpen, setIsProjectSettingsModalOpen] = useState(false); const deleteProjectFetcher = useFetcher(); + const updateProjectFetcher = useFetcher(); const projectActionList: ProjectActionItem[] = [ { @@ -98,12 +107,90 @@ export const ProjectDropdown: FC = ({ project, organizationId }) => { - {isProjectSettingsModalOpen && ( - setIsProjectSettingsModalOpen(false)} - project={project} - /> - )} + + + setIsProjectSettingsModalOpen(false)} className="outline-none"> + {({ close }) => ( +
+
+ Project Settings + +
+ {isDefaultOrganizationProject(project) &&

+ This is the default project for your organization. You can not delete it or change its type. +

} +
{ + updateProjectFetcher.submit(e.currentTarget, { + action: `/organization/${organizationId}/project/${project._id}/update`, + method: 'post', + }); + + close(); + }} + > + + + + + + +
+ + + Secure Cloud +

+ End-to-end encrypted (E2EE) and synced securely to the cloud, ideal for collaboration. +

+
+ + + Local Vault +

+ Stored locally only with no cloud. Ideal when collaboration is not needed. +

+
+
+
+
+ +
+
+
+ )} +
+
+
); }; diff --git a/packages/insomnia/src/ui/components/modals/project-settings-modal.tsx b/packages/insomnia/src/ui/components/modals/project-settings-modal.tsx deleted file mode 100644 index 2d5ea658cc..0000000000 --- a/packages/insomnia/src/ui/components/modals/project-settings-modal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { FC, Fragment, useEffect, useRef } from 'react'; -import { OverlayContainer } from 'react-aria'; -import { useFetcher, useParams } from 'react-router-dom'; - -import { strings } from '../../../common/strings'; -import { isDefaultOrganizationProject, Project } from '../../../models/project'; -import { Modal, type ModalHandle, ModalProps } from '../base/modal'; -import { ModalBody } from '../base/modal-body'; -import { ModalHeader } from '../base/modal-header'; -import { PromptButton } from '../base/prompt-button'; -import { showAlert } from '.'; - -export interface ProjectSettingsModalProps extends ModalProps { - project: Project; -} - -export const ProjectSettingsModal: FC = ({ project, onHide }) => { - const modalRef = useRef(null); - const { organizationId } = useParams<{organizationId: string}>(); - const { submit, Form, data, state } = useFetcher(); - - useEffect(() => { - modalRef.current?.show(); - }, []); - - useEffect(() => { - if (data && data.error && state === 'idle') { - showAlert({ - title: 'Could not rename project', - message: data.error, - }); - } - }, [data, state]); - - return ( - - - - {strings.project.singular} Settings{' '} -
{project._id}
-
- -
- - -
- {!isDefaultOrganizationProject(project) && -

Actions

-
- - submit( - {}, - { method: 'post', action: `/organization/${organizationId}/project/${project._id}/delete` } - ) - } - className="width-auto btn btn--clicky inline-block" - > - Delete - -
-
} - -
-
-
- ); -}; - -ProjectSettingsModal.displayName = 'ProjectSettingsModal'; - -export default ProjectSettingsModal; diff --git a/packages/insomnia/src/ui/components/settings/import-export.tsx b/packages/insomnia/src/ui/components/settings/import-export.tsx index 150f282213..77078fc0af 100644 --- a/packages/insomnia/src/ui/components/settings/import-export.tsx +++ b/packages/insomnia/src/ui/components/settings/import-export.tsx @@ -5,7 +5,7 @@ import { useRouteLoaderData } from 'react-router-dom'; import { isLoggedIn } from '../../../account/session'; import { getProductName } from '../../../common/constants'; -import { exportAllToFile } from '../../../common/export'; +import { exportProjectToFile } from '../../../common/export'; import { exportAllData } from '../../../common/export-all-data'; import { getWorkspaceLabel } from '../../../common/get-workspace-label'; import { strings } from '../../../common/strings'; @@ -48,7 +48,6 @@ export const ImportExport: FC = ({ hideSettingsModal }) => { const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined; const activeWorkspaceName = workspaceData?.activeWorkspace.name; - const projectName = workspaceData?.activeProject.name ?? getProductName(); const { workspaceCount } = useRootLoaderData(); const workspacesFetcher = useFetcher(); useEffect(() => { @@ -57,13 +56,15 @@ export const ImportExport: FC = ({ hideSettingsModal }) => { workspacesFetcher.load(`/organization/${organizationId}/project/${projectId}`); } }, [organizationId, projectId, workspacesFetcher]); - const projectLoaderData = workspacesFetcher?.data as ProjectLoaderData; + const projectLoaderData = workspacesFetcher?.data as ProjectLoaderData | undefined; const workspacesForActiveProject = projectLoaderData?.workspaces.map(w => w.workspace) || []; + const projectName = projectLoaderData?.activeProject.name ?? getProductName(); + const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [isExportModalOpen, setIsExportModalOpen] = useState(false); - const handleExportAllToFile = () => { - exportAllToFile(projectName, workspacesForActiveProject); + const handleExportProjectToFile = () => { + exportProjectToFile(projectName, workspacesForActiveProject); hideSettingsModal(); }; @@ -75,7 +76,7 @@ export const ImportExport: FC = ({ hideSettingsModal }) => {
- Export: + Export
{workspaceData?.activeWorkspace ? isScratchpad(workspaceData.activeWorkspace) ? @@ -105,7 +106,7 @@ export const ImportExport: FC = ({ hideSettingsModal }) => { @@ -113,7 +114,7 @@ export const ImportExport: FC = ({ hideSettingsModal }) => { ) : ( @@ -171,7 +172,7 @@ export const ImportExport: FC = ({ hideSettingsModal }) => {
- Import: + Import