Skip to content

Commit 519014a

Browse files
xiaodemenyaoweiprc
andauthored
feat: new pricing features (#9176)
* feat: new pricing feat: invite check fix: fixes based on comments feat: invite_not_permitted event fix: sync user info after trial Popup for upgrade plan when creating a git project change wording fix theme color feat: add segment events feat: report user local git project count fix theme issue feat: INS-1582 new design for upgrade modal Add pop-up for inviting members feat: add the plan indicator feat: new design of top-right corner fix: fix quota fix: fix top-right corner width * fix: remove button * Add upgrade plan banner in invite modal * fix type issue * fix smoke test * Remove console.log * fix: fix quota issue * update useIsLightTheme * fix type * add annotation * move doc link * fix: fix source --------- Co-authored-by: yaoweiprc <[email protected]>
1 parent 9444289 commit 519014a

31 files changed

+1340
-132
lines changed

packages/insomnia-smoke-test/server/insomnia-api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,10 @@ export default (app: Application) => {
635635
app.delete('/v1/desktop/organizations/:organizationId/collaborators/:collaboratorId/unlink', (_req, res) => {
636636
res.json(null);
637637
});
638+
639+
app.post('/v1/organizations/:organizationId/check-seats', (_req, res) => {
640+
res.json({
641+
isAllowed: true,
642+
});
643+
});
638644
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from '../ui/components/icon';
2+
// https://fontawesome.com/search?q=user&ic=free&o=r
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import classnames from 'classnames';
2+
import type React from 'react';
3+
import { Button, Dialog, Heading, Modal as RAModal, ModalOverlay } from 'react-aria-components';
4+
5+
import { Icon } from '~/basic-components/icon';
6+
7+
interface Props {
8+
isOpen: boolean;
9+
onClose?: () => void;
10+
title?: React.ReactNode;
11+
closable?: boolean;
12+
className?: string;
13+
}
14+
15+
export const Modal: React.FC<React.PropsWithChildren<Props>> = ({
16+
isOpen,
17+
onClose,
18+
className,
19+
title,
20+
closable,
21+
children,
22+
}) => {
23+
return (
24+
<ModalOverlay
25+
isOpen={isOpen}
26+
onOpenChange={isOpen => {
27+
!isOpen && onClose?.();
28+
}}
29+
isDismissable
30+
className="fixed left-0 top-0 z-10 flex h-[--visual-viewport-height] w-full items-center justify-center bg-black/30"
31+
>
32+
<RAModal
33+
onOpenChange={isOpen => {
34+
!isOpen && onClose?.();
35+
}}
36+
className={classnames(
37+
'flex flex-col rounded-md border border-solid border-[--hl-sm] bg-[--color-bg] p-[--padding-lg] text-[--color-font]',
38+
className,
39+
)}
40+
>
41+
<Dialog className="flex h-full flex-1 flex-col overflow-hidden outline-none">
42+
{({ close }) => (
43+
<>
44+
<div className="flex flex-1 flex-col gap-4 overflow-hidden">
45+
{' '}
46+
<div className="flex flex-shrink-0 items-center justify-between gap-2">
47+
{title && (
48+
<Heading slot="title" className="text-3xl">
49+
{title}
50+
</Heading>
51+
)}
52+
{closable && (
53+
<Button
54+
className="flex aspect-square h-6 flex-shrink-0 items-center justify-center rounded-sm text-sm text-[--color-font] ring-1 ring-transparent transition-all hover:bg-[--hl-xs] focus:ring-inset focus:ring-[--hl-md] aria-pressed:bg-[--hl-sm]"
55+
onPress={() => close()}
56+
>
57+
<Icon icon="x" />
58+
</Button>
59+
)}
60+
</div>
61+
</div>
62+
{children}
63+
</>
64+
)}
65+
</Dialog>
66+
</RAModal>
67+
</ModalOverlay>
68+
);
69+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import classNames from 'classnames';
2+
3+
interface Props {
4+
percent: number;
5+
className?: string;
6+
status?: 'success' | 'error' | 'normal';
7+
}
8+
9+
export const Progress = ({ className, percent, status }: Props) => {
10+
return (
11+
<div
12+
// FIXME: use css variables for colors
13+
className={classNames(
14+
'h-[10px] flex-grow overflow-hidden rounded-full bg-[#f1e6ff]',
15+
status === 'error' && 'bg-[#db110040]',
16+
status === 'success' && 'bg-[#00bf7340]',
17+
className,
18+
)}
19+
>
20+
<div
21+
className={classNames(
22+
'transition-width h-full rounded-full bg-[--color-surprise] duration-1000 ease-in-out',
23+
status === 'error' && 'bg-[--color-danger]',
24+
status === 'success' && 'bg-[--color-success]',
25+
)}
26+
style={{
27+
width: `${percent}%`,
28+
}}
29+
/>
30+
</div>
31+
);
32+
};

packages/insomnia/src/common/documentation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export const docsIntroductionToInsoCLI = insomniaDocs('/inso-cli/introduction');
1414
export const docsPreRequestScript = insomniaDocs('/insomnia/pre-request-script');
1515
export const docsAfterResponseScript = insomniaDocs('/insomnia/after-response-script');
1616
export const docsMcpClient = insomniaDocs('/insomnia/mcp-clients-in-insomnia');
17+
export const docsPricingLearnMoreLink =
18+
'https://developer.konghq.com/insomnia/storage/#what-are-the-user-and-git-sync-limits-for-the-essentials-plan';
1719

1820
export const docsGitAccessToken = {
1921
github: 'https://docs.github.com/github/authenticating-to-github/creating-a-personal-access-token',

packages/insomnia/src/common/import.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ interface ResourceCacheType {
130130

131131
let resourceCacheList: ResourceCacheType[] = [];
132132

133+
// All models that can be exported should be listed here
133134
export const MODELS_BY_EXPORT_TYPE: Record<AllExportTypes, AllTypes> = {
134135
request: 'Request',
135136
websocket_payload: 'WebSocketPayload',

packages/insomnia/src/models/organization.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export type PersonalPlanType = 'free' | 'individual' | 'team' | 'enterprise' | '
6565
export const formatCurrentPlanType = (type: PersonalPlanType) => {
6666
switch (type) {
6767
case 'free': {
68-
return 'Hobby';
68+
return 'Essentials';
6969
}
7070
case 'individual': {
7171
return 'Individual';
@@ -94,4 +94,6 @@ export interface CurrentPlan {
9494
quantity: number;
9595
type: PersonalPlanType;
9696
planName: string;
97+
status: 'trialing' | 'active';
98+
trialingEnd: string;
9799
}

packages/insomnia/src/models/request-version.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import { isRequest, type Request } from './request';
1010
import { isSocketIORequest, type SocketIORequest } from './socket-io-request';
1111
import { isWebSocketRequest, type WebSocketRequest } from './websocket-request';
1212

13+
/* When viewing a specific request, the user can click the Send button to test-send it.
14+
Each time the user sends the request, the parameters may differ—they might edit the body, headers, and so on—and Insomnia records every sent request as history.
15+
When the user browses the send history for a request and selects one of the entries, the current request is restored to the exact state it had when that request was sent, including the body, headers, and other settings.
16+
A Request Version is essentially a snapshot of the request at the moment it was test-sent. */
17+
1318
export const name = 'Request Version';
1419

1520
export const type = 'RequestVersion';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { href } from 'react-router';
2+
import { v4 as uuidv4 } from 'uuid';
3+
4+
import { userSession } from '~/models';
5+
import { insomniaFetch } from '~/ui/insomniaFetch';
6+
import { createFetcherLoadHook } from '~/utils/router';
7+
8+
import type { Route } from './+types/organization.$organizationId.collaborators-check-seats';
9+
10+
export const needsToUpgrade = 'NEEDS_TO_UPGRADE';
11+
export const needsToIncreaseSeats = 'NEEDS_TO_INCREASE_SEATS';
12+
13+
export interface CheckSeatsResponse {
14+
isAllowed: boolean;
15+
code?: typeof needsToUpgrade | typeof needsToIncreaseSeats;
16+
}
17+
18+
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
19+
const { id: sessionId } = await userSession.get();
20+
21+
const { organizationId } = params;
22+
23+
try {
24+
// Check whether the user can add a new collaborator
25+
// Use a random email to avoid hitting any existing member emails
26+
const checkResponseData = await insomniaFetch<CheckSeatsResponse>({
27+
method: 'POST',
28+
path: `/v1/organizations/${organizationId}/check-seats`,
29+
data: { emails: [`insomnia-mock-check-seats-${uuidv4()}@example.net`] },
30+
sessionId,
31+
onlyResolveOnSuccess: true,
32+
});
33+
return checkResponseData;
34+
} catch {
35+
return { isAllowed: true };
36+
}
37+
}
38+
39+
export const useCollaboratorsCheckSeatsLoaderFetcher = createFetcherLoadHook(
40+
load =>
41+
({ organizationId, query }: { organizationId: string; query?: string }) => {
42+
return load(
43+
`${href(`/organization/:organizationId/collaborators-check-seats`, { organizationId })}?${encodeURIComponent(query || '')}`,
44+
);
45+
},
46+
clientLoader,
47+
);

packages/insomnia/src/routes/organization.$organizationId.project.$projectId.delete.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { href, redirect } from 'react-router';
33
import { database } from '~/common/database';
44
import { projectLock } from '~/common/project';
55
import * as models from '~/models';
6+
import { reportGitProjectCount } from '~/routes/organization.$organizationId.project.new';
67
import { insomniaFetch } from '~/ui/insomniaFetch';
78
import { invariant } from '~/utils/invariant';
89
import { createFetcherSubmitHook, getInitialRouteForOrganization } from '~/utils/router';
@@ -53,6 +54,8 @@ export async function clientAction({ params }: Route.ClientActionArgs) {
5354

5455
await database.flushChanges(bufferId);
5556

57+
project.gitRepositoryId && reportGitProjectCount(organizationId, sessionId);
58+
5659
// When redirect to `/organizations/:organizationId`, it sometimes doesn't reload the index loader, so manually redirect to the initial route for the organization
5760
const initialOrganizationRoute = await getInitialRouteForOrganization({ organizationId });
5861
return redirect(initialOrganizationRoute);

0 commit comments

Comments
 (0)