mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 22:57:59 -04:00
fix: Manage Cookie Modal [INS-5007] (#8385)
* refactor: remove CookieList component * refactor: remove unused defaultPerPage export from Invite modal * refactor: remove unused styles for CookieList component * refactor: clean up unused code in the cookie modal * refactor: update CookiesModal props for better state management * refactor: update CookiesModal props for improved state handling * refactor: streamline state management in CookiesModal component * refactor: update cookie editor test selectors for improved stability * refactor: fix cookie editor test selector for accurate interaction * refactor: update cookie editor test to use getByText for improved selector accuracy * refactor: enhance cookie editor test to assert visibility of cookie entries * refactor: update cookie editor test to assert visibility of specific cookie entries * refactor: remove isOpen prop from CookiesModal for simplified usage
This commit is contained in:
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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 <tr className="selectable" key={cookie.id}>
|
||||
<td>
|
||||
<RenderedText>{cookie.domain || ''}</RenderedText>
|
||||
</td>
|
||||
<td className="force-wrap wide">
|
||||
<RenderedText>{cookieString || ''}</RenderedText>
|
||||
</td>
|
||||
<td onClick={() => { }} className="text-right no-wrap">
|
||||
<button
|
||||
className="btn btn--super-compact btn--outlined"
|
||||
onClick={() => setIsCookieModalOpen(true)}
|
||||
title="Edit cookie properties"
|
||||
>
|
||||
Edit
|
||||
</button>{' '}
|
||||
<PromptButton
|
||||
className="btn btn--super-compact btn--outlined"
|
||||
confirmMessage=""
|
||||
onClick={() => deleteCookie(cookie)}
|
||||
title="Delete cookie"
|
||||
>
|
||||
<i className="fa fa-trash-o" />
|
||||
</PromptButton>
|
||||
{isCookieModalOpen && (
|
||||
<CookieModifyModal
|
||||
cookie={cookie}
|
||||
onHide={() => setIsCookieModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>;
|
||||
|
||||
};
|
||||
|
||||
export const CookieList: FC<CookieListProps> = ({
|
||||
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 <div>
|
||||
<table className="table--fancy cookie-table table--striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
style={{
|
||||
minWidth: '10rem',
|
||||
}}
|
||||
>
|
||||
Domain
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
Cookie
|
||||
</th>
|
||||
<th
|
||||
style={{
|
||||
width: '2rem',
|
||||
}}
|
||||
className="text-right"
|
||||
>
|
||||
<Dropdown
|
||||
aria-label='Cookie Actions Dropdown'
|
||||
triggerButton={
|
||||
<Button
|
||||
className="btn btn--super-super-compact btn--outlined txt-md"
|
||||
>
|
||||
Actions <i className="fa fa-caret-down" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<DropdownItem aria-label='Add Cookie'>
|
||||
<ItemContent
|
||||
icon="plus-circle"
|
||||
label="Add Cookie"
|
||||
onClick={addCookie}
|
||||
/>
|
||||
</DropdownItem>
|
||||
<DropdownItem aria-label='Delete All'>
|
||||
<ItemContent
|
||||
icon="trash-o"
|
||||
label="Delete All"
|
||||
withPrompt
|
||||
onClick={handleDeleteAll}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody key={cookies.length}>
|
||||
{cookies.map(cookie => (
|
||||
<CookieRow
|
||||
cookie={cookie}
|
||||
key={cookie.id}
|
||||
deleteCookie={handleCookieDelete}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{cookies.length === 0 && <div className="pad faint italic text-center">
|
||||
<p>I couldn't find any cookies for you.</p>
|
||||
<p className='pt-4'>
|
||||
<Button className="border border-solid border-[--hl-lg] px-[--padding-md] h-[--line-height-xs] rounded-[--radius-md] hover:bg-[--hl-xs]" onPress={addCookie}>
|
||||
<Icon icon="plus" /> Add Cookie
|
||||
</Button>
|
||||
</p>
|
||||
</div>}
|
||||
</div>;
|
||||
};
|
||||
@@ -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<ModalHandle>(null);
|
||||
const [cookie, setCookie] = useState<Cookie | null>(props.cookie);
|
||||
const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
|
||||
const updateCookieJarFetcher = useFetcher<CookieJar>();
|
||||
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 (
|
||||
<OverlayContainer>
|
||||
<Modal ref={modalRef} onHide={props.onHide}>
|
||||
<ModalHeader>Edit Cookie</ModalHeader>
|
||||
<ModalBody className="cookie-modify">
|
||||
{activeCookieJar && cookie && (
|
||||
<Tabs aria-label='Cookie modify tabs' className="flex-1 w-full h-full flex flex-col">
|
||||
<TabList className='w-full flex-shrink-0 overflow-x-auto border-solid scro border-b border-b-[--hl-md] bg-[--color-bg] flex items-center h-[--line-height-sm]' aria-label='Request pane tabs'>
|
||||
<Tab
|
||||
className='flex-shrink-0 h-full flex items-center justify-between cursor-pointer gap-2 outline-none select-none px-3 py-1 text-[--hl] aria-selected:text-[--color-font] hover:bg-[--hl-sm] hover:text-[--color-font] aria-selected:bg-[--hl-xs] aria-selected:focus:bg-[--hl-sm] aria-selected:hover:bg-[--hl-sm] focus:bg-[--hl-sm] transition-colors duration-300'
|
||||
id='friendly'
|
||||
>
|
||||
Friendly
|
||||
</Tab>
|
||||
<Tab
|
||||
className='flex-shrink-0 h-full flex items-center justify-between cursor-pointer gap-2 outline-none select-none px-3 py-1 text-[--hl] aria-selected:text-[--color-font] hover:bg-[--hl-sm] hover:text-[--color-font] aria-selected:bg-[--hl-xs] aria-selected:focus:bg-[--hl-sm] aria-selected:hover:bg-[--hl-sm] focus:bg-[--hl-sm] transition-colors duration-300'
|
||||
id='raw'
|
||||
>
|
||||
Raw
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className='w-full flex-1 flex flex-col overflow-y-auto' id='friendly'>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieKey">
|
||||
Key
|
||||
<OneLineEditor
|
||||
id="cookie-key"
|
||||
defaultValue={(cookie && cookie.key || '').toString()}
|
||||
onChange={value => handleCookieUpdate(Object.assign({}, cookie, { key: value.trim() }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieValue">
|
||||
Value
|
||||
<OneLineEditor
|
||||
id="cookie-value"
|
||||
defaultValue={(cookie && cookie.value || '').toString()}
|
||||
onChange={value => handleCookieUpdate(Object.assign({}, cookie, { value: value.trim() }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieDomain">
|
||||
Domain
|
||||
<OneLineEditor
|
||||
id="cookie-domain"
|
||||
defaultValue={(cookie && cookie.domain || '').toString()}
|
||||
onChange={value => handleCookieUpdate(Object.assign({}, cookie, { domain: value.trim() }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookiePath">
|
||||
Path
|
||||
<OneLineEditor
|
||||
id="cookie-path"
|
||||
defaultValue={(cookie && cookie.path || '').toString()}
|
||||
onChange={value => handleCookieUpdate(Object.assign({}, cookie, { path: value.trim() }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieExpires">
|
||||
Expires
|
||||
<input type="datetime-local" defaultValue={localDateTime} onChange={event => handleCookieUpdate(Object.assign({}, cookie, { expires: event.target.value }))} />
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 w-full">
|
||||
<label>
|
||||
Secure
|
||||
<input
|
||||
className="space-left"
|
||||
type="checkbox"
|
||||
name="secure"
|
||||
defaultChecked={cookie.secure || false}
|
||||
onChange={event => handleCookieUpdate(Object.assign({}, cookie, { secure: event.target.checked }))}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
httpOnly
|
||||
<input
|
||||
className="space-left"
|
||||
type="checkbox"
|
||||
name="httpOnly"
|
||||
defaultChecked={cookie.httpOnly || false}
|
||||
onChange={event => handleCookieUpdate(Object.assign({}, cookie, { httpOnly: event.target.checked }))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel className='w-full flex-1 flex flex-col overflow-y-auto' id='raw'>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Raw Cookie String
|
||||
<input
|
||||
type="text"
|
||||
onChange={event => {
|
||||
try {
|
||||
// NOTE: Perform toJSON so we have a plain JS object instead of Cookie instance
|
||||
const parsed = ToughCookie.parse(event.target.value, { loose: true })?.toJSON();
|
||||
if (parsed) {
|
||||
// Make sure cookie has an id
|
||||
parsed.id = cookie.id;
|
||||
handleCookieUpdate(parsed);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Failed to parse cookie string "${event.target.value}"`, err);
|
||||
return;
|
||||
}
|
||||
}}
|
||||
defaultValue={rawDefaultValue}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</OverlayContainer>
|
||||
);
|
||||
});
|
||||
@@ -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<ModalHandle>(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<T>(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<string>('');
|
||||
const [visibleCookieIndexes, setVisibleCookieIndexes] = useState<number[] | null>(null);
|
||||
const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
|
||||
const updateCookieJarFetcher = useFetcher<CookieJar>();
|
||||
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<CookieJar>();
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [filter, setFilter] = useState<string>('');
|
||||
const [filteredCookies, setFilteredCookies] = useState<Cookie[][]>(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 (
|
||||
<OverlayContainer>
|
||||
<Modal ref={modalRef} wide tall onHide={onHide}>
|
||||
<ModalHeader>Manage Cookies</ModalHeader>
|
||||
<ModalBody noScroll>
|
||||
{activeCookieJar && (
|
||||
<div className="cookie-list">
|
||||
<div className="pad">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Filter Cookies
|
||||
<input
|
||||
onChange={async event => {
|
||||
setFilter(event.target.value);
|
||||
const renderedCookies: Cookie[] = [];
|
||||
for (const cookie of (activeCookieJar?.cookies || [])) {
|
||||
try {
|
||||
renderedCookies.push(await handleRender(cookie));
|
||||
} catch (err) {
|
||||
renderedCookies.push(cookie);
|
||||
}
|
||||
}
|
||||
if (!filter) {
|
||||
setVisibleCookieIndexes(null);
|
||||
}
|
||||
const visibleCookieIndexes: number[] = [];
|
||||
renderedCookies.forEach((cookie, i) => {
|
||||
if (fuzzyMatch(filter, JSON.stringify(cookie), { splitSpace: true })) {
|
||||
visibleCookieIndexes.push(i);
|
||||
}
|
||||
});
|
||||
setVisibleCookieIndexes(visibleCookieIndexes);
|
||||
}}
|
||||
type="text"
|
||||
placeholder="insomnia.rest"
|
||||
defaultValue=""
|
||||
/>
|
||||
</label>
|
||||
<ModalOverlay
|
||||
isDismissable={true}
|
||||
isOpen={true}
|
||||
onOpenChange={setIsOpen}
|
||||
className="w-full h-[--visual-viewport-height] fixed z-10 top-0 left-0 flex items-center justify-center bg-[--color-bg] theme--transparent-overlay"
|
||||
>
|
||||
<Modal className="fixed top-[100px] w-full max-w-[900px] rounded-md border border-solid border-[--hl-sm] p-[32px] h-fit bg-[--color-bg] text-[--color-font] theme--dialog">
|
||||
<Dialog className="outline-none relative">
|
||||
{({ close }) => (
|
||||
<>
|
||||
{activeCookieJar && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Heading slot="title" className="text-[22px] leading-[34px] mb-[14px]">
|
||||
Manage Cookies
|
||||
</Heading>
|
||||
<Button onPress={close} className="fa fa-times absolute top-0 right-0 text-xl" />
|
||||
|
||||
<div className='flex gap-4 justify-between'>
|
||||
<Group
|
||||
className="w-[50%] bg-[--hl-xs] py-[4px] px-[8px] rounded flex items-center gap-2"
|
||||
>
|
||||
<i className="fa fa-search" />
|
||||
<TextField
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
aria-label="Cookie search query"
|
||||
className="flex-1"
|
||||
>
|
||||
<Input
|
||||
className="w-full"
|
||||
placeholder="Search cookies"
|
||||
/>
|
||||
</TextField>
|
||||
{filter && (
|
||||
<Button onPress={() => handleFilterChange('')}>
|
||||
<Icon icon="circle-xmark" className='h-4 w-4' />
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
<div className="flex gap-4 items-end">
|
||||
<Button
|
||||
className="flex items-center gap-2 min-w-[75px] py-1 px-2 font-semibold aria-pressed:bg-[--hl-sm] text-[--color-font] transition-all text-sm"
|
||||
onPress={handleAddCookie}
|
||||
>
|
||||
<Icon icon="plus" /> Add Cookie
|
||||
</Button>
|
||||
<PromptButton
|
||||
className="flex items-center gap-2 min-w-[85px] py-1 px-2 font-semibold aria-pressed:bg-[--hl-sm] text-[--color-font] transition-all text-sm"
|
||||
confirmMessage='Confirm'
|
||||
onClick={handleDeleteAll}
|
||||
>
|
||||
<Icon icon="trash" /> Delete All
|
||||
</PromptButton>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="border my-[14px]" />
|
||||
{filteredCookies.length === 0 ?
|
||||
(<div className='flex items-center justify-center h-[200px]'>
|
||||
<p className="text-[12px] text-[--color-font]">{filter ? `No cookies match your search: "${filter}"` : 'No cookies found.'}</p>
|
||||
</div>)
|
||||
: (
|
||||
<>
|
||||
<CookieList
|
||||
cookies={filteredCookies[page] || []}
|
||||
onCookieDelete={handleCookieDelete}
|
||||
onUpdateCookie={handleCookieUpdate}
|
||||
/>
|
||||
<PaginationBar
|
||||
isPrevDisabled={page === 0}
|
||||
isNextDisabled={filteredCookies.length === 1 || page === filteredCookies.length - 1}
|
||||
isHidden={filteredCookies.length === 1}
|
||||
page={page + 1}
|
||||
totalPages={filteredCookies.length}
|
||||
onPrevPress={() => {
|
||||
setPage(page - 1);
|
||||
}}
|
||||
onNextPress={() => {
|
||||
setPage(page + 1);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center justify-between gap-3 mt-[2rem]'>
|
||||
<div className="italic text-[12px]">
|
||||
* cookies are automatically sent with relevant requests
|
||||
</div>
|
||||
<Button
|
||||
className="text-[--color-font-surprise] font-semibold border border-solid border-[--hl-md] bg-opacity-100 bg-[rgba(var(--color-surprise-rgb),var(--tw-bg-opacity))] px-4 py-2 h-full flex items-center justify-center gap-2 aria-pressed:opacity-80 rounded-md hover:bg-opacity-80 focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
|
||||
onPress={close}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
<div className="cookie-list__list border-tops pad">
|
||||
<CookieList
|
||||
cookies={filteredCookies}
|
||||
handleDeleteAll={() => {
|
||||
const updated = activeCookieJar;
|
||||
updated.cookies = [];
|
||||
updateCookieJar(activeCookieJar._id, updated);
|
||||
}}
|
||||
handleCookieAdd={cookie => {
|
||||
const updated = activeCookieJar;
|
||||
updated.cookies = [cookie, ...activeCookieJar.cookies];
|
||||
updateCookieJar(activeCookieJar._id, updated);
|
||||
}}
|
||||
handleCookieDelete={cookie => {
|
||||
const updated = activeCookieJar;
|
||||
updated.cookies = activeCookieJar.cookies.filter(c => c.id !== cookie.id);
|
||||
updateCookieJar(activeCookieJar._id, updated);
|
||||
}}
|
||||
// Set the domain to the filter so that it shows up if we're filtering
|
||||
newCookieDomainName={filter || 'domain.com'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left faint italic txt-sm">
|
||||
* cookies are automatically sent with relevant requests
|
||||
</div>
|
||||
<button className="btn" onClick={() => modalRef.current?.hide()}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</OverlayContainer>
|
||||
</ModalOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export interface CookieListProps {
|
||||
cookies: Cookie[];
|
||||
onCookieDelete: (cookieId: string) => void;
|
||||
onUpdateCookie: (cookie: Cookie) => void;
|
||||
}
|
||||
|
||||
const CookieList = ({ cookies, onCookieDelete, onUpdateCookie }: CookieListProps) => {
|
||||
const [cookieToEdit, setCookieToEdit] = useState<Cookie | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListBox
|
||||
aria-label="Cookies list"
|
||||
className="flex flex-col w-full min-h-[200px]"
|
||||
>
|
||||
{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 (
|
||||
<ListBoxItem
|
||||
key={cookie.id}
|
||||
id={cookie.id}
|
||||
data-testid={`cookie-test-iteration-${index}`}
|
||||
textValue={cookie.domain}
|
||||
className='flex justify-between outline-none gap-2 leading-[36px] odd:bg-[--hl-xs] px-2 py-1 rounded-sm min-h-[40px]'
|
||||
>
|
||||
<span className='flex items-center leading-relaxed min-w-[20%]'><RenderedText>{cookie.domain || ''}</RenderedText></span>
|
||||
<span className='flex items-center leading-relaxed w-[70%]'><RenderedText>{cookieString || ''}</RenderedText></span>
|
||||
<div className='flex gap-1 min-w-[10%] items-center justify-end'>
|
||||
<Button
|
||||
className="flex items-center gap-2 min-w-[35px] py-1 px-2 justify-center font-semibold aria-pressed:bg-[--hl-sm] text-[--color-font] transition-all text-sm"
|
||||
onPress={() => setCookieToEdit(cookie)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<PromptButton
|
||||
className="flex items-center gap-2 min-w-[15px] py-1 px-2 font-semibold aria-pressed:bg-[--hl-sm] text-[--color-font] transition-all text-sm"
|
||||
confirmMessage=""
|
||||
doneMessage=''
|
||||
onClick={() => onCookieDelete(cookie.id)}
|
||||
title="Delete cookie"
|
||||
>
|
||||
<i className="fa fa-trash-o" />
|
||||
</PromptButton>
|
||||
</div>
|
||||
</ListBoxItem>
|
||||
);
|
||||
})}
|
||||
</ListBox>
|
||||
{cookieToEdit && <CookieModifyModal
|
||||
isOpen={cookieToEdit !== null}
|
||||
cookie={cookieToEdit as Cookie}
|
||||
setIsOpen={() => 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 (
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex h-[50px] w-full flex-shrink-0 items-center justify-between">
|
||||
<Button
|
||||
isDisabled={isPrevDisabled}
|
||||
aria-label="previous page"
|
||||
className="flex h-[25px] items-center justify-center gap-[5px] p-1"
|
||||
onPress={onPrevPress}
|
||||
>
|
||||
<Icon icon="arrow-left" className="h-[12px] w-[12px] text text-[--color-font] disabled:text-[#00000080]" />
|
||||
<p className="m-0 text-[12px] font-normal capitalize leading-[15px] text-[--color-font] disabled:text-[#00000080]">Previous</p>
|
||||
</Button>
|
||||
<div className="flex gap-2 items-center">
|
||||
<p className="m-0 text-[10px] font-normal leading-[15px] text-[--color-font] disabled:text-[#00000080]">{page}</p>
|
||||
<p className="m-0 text-[10px] font-normal leading-[15px] text-[--color-font] disabled:text-[#00000080]">of</p>
|
||||
<p className="m-0 text-[10px] font-normal leading-[15px] text-[--color-font] disabled:text-[#00000080]">{totalPages}</p>
|
||||
</div>
|
||||
<Button
|
||||
isDisabled={isNextDisabled}
|
||||
aria-label="next page"
|
||||
className="flex h-[25px] items-center justify-center gap-[5px] p-1"
|
||||
onPress={onNextPress}
|
||||
>
|
||||
<p className="m-0 text-[12px] font-normal capitalize leading-[15px] text-[--color-font] disabled:text-[#00000080]">Next</p>
|
||||
<Icon icon="arrow-right" className="h-[12px] w-[12px] text-[--color-font] disabled:text-[#00000080]" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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>(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 (
|
||||
<ModalOverlay
|
||||
isDismissable={true}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
className="w-full h-[--visual-viewport-height] fixed z-10 top-0 left-0 flex items-center justify-center bg-[--color-bg] theme--transparent-overlay"
|
||||
>
|
||||
<Modal className="fixed top-[100px] w-full max-w-[900px] rounded-md border border-solid border-[--hl-sm] p-[32px] h-fit bg-[--color-bg] text-[--color-font] theme--dialog">
|
||||
<Dialog className="outline-none relative">
|
||||
{({ close }) => (
|
||||
<>
|
||||
{editCookie && (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Heading slot="title" className="text-[22px] leading-[34px] mb-[14px]">
|
||||
Manage Cookies
|
||||
</Heading>
|
||||
<Button onPress={close} className="fa fa-times absolute top-0 right-0 text-xl" />
|
||||
|
||||
<Tabs aria-label='Cookie modify tabs' className="flex-1 w-full h-full flex flex-col">
|
||||
<TabList className='w-full flex-shrink-0 overflow-x-auto border-solid scro border-b border-b-[--hl-md] bg-[--color-bg] flex items-center h-[--line-height-sm]' aria-label='Request pane tabs'>
|
||||
<Tab
|
||||
className='flex-shrink-0 h-full flex items-center justify-between cursor-pointer gap-2 outline-none select-none px-3 py-1 text-[--hl] aria-selected:text-[--color-font] hover:bg-[--hl-sm] hover:text-[--color-font] aria-selected:bg-[--hl-xs] aria-selected:focus:bg-[--hl-sm] aria-selected:hover:bg-[--hl-sm] focus:bg-[--hl-sm] transition-colors duration-300'
|
||||
id='friendly'
|
||||
>
|
||||
Friendly
|
||||
</Tab>
|
||||
<Tab
|
||||
className='flex-shrink-0 h-full flex items-center justify-between cursor-pointer gap-2 outline-none select-none px-3 py-1 text-[--hl] aria-selected:text-[--color-font] hover:bg-[--hl-sm] hover:text-[--color-font] aria-selected:bg-[--hl-xs] aria-selected:focus:bg-[--hl-sm] aria-selected:hover:bg-[--hl-sm] focus:bg-[--hl-sm] transition-colors duration-300'
|
||||
id='raw'
|
||||
>
|
||||
Raw
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className='w-full flex-1 flex flex-col overflow-y-auto pt-3' id='friendly'>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieKey">
|
||||
Key
|
||||
<OneLineEditor
|
||||
id="cookie-key"
|
||||
defaultValue={(editCookie && editCookie.key || '').toString()}
|
||||
onChange={value => setEditCookie({ ...editCookie, key: value.trim() })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieValue">
|
||||
Value
|
||||
<OneLineEditor
|
||||
id="cookie-value"
|
||||
defaultValue={(editCookie && editCookie.value || '').toString()}
|
||||
onChange={value => setEditCookie({ ...editCookie, value: value.trim() })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieDomain">
|
||||
Domain
|
||||
<OneLineEditor
|
||||
id="cookie-domain"
|
||||
defaultValue={(editCookie && editCookie.domain || '').toString()}
|
||||
onChange={value => setEditCookie({ ...editCookie, domain: value.trim() })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookiePath">
|
||||
Path
|
||||
<OneLineEditor
|
||||
id="cookie-path"
|
||||
defaultValue={(editCookie && editCookie.path || '').toString()}
|
||||
onChange={value => setEditCookie({ ...editCookie, path: value.trim() })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label data-testid="CookieExpires">
|
||||
Expires
|
||||
<input
|
||||
type="datetime-local"
|
||||
defaultValue={localDateTime}
|
||||
className='calendar-invert'
|
||||
onChange={event => setEditCookie({ ...editCookie, expires: event.target.value })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 w-full">
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
className="space-left"
|
||||
type="checkbox"
|
||||
name="secure"
|
||||
defaultChecked={editCookie.secure || false}
|
||||
onChange={event => setEditCookie({ ...editCookie, secure: event.target.checked })}
|
||||
/>
|
||||
Secure
|
||||
</label>
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
className="space-left"
|
||||
type="checkbox"
|
||||
name="httpOnly"
|
||||
defaultChecked={editCookie.httpOnly || false}
|
||||
onChange={event => setEditCookie({ ...editCookie, httpOnly: event.target.checked })}
|
||||
/>
|
||||
httpOnly
|
||||
</label>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel className='w-full flex-1 flex flex-col overflow-y-auto pt-3' id='raw'>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
Raw Cookie String
|
||||
<input
|
||||
type="text"
|
||||
onChange={event => {
|
||||
try {
|
||||
// NOTE: Perform toJSON so we have a plain JS object instead of Cookie instance
|
||||
const parsed = ToughCookie.parse(event.target.value, { loose: true })?.toJSON();
|
||||
if (parsed) {
|
||||
// Make sure cookie has an id
|
||||
parsed.id = editCookie.id;
|
||||
setEditCookie(parsed as Cookie);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Failed to parse cookie string "${event.target.value}"`, err);
|
||||
return;
|
||||
}
|
||||
}}
|
||||
defaultValue={rawDefaultValue}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className='flex items-center justify-end mt-[2rem]'>
|
||||
<Button
|
||||
className="text-[--color-font-surprise] font-semibold border border-solid border-[--hl-md] bg-opacity-100 bg-[rgba(var(--color-surprise-rgb),var(--tw-bg-opacity))] px-4 py-2 h-full flex items-center justify-center gap-2 aria-pressed:opacity-80 rounded-md hover:bg-opacity-80 focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
|
||||
onPress={() => {
|
||||
onUpdateCookie(editCookie as Cookie);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalOverlay>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -433,8 +433,6 @@ const MemberListItem: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
export const defaultPerPage = 10;
|
||||
|
||||
interface PaginationBarProps {
|
||||
isPrevDisabled?: boolean;
|
||||
isNextDisabled?: boolean;
|
||||
|
||||
@@ -65,9 +65,7 @@ export const ResponseCookiesViewer: FC<Props> = props => {
|
||||
</button>
|
||||
</p>
|
||||
{isCookieModalOpen && (
|
||||
<CookiesModal
|
||||
onHide={() => setIsCookieModalOpen(false)}
|
||||
/>
|
||||
<CookiesModal setIsOpen={setIsCookieModalOpen} />
|
||||
)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1129,7 +1129,7 @@ export const Debug: FC = () => {
|
||||
/>
|
||||
)}
|
||||
{isCookieModalOpen && (
|
||||
<CookiesModal onHide={() => setIsCookieModalOpen(false)} />
|
||||
<CookiesModal setIsOpen={setIsCookieModalOpen} />
|
||||
)}
|
||||
{isCertificatesModalOpen && (
|
||||
<CertificatesModal onClose={() => setCertificatesModalOpen(false)} />
|
||||
|
||||
@@ -991,7 +991,7 @@ const Design: FC = () => {
|
||||
/>
|
||||
)}
|
||||
{isCookieModalOpen && (
|
||||
<CookiesModal onHide={() => setIsCookieModalOpen(false)} />
|
||||
<CookiesModal setIsOpen={setIsCookieModalOpen} />
|
||||
)}
|
||||
{isCertificatesModalOpen && (
|
||||
<CertificatesModal onClose={() => setCertificatesModalOpen(false)} />
|
||||
|
||||
@@ -464,7 +464,7 @@ const TestRoute: FC = () => {
|
||||
/>
|
||||
)}
|
||||
{isCookieModalOpen && (
|
||||
<CookiesModal onHide={() => setIsCookieModalOpen(false)} />
|
||||
<CookiesModal setIsOpen={setIsCookieModalOpen} />
|
||||
)}
|
||||
{isCertificatesModalOpen && (
|
||||
<CertificatesModal onClose={() => setCertificatesModalOpen(false)} />
|
||||
|
||||
Reference in New Issue
Block a user