Skip to content

Commit 2100f8f

Browse files
committed
feat: send cookies
1 parent 8d279b3 commit 2100f8f

12 files changed

Lines changed: 323 additions & 30 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { attach, createEvent, sample } from 'effector';
2+
3+
import { $requestProfiles, $selectedRequestProfile, profileUpdated } from '#entities/request-profile/model';
4+
import { RequestCookie } from '#entities/request-profile/types';
5+
6+
export const selectedProfileRequestCookieCleared = createEvent<RequestCookie['id']>();
7+
8+
const selectedProfileRequestCookiesClearedFx = attach({
9+
source: { profiles: $requestProfiles, selectedProfile: $selectedRequestProfile },
10+
effect: ({ profiles, selectedProfile }, requestCookieId: RequestCookie['id']) => {
11+
const profile = profiles.find(p => p.id === selectedProfile);
12+
13+
if (!profile) {
14+
throw new Error('Profile not found');
15+
}
16+
17+
return {
18+
...profile,
19+
requestCookies: (profile.requestCookies ?? []).map(requestCookie =>
20+
requestCookie.id === requestCookieId ? { ...requestCookie, value: '' } : requestCookie,
21+
),
22+
};
23+
},
24+
});
25+
26+
sample({ clock: selectedProfileRequestCookieCleared, target: selectedProfileRequestCookiesClearedFx });
27+
sample({ clock: selectedProfileRequestCookiesClearedFx.doneData, target: profileUpdated });
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum COPY_RESULT_STATUS {
2+
Success = 'Copied request cookie to clipboard.',
3+
Error = 'Failed to copy request cookie.',
4+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createEffect, createEvent, sample } from 'effector';
2+
3+
import { notificationAdded } from '#entities/notification/model';
4+
import { RequestCookie } from '#entities/request-profile/types';
5+
import { copyToClipboard } from '#shared/utils/copyToClipboard';
6+
7+
import { COPY_RESULT_STATUS } from './constants';
8+
9+
type SelectedProfileRequestCookieCopied = Pick<RequestCookie, 'name' | 'value'>;
10+
11+
export const selectedProfileRequestCookieCopied = createEvent<SelectedProfileRequestCookieCopied>();
12+
13+
const copySelectedProfileRequestCookieToClipboardFx = createEffect(
14+
({ name, value }: SelectedProfileRequestCookieCopied) => {
15+
const isSupportClipboard = 'clipboard' in navigator;
16+
if (!isSupportClipboard) {
17+
throw new Error('Clipboard API is not supported in your browser');
18+
}
19+
20+
copyToClipboard(`${name}=${value}`);
21+
},
22+
);
23+
24+
sample({ clock: selectedProfileRequestCookieCopied, target: copySelectedProfileRequestCookieToClipboardFx });
25+
26+
sample({
27+
source: copySelectedProfileRequestCookieToClipboardFx.doneData,
28+
fn: () => ({ message: COPY_RESULT_STATUS.Success }),
29+
target: notificationAdded,
30+
});
31+
32+
sample({
33+
source: copySelectedProfileRequestCookieToClipboardFx.failData,
34+
fn: () => ({ message: COPY_RESULT_STATUS.Error }),
35+
target: notificationAdded,
36+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { attach, createEvent, sample } from 'effector';
2+
3+
import { $requestProfiles, $selectedRequestProfile, profileUpdated } from '#entities/request-profile/model';
4+
import { RequestCookie } from '#entities/request-profile/types';
5+
import { generateId } from '#shared/utils/generateId';
6+
7+
export const selectedProfileRequestCookieDuplicated = createEvent<RequestCookie['id']>();
8+
9+
const selectedProfileRequestCookiesDuplicatedFx = attach({
10+
source: { profiles: $requestProfiles, selectedProfile: $selectedRequestProfile },
11+
effect: ({ profiles, selectedProfile }, requestCookieId: RequestCookie['id']) => {
12+
const profile = profiles.find(p => p.id === selectedProfile);
13+
14+
if (!profile) {
15+
throw new Error('Profile not found');
16+
}
17+
18+
const currentRequestCookie = (profile.requestCookies ?? []).find(c => c.id === requestCookieId);
19+
20+
if (!currentRequestCookie) {
21+
throw new Error('Request cookie not found');
22+
}
23+
24+
return {
25+
...profile,
26+
requestCookies: [...(profile.requestCookies ?? []), { ...currentRequestCookie, id: generateId() }],
27+
};
28+
},
29+
});
30+
31+
sample({ clock: selectedProfileRequestCookieDuplicated, target: selectedProfileRequestCookiesDuplicatedFx });
32+
sample({ clock: selectedProfileRequestCookiesDuplicatedFx.doneData, target: profileUpdated });
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum REMOVE_ALL_REQUEST_COOKIES_RESULT_STATUS {
2+
Success = 'All request cookies removed successfully.',
3+
Error = 'Failed to remove request cookies.',
4+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { attach, createEvent, sample } from 'effector';
2+
3+
import { notificationAdded } from '#entities/notification/model';
4+
import { $requestProfiles, $selectedRequestProfile, profileUpdated } from '#entities/request-profile/model';
5+
6+
import { REMOVE_ALL_REQUEST_COOKIES_RESULT_STATUS } from './constants';
7+
8+
export const selectedProfileAllRequestCookiesRemoved = createEvent();
9+
10+
const selectedProfileAllRequestCookiesRemovedFx = attach({
11+
source: { profiles: $requestProfiles, selectedProfile: $selectedRequestProfile },
12+
effect: ({ profiles, selectedProfile }) => {
13+
const profile = profiles.find(p => p.id === selectedProfile);
14+
15+
if (!profile) {
16+
throw new Error('Profile not found');
17+
}
18+
19+
return {
20+
...profile,
21+
requestCookies: [],
22+
};
23+
},
24+
});
25+
26+
sample({ clock: selectedProfileAllRequestCookiesRemoved, target: selectedProfileAllRequestCookiesRemovedFx });
27+
sample({ clock: selectedProfileAllRequestCookiesRemovedFx.doneData, target: profileUpdated });
28+
29+
sample({
30+
source: selectedProfileAllRequestCookiesRemovedFx.doneData,
31+
fn: () => ({ message: REMOVE_ALL_REQUEST_COOKIES_RESULT_STATUS.Success }),
32+
target: notificationAdded,
33+
});
34+
35+
sample({
36+
source: selectedProfileAllRequestCookiesRemovedFx.failData,
37+
fn: () => ({ message: REMOVE_ALL_REQUEST_COOKIES_RESULT_STATUS.Error }),
38+
target: notificationAdded,
39+
});

src/pages/main/components/ProfileActions/CookiesActions/CookiesActions.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ProfileActionsLayout } from '#shared/components';
1212
import { RequestCookies } from '#widgets/request-cookies';
1313

1414
import { AllRequestCookiesCheckbox } from './AllRequestCookiesCheckbox';
15+
import { RemoveAllRequestCookies } from './RemoveAllRequestCookies';
1516
import * as S from './styled';
1617

1718
export function CookiesActions() {
@@ -47,12 +48,15 @@ export function CookiesActions() {
4748
);
4849

4950
const rightHeaderActions = (
50-
<ButtonFunction
51-
disabled={isPaused}
52-
data-test-id='add-request-cookie-button'
53-
icon={<PlusSVG />}
54-
onClick={onAddRequestCookie}
55-
/>
51+
<>
52+
<ButtonFunction
53+
disabled={isPaused}
54+
data-test-id='add-request-cookie-button'
55+
icon={<PlusSVG />}
56+
onClick={onAddRequestCookie}
57+
/>
58+
<RemoveAllRequestCookies />
59+
</>
5660
);
5761

5862
return (
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useUnit } from 'effector-react';
2+
import { useState } from 'react';
3+
4+
import { ButtonFunction } from '@snack-uikit/button';
5+
import { TrashSVG } from '@snack-uikit/icons';
6+
import { Modal } from '@snack-uikit/modal';
7+
import { Typography } from '@snack-uikit/typography';
8+
9+
import { $isPaused } from '#entities/is-paused/model';
10+
import { $selectedProfileRequestCookies } from '#entities/request-profile/model';
11+
import { selectedProfileAllRequestCookiesRemoved } from '#features/selected-profile-request-cookies/remove-all/model';
12+
13+
export function RemoveAllRequestCookies() {
14+
const [isOpen, setIsOpen] = useState(false);
15+
const [isPaused, handleRemoveAllRequestCookies, requestCookies] = useUnit([
16+
$isPaused,
17+
selectedProfileAllRequestCookiesRemoved,
18+
$selectedProfileRequestCookies,
19+
]);
20+
21+
const handleClose = () => {
22+
setIsOpen(false);
23+
};
24+
25+
const handleRemove = () => {
26+
handleRemoveAllRequestCookies();
27+
setIsOpen(false);
28+
};
29+
30+
return (
31+
<>
32+
<ButtonFunction
33+
icon={<TrashSVG />}
34+
disabled={isPaused || requestCookies.length === 0}
35+
onClick={() => setIsOpen(true)}
36+
data-test-id='remove-all-request-cookies-button'
37+
/>
38+
<Modal
39+
open={isOpen}
40+
onClose={handleClose}
41+
title='Remove all request cookies'
42+
content={
43+
<Typography.SansBodyM>
44+
All request cookies will be removed from the list. Do you really want to remove cookies?
45+
</Typography.SansBodyM>
46+
}
47+
approveButton={{ onClick: handleRemove, label: 'Delete', appearance: 'destructive' }}
48+
cancelButton={{ onClick: handleClose, label: 'Cancel' }}
49+
/>
50+
</>
51+
);
52+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useUnit } from 'effector-react';
2+
import { useState } from 'react';
3+
4+
import { ButtonFunction } from '@snack-uikit/button';
5+
import { CopySVG, CrossSVG, KebabSVG } from '@snack-uikit/icons';
6+
7+
import { $isPaused } from '#entities/is-paused/model';
8+
import { RequestCookie } from '#entities/request-profile/types';
9+
import { selectedProfileRequestCookieCleared } from '#features/selected-profile-request-cookies/clear/model';
10+
import { selectedProfileRequestCookieCopied } from '#features/selected-profile-request-cookies/copy/model';
11+
import { selectedProfileRequestCookieDuplicated } from '#features/selected-profile-request-cookies/duplicate/model';
12+
import { DuplicateSVG } from '#shared/assets/svg';
13+
14+
import * as S from './styled';
15+
16+
export function RequestCookieMenu({ id, name, value }: RequestCookie) {
17+
const [handleDuplicate, handleRequestCookieCopy, handleClear, isPaused] = useUnit([
18+
selectedProfileRequestCookieDuplicated,
19+
selectedProfileRequestCookieCopied,
20+
selectedProfileRequestCookieCleared,
21+
$isPaused,
22+
]);
23+
24+
const [isOpen, setIsOpen] = useState(false);
25+
26+
const handleCopy = () => {
27+
handleRequestCookieCopy({ name, value });
28+
setIsOpen(false);
29+
};
30+
31+
return (
32+
<S.StyledDroplist
33+
open={isOpen}
34+
onOpenChange={setIsOpen}
35+
placement='bottom-end'
36+
size='m'
37+
items={[
38+
{
39+
id: 'duplicate-value',
40+
content: { option: 'Duplicate' },
41+
beforeContent: <DuplicateSVG />,
42+
onClick: () => handleDuplicate(id),
43+
},
44+
{
45+
id: 'copy-value',
46+
content: { option: 'Copy' },
47+
beforeContent: <CopySVG />,
48+
onClick: handleCopy,
49+
},
50+
{
51+
id: 'clear-value',
52+
content: { option: 'Clear Value' },
53+
beforeContent: <CrossSVG />,
54+
onClick: () => handleClear(id),
55+
},
56+
]}
57+
>
58+
<ButtonFunction size='s' data-test-id='request-cookie-menu-button' icon={<KebabSVG />} disabled={isPaused} />
59+
</S.StyledDroplist>
60+
);
61+
}

src/widgets/request-cookies/components/RequestCookieRow/RequestCookieRow.tsx

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useUnit } from 'effector-react/effector-react.mjs';
33
import { type KeyboardEvent, useState } from 'react';
44

55
import { ButtonFunction } from '@snack-uikit/button';
6-
import { FieldText } from '@snack-uikit/fields';
76
import { CrossSVG } from '@snack-uikit/icons';
87
import { Checkbox, CheckboxProps } from '@snack-uikit/toggles';
98
import { Tooltip } from '@snack-uikit/tooltip';
@@ -15,6 +14,7 @@ import { selectedProfileRequestCookiesRemoved } from '#features/selected-profile
1514
import { selectedProfileRequestCookiesUpdated } from '#features/selected-profile-request-cookies/update/model';
1615
import { validateCookieName, validateCookieValue } from '#shared/utils/cookies';
1716

17+
import { RequestCookieMenu } from './RequestCookieMenu';
1818
import * as S from './styled';
1919

2020
export function RequestCookieRow(props: RequestCookie) {
@@ -57,15 +57,18 @@ export function RequestCookieRow(props: RequestCookie) {
5757
checked={!disabled}
5858
onChange={handleChecked}
5959
/>
60+
</S.LeftHeaderActions>
6061

62+
<S.CookieFieldWrapper grow={216}>
6163
<Tooltip
6264
open={cookieNameFocused && name.length > 0 && !isNameFormatVerified}
6365
tip='Cookie names may only include ASCII characters without spaces, semicolons, or equals signs'
6466
placement='top'
6567
>
66-
<FieldText
68+
<S.CookieNameField
6769
data-test-id='cookie-name-input'
6870
size='m'
71+
inputMode='text'
6972
value={name}
7073
placeholder='Cookie name'
7174
onChange={handleChange('name')}
@@ -75,29 +78,30 @@ export function RequestCookieRow(props: RequestCookie) {
7578
showClearButton={false}
7679
disabled={isPaused}
7780
validationState={name.length > 0 && !isNameFormatVerified ? 'error' : 'default'}
78-
inputMode='text'
7981
/>
8082
</Tooltip>
81-
</S.LeftHeaderActions>
83+
</S.CookieFieldWrapper>
8284

83-
<Tooltip
84-
tip='Incorrect format for cookie value'
85-
placement='top'
86-
open={value.length > 0 && !isValueFormatVerified}
87-
>
88-
<FieldText
89-
size='m'
90-
value={value}
91-
placeholder='Cookie value'
92-
onChange={handleChange('value')}
93-
onKeyDown={handleKeyPress}
94-
showClearButton={false}
95-
disabled={isPaused}
96-
data-test-id='cookie-value-input'
97-
validationState={value.length > 0 && !isValueFormatVerified ? 'error' : 'default'}
98-
inputMode='text'
99-
/>
100-
</Tooltip>
85+
<S.CookieFieldWrapper grow={205}>
86+
<Tooltip
87+
tip='Incorrect format for cookie value'
88+
placement='top'
89+
open={value.length > 0 && !isValueFormatVerified}
90+
>
91+
<S.CookieValueField
92+
size='m'
93+
inputMode='text'
94+
value={value}
95+
placeholder='Cookie value'
96+
onChange={handleChange('value')}
97+
onKeyDown={handleKeyPress}
98+
showClearButton={false}
99+
disabled={isPaused}
100+
data-test-id='cookie-value-input'
101+
validationState={value.length > 0 && !isValueFormatVerified ? 'error' : 'default'}
102+
/>
103+
</Tooltip>
104+
</S.CookieFieldWrapper>
101105

102106
<ButtonFunction
103107
disabled={isPaused}
@@ -106,6 +110,8 @@ export function RequestCookieRow(props: RequestCookie) {
106110
icon={<CrossSVG />}
107111
onClick={() => onRequestCookiesRemoved([id])}
108112
/>
113+
114+
<RequestCookieMenu {...props} />
109115
</S.Wrapper>
110116
);
111117
}

0 commit comments

Comments
 (0)