diff --git a/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts
index 37fb9aa18c..74d79cc9a5 100644
--- a/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts
@@ -1,3 +1,5 @@
+import { expect } from '@playwright/test';
+
import { loadFixture } from '../../playwright/paths';
import { test } from '../../playwright/test';
@@ -23,18 +25,18 @@ test.describe('Cookie editor', async () => {
await page.click('pre[role="presentation"]:has-text("bar")');
await page.locator('[data-testid="CookieValue"] >> textarea').nth(1).fill('123');
await page.locator('text=Done').nth(1).click();
- await page.getByRole('cell', { name: 'foo=b123ar; Expires=' }).click();
+ await page.getByTestId('cookie-test-iteration-0').click();
// Create a new cookie
- await page.locator('.cookie-list').getByRole('button', { name: 'Actions' }).click();
- await page.getByRole('menuitem', { name: 'Add Cookie' }).click();
+ await page.getByRole('button', { name: 'Add Cookie' }).click();
+
await page.getByRole('button', { name: 'Edit' }).first().click();
// Try to replace text in Raw view
await page.getByRole('tab', { name: 'Raw' }).click();
await page.locator('text=Raw Cookie String >> input[type="text"]').fill('foo2=bar2; Expires=Tue, 19 Jan 2038 03:14:07 GMT; Domain=localhost; Path=/');
await page.locator('text=Done').nth(1).click();
- await page.getByRole('cell', { name: 'foo2=bar2; Expires=' }).click();
+ await page.getByTestId('cookie-test-iteration-0').click();
await page.click('text=Done');
@@ -44,7 +46,7 @@ test.describe('Cookie editor', async () => {
// Check in the timeline that the cookie was sent
await page.getByRole('tab', { name: 'Console' }).click();
- await page.click('text=foo2=bar2; foo=b123ar');
+ await expect(page.getByText('foo2=bar2')).toBeVisible();
// Send ws request
await page.getByLabel('Request Collection').getByTestId('example websocket').press('Enter');
@@ -53,7 +55,6 @@ test.describe('Cookie editor', async () => {
// Check in the timeline that the cookie was sent
await page.getByRole('tab', { name: 'Console' }).click();
- await page.click('text=foo2=bar2; foo=b123ar;');
+ await expect(page.getByText('foo2=bar2')).toBeVisible();
});
-
});
diff --git a/packages/insomnia/src/ui/components/cookie-list.tsx b/packages/insomnia/src/ui/components/cookie-list.tsx
deleted file mode 100644
index 9839bf87df..0000000000
--- a/packages/insomnia/src/ui/components/cookie-list.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import { isValid } from 'date-fns';
-import React, { type FC, useCallback, useState } from 'react';
-import { Button } from 'react-aria-components';
-import { Cookie as ToughCookie } from 'tough-cookie';
-import { v4 as uuidv4 } from 'uuid';
-
-import { cookieToString } from '../../common/cookies';
-import type { Cookie } from '../../models/cookie-jar';
-import { Dropdown, DropdownItem, ItemContent } from './base/dropdown';
-import { PromptButton } from './base/prompt-button';
-import { Icon } from './icon';
-import { CookieModifyModal } from './modals/cookie-modify-modal';
-import { RenderedText } from './rendered-text';
-
-export interface CookieListProps {
- handleCookieAdd: (cookie: Cookie) => void;
- handleCookieDelete: (cookie: Cookie) => void;
- handleDeleteAll: () => void;
- cookies: Cookie[];
- newCookieDomainName: string;
-}
-
-// Use tough-cookie MAX_DATE value
-// https://github.com/salesforce/tough-cookie/blob/5ae97c6a28122f3fb309adcd8428274d9b2bd795/lib/cookie.js#L77
-const MAX_TIME = 2147483647000;
-
-const CookieRow: FC<{
- cookie: Cookie;
- deleteCookie: (cookie: Cookie) => void;
-}> = ({ cookie, deleteCookie }) => {
- const [isCookieModalOpen, setIsCookieModalOpen] = useState(false);
- if (cookie.expires && !isValid(new Date(cookie.expires))) {
- cookie.expires = null;
- }
-
- const c = ToughCookie.fromJSON(cookie);
- const cookieString = c ? cookieToString(c) : '';
- return
- |
- {cookie.domain || ''}
- |
-
- {cookieString || ''}
- |
- { }} className="text-right no-wrap">
- {' '}
- deleteCookie(cookie)}
- title="Delete cookie"
- >
-
-
- {isCookieModalOpen && (
- setIsCookieModalOpen(false)}
- />
- )}
- |
-
;
-
-};
-
-export const CookieList: FC = ({
- cookies,
- handleDeleteAll,
- handleCookieAdd,
- newCookieDomainName,
- handleCookieDelete,
-}) => {
- const addCookie = useCallback(() => handleCookieAdd({
- id: uuidv4(),
- key: 'foo',
- value: 'bar',
- domain: newCookieDomainName,
- expires: MAX_TIME as unknown as Date,
- path: '/',
- secure: false,
- httpOnly: false,
- }), [newCookieDomainName, handleCookieAdd]);
-
- return
-
-
-
- |
- Domain
- |
-
- Cookie
- |
-
-
- Actions
-
- }
- >
-
-
-
-
-
-
-
- |
-
-
-
- {cookies.map(cookie => (
-
- ))}
-
-
- {cookies.length === 0 &&
-
I couldn't find any cookies for you.
-
-
-
-
}
-
;
-};
diff --git a/packages/insomnia/src/ui/components/modals/cookie-modify-modal.tsx b/packages/insomnia/src/ui/components/modals/cookie-modify-modal.tsx
deleted file mode 100644
index 1df756b253..0000000000
--- a/packages/insomnia/src/ui/components/modals/cookie-modify-modal.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import clone from 'clone';
-import { isValid } from 'date-fns';
-import React, { useEffect, useRef, useState } from 'react';
-import { OverlayContainer } from 'react-aria';
-import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
-import { useRouteLoaderData } from 'react-router-dom';
-import { useFetcher, useParams } from 'react-router-dom';
-import { Cookie as ToughCookie } from 'tough-cookie';
-
-import { cookieToString } from '../../../common/cookies';
-import type { Cookie, CookieJar } from '../../../models/cookie-jar';
-import type { WorkspaceLoaderData } from '../../routes/workspace';
-import { Modal, type ModalHandle, type ModalProps } from '../base/modal';
-import { ModalBody } from '../base/modal-body';
-import { ModalFooter } from '../base/modal-footer';
-import { ModalHeader } from '../base/modal-header';
-import { OneLineEditor } from '../codemirror/one-line-editor';
-export interface CookieModifyModalOptions {
- cookie: Cookie;
-}
-
-export const CookieModifyModal = ((props: ModalProps & CookieModifyModalOptions) => {
- const modalRef = useRef(null);
- const [cookie, setCookie] = useState(props.cookie);
- const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
- const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
- const updateCookieJarFetcher = useFetcher();
- useEffect(() => {
- modalRef.current?.show();
- }, []);
- const updateCookieJar = async (cookieJarId: string, patch: CookieJar) => {
- updateCookieJarFetcher.submit(JSON.stringify({ patch, cookieJarId }), {
- encType: 'application/json',
- method: 'post',
- action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/cookieJar/update`,
- });
- };
- const handleCookieUpdate = async (nextCookie: any) => {
- if (!cookie) {
- return;
- }
- const newcookie = clone(nextCookie);
- // transform to Date object or fallback to null
- let dateFormat = null;
- if (newcookie.expires && isValid(new Date(newcookie.expires))) {
- dateFormat = new Date(newcookie.expires);
- }
- newcookie.expires = dateFormat;
- setCookie(newcookie);
-
- // Clone so we don't modify the original
- const cookieJar = clone(activeCookieJar);
- const index = activeCookieJar.cookies.findIndex(c => c.id === cookie.id);
- if (index < 0) {
- console.warn(`Could not find cookie with id=${cookie.id} to edit`);
- return;
- }
- cookieJar.cookies = [...cookieJar.cookies.slice(0, index), newcookie, ...cookieJar.cookies.slice(index + 1)];
- updateCookieJar(cookieJar._id, cookieJar);
- };
-
- let localDateTime;
- if (cookie && cookie.expires && isValid(new Date(cookie.expires))) {
- localDateTime = new Date(cookie.expires).toISOString().slice(0, 16);
- }
-
- let rawDefaultValue;
- if (!cookie) {
- rawDefaultValue = '';
- } else {
- try {
- const c = ToughCookie.fromJSON(JSON.stringify(cookie));
- rawDefaultValue = c ? cookieToString(c) : '';
- } catch (err) {
- console.warn('Failed to parse cookie string', err);
- rawDefaultValue = '';
- }
- }
- return (
-
-
- Edit Cookie
-
- {activeCookieJar && cookie && (
-
-
-
- Friendly
-
-
- Raw
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
-
-
-
- );
-});
diff --git a/packages/insomnia/src/ui/components/modals/cookies-modal.tsx b/packages/insomnia/src/ui/components/modals/cookies-modal.tsx
index e3442052f4..3f971d0477 100644
--- a/packages/insomnia/src/ui/components/modals/cookies-modal.tsx
+++ b/packages/insomnia/src/ui/components/modals/cookies-modal.tsx
@@ -1,111 +1,541 @@
-import React, { useEffect, useRef, useState } from 'react';
-import { OverlayContainer } from 'react-aria';
+import clone from 'clone';
+import { isValid } from 'date-fns';
+import React, { useState } from 'react';
+import { Button, Dialog, Group, Heading, Input, ListBox, ListBoxItem, Modal, ModalOverlay, Tab, TabList, TabPanel, Tabs, TextField } from 'react-aria-components';
import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
+import { Cookie as ToughCookie } from 'tough-cookie';
+import { v4 as uuidv4 } from 'uuid';
+import { cookieToString } from '../../../common/cookies';
import { fuzzyMatch } from '../../../common/misc';
import type { Cookie, CookieJar } from '../../../models/cookie-jar';
import { useNunjucks } from '../../context/nunjucks/use-nunjucks';
import type { WorkspaceLoaderData } from '../../routes/workspace';
-import { Modal, type ModalHandle, type ModalProps } from '../base/modal';
-import { ModalBody } from '../base/modal-body';
-import { ModalFooter } from '../base/modal-footer';
-import { ModalHeader } from '../base/modal-header';
-import { CookieList } from '../cookie-list';
+import { PromptButton } from '../base/prompt-button';
+import { OneLineEditor } from '../codemirror/one-line-editor';
+import { Icon } from '../icon';
+import { RenderedText } from '../rendered-text';
-export const CookiesModal = ({ onHide }: ModalProps) => {
- const modalRef = useRef(null);
+// Use tough-cookie MAX_DATE value
+// https://github.com/salesforce/tough-cookie/blob/5ae97c6a28122f3fb309adcd8428274d9b2bd795/lib/cookie.js#L77
+const MAX_TIME = 2147483647000;
+const ItemsPerPage = 5;
+const DefaultCookie: Cookie = {
+ id: uuidv4(),
+ key: 'foo',
+ value: 'bar',
+ domain: 'domain.com',
+ expires: MAX_TIME as unknown as Date,
+ path: '/',
+ secure: false,
+ httpOnly: false,
+};
+
+export function chunkArray(array: T[], chunkSize: number = ItemsPerPage): T[][] {
+ const chunks: T[][] = [];
+ for (let i = 0; i < array.length; i += chunkSize) {
+ chunks.push(array.slice(i, i + chunkSize));
+ }
+ return chunks;
+}
+
+interface Props {
+ setIsOpen: (isOpen: boolean) => void;
+}
+
+export const CookiesModal = ({ setIsOpen }: Props) => {
const { handleRender } = useNunjucks();
- const [filter, setFilter] = useState('');
- const [visibleCookieIndexes, setVisibleCookieIndexes] = useState(null);
- const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
- const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
- const updateCookieJarFetcher = useFetcher();
- useEffect(() => {
- modalRef.current?.show();
- }, []);
- const updateCookieJar = async (cookieJarId: string, patch: CookieJar) => {
+ const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
+ const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
+ const updateCookieJarFetcher = useFetcher();
+
+ const [page, setPage] = useState(0);
+ const [filter, setFilter] = useState('');
+ const [filteredCookies, setFilteredCookies] = useState(chunkArray(activeCookieJar?.cookies || []));
+
+ const updateCookieJar = (cookieJarId: string, patch: CookieJar) => {
updateCookieJarFetcher.submit(JSON.stringify({ patch, cookieJarId }), {
encType: 'application/json',
method: 'post',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/cookieJar/update`,
});
+
+ setFilteredCookies(chunkArray(patch.cookies));
};
- const filteredCookies = visibleCookieIndexes ? (activeCookieJar?.cookies || []).filter((_, i) => visibleCookieIndexes.includes(i)) : (activeCookieJar?.cookies || []);
+
+ const handleFilterChange = async (value: string) => {
+ setFilter(value);
+ const renderedCookies: Cookie[] = [];
+
+ for (const cookie of (activeCookieJar?.cookies || [])) {
+ try {
+ renderedCookies.push(await handleRender(cookie));
+ } catch (err) {
+ renderedCookies.push(cookie);
+ }
+ }
+
+ if (!value) {
+ setFilteredCookies(chunkArray(renderedCookies));
+ return;
+ }
+
+ const filteredCookies: Cookie[] = [];
+
+ renderedCookies.forEach(cookie => {
+ if (fuzzyMatch(value, JSON.stringify(cookie), { splitSpace: true })) {
+ filteredCookies.push(cookie);
+ }
+ });
+
+ setFilteredCookies(chunkArray(filteredCookies));
+ };
+
+ const handleCookieDelete = (cookieId: string) => {
+ const updatedActiveCookieJar = activeCookieJar;
+ updatedActiveCookieJar.cookies = activeCookieJar.cookies.filter(c => c.id !== cookieId);
+ updateCookieJar(activeCookieJar._id, updatedActiveCookieJar);
+ };
+
+ const handleDeleteAll = () => {
+ const updatedActiveCookieJar = activeCookieJar;
+ updatedActiveCookieJar.cookies = [];
+
+ updateCookieJar(activeCookieJar._id, updatedActiveCookieJar);
+ };
+
+ const handleAddCookie = () => {
+ const updatedActiveCookieJar = activeCookieJar;
+ updatedActiveCookieJar.cookies = [DefaultCookie, ...activeCookieJar.cookies];
+
+ updateCookieJar(activeCookieJar._id, updatedActiveCookieJar);
+ };
+
+ const handleCookieUpdate = (cookie: Cookie) => {
+ const newCookie = clone(cookie);
+
+ // transform to Date object or fallback to null
+ let dateFormat = null;
+
+ if (newCookie.expires && isValid(new Date(newCookie.expires))) {
+ dateFormat = new Date(newCookie.expires);
+ }
+ newCookie.expires = dateFormat;
+
+ // Clone so we don't modify the original
+ const cookieJar = clone(activeCookieJar);
+ const index = activeCookieJar.cookies.findIndex(c => c.id === cookie.id);
+
+ if (index < 0) {
+ console.warn(`Could not find cookie with id=${cookie.id} to edit`);
+ return;
+ }
+
+ cookieJar.cookies = [...cookieJar.cookies.slice(0, index), newCookie, ...cookieJar.cookies.slice(index + 1)];
+ updateCookieJar(cookieJar._id, cookieJar);
+ };
+
return (
-
-
- Manage Cookies
-
- {activeCookieJar && (
-
-
-
-
+
+
+
+ >
)}
-
-
-
- * cookies are automatically sent with relevant requests
-
-
-
+
-
+
);
};
+
+export interface CookieListProps {
+ cookies: Cookie[];
+ onCookieDelete: (cookieId: string) => void;
+ onUpdateCookie: (cookie: Cookie) => void;
+}
+
+const CookieList = ({ cookies, onCookieDelete, onUpdateCookie }: CookieListProps) => {
+ const [cookieToEdit, setCookieToEdit] = useState
(null);
+
+ return (
+ <>
+
+ {cookies.map((cookie, index) => {
+ const cookieJSON = ToughCookie.fromJSON(cookie);
+ const cookieString = cookieJSON ? cookieToString(cookieJSON) : '';
+
+ if (cookie.expires && !isValid(new Date(cookie.expires))) {
+ cookie.expires = null;
+ }
+
+ return (
+
+ {cookie.domain || ''}
+ {cookieString || ''}
+
+
+
onCookieDelete(cookie.id)}
+ title="Delete cookie"
+ >
+
+
+
+
+ );
+ })}
+
+ {cookieToEdit && setCookieToEdit(null)}
+ onUpdateCookie={onUpdateCookie}
+ />}
+ >
+ );
+};
+
+interface PaginationBarProps {
+ isPrevDisabled?: boolean;
+ isNextDisabled?: boolean;
+ isHidden?: boolean;
+ page: number;
+ totalPages: number;
+ onPrevPress?: () => void;
+ onNextPress?: () => void;
+};
+
+const PaginationBar = ({ isNextDisabled, isPrevDisabled, isHidden, page, totalPages, onPrevPress, onNextPress }: PaginationBarProps) => {
+ if (isHidden) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
{page}
+
of
+
{totalPages}
+
+
+
+
+ );
+};
+
+interface CookieModifyModalProps {
+ cookie: Cookie;
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ onUpdateCookie: (cookie: Cookie) => void;
+}
+
+const CookieModifyModal = (({ cookie, isOpen, setIsOpen, onUpdateCookie }: CookieModifyModalProps) => {
+ const [editCookie, setEditCookie] = useState(cookie);
+
+ let localDateTime: string;
+ if (editCookie && editCookie.expires && isValid(new Date(editCookie.expires))) {
+ localDateTime = new Date(editCookie.expires).toISOString().slice(0, 16);
+ }
+
+ let rawDefaultValue;
+ if (!editCookie) {
+ rawDefaultValue = '';
+ } else {
+ try {
+ const c = ToughCookie.fromJSON(JSON.stringify(editCookie));
+ rawDefaultValue = c ? cookieToString(c) : '';
+ } catch (err) {
+ console.warn('Failed to parse cookie string', err);
+ rawDefaultValue = '';
+ }
+ }
+
+ return (
+
+
+
+
+
+ );
+});
diff --git a/packages/insomnia/src/ui/components/modals/invite-modal/invite-modal.tsx b/packages/insomnia/src/ui/components/modals/invite-modal/invite-modal.tsx
index 018040493b..b7d660cfa7 100644
--- a/packages/insomnia/src/ui/components/modals/invite-modal/invite-modal.tsx
+++ b/packages/insomnia/src/ui/components/modals/invite-modal/invite-modal.tsx
@@ -433,8 +433,6 @@ const MemberListItem: FC<{
);
};
-export const defaultPerPage = 10;
-
interface PaginationBarProps {
isPrevDisabled?: boolean;
isNextDisabled?: boolean;
diff --git a/packages/insomnia/src/ui/components/viewers/response-cookies-viewer.tsx b/packages/insomnia/src/ui/components/viewers/response-cookies-viewer.tsx
index bdda7bbd82..8e957d8937 100644
--- a/packages/insomnia/src/ui/components/viewers/response-cookies-viewer.tsx
+++ b/packages/insomnia/src/ui/components/viewers/response-cookies-viewer.tsx
@@ -65,9 +65,7 @@ export const ResponseCookiesViewer: FC = props => {
{isCookieModalOpen && (
- setIsCookieModalOpen(false)}
- />
+
)}
;
};
diff --git a/packages/insomnia/src/ui/css/main.css b/packages/insomnia/src/ui/css/main.css
index 8df2e47d43..3f6f1b8782 100644
--- a/packages/insomnia/src/ui/css/main.css
+++ b/packages/insomnia/src/ui/css/main.css
@@ -1894,47 +1894,8 @@ html {
.changelog hr {
margin: var(--padding-lg) 0 !important;
}
-.cookie-list {
- height: 100%;
- display: grid;
- grid-template-rows: auto minmax(0, 1fr);
-}
-.cookie-list table input:not([type='checkbox']) {
- padding: var(--padding-xs) var(--padding-xxs);
- width: 100%;
- background: none;
-}
-.cookie-list table .btn {
- cursor: pointer;
- margin: 0;
- padding: 0 var(--padding-sm);
- height: var(--line-height-xs);
-}
-.cookie-list .cookie-list__list {
- height: 100%;
- padding: 0 var(--padding-md) var(--padding-md) var(--padding-md);
- position: relative;
- overflow-y: auto;
-}
-.cookie-modify.modal__body {
- overflow: visible;
- display: grid;
- grid-template-rows: auto minmax(0, 1fr);
-}
-.cookie-modify.modal__body table input:not([type='checkbox']) {
- padding: var(--padding-xs) var(--padding-xxs);
- width: 100%;
- background: none;
-}
-.cookie-modify.modal__body table td {
- cursor: pointer;
- vertical-align: middle;
-}
-.cookie-modify.modal__body table .btn {
- cursor: pointer;
- margin: 0;
- padding: 0 var(--padding-sm);
- height: var(--line-height-xs);
+.calendar-invert::-webkit-calendar-picker-indicator {
+ filter: invert(1);
}
.graphql-editor {
position: relative;
diff --git a/packages/insomnia/src/ui/routes/debug.tsx b/packages/insomnia/src/ui/routes/debug.tsx
index 0fe85d69f8..9833187029 100644
--- a/packages/insomnia/src/ui/routes/debug.tsx
+++ b/packages/insomnia/src/ui/routes/debug.tsx
@@ -1129,7 +1129,7 @@ export const Debug: FC = () => {
/>
)}
{isCookieModalOpen && (
-
setIsCookieModalOpen(false)} />
+
)}
{isCertificatesModalOpen && (
setCertificatesModalOpen(false)} />
diff --git a/packages/insomnia/src/ui/routes/design.tsx b/packages/insomnia/src/ui/routes/design.tsx
index 3b6e6be00f..9ecad1cc1a 100644
--- a/packages/insomnia/src/ui/routes/design.tsx
+++ b/packages/insomnia/src/ui/routes/design.tsx
@@ -991,7 +991,7 @@ const Design: FC = () => {
/>
)}
{isCookieModalOpen && (
- setIsCookieModalOpen(false)} />
+
)}
{isCertificatesModalOpen && (
setCertificatesModalOpen(false)} />
diff --git a/packages/insomnia/src/ui/routes/unit-test.tsx b/packages/insomnia/src/ui/routes/unit-test.tsx
index 59d79eb25b..5eb2369bc1 100644
--- a/packages/insomnia/src/ui/routes/unit-test.tsx
+++ b/packages/insomnia/src/ui/routes/unit-test.tsx
@@ -464,7 +464,7 @@ const TestRoute: FC = () => {
/>
)}
{isCookieModalOpen && (
- setIsCookieModalOpen(false)} />
+
)}
{isCertificatesModalOpen && (
setCertificatesModalOpen(false)} />