@shipfox/client-integrations 14.0.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/application/complete-integration-callback.d.ts +2 -2
- package/dist/components/callback-status-shell.js +3 -3
- package/dist/components/callback-status-shell.js.map +1 -1
- package/dist/components/connection-picker.js +3 -3
- package/dist/components/connection-picker.js.map +1 -1
- package/dist/components/installed-integrations-section.js +9 -9
- package/dist/components/installed-integrations-section.js.map +1 -1
- package/dist/components/integration-delete-confirm-modal.js +1 -1
- package/dist/components/integration-delete-confirm-modal.js.map +1 -1
- package/dist/components/integration-gallery-for-workspace.js +3 -3
- package/dist/components/integration-gallery-for-workspace.js.map +1 -1
- package/dist/components/integration-gallery.stories.d.ts +1 -0
- package/dist/components/integration-gallery.stories.d.ts.map +1 -1
- package/dist/components/integration-gallery.stories.js +28 -0
- package/dist/components/integration-gallery.stories.js.map +1 -1
- package/dist/components/integration-usage-modal.d.ts +1 -1
- package/dist/components/integration-usage-modal.d.ts.map +1 -1
- package/dist/components/integration-usage-modal.js +8 -7
- package/dist/components/integration-usage-modal.js.map +1 -1
- package/dist/components/provider-grid.d.ts +1 -1
- package/dist/components/provider-grid.d.ts.map +1 -1
- package/dist/components/provider-grid.js +10 -10
- package/dist/components/provider-grid.js.map +1 -1
- package/dist/components/redirect-install-page.js +1 -1
- package/dist/components/redirect-install-page.js.map +1 -1
- package/dist/components/repository-picker.js +4 -4
- package/dist/components/repository-picker.js.map +1 -1
- package/dist/components/webhook/copyable-value.js +3 -3
- package/dist/components/webhook/copyable-value.js.map +1 -1
- package/dist/components/webhook/webhook-create-modal.js +1 -1
- package/dist/components/webhook/webhook-create-modal.js.map +1 -1
- package/dist/components/webhook/webhook-modals.stories.d.ts.map +1 -1
- package/dist/components/webhook/webhook-usage-details.js +2 -2
- package/dist/components/webhook/webhook-usage-details.js.map +1 -1
- package/dist/feature.d.ts +8 -8
- package/dist/feature.d.ts.map +1 -1
- package/dist/hooks/api/integrations.d.ts +4 -4
- package/dist/hooks/api/integrations.d.ts.map +1 -1
- package/dist/hooks/api/webhook-connections.d.ts +1 -1
- package/dist/hooks/api/webhook-connections.d.ts.map +1 -1
- package/dist/pages/gitea-install-page.js +4 -4
- package/dist/pages/gitea-install-page.js.map +1 -1
- package/dist/pages/jira-callback-page.js +7 -7
- package/dist/pages/jira-callback-page.js.map +1 -1
- package/dist/pages/sentry-callback-page.js +7 -7
- package/dist/pages/sentry-callback-page.js.map +1 -1
- package/dist/pages/source-control-onboarding-page.js +2 -2
- package/dist/pages/source-control-onboarding-page.js.map +1 -1
- package/dist/routes/gitea.d.ts.map +1 -1
- package/dist/routes/github-callback.d.ts.map +1 -1
- package/dist/routes/github.d.ts.map +1 -1
- package/dist/routes/integrations-settings.d.ts.map +1 -1
- package/dist/routes/integrations.d.ts.map +1 -1
- package/dist/routes/jira-callback.d.ts.map +1 -1
- package/dist/routes/jira.d.ts.map +1 -1
- package/dist/routes/linear-callback.d.ts.map +1 -1
- package/dist/routes/linear.d.ts.map +1 -1
- package/dist/routes/sentry-callback.d.ts.map +1 -1
- package/dist/routes/sentry.d.ts.map +1 -1
- package/dist/routes/slack-callback.d.ts.map +1 -1
- package/dist/routes/slack.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/src/components/callback-status-shell.tsx +3 -3
- package/src/components/connection-picker.tsx +3 -3
- package/src/components/installed-integrations-section.tsx +9 -9
- package/src/components/integration-delete-confirm-modal.tsx +1 -1
- package/src/components/integration-gallery-for-workspace.tsx +3 -3
- package/src/components/integration-gallery.stories.tsx +20 -0
- package/src/components/integration-gallery.test.tsx +21 -0
- package/src/components/integration-usage-modal.tsx +9 -7
- package/src/components/provider-grid.tsx +10 -10
- package/src/components/redirect-install-page.tsx +1 -1
- package/src/components/repository-picker.test.tsx +2 -2
- package/src/components/repository-picker.tsx +6 -4
- package/src/components/webhook/copyable-value.tsx +3 -3
- package/src/components/webhook/webhook-create-modal.tsx +1 -1
- package/src/components/webhook/webhook-usage-details.tsx +2 -2
- package/src/pages/gitea-install-page.tsx +4 -4
- package/src/pages/jira-callback-page.tsx +7 -7
- package/src/pages/sentry-callback-page.tsx +7 -7
- package/src/pages/source-control-onboarding-page.tsx +2 -2
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-integrations",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "16.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@swc/helpers": "^0.5.17",
|
|
29
29
|
"@tanstack/react-form": "^1.32.0",
|
|
30
|
-
"@shipfox/api-integration-core-dto": "12.
|
|
31
|
-
"@shipfox/api-integration-gitea-dto": "12.
|
|
32
|
-
"@shipfox/api-integration-github-dto": "12.
|
|
33
|
-
"@shipfox/api-integration-
|
|
34
|
-
"@shipfox/api-integration-
|
|
35
|
-
"@shipfox/api-integration-slack-dto": "12.
|
|
36
|
-
"@shipfox/api-integration-
|
|
37
|
-
"@shipfox/api-integration-
|
|
30
|
+
"@shipfox/api-integration-core-dto": "12.2.0",
|
|
31
|
+
"@shipfox/api-integration-gitea-dto": "12.2.0",
|
|
32
|
+
"@shipfox/api-integration-github-dto": "12.2.0",
|
|
33
|
+
"@shipfox/api-integration-linear-dto": "12.2.0",
|
|
34
|
+
"@shipfox/api-integration-jira-dto": "12.2.0",
|
|
35
|
+
"@shipfox/api-integration-slack-dto": "12.2.0",
|
|
36
|
+
"@shipfox/api-integration-webhook-dto": "12.2.0",
|
|
37
|
+
"@shipfox/api-integration-sentry-dto": "12.2.0",
|
|
38
38
|
"@shipfox/client-api": "6.0.1",
|
|
39
|
-
"@shipfox/client-auth": "
|
|
40
|
-
"@shipfox/client-shell": "
|
|
41
|
-
"@shipfox/client-ui": "
|
|
42
|
-
"@shipfox/
|
|
43
|
-
"@shipfox/
|
|
39
|
+
"@shipfox/client-auth": "16.0.0",
|
|
40
|
+
"@shipfox/client-shell": "16.0.0",
|
|
41
|
+
"@shipfox/client-ui": "16.0.0",
|
|
42
|
+
"@shipfox/react-ui": "1.1.0",
|
|
43
|
+
"@shipfox/integration-icons": "0.3.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -43,15 +43,15 @@ export function CallbackStatusShell({
|
|
|
43
43
|
: undefined;
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
|
-
<main className="flex min-h-screen bg-background-subtle-base px-
|
|
47
|
-
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-
|
|
46
|
+
<main className="flex min-h-screen bg-background-subtle-base px-row py-frame">
|
|
47
|
+
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-section">
|
|
48
48
|
<h2 ref={headingRef} tabIndex={-1} className="text-24 font-semibold outline-none">
|
|
49
49
|
{title}
|
|
50
50
|
</h2>
|
|
51
51
|
<Callout role={status === 'error' ? 'alert' : 'status'} type={status}>
|
|
52
52
|
<Text size="sm">{message}</Text>
|
|
53
53
|
</Callout>
|
|
54
|
-
<div className="flex flex-col gap-
|
|
54
|
+
<div className="flex flex-col gap-inline sm:flex-row sm:items-center">
|
|
55
55
|
{switchAccount ? (
|
|
56
56
|
<ButtonLink asChild className="min-h-44 w-full sm:w-fit">
|
|
57
57
|
<Link
|
|
@@ -19,7 +19,7 @@ export function ConnectionPicker({
|
|
|
19
19
|
const labelId = useId();
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<div className="flex flex-col gap-
|
|
22
|
+
<div className="flex flex-col gap-inline">
|
|
23
23
|
<Label id={labelId} className="sr-only">
|
|
24
24
|
Source integration
|
|
25
25
|
</Label>
|
|
@@ -27,7 +27,7 @@ export function ConnectionPicker({
|
|
|
27
27
|
aria-labelledby={labelId}
|
|
28
28
|
value={selectedConnectionId ?? ''}
|
|
29
29
|
onValueChange={onSelect}
|
|
30
|
-
className="grid grid-cols-2 gap-
|
|
30
|
+
className="grid grid-cols-2 gap-inline max-[760px]:grid-cols-1"
|
|
31
31
|
>
|
|
32
32
|
{connections.map((connection) => (
|
|
33
33
|
<RadioGroupItem key={connection.id} value={connection.id}>
|
|
@@ -43,7 +43,7 @@ function ConnectionOption({connection}: {connection: IntegrationConnection}) {
|
|
|
43
43
|
const {ref: nameRef, isTruncated} = useIsTextTruncated<HTMLSpanElement>(connection.displayName);
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
|
-
<span className="flex min-w-0 items-center gap-
|
|
46
|
+
<span className="flex min-w-0 items-center gap-inline">
|
|
47
47
|
<Tooltip>
|
|
48
48
|
<TooltipTrigger asChild>
|
|
49
49
|
<span className="shrink-0">
|
|
@@ -48,8 +48,8 @@ export function InstalledIntegrationsSection({
|
|
|
48
48
|
providerDisplayName,
|
|
49
49
|
}: InstalledIntegrationsSectionProps) {
|
|
50
50
|
return (
|
|
51
|
-
<section className="flex flex-col gap-
|
|
52
|
-
<div className="flex flex-col gap-
|
|
51
|
+
<section className="flex flex-col gap-group" aria-label="Installed integrations">
|
|
52
|
+
<div className="flex flex-col gap-tight">
|
|
53
53
|
<Header variant="h3">Installed integrations</Header>
|
|
54
54
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
55
55
|
Provider accounts installed in this workspace.
|
|
@@ -59,7 +59,7 @@ export function InstalledIntegrationsSection({
|
|
|
59
59
|
{isPending ? <InstalledSkeleton label="Loading integrations" /> : null}
|
|
60
60
|
|
|
61
61
|
{error ? (
|
|
62
|
-
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-
|
|
62
|
+
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-row')}>
|
|
63
63
|
<QueryLoadError
|
|
64
64
|
query={{isError: true, isFetching, data: undefined, error, refetch: onRetry}}
|
|
65
65
|
subject="integrations"
|
|
@@ -68,7 +68,7 @@ export function InstalledIntegrationsSection({
|
|
|
68
68
|
) : null}
|
|
69
69
|
|
|
70
70
|
{!isPending && !error && connections.length === 0 ? (
|
|
71
|
-
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-
|
|
71
|
+
<div className={cn(INSTALLED_SURFACE_CLASS, 'px-row')}>
|
|
72
72
|
<EmptyState
|
|
73
73
|
icon="componentLine"
|
|
74
74
|
title="No integrations installed yet"
|
|
@@ -120,7 +120,7 @@ function InstalledRow({
|
|
|
120
120
|
const providerName = providerDisplayName(connection.provider);
|
|
121
121
|
|
|
122
122
|
return (
|
|
123
|
-
<li className="flex items-center gap-
|
|
123
|
+
<li className="flex items-center gap-cluster px-row py-row transition-colors hover:bg-background-components-hover">
|
|
124
124
|
<IntegrationIcon
|
|
125
125
|
source={connection.provider}
|
|
126
126
|
aria-hidden
|
|
@@ -129,8 +129,8 @@ function InstalledRow({
|
|
|
129
129
|
muted ? 'text-foreground-neutral-disabled' : 'text-foreground-neutral-base',
|
|
130
130
|
)}
|
|
131
131
|
/>
|
|
132
|
-
<div className="flex min-w-0 flex-1 flex-col gap-
|
|
133
|
-
<div className="flex min-w-0 items-center gap-
|
|
132
|
+
<div className="flex min-w-0 flex-1 flex-col gap-tight">
|
|
133
|
+
<div className="flex min-w-0 items-center gap-inline">
|
|
134
134
|
<Text
|
|
135
135
|
size="md"
|
|
136
136
|
bold
|
|
@@ -196,9 +196,9 @@ function InstalledSkeleton({label}: {label: string}) {
|
|
|
196
196
|
className={cn('divide-y divide-border-neutral-base', INSTALLED_SURFACE_CLASS)}
|
|
197
197
|
>
|
|
198
198
|
{[0, 1, 2].map((row) => (
|
|
199
|
-
<li key={row} className="flex items-center gap-
|
|
199
|
+
<li key={row} className="flex items-center gap-cluster px-row py-row">
|
|
200
200
|
<Skeleton className="size-24 shrink-0" />
|
|
201
|
-
<div className="flex min-w-0 flex-1 flex-col gap-
|
|
201
|
+
<div className="flex min-w-0 flex-1 flex-col gap-tight">
|
|
202
202
|
<Skeleton className="h-16 w-120" />
|
|
203
203
|
<Skeleton className="h-12 w-80" />
|
|
204
204
|
</div>
|
|
@@ -36,7 +36,7 @@ export function IntegrationDeleteConfirmModal({
|
|
|
36
36
|
<ModalContent aria-describedby={undefined}>
|
|
37
37
|
<ModalTitle className="sr-only">Delete integration</ModalTitle>
|
|
38
38
|
<ModalHeader title="Delete integration" showClose={!isPending} />
|
|
39
|
-
<ModalBody className="gap-
|
|
39
|
+
<ModalBody className="gap-group">
|
|
40
40
|
<Text size="sm">
|
|
41
41
|
Are you sure you want to delete <strong className="font-medium">{name}</strong>? Once
|
|
42
42
|
deleted, Shipfox will immediately stop processing events from this integration. This
|
|
@@ -123,7 +123,7 @@ export function IntegrationGalleryForWorkspace({
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
return (
|
|
126
|
-
<div className="flex flex-col gap-
|
|
126
|
+
<div className="flex flex-col gap-section">
|
|
127
127
|
<InstalledIntegrationsSection
|
|
128
128
|
workspaceSlug={workspaceSlug}
|
|
129
129
|
connections={sortedConnections}
|
|
@@ -145,8 +145,8 @@ export function IntegrationGalleryForWorkspace({
|
|
|
145
145
|
providerDisplayName={providerDisplayName}
|
|
146
146
|
/>
|
|
147
147
|
|
|
148
|
-
<section className="flex flex-col gap-
|
|
149
|
-
<div className="flex flex-col gap-
|
|
148
|
+
<section className="flex flex-col gap-group" aria-label="Available integrations">
|
|
149
|
+
<div className="flex flex-col gap-tight">
|
|
150
150
|
<Header variant="h3">Available integrations</Header>
|
|
151
151
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
152
152
|
Providers available to install in this workspace.
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '@tanstack/react-router';
|
|
18
18
|
import {createStore, Provider as JotaiProvider} from 'jotai';
|
|
19
19
|
import {useMemo} from 'react';
|
|
20
|
+
import {expect, screen, userEvent, within} from 'storybook/test';
|
|
20
21
|
import {IntegrationGallery} from './integration-gallery.js';
|
|
21
22
|
|
|
22
23
|
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
@@ -148,6 +149,25 @@ export const LongNames: Story = {
|
|
|
148
149
|
args: {scenario: 'long-names'},
|
|
149
150
|
};
|
|
150
151
|
|
|
152
|
+
export const UsageModalCloseRestoresInteraction: Story = {
|
|
153
|
+
play: async ({canvasElement}) => {
|
|
154
|
+
const user = userEvent.setup();
|
|
155
|
+
const canvas = within(canvasElement);
|
|
156
|
+
const actionsButton = await canvas.findByRole('button', {
|
|
157
|
+
name: 'Open acme-corp integration actions',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await user.click(actionsButton);
|
|
161
|
+
await user.click(await screen.findByRole('menuitem', {name: 'Use this integration'}));
|
|
162
|
+
await screen.findByRole('dialog', {name: 'Use acme-corp'});
|
|
163
|
+
await user.click(await screen.findByRole('button', {name: 'Done'}));
|
|
164
|
+
expect(screen.queryByRole('dialog', {name: 'Use acme-corp'})).not.toBeInTheDocument();
|
|
165
|
+
expect(document.body.style.pointerEvents).not.toBe('none');
|
|
166
|
+
await user.click(actionsButton);
|
|
167
|
+
await screen.findByRole('menuitem', {name: 'Use this integration'});
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
151
171
|
function fetchForScenario(scenario: Scenario): typeof fetch {
|
|
152
172
|
return (input, init) => {
|
|
153
173
|
const url = requestUrl(input);
|
|
@@ -367,6 +367,27 @@ describe('IntegrationGallery — installed section', () => {
|
|
|
367
367
|
expect(screen.getByText(webhookConnectionDto.inbound_url)).toBeVisible();
|
|
368
368
|
});
|
|
369
369
|
|
|
370
|
+
test('restores page interactivity after closing the usage modal', async () => {
|
|
371
|
+
renderGallery({}, {connections: [webhookConnection]});
|
|
372
|
+
|
|
373
|
+
await openActions('Open Stripe production integration actions');
|
|
374
|
+
expect(document.body.style.pointerEvents).toBe('none');
|
|
375
|
+
fireEvent.click(screen.getByRole('menuitem', {name: 'Use this integration'}));
|
|
376
|
+
expect(await screen.findByRole('dialog', {name: 'Use Stripe production'})).toBeVisible();
|
|
377
|
+
expect(document.body.style.pointerEvents).toBe('none');
|
|
378
|
+
|
|
379
|
+
const usageDialog = screen.getByRole('dialog', {name: 'Use Stripe production'});
|
|
380
|
+
fireEvent.click(within(usageDialog).getByRole('button', {name: 'Done'}));
|
|
381
|
+
|
|
382
|
+
await waitFor(() => {
|
|
383
|
+
expect(screen.queryByRole('dialog', {name: 'Use Stripe production'})).not.toBeInTheDocument();
|
|
384
|
+
expect(document.body).not.toHaveStyle({pointerEvents: 'none'});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
await openActions('Open Stripe production integration actions');
|
|
388
|
+
expect(screen.getByRole('menuitem', {name: 'Use this integration'})).toBeVisible();
|
|
389
|
+
});
|
|
390
|
+
|
|
370
391
|
test('opens the usage modal with a GitHub event selector', async () => {
|
|
371
392
|
renderGallery({}, {connections: [githubConnection]});
|
|
372
393
|
|
|
@@ -71,13 +71,15 @@ export function IntegrationUsageModal({
|
|
|
71
71
|
);
|
|
72
72
|
const title = connection ? `Use ${connection.displayName}` : 'Use integration';
|
|
73
73
|
|
|
74
|
+
if (!open || connection === null) return null;
|
|
75
|
+
|
|
74
76
|
return (
|
|
75
|
-
<Modal open={open
|
|
77
|
+
<Modal open={open} onOpenChange={onOpenChange}>
|
|
76
78
|
<ModalContent aria-describedby={undefined} className="max-h-[calc(100vh-32px)] max-w-[640px]">
|
|
77
79
|
<ModalTitle className="sr-only">{title}</ModalTitle>
|
|
78
80
|
<ModalHeader>
|
|
79
|
-
<div className="flex min-w-0 flex-col gap-
|
|
80
|
-
<div className="flex min-w-0 items-center gap-
|
|
81
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
82
|
+
<div className="flex min-w-0 items-center gap-inline">
|
|
81
83
|
<Text size="lg" aria-hidden="true" className="truncate">
|
|
82
84
|
{title}
|
|
83
85
|
</Text>
|
|
@@ -92,13 +94,13 @@ export function IntegrationUsageModal({
|
|
|
92
94
|
</ModalHeader>
|
|
93
95
|
{connection ? (
|
|
94
96
|
<>
|
|
95
|
-
<ModalBody className="min-h-0 flex-1 gap-
|
|
96
|
-
<section className="flex w-full flex-col gap-
|
|
97
|
+
<ModalBody className="min-h-0 flex-1 gap-section overflow-y-auto overflow-x-clip scrollbar">
|
|
98
|
+
<section className="flex w-full flex-col gap-cluster">
|
|
97
99
|
<Text size="md" bold>
|
|
98
100
|
Usage
|
|
99
101
|
</Text>
|
|
100
102
|
{events.length > 1 ? (
|
|
101
|
-
<div className="flex flex-col gap-
|
|
103
|
+
<div className="flex flex-col gap-inline">
|
|
102
104
|
<label
|
|
103
105
|
htmlFor="integration-usage-event"
|
|
104
106
|
className="font-display text-sm font-medium text-foreground-neutral-base"
|
|
@@ -144,7 +146,7 @@ export function IntegrationUsageModal({
|
|
|
144
146
|
</CodeBlock>
|
|
145
147
|
</section>
|
|
146
148
|
{children ? (
|
|
147
|
-
<section className="flex w-full flex-col gap-
|
|
149
|
+
<section className="flex w-full flex-col gap-cluster">
|
|
148
150
|
<Text size="md" bold>
|
|
149
151
|
Details
|
|
150
152
|
</Text>
|
|
@@ -23,7 +23,7 @@ export interface ProviderGridProps {
|
|
|
23
23
|
onOpenProvider?: ((provider: string) => void) | undefined;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export const PROVIDER_GRID_CLASS = 'grid grid-cols-2 gap-
|
|
26
|
+
export const PROVIDER_GRID_CLASS = 'grid grid-cols-2 gap-cluster max-[760px]:grid-cols-1';
|
|
27
27
|
|
|
28
28
|
export const PROVIDER_SURFACE_CLASS =
|
|
29
29
|
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
@@ -46,7 +46,7 @@ export function ProviderGrid({
|
|
|
46
46
|
|
|
47
47
|
if (error) {
|
|
48
48
|
return (
|
|
49
|
-
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-
|
|
49
|
+
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-row')}>
|
|
50
50
|
<QueryLoadError
|
|
51
51
|
query={{
|
|
52
52
|
isError: true,
|
|
@@ -63,7 +63,7 @@ export function ProviderGrid({
|
|
|
63
63
|
|
|
64
64
|
if (installableProviders.length === 0) {
|
|
65
65
|
return (
|
|
66
|
-
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-
|
|
66
|
+
<div className={cn(PROVIDER_SURFACE_CLASS, 'px-row')}>
|
|
67
67
|
<EmptyState
|
|
68
68
|
icon="componentLine"
|
|
69
69
|
title="No integrations available"
|
|
@@ -106,7 +106,7 @@ function ProviderCard({
|
|
|
106
106
|
type="button"
|
|
107
107
|
aria-label={`Add ${provider.displayName}`}
|
|
108
108
|
onClick={() => onOpenProvider?.(provider.provider)}
|
|
109
|
-
className="group flex h-full w-full min-w-0 items-center justify-between gap-
|
|
109
|
+
className="group flex h-full w-full min-w-0 items-center justify-between gap-cluster rounded-8 border border-border-neutral-base bg-background-neutral-base p-panel-compact text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
|
|
110
110
|
>
|
|
111
111
|
<ProviderCardContent provider={provider} action="Add" />
|
|
112
112
|
</button>
|
|
@@ -118,7 +118,7 @@ function ProviderCard({
|
|
|
118
118
|
to={catalog.setupPath}
|
|
119
119
|
params={{workspaceSlug}}
|
|
120
120
|
aria-label={`Install ${provider.displayName}`}
|
|
121
|
-
className="group flex h-full min-w-0 items-center justify-between gap-
|
|
121
|
+
className="group flex h-full min-w-0 items-center justify-between gap-cluster rounded-8 border border-border-neutral-base bg-background-neutral-base p-panel-compact transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus focus-visible:outline-none"
|
|
122
122
|
>
|
|
123
123
|
<ProviderCardContent provider={provider} action="Install" />
|
|
124
124
|
</Link>
|
|
@@ -134,7 +134,7 @@ function ProviderCardContent({
|
|
|
134
134
|
}) {
|
|
135
135
|
return (
|
|
136
136
|
<>
|
|
137
|
-
<span className="flex min-w-0 items-center gap-
|
|
137
|
+
<span className="flex min-w-0 items-center gap-cluster">
|
|
138
138
|
<IntegrationIcon
|
|
139
139
|
source={provider.provider}
|
|
140
140
|
aria-hidden
|
|
@@ -144,7 +144,7 @@ function ProviderCardContent({
|
|
|
144
144
|
{provider.displayName}
|
|
145
145
|
</Text>
|
|
146
146
|
</span>
|
|
147
|
-
<span className="flex shrink-0 items-center gap-
|
|
147
|
+
<span className="flex shrink-0 items-center gap-tight text-foreground-neutral-muted transition-colors group-hover:text-foreground-highlight-interactive">
|
|
148
148
|
<Text as="span" size="sm">
|
|
149
149
|
{action}
|
|
150
150
|
</Text>
|
|
@@ -159,9 +159,9 @@ function ProviderGridSkeleton({label}: {label: string}) {
|
|
|
159
159
|
<ul role="status" aria-label={label} className={PROVIDER_GRID_CLASS}>
|
|
160
160
|
{[0, 1, 2, 3].map((tile) => (
|
|
161
161
|
<li key={tile}>
|
|
162
|
-
<Card className="h-full p-
|
|
163
|
-
<div className="flex items-center justify-between gap-
|
|
164
|
-
<div className="flex min-w-0 items-center gap-
|
|
162
|
+
<Card className="h-full p-panel-compact">
|
|
163
|
+
<div className="flex items-center justify-between gap-cluster">
|
|
164
|
+
<div className="flex min-w-0 items-center gap-cluster">
|
|
165
165
|
<Skeleton className="size-24 shrink-0" />
|
|
166
166
|
<Skeleton className="h-16 w-100" />
|
|
167
167
|
</div>
|
|
@@ -54,7 +54,7 @@ export function RedirectInstallPage({
|
|
|
54
54
|
|
|
55
55
|
if (errorMessage) {
|
|
56
56
|
return (
|
|
57
|
-
<div className="mx-auto flex w-full max-w-[480px] flex-col gap-
|
|
57
|
+
<div className="mx-auto flex w-full max-w-[480px] flex-col gap-group">
|
|
58
58
|
<Callout role="alert" type="error">
|
|
59
59
|
<Text size="sm">{errorMessage}</Text>
|
|
60
60
|
</Callout>
|
|
@@ -75,7 +75,7 @@ describe('RepositoryPicker', () => {
|
|
|
75
75
|
const loadingGrid = container.querySelector('[aria-hidden="true"]');
|
|
76
76
|
if (!loadingGrid) throw new Error('Repository loading grid was not rendered');
|
|
77
77
|
|
|
78
|
-
expect(loadingGrid).toHaveClass('grid', 'grid-cols-2', 'gap-
|
|
78
|
+
expect(loadingGrid).toHaveClass('grid', 'grid-cols-2', 'gap-inline', 'max-[760px]:grid-cols-1');
|
|
79
79
|
expect(loadingGrid.querySelectorAll('[data-slot="skeleton"]')).toHaveLength(4);
|
|
80
80
|
for (const placeholder of loadingGrid.querySelectorAll(':scope > div')) {
|
|
81
81
|
expect(placeholder).toHaveClass(
|
|
@@ -84,7 +84,7 @@ describe('RepositoryPicker', () => {
|
|
|
84
84
|
'border',
|
|
85
85
|
'border-border-neutral-base',
|
|
86
86
|
'bg-background-neutral-base',
|
|
87
|
-
'p-
|
|
87
|
+
'p-[14px]',
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
});
|
|
@@ -9,7 +9,7 @@ import {Text} from '@shipfox/react-ui/typography';
|
|
|
9
9
|
import {useId} from 'react';
|
|
10
10
|
import type {Repository} from '#core/models.js';
|
|
11
11
|
|
|
12
|
-
const REPOSITORY_GRID_CLASS_NAME = 'grid grid-cols-2 gap-
|
|
12
|
+
const REPOSITORY_GRID_CLASS_NAME = 'grid grid-cols-2 gap-inline max-[760px]:grid-cols-1';
|
|
13
13
|
const REPOSITORY_SKELETON_WIDTHS = ['w-64', 'w-96', 'w-80', 'w-112'] as const;
|
|
14
14
|
|
|
15
15
|
export function RepositoryPicker({
|
|
@@ -41,7 +41,7 @@ export function RepositoryPicker({
|
|
|
41
41
|
const showSearch = onSearchChange !== undefined;
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
|
-
<div className="flex flex-col gap-
|
|
44
|
+
<div className="flex flex-col gap-inline">
|
|
45
45
|
<Label id={labelId} className="sr-only">
|
|
46
46
|
Repository
|
|
47
47
|
</Label>
|
|
@@ -60,7 +60,7 @@ export function RepositoryPicker({
|
|
|
60
60
|
{isLoading ? <RepositoryLoadingState /> : null}
|
|
61
61
|
|
|
62
62
|
{!isLoading && repositories.length === 0 ? (
|
|
63
|
-
<div className="rounded-8 border border-border-neutral-base bg-background-subtle-base p-
|
|
63
|
+
<div className="rounded-8 border border-border-neutral-base bg-background-subtle-base p-panel-compact">
|
|
64
64
|
<Text size="sm">{emptyMessage}</Text>
|
|
65
65
|
</div>
|
|
66
66
|
) : null}
|
|
@@ -119,10 +119,12 @@ function RepositoryLoadingState() {
|
|
|
119
119
|
Loading repositories.
|
|
120
120
|
</div>
|
|
121
121
|
<div aria-hidden="true" className={REPOSITORY_GRID_CLASS_NAME}>
|
|
122
|
+
{/* The skeleton mirrors RadioGroupItem's own 14px padding contract so the
|
|
123
|
+
placeholder and the loaded card stay the same height. */}
|
|
122
124
|
{REPOSITORY_SKELETON_WIDTHS.map((width) => (
|
|
123
125
|
<div
|
|
124
126
|
key={width}
|
|
125
|
-
className="h-50 min-w-0 rounded-8 border border-border-neutral-base bg-background-neutral-base p-
|
|
127
|
+
className="h-50 min-w-0 rounded-8 border border-border-neutral-base bg-background-neutral-base p-[14px]"
|
|
126
128
|
>
|
|
127
129
|
<Skeleton className={`h-20 ${width}`} />
|
|
128
130
|
</div>
|
|
@@ -40,12 +40,12 @@ export function CopyableValue({label, value, note, className}: CopyableValueProp
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
|
-
<div className={cn('flex w-full min-w-0 flex-col gap-
|
|
44
|
-
<div className="flex min-w-0 items-center gap-
|
|
43
|
+
<div className={cn('flex w-full min-w-0 flex-col gap-inline', className)}>
|
|
44
|
+
<div className="flex min-w-0 items-center gap-inline">
|
|
45
45
|
<Code
|
|
46
46
|
as="span"
|
|
47
47
|
variant="paragraph"
|
|
48
|
-
className="min-w-0 flex-1 break-all rounded-4 border border-border-neutral-base bg-background-components-base px-
|
|
48
|
+
className="min-w-0 flex-1 break-all rounded-4 border border-border-neutral-base bg-background-components-base px-tight py-[6px] text-foreground-neutral-base"
|
|
49
49
|
>
|
|
50
50
|
{value}
|
|
51
51
|
</Code>
|
|
@@ -97,7 +97,7 @@ export function WebhookCreateModal({
|
|
|
97
97
|
void form.handleSubmit();
|
|
98
98
|
}}
|
|
99
99
|
>
|
|
100
|
-
<ModalBody className="gap-
|
|
100
|
+
<ModalBody className="gap-group">
|
|
101
101
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
102
102
|
Create a named inbound webhook URL for this workspace.
|
|
103
103
|
</Text>
|
|
@@ -29,8 +29,8 @@ export function WebhookUsageDetails({workspaceId, connectionId}: WebhookUsageDet
|
|
|
29
29
|
if (!connection) return null;
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<div className="flex w-full flex-col gap-
|
|
33
|
-
<div className="flex w-full min-w-0 flex-col gap-
|
|
32
|
+
<div className="flex w-full flex-col gap-group">
|
|
33
|
+
<div className="flex w-full min-w-0 flex-col gap-inline">
|
|
34
34
|
<Text size="sm" bold>
|
|
35
35
|
Inbound URL
|
|
36
36
|
</Text>
|
|
@@ -40,8 +40,8 @@ export function GiteaInstallPage() {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
|
-
<div className="mx-auto flex w-full max-w-[480px] flex-col gap-
|
|
44
|
-
<header className="flex flex-col gap-
|
|
43
|
+
<div className="mx-auto flex w-full max-w-[480px] flex-col gap-section">
|
|
44
|
+
<header className="flex flex-col gap-inline">
|
|
45
45
|
<Header variant="h1">Install Gitea</Header>
|
|
46
46
|
<Text size="md" className="text-foreground-neutral-muted">
|
|
47
47
|
Enter the Gitea organization to install in this workspace.
|
|
@@ -49,7 +49,7 @@ export function GiteaInstallPage() {
|
|
|
49
49
|
</header>
|
|
50
50
|
|
|
51
51
|
<form
|
|
52
|
-
className="flex flex-col gap-
|
|
52
|
+
className="flex flex-col gap-group"
|
|
53
53
|
noValidate
|
|
54
54
|
onSubmit={(event) => {
|
|
55
55
|
event.preventDefault();
|
|
@@ -83,7 +83,7 @@ export function GiteaInstallPage() {
|
|
|
83
83
|
</FormField>
|
|
84
84
|
)}
|
|
85
85
|
</form.Field>
|
|
86
|
-
<div className="flex items-center gap-
|
|
86
|
+
<div className="flex items-center gap-cluster">
|
|
87
87
|
<Button type="submit" isLoading={connect.isPending}>
|
|
88
88
|
Install
|
|
89
89
|
</Button>
|
|
@@ -300,9 +300,9 @@ function JiraSiteSelectionPage({
|
|
|
300
300
|
onSelect: (site: JiraSite) => void;
|
|
301
301
|
}) {
|
|
302
302
|
return (
|
|
303
|
-
<main className="flex min-h-screen bg-background-subtle-base px-
|
|
304
|
-
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-
|
|
305
|
-
<header className="flex flex-col gap-
|
|
303
|
+
<main className="flex min-h-screen bg-background-subtle-base px-row py-frame">
|
|
304
|
+
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-section">
|
|
305
|
+
<header className="flex flex-col gap-inline">
|
|
306
306
|
<Header variant="h2">Choose a Jira site</Header>
|
|
307
307
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
308
308
|
Select the Jira site to connect to this Shipfox workspace.
|
|
@@ -315,10 +315,10 @@ function JiraSiteSelectionPage({
|
|
|
315
315
|
</Callout>
|
|
316
316
|
) : null}
|
|
317
317
|
|
|
318
|
-
<section className="flex flex-col gap-
|
|
318
|
+
<section className="flex flex-col gap-inline" aria-label="Choose a Jira site">
|
|
319
319
|
{sites.map((site) => (
|
|
320
|
-
<Card key={site.cloudId} className="p-
|
|
321
|
-
<div className="flex items-center justify-between gap-
|
|
320
|
+
<Card key={site.cloudId} className="p-panel-compact">
|
|
321
|
+
<div className="flex items-center justify-between gap-cluster">
|
|
322
322
|
<div className="min-w-0">
|
|
323
323
|
<Text size="md" bold className="truncate">
|
|
324
324
|
{site.name}
|
|
@@ -340,7 +340,7 @@ function JiraSiteSelectionPage({
|
|
|
340
340
|
))}
|
|
341
341
|
</section>
|
|
342
342
|
|
|
343
|
-
<div className="flex flex-col gap-
|
|
343
|
+
<div className="flex flex-col gap-inline sm:flex-row sm:items-center">
|
|
344
344
|
{failure?.startOver && workspaceSlug ? (
|
|
345
345
|
<ButtonLink asChild variant="muted" className="min-h-44 w-full sm:w-fit">
|
|
346
346
|
<Link to="/w/$workspaceSlug/integrations/jira" params={{workspaceSlug}}>
|
|
@@ -170,7 +170,7 @@ export function SentryCallbackPage() {
|
|
|
170
170
|
|
|
171
171
|
return (
|
|
172
172
|
<CallbackColumn>
|
|
173
|
-
<header className="flex flex-col gap-
|
|
173
|
+
<header className="flex flex-col gap-inline">
|
|
174
174
|
<Header variant="h2">Install Sentry</Header>
|
|
175
175
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
176
176
|
{params.orgSlug
|
|
@@ -181,7 +181,7 @@ export function SentryCallbackPage() {
|
|
|
181
181
|
|
|
182
182
|
{failure ? (
|
|
183
183
|
<Callout role="alert" type="error">
|
|
184
|
-
<div className="flex flex-col gap-
|
|
184
|
+
<div className="flex flex-col gap-inline">
|
|
185
185
|
<Text size="sm">{failure.failure.message}</Text>
|
|
186
186
|
{failure.failure.kind === 'retryable' ? (
|
|
187
187
|
<Button
|
|
@@ -210,10 +210,10 @@ export function SentryCallbackPage() {
|
|
|
210
210
|
</Callout>
|
|
211
211
|
) : null}
|
|
212
212
|
|
|
213
|
-
<section className="flex flex-col gap-
|
|
213
|
+
<section className="flex flex-col gap-inline" aria-label="Choose a workspace">
|
|
214
214
|
{orderedWorkspaces.map((workspace) => (
|
|
215
|
-
<Card key={workspace.id} className="p-
|
|
216
|
-
<div className="flex items-center justify-between gap-
|
|
215
|
+
<Card key={workspace.id} className="p-panel-compact">
|
|
216
|
+
<div className="flex items-center justify-between gap-cluster">
|
|
217
217
|
<Text size="md" bold className="truncate">
|
|
218
218
|
{workspace.name}
|
|
219
219
|
</Text>
|
|
@@ -246,8 +246,8 @@ export function SentryCallbackPage() {
|
|
|
246
246
|
|
|
247
247
|
function CallbackColumn({children}: {children: React.ReactNode}) {
|
|
248
248
|
return (
|
|
249
|
-
<main className="flex min-h-screen bg-background-subtle-base px-
|
|
250
|
-
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-
|
|
249
|
+
<main className="flex min-h-screen bg-background-subtle-base px-row py-frame">
|
|
250
|
+
<div className="mx-auto flex w-full max-w-[480px] flex-col justify-center gap-section">
|
|
251
251
|
{children}
|
|
252
252
|
</div>
|
|
253
253
|
</main>
|
|
@@ -8,8 +8,8 @@ export function SourceControlOnboardingPage() {
|
|
|
8
8
|
const providersQuery = useIntegrationProvidersQuery({capability: 'source_control'});
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<div className="mx-auto flex w-full max-w-[640px] flex-col gap-
|
|
12
|
-
<header className="flex flex-col gap-
|
|
11
|
+
<div className="mx-auto flex w-full max-w-[640px] flex-col gap-section">
|
|
12
|
+
<header className="flex flex-col gap-inline">
|
|
13
13
|
<Header variant="h1">Install source control</Header>
|
|
14
14
|
<Text size="md" className="text-foreground-neutral-muted">
|
|
15
15
|
Shipfox needs a source control integration to import your repositories.
|