@gntik-ai/templates 0.0.0-stage → 0.6.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/dist/index.d.ts +2420 -0
- package/dist/index.js +14520 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -3
- package/src/accept-invitation/Page.test.tsx +40 -0
- package/src/accept-invitation/Page.tsx +153 -0
- package/src/accept-invitation/data.ts +22 -0
- package/src/accept-invitation/template.meta.ts +12 -0
- package/src/assistant-chat/Page.test.tsx +51 -0
- package/src/assistant-chat/Page.tsx +273 -0
- package/src/assistant-chat/data.ts +71 -0
- package/src/assistant-chat/template.meta.ts +12 -0
- package/src/audit-log/Page.test.tsx +58 -0
- package/src/audit-log/Page.tsx +284 -0
- package/src/audit-log/data.ts +133 -0
- package/src/audit-log/template.meta.ts +12 -0
- package/src/board/Lane.tsx +117 -0
- package/src/board/Page.test.tsx +68 -0
- package/src/board/Page.tsx +178 -0
- package/src/board/data.ts +127 -0
- package/src/board/template.meta.ts +12 -0
- package/src/board/useBoard.ts +146 -0
- package/src/changelog/Page.test.tsx +31 -0
- package/src/changelog/Page.tsx +174 -0
- package/src/changelog/data.ts +105 -0
- package/src/changelog/template.meta.ts +12 -0
- package/src/code-editor/Page.test.tsx +83 -0
- package/src/code-editor/Page.tsx +207 -0
- package/src/code-editor/data.ts +130 -0
- package/src/code-editor/template.meta.ts +12 -0
- package/src/contact/Page.test.tsx +33 -0
- package/src/contact/Page.tsx +272 -0
- package/src/contact/PublicFrame.tsx +84 -0
- package/src/contact/data.ts +129 -0
- package/src/contact/template.meta.ts +12 -0
- package/src/create-wizard/Overlay.test.tsx +68 -0
- package/src/create-wizard/Page.test.tsx +57 -0
- package/src/create-wizard/Page.tsx +246 -0
- package/src/create-wizard/data.ts +139 -0
- package/src/create-wizard/examples/Overlay.tsx +60 -0
- package/src/create-wizard/template.meta.ts +12 -0
- package/src/create-workspace/Page.test.tsx +41 -0
- package/src/create-workspace/Page.tsx +227 -0
- package/src/create-workspace/data.ts +61 -0
- package/src/create-workspace/template.meta.ts +12 -0
- package/src/data-explorer/Page.test.tsx +45 -0
- package/src/data-explorer/Page.tsx +247 -0
- package/src/data-explorer/data.ts +94 -0
- package/src/data-explorer/template.meta.ts +12 -0
- package/src/docs-article/ArticleFeedback.tsx +75 -0
- package/src/docs-article/Page.test.tsx +34 -0
- package/src/docs-article/Page.tsx +200 -0
- package/src/docs-article/data.ts +65 -0
- package/src/docs-article/template.meta.ts +12 -0
- package/src/evaluations/Page.test.tsx +40 -0
- package/src/evaluations/Page.tsx +238 -0
- package/src/evaluations/data.ts +194 -0
- package/src/evaluations/template.meta.ts +12 -0
- package/src/forgot-password/Page.test.tsx +199 -0
- package/src/forgot-password/Page.tsx +66 -0
- package/src/forgot-password/data.ts +5 -0
- package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
- package/src/forgot-password/template.meta.ts +12 -0
- package/src/getting-started/Page.test.tsx +27 -0
- package/src/getting-started/Page.tsx +125 -0
- package/src/getting-started/data.ts +67 -0
- package/src/getting-started/template.meta.ts +12 -0
- package/src/help-home/Page.test.tsx +33 -0
- package/src/help-home/Page.tsx +159 -0
- package/src/help-home/data.ts +151 -0
- package/src/help-home/template.meta.ts +12 -0
- package/src/home-dashboard/Page.test.tsx +57 -0
- package/src/home-dashboard/Page.tsx +152 -0
- package/src/home-dashboard/data.ts +66 -0
- package/src/home-dashboard/template.meta.ts +12 -0
- package/src/incident-board/Page.test.tsx +77 -0
- package/src/incident-board/Page.tsx +263 -0
- package/src/incident-board/data.ts +220 -0
- package/src/incident-board/template.meta.ts +12 -0
- package/src/incident-console/Page.test.tsx +49 -0
- package/src/incident-console/Page.tsx +274 -0
- package/src/incident-console/data.ts +176 -0
- package/src/incident-console/template.meta.ts +12 -0
- package/src/index.ts +250 -0
- package/src/keyboard-shortcuts/Page.test.tsx +45 -0
- package/src/keyboard-shortcuts/Page.tsx +94 -0
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
- package/src/keyboard-shortcuts/data.ts +84 -0
- package/src/keyboard-shortcuts/template.meta.ts +12 -0
- package/src/landing/Page.test.tsx +27 -0
- package/src/landing/Page.tsx +110 -0
- package/src/landing/data.ts +17 -0
- package/src/landing/template.meta.ts +11 -0
- package/src/live-event-stream/Page.test.tsx +251 -0
- package/src/live-event-stream/Page.tsx +307 -0
- package/src/live-event-stream/data.ts +44 -0
- package/src/live-event-stream/examples/Connecting.tsx +5 -0
- package/src/live-event-stream/examples/Error.tsx +5 -0
- package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
- package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
- package/src/live-event-stream/examples/Stopped.tsx +6 -0
- package/src/live-event-stream/template.meta.ts +74 -0
- package/src/messaging/Page.test.tsx +34 -0
- package/src/messaging/Page.tsx +236 -0
- package/src/messaging/data.ts +118 -0
- package/src/messaging/template.meta.ts +12 -0
- package/src/meta.ts +24 -0
- package/src/mfa/Page.test.tsx +22 -0
- package/src/mfa/Page.tsx +45 -0
- package/src/mfa/data.ts +6 -0
- package/src/mfa/template.meta.ts +12 -0
- package/src/model-providers/Page.test.tsx +58 -0
- package/src/model-providers/Page.tsx +198 -0
- package/src/model-providers/data.ts +101 -0
- package/src/model-providers/template.meta.ts +12 -0
- package/src/notification-inbox/Page.test.tsx +49 -0
- package/src/notification-inbox/Page.tsx +237 -0
- package/src/notification-inbox/data.ts +129 -0
- package/src/notification-inbox/template.meta.ts +12 -0
- package/src/pending-activation/Page.test.tsx +390 -0
- package/src/pending-activation/Page.tsx +145 -0
- package/src/pending-activation/data.ts +25 -0
- package/src/pending-activation/template.meta.ts +12 -0
- package/src/pricing/ComparisonTable.tsx +75 -0
- package/src/pricing/Page.test.tsx +29 -0
- package/src/pricing/Page.tsx +83 -0
- package/src/pricing/PublicFrame.tsx +84 -0
- package/src/pricing/data.ts +104 -0
- package/src/pricing/template.meta.ts +12 -0
- package/src/prompt-playground/Page.test.tsx +72 -0
- package/src/prompt-playground/Page.tsx +254 -0
- package/src/prompt-playground/data.ts +68 -0
- package/src/prompt-playground/template.meta.ts +12 -0
- package/src/public-hub/Page.test.tsx +176 -0
- package/src/public-hub/Page.tsx +125 -0
- package/src/public-hub/Source.test.ts +54 -0
- package/src/public-hub/data.ts +24 -0
- package/src/public-hub/examples/Dark.tsx +11 -0
- package/src/public-hub/examples/Full.tsx +6 -0
- package/src/public-hub/examples/HighContrast.tsx +11 -0
- package/src/public-hub/examples/Light.tsx +11 -0
- package/src/public-hub/examples/Loading.tsx +6 -0
- package/src/public-hub/examples/Minimal.tsx +5 -0
- package/src/public-hub/examples/MinimalLoading.tsx +5 -0
- package/src/public-hub/template.meta.ts +60 -0
- package/src/reset-password/Page.test.tsx +27 -0
- package/src/reset-password/Page.tsx +46 -0
- package/src/reset-password/data.ts +5 -0
- package/src/reset-password/template.meta.ts +12 -0
- package/src/resource-detail/Page.test.tsx +260 -0
- package/src/resource-detail/Page.tsx +262 -0
- package/src/resource-detail/data.ts +55 -0
- package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
- package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
- package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
- package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
- package/src/resource-detail/examples/shell.ts +12 -0
- package/src/resource-detail/template.meta.ts +21 -0
- package/src/resource-form/Page.test.tsx +40 -0
- package/src/resource-form/Page.tsx +186 -0
- package/src/resource-form/data.ts +63 -0
- package/src/resource-form/template.meta.ts +12 -0
- package/src/resource-gallery/Page.test.tsx +42 -0
- package/src/resource-gallery/Page.tsx +125 -0
- package/src/resource-gallery/data.ts +44 -0
- package/src/resource-gallery/template.meta.ts +20 -0
- package/src/resource-index/Page.test.tsx +120 -0
- package/src/resource-index/Page.tsx +216 -0
- package/src/resource-index/data.ts +84 -0
- package/src/resource-index/template.meta.ts +21 -0
- package/src/resource-tree/Page.test.tsx +31 -0
- package/src/resource-tree/Page.tsx +232 -0
- package/src/resource-tree/data.ts +180 -0
- package/src/resource-tree/template.meta.ts +12 -0
- package/src/run-trace/Page.test.tsx +56 -0
- package/src/run-trace/Page.tsx +242 -0
- package/src/run-trace/data.ts +171 -0
- package/src/run-trace/template.meta.ts +12 -0
- package/src/scorecard/Page.test.tsx +38 -0
- package/src/scorecard/Page.tsx +160 -0
- package/src/scorecard/TargetsTable.tsx +92 -0
- package/src/scorecard/data.ts +118 -0
- package/src/scorecard/template.meta.ts +12 -0
- package/src/search-results/Page.test.tsx +43 -0
- package/src/search-results/Page.tsx +243 -0
- package/src/search-results/data.ts +130 -0
- package/src/search-results/template.meta.ts +12 -0
- package/src/settings-api-keys/Page.test.tsx +39 -0
- package/src/settings-api-keys/Page.tsx +171 -0
- package/src/settings-api-keys/data.ts +58 -0
- package/src/settings-api-keys/template.meta.ts +12 -0
- package/src/settings-appearance/Page.test.tsx +44 -0
- package/src/settings-appearance/Page.tsx +92 -0
- package/src/settings-appearance/data.ts +21 -0
- package/src/settings-appearance/template.meta.ts +11 -0
- package/src/settings-billing/Page.test.tsx +31 -0
- package/src/settings-billing/Page.tsx +71 -0
- package/src/settings-billing/data.ts +15 -0
- package/src/settings-billing/template.meta.ts +19 -0
- package/src/settings-integrations/Page.test.tsx +31 -0
- package/src/settings-integrations/Page.tsx +185 -0
- package/src/settings-integrations/data.ts +116 -0
- package/src/settings-integrations/template.meta.ts +11 -0
- package/src/settings-members/Page.test.tsx +32 -0
- package/src/settings-members/Page.tsx +140 -0
- package/src/settings-members/data.ts +28 -0
- package/src/settings-members/template.meta.ts +12 -0
- package/src/settings-notifications/Page.test.tsx +31 -0
- package/src/settings-notifications/Page.tsx +71 -0
- package/src/settings-notifications/data.ts +12 -0
- package/src/settings-notifications/template.meta.ts +11 -0
- package/src/settings-profile/Baseline.test.tsx +9 -0
- package/src/settings-profile/Editable.test.tsx +21 -0
- package/src/settings-profile/Page.test.tsx +41 -0
- package/src/settings-profile/Page.tsx +133 -0
- package/src/settings-profile/ReadOnly.test.tsx +143 -0
- package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
- package/src/settings-profile/SettingsFrame.test.tsx +55 -0
- package/src/settings-profile/SettingsFrame.tsx +88 -0
- package/src/settings-profile/__tests__/dom.ts +8 -0
- package/src/settings-profile/data.ts +41 -0
- package/src/settings-profile/examples/ReadOnly.tsx +31 -0
- package/src/settings-profile/fixtures/editable.snap +1 -0
- package/src/settings-profile/settings-nav.ts +98 -0
- package/src/settings-profile/template.meta.ts +21 -0
- package/src/settings-security/MfaSetupDialog.tsx +96 -0
- package/src/settings-security/Page.test.tsx +44 -0
- package/src/settings-security/Page.tsx +158 -0
- package/src/settings-security/data.ts +11 -0
- package/src/settings-security/template.meta.ts +11 -0
- package/src/settings-workspace/Page.test.tsx +44 -0
- package/src/settings-workspace/Page.tsx +120 -0
- package/src/settings-workspace/data.ts +44 -0
- package/src/settings-workspace/template.meta.ts +19 -0
- package/src/shared/ConsoleShell.test.tsx +81 -0
- package/src/shared/ConsoleShell.tsx +98 -0
- package/src/shell-sidebar/Page.test.tsx +21 -0
- package/src/shell-sidebar/Page.tsx +85 -0
- package/src/shell-sidebar/data.ts +24 -0
- package/src/shell-sidebar/template.meta.ts +12 -0
- package/src/shell-topnav/Page.test.tsx +25 -0
- package/src/shell-topnav/Page.tsx +94 -0
- package/src/shell-topnav/data.ts +25 -0
- package/src/shell-topnav/template.meta.ts +12 -0
- package/src/sign-in/BrandPanel.tsx +34 -0
- package/src/sign-in/Page.test.tsx +41 -0
- package/src/sign-in/Page.tsx +73 -0
- package/src/sign-in/data.ts +11 -0
- package/src/sign-in/template.meta.ts +12 -0
- package/src/sign-in-card/Page.test.tsx +182 -0
- package/src/sign-in-card/Page.tsx +82 -0
- package/src/sign-in-card/data.ts +8 -0
- package/src/sign-in-card/examples/Username.tsx +28 -0
- package/src/sign-in-card/template.meta.ts +12 -0
- package/src/sign-up/Page.test.tsx +33 -0
- package/src/sign-up/Page.tsx +70 -0
- package/src/sign-up/data.ts +15 -0
- package/src/sign-up/template.meta.ts +12 -0
- package/src/sso-login/Page.test.tsx +41 -0
- package/src/sso-login/Page.tsx +151 -0
- package/src/sso-login/data.ts +10 -0
- package/src/sso-login/template.meta.ts +12 -0
- package/src/status-403/Page.test.tsx +23 -0
- package/src/status-403/Page.tsx +92 -0
- package/src/status-403/data.ts +13 -0
- package/src/status-403/template.meta.ts +12 -0
- package/src/status-404/Page.test.tsx +23 -0
- package/src/status-404/Page.tsx +60 -0
- package/src/status-404/PageHeading.test.tsx +52 -0
- package/src/status-404/data.ts +12 -0
- package/src/status-404/examples/PageHeader.tsx +28 -0
- package/src/status-404/template.meta.ts +12 -0
- package/src/status-500/Page.test.tsx +24 -0
- package/src/status-500/Page.tsx +70 -0
- package/src/status-500/data.ts +20 -0
- package/src/status-500/template.meta.ts +12 -0
- package/src/status-maintenance/Page.test.tsx +24 -0
- package/src/status-maintenance/Page.tsx +84 -0
- package/src/status-maintenance/data.ts +14 -0
- package/src/status-maintenance/template.meta.ts +12 -0
- package/src/styles.css +11 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/setup.ts +36 -0
- package/src/upgrade-required/Page.test.tsx +28 -0
- package/src/upgrade-required/Page.tsx +79 -0
- package/src/upgrade-required/data.ts +19 -0
- package/src/upgrade-required/template.meta.ts +12 -0
- package/src/usage-analytics/Page.test.tsx +83 -0
- package/src/usage-analytics/Page.tsx +184 -0
- package/src/usage-analytics/data.ts +161 -0
- package/src/usage-analytics/template.meta.ts +21 -0
- package/src/verify-email/Page.test.tsx +32 -0
- package/src/verify-email/Page.tsx +118 -0
- package/src/verify-email/data.ts +7 -0
- package/src/verify-email/template.meta.ts +12 -0
- package/src/workflow-builder/Page.test.tsx +97 -0
- package/src/workflow-builder/Page.tsx +163 -0
- package/src/workflow-builder/data.ts +13 -0
- package/src/workflow-builder/template.meta.ts +12 -0
- package/README.md +0 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ThemeProvider } from '@gntik-ai/ui';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
6
|
+
import SettingsAppearancePage from './Page';
|
|
7
|
+
|
|
8
|
+
const renderPage = (props: Parameters<typeof SettingsAppearancePage>[0] = {}) =>
|
|
9
|
+
render(
|
|
10
|
+
<ThemeProvider storageKey={null}>
|
|
11
|
+
<SettingsAppearancePage {...props} />
|
|
12
|
+
</ThemeProvider>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
describe('SettingsAppearancePage', () => {
|
|
16
|
+
it('renders theme, density and language', () => {
|
|
17
|
+
renderPage();
|
|
18
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Appearance' })).toBeInTheDocument();
|
|
19
|
+
expect(screen.getAllByRole('group', { name: 'Theme' }).length).toBeGreaterThan(0);
|
|
20
|
+
expect(screen.getByRole('group', { name: 'Density' })).toBeInTheDocument();
|
|
21
|
+
expect(screen.getByRole('combobox', { name: 'Language' })).toBeInTheDocument();
|
|
22
|
+
}, 15000);
|
|
23
|
+
|
|
24
|
+
it('switches the theme through useTheme', async () => {
|
|
25
|
+
renderPage();
|
|
26
|
+
const display = screen.getByRole('region', { name: 'Display' });
|
|
27
|
+
const light = within(display).getByRole('button', { name: 'Light' });
|
|
28
|
+
await userEvent.click(light);
|
|
29
|
+
expect(document.documentElement).not.toHaveClass('dark');
|
|
30
|
+
expect(light).toHaveAttribute('aria-pressed', 'true');
|
|
31
|
+
}, 15000);
|
|
32
|
+
|
|
33
|
+
it('changes density', async () => {
|
|
34
|
+
const onDensityChange = vi.fn();
|
|
35
|
+
renderPage({ onDensityChange });
|
|
36
|
+
await userEvent.click(screen.getByRole('button', { name: 'Compact' }));
|
|
37
|
+
expect(onDensityChange).toHaveBeenCalledWith('compact');
|
|
38
|
+
}, 15000);
|
|
39
|
+
|
|
40
|
+
it('has no axe violations', async () => {
|
|
41
|
+
const { container } = renderPage();
|
|
42
|
+
await expectNoAxeViolations(container);
|
|
43
|
+
}, 15000);
|
|
44
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { SettingsRow } from '@gntik-ai/blocks';
|
|
2
|
+
import { Section, SimpleSelect, ThemeSwitcher, Toggle, ToggleGroup, useTheme } from '@gntik-ai/ui';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { SettingsFrame, type SettingsFrameOptions } from '../settings-profile/SettingsFrame';
|
|
5
|
+
import { THEME_LABELS, densities, languages as defaultLanguages, type Density, type LanguageOption } from './data';
|
|
6
|
+
|
|
7
|
+
export interface SettingsAppearanceProps extends SettingsFrameOptions {
|
|
8
|
+
density: Density;
|
|
9
|
+
onDensityChange: (density: Density) => void;
|
|
10
|
+
language: string;
|
|
11
|
+
languages: LanguageOption[];
|
|
12
|
+
onLanguageChange: (language: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Appearance settings: ThemeSwitcher (reads and sets the mode through useTheme, so it needs the
|
|
17
|
+
* app's ThemeProvider), density ToggleGroup and language Select, laid out as SettingsRows.
|
|
18
|
+
*/
|
|
19
|
+
export default function SettingsAppearancePage({
|
|
20
|
+
density: initialDensity = 'comfortable',
|
|
21
|
+
onDensityChange,
|
|
22
|
+
language: initialLanguage = 'en',
|
|
23
|
+
languages = defaultLanguages,
|
|
24
|
+
onLanguageChange,
|
|
25
|
+
...frame
|
|
26
|
+
}: Partial<SettingsAppearanceProps>) {
|
|
27
|
+
const { mode, resolved } = useTheme();
|
|
28
|
+
const [density, setDensity] = useState<Density>(initialDensity);
|
|
29
|
+
const [language, setLanguage] = useState(initialLanguage);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<SettingsFrame
|
|
33
|
+
page="appearance"
|
|
34
|
+
description="How the interface looks on this device. Saved automatically."
|
|
35
|
+
{...frame}
|
|
36
|
+
>
|
|
37
|
+
<Section title="Display" divided>
|
|
38
|
+
<SettingsRow
|
|
39
|
+
label="Theme"
|
|
40
|
+
description={
|
|
41
|
+
mode === 'system'
|
|
42
|
+
? `Follows your system setting (now ${THEME_LABELS[resolved]}).`
|
|
43
|
+
: 'Dark is the default. High contrast strengthens borders and text.'
|
|
44
|
+
}
|
|
45
|
+
control={<ThemeSwitcher label="Theme" />}
|
|
46
|
+
/>
|
|
47
|
+
<SettingsRow
|
|
48
|
+
label="Density"
|
|
49
|
+
description="Compact fits more rows in tables and lists."
|
|
50
|
+
control={({ labelId, descriptionId }) => (
|
|
51
|
+
<ToggleGroup
|
|
52
|
+
size="sm"
|
|
53
|
+
aria-labelledby={labelId}
|
|
54
|
+
aria-describedby={descriptionId}
|
|
55
|
+
value={[density]}
|
|
56
|
+
onValueChange={(v) => {
|
|
57
|
+
const next = v[0] as Density | undefined;
|
|
58
|
+
if (!next) return;
|
|
59
|
+
setDensity(next);
|
|
60
|
+
onDensityChange?.(next);
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
{densities.map((d) => (
|
|
64
|
+
<Toggle key={d.value} value={d.value}>
|
|
65
|
+
{d.label}
|
|
66
|
+
</Toggle>
|
|
67
|
+
))}
|
|
68
|
+
</ToggleGroup>
|
|
69
|
+
)}
|
|
70
|
+
/>
|
|
71
|
+
<SettingsRow
|
|
72
|
+
label="Language"
|
|
73
|
+
description="Used for menus, emails and dates."
|
|
74
|
+
control={
|
|
75
|
+
<SimpleSelect
|
|
76
|
+
aria-label="Language"
|
|
77
|
+
size="sm"
|
|
78
|
+
items={languages}
|
|
79
|
+
value={language}
|
|
80
|
+
onValueChange={(v) => {
|
|
81
|
+
if (v == null) return;
|
|
82
|
+
setLanguage(v);
|
|
83
|
+
onLanguageChange?.(v);
|
|
84
|
+
}}
|
|
85
|
+
className="w-44"
|
|
86
|
+
/>
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
</Section>
|
|
90
|
+
</SettingsFrame>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Density = 'comfortable' | 'compact';
|
|
2
|
+
|
|
3
|
+
export interface LanguageOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const densities: Array<{ value: Density; label: string }> = [
|
|
9
|
+
{ value: 'comfortable', label: 'Comfortable' },
|
|
10
|
+
{ value: 'compact', label: 'Compact' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const languages: LanguageOption[] = [
|
|
14
|
+
{ value: 'en', label: 'English' },
|
|
15
|
+
{ value: 'es', label: 'Español' },
|
|
16
|
+
{ value: 'pt', label: 'Português' },
|
|
17
|
+
{ value: 'de', label: 'Deutsch' },
|
|
18
|
+
{ value: 'fr', label: 'Français' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const THEME_LABELS = { dark: 'Dark', light: 'Light', high_contrast: 'High contrast' } as const;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Appearance settings',
|
|
5
|
+
family: 'Settings',
|
|
6
|
+
priority: 'P2',
|
|
7
|
+
status: 'beta',
|
|
8
|
+
description: 'Theme switcher wired to the ThemeProvider, table density and interface language.',
|
|
9
|
+
layout: 'SettingsLayout',
|
|
10
|
+
blocks: ['app-sidebar', 'app-topbar', 'page-header', 'settings-row'],
|
|
11
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
5
|
+
import SettingsBillingPage from './Page';
|
|
6
|
+
|
|
7
|
+
describe('SettingsBillingPage', () => {
|
|
8
|
+
it('renders plan, usage, payment and invoices', () => {
|
|
9
|
+
render(<SettingsBillingPage />);
|
|
10
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Billing' })).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByRole('heading', { level: 2, name: 'Scale plan' })).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByRole('heading', { level: 2, name: 'Payment method' })).toBeInTheDocument();
|
|
13
|
+
expect(screen.queryByRole('heading', { name: 'Plan' })).toBeNull();
|
|
14
|
+
expect(screen.getByRole('region', { name: 'Plan quotas' })).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByRole('heading', { name: 'Invoices' })).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
17
|
+
}, 15000);
|
|
18
|
+
|
|
19
|
+
it('switches the billing cycle', async () => {
|
|
20
|
+
const onCycleChange = vi.fn();
|
|
21
|
+
render(<SettingsBillingPage onCycleChange={onCycleChange} />);
|
|
22
|
+
await userEvent.click(screen.getByRole('button', { name: /^Annual/ }));
|
|
23
|
+
expect(onCycleChange).toHaveBeenCalledWith('annual');
|
|
24
|
+
expect(screen.getByText(/Billed annually/)).toBeInTheDocument();
|
|
25
|
+
}, 15000);
|
|
26
|
+
|
|
27
|
+
it('has no axe violations', async () => {
|
|
28
|
+
const { container } = render(<SettingsBillingPage />);
|
|
29
|
+
await expectNoAxeViolations(container);
|
|
30
|
+
}, 15000);
|
|
31
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvoiceTable,
|
|
3
|
+
PaymentMethodCard,
|
|
4
|
+
PlanCard,
|
|
5
|
+
QuotaMeters,
|
|
6
|
+
type BillingCycle,
|
|
7
|
+
type BillingPlan,
|
|
8
|
+
type Invoice,
|
|
9
|
+
type PaymentMethod,
|
|
10
|
+
type Quota,
|
|
11
|
+
} from '@gntik-ai/blocks';
|
|
12
|
+
import { Section } from '@gntik-ai/ui';
|
|
13
|
+
import { SettingsFrame, type SettingsFrameOptions } from '../settings-profile/SettingsFrame';
|
|
14
|
+
import {
|
|
15
|
+
invoices as defaultInvoices,
|
|
16
|
+
paymentMethod as defaultPaymentMethod,
|
|
17
|
+
plan as defaultPlan,
|
|
18
|
+
quotas as defaultQuotas,
|
|
19
|
+
} from './data';
|
|
20
|
+
|
|
21
|
+
export interface SettingsBillingProps extends SettingsFrameOptions {
|
|
22
|
+
plan: BillingPlan;
|
|
23
|
+
quotas: Quota[];
|
|
24
|
+
paymentMethod: PaymentMethod;
|
|
25
|
+
invoices: Invoice[];
|
|
26
|
+
/** ISO 4217 code for the invoice table. */
|
|
27
|
+
currency: string;
|
|
28
|
+
onUpgrade: () => void;
|
|
29
|
+
onManagePlan: () => void;
|
|
30
|
+
onCycleChange: (cycle: BillingCycle) => void;
|
|
31
|
+
onUpdatePaymentMethod: () => void;
|
|
32
|
+
onDownloadInvoice: (invoice: Invoice) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Billing settings: PlanCard, QuotaMeters, PaymentMethodCard and InvoiceTable. */
|
|
36
|
+
export default function SettingsBillingPage({
|
|
37
|
+
plan = defaultPlan,
|
|
38
|
+
quotas = defaultQuotas,
|
|
39
|
+
paymentMethod = defaultPaymentMethod,
|
|
40
|
+
invoices = defaultInvoices,
|
|
41
|
+
currency = 'USD',
|
|
42
|
+
onUpgrade,
|
|
43
|
+
onManagePlan,
|
|
44
|
+
onCycleChange,
|
|
45
|
+
onUpdatePaymentMethod,
|
|
46
|
+
onDownloadInvoice,
|
|
47
|
+
...frame
|
|
48
|
+
}: Partial<SettingsBillingProps>) {
|
|
49
|
+
return (
|
|
50
|
+
<SettingsFrame
|
|
51
|
+
page="billing"
|
|
52
|
+
width="wide"
|
|
53
|
+
description="Your plan, usage this cycle, payment method and invoices."
|
|
54
|
+
{...frame}
|
|
55
|
+
>
|
|
56
|
+
<PlanCard titleAs="h2" plan={plan} onUpgrade={onUpgrade} onManage={onManagePlan} onCycleChange={onCycleChange} />
|
|
57
|
+
<Section title="Usage" description="Resets at the start of each billing cycle.">
|
|
58
|
+
<QuotaMeters quotas={quotas} title="Plan quotas" />
|
|
59
|
+
</Section>
|
|
60
|
+
<PaymentMethodCard
|
|
61
|
+
titleAs="h2"
|
|
62
|
+
method={paymentMethod}
|
|
63
|
+
description="Charged on the first day of each cycle."
|
|
64
|
+
onUpdate={onUpdatePaymentMethod}
|
|
65
|
+
/>
|
|
66
|
+
<Section title="Invoices">
|
|
67
|
+
<InvoiceTable invoices={invoices} currency={currency} onDownload={onDownloadInvoice} />
|
|
68
|
+
</Section>
|
|
69
|
+
</SettingsFrame>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sampleInvoices,
|
|
3
|
+
samplePaymentMethod,
|
|
4
|
+
samplePlan,
|
|
5
|
+
sampleQuotas,
|
|
6
|
+
type BillingPlan,
|
|
7
|
+
type Invoice,
|
|
8
|
+
type PaymentMethod,
|
|
9
|
+
type Quota,
|
|
10
|
+
} from '@gntik-ai/blocks';
|
|
11
|
+
|
|
12
|
+
export const plan: BillingPlan = samplePlan;
|
|
13
|
+
export const quotas: Quota[] = sampleQuotas;
|
|
14
|
+
export const paymentMethod: PaymentMethod = samplePaymentMethod;
|
|
15
|
+
export const invoices: Invoice[] = sampleInvoices;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Billing settings',
|
|
5
|
+
family: 'Settings',
|
|
6
|
+
priority: 'P2',
|
|
7
|
+
status: 'beta',
|
|
8
|
+
description: 'Current plan with billing cycle, quota meters, the default payment method and invoice history.',
|
|
9
|
+
layout: 'SettingsLayout',
|
|
10
|
+
blocks: [
|
|
11
|
+
'app-sidebar',
|
|
12
|
+
'app-topbar',
|
|
13
|
+
'page-header',
|
|
14
|
+
'plan-card',
|
|
15
|
+
'quota-meters',
|
|
16
|
+
'payment-method-card',
|
|
17
|
+
'invoice-table',
|
|
18
|
+
],
|
|
19
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { render, screen, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { expectNoAxeViolations } from '../test/a11y';
|
|
5
|
+
import SettingsIntegrationsPage from './Page';
|
|
6
|
+
|
|
7
|
+
describe('SettingsIntegrationsPage', () => {
|
|
8
|
+
it('renders connectors, endpoints and deliveries', () => {
|
|
9
|
+
render(<SettingsIntegrationsPage />);
|
|
10
|
+
expect(screen.getByRole('heading', { level: 1, name: 'Integrations' })).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByText('3 of 6 connected.')).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByRole('table', { name: 'Webhook endpoints' })).toBeInTheDocument();
|
|
13
|
+
expect(within(screen.getByRole('table', { name: 'Webhook deliveries' })).getAllByRole('row')).toHaveLength(7);
|
|
14
|
+
}, 15000);
|
|
15
|
+
|
|
16
|
+
it('toggles a connector', async () => {
|
|
17
|
+
const onConnectorChange = vi.fn();
|
|
18
|
+
render(<SettingsIntegrationsPage onConnectorChange={onConnectorChange} />);
|
|
19
|
+
const toggle = screen.getByRole('switch', { name: 'Issue tracker' });
|
|
20
|
+
expect(toggle).not.toBeChecked();
|
|
21
|
+
await userEvent.click(toggle);
|
|
22
|
+
expect(toggle).toBeChecked();
|
|
23
|
+
expect(onConnectorChange).toHaveBeenCalledWith('issues', true);
|
|
24
|
+
expect(screen.getByText('4 of 6 connected.')).toBeInTheDocument();
|
|
25
|
+
}, 15000);
|
|
26
|
+
|
|
27
|
+
it('has no axe violations', async () => {
|
|
28
|
+
const { container } = render(<SettingsIntegrationsPage />);
|
|
29
|
+
await expectNoAxeViolations(container);
|
|
30
|
+
}, 15000);
|
|
31
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { SettingsRow } from '@gntik-ai/blocks';
|
|
2
|
+
import { Plus } from '@gntik-ai/icons';
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Card,
|
|
6
|
+
Grid,
|
|
7
|
+
Section,
|
|
8
|
+
SimpleSelect,
|
|
9
|
+
StatusTag,
|
|
10
|
+
Table,
|
|
11
|
+
TableBody,
|
|
12
|
+
TableCaption,
|
|
13
|
+
TableCell,
|
|
14
|
+
TableHead,
|
|
15
|
+
TableHeader,
|
|
16
|
+
TableRow,
|
|
17
|
+
Timestamp,
|
|
18
|
+
type StatusDefinition,
|
|
19
|
+
} from '@gntik-ai/ui';
|
|
20
|
+
import { useState } from 'react';
|
|
21
|
+
import { SettingsFrame, type SettingsFrameOptions } from '../settings-profile/SettingsFrame';
|
|
22
|
+
import {
|
|
23
|
+
connectors as defaultConnectors,
|
|
24
|
+
webhookDeliveries,
|
|
25
|
+
webhookEndpoints,
|
|
26
|
+
type Connector,
|
|
27
|
+
type WebhookDelivery,
|
|
28
|
+
type WebhookEndpoint,
|
|
29
|
+
} from './data';
|
|
30
|
+
|
|
31
|
+
export interface SettingsIntegrationsProps extends SettingsFrameOptions {
|
|
32
|
+
connectors: Connector[];
|
|
33
|
+
endpoints: WebhookEndpoint[];
|
|
34
|
+
deliveries: WebhookDelivery[];
|
|
35
|
+
onConnectorChange: (connectorId: string, enabled: boolean) => void;
|
|
36
|
+
onAddEndpoint: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ENDPOINT_STATUSES: Record<string, StatusDefinition> = {
|
|
40
|
+
active: { label: 'Active', tone: 'success' },
|
|
41
|
+
paused: { label: 'Paused', tone: 'neutral' },
|
|
42
|
+
failing: { label: 'Failing', tone: 'destructive' },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const deliveryTone = (code: number | null) =>
|
|
46
|
+
code == null || code >= 500 ? 'destructive' : code >= 400 ? 'warning' : 'success';
|
|
47
|
+
|
|
48
|
+
/** Integrations settings: connector cards (SettingsRow switches), webhook endpoints Table, delivery log Table. */
|
|
49
|
+
export default function SettingsIntegrationsPage({
|
|
50
|
+
connectors = defaultConnectors,
|
|
51
|
+
endpoints = webhookEndpoints,
|
|
52
|
+
deliveries = webhookDeliveries,
|
|
53
|
+
onConnectorChange,
|
|
54
|
+
onAddEndpoint,
|
|
55
|
+
...frame
|
|
56
|
+
}: Partial<SettingsIntegrationsProps>) {
|
|
57
|
+
const [enabled, setEnabled] = useState<Record<string, boolean>>(() =>
|
|
58
|
+
Object.fromEntries(connectors.map((c) => [c.id, c.enabled])),
|
|
59
|
+
);
|
|
60
|
+
const [endpointFilter, setEndpointFilter] = useState('all');
|
|
61
|
+
const shown = endpointFilter === 'all' ? deliveries : deliveries.filter((d) => d.endpointId === endpointFilter);
|
|
62
|
+
const urlOf = (id: string) => endpoints.find((e) => e.id === id)?.url ?? id;
|
|
63
|
+
const connectedCount = Object.values(enabled).filter(Boolean).length;
|
|
64
|
+
|
|
65
|
+
const toggle = (id: string, on: boolean) => {
|
|
66
|
+
setEnabled((s) => ({ ...s, [id]: on }));
|
|
67
|
+
onConnectorChange?.(id, on);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<SettingsFrame
|
|
72
|
+
page="integrations"
|
|
73
|
+
width="wide"
|
|
74
|
+
description="Connect the workspace to other tools and send events to your own endpoints."
|
|
75
|
+
actions={[{ label: 'Add endpoint', icon: Plus, variant: 'primary', onClick: onAddEndpoint }]}
|
|
76
|
+
{...frame}
|
|
77
|
+
>
|
|
78
|
+
<Section title="Connectors" description={`${connectedCount} of ${connectors.length} connected.`}>
|
|
79
|
+
<Grid cols={{ base: 1, md: 2 }} gap={4}>
|
|
80
|
+
{connectors.map(({ id, name, description, icon: ConnectorIcon }) => (
|
|
81
|
+
<Card key={id} className="px-5">
|
|
82
|
+
<SettingsRow
|
|
83
|
+
label={
|
|
84
|
+
<span className="flex items-center gap-2.5">
|
|
85
|
+
<ConnectorIcon size={16} aria-hidden className="text-muted-foreground" />
|
|
86
|
+
{name}
|
|
87
|
+
</span>
|
|
88
|
+
}
|
|
89
|
+
description={description}
|
|
90
|
+
checked={enabled[id] ?? false}
|
|
91
|
+
onCheckedChange={(on) => toggle(id, on)}
|
|
92
|
+
>
|
|
93
|
+
<Badge size="sm" tone={enabled[id] ? 'success' : 'neutral'} dot className="mt-2">
|
|
94
|
+
{enabled[id] ? 'Connected' : 'Off'}
|
|
95
|
+
</Badge>
|
|
96
|
+
</SettingsRow>
|
|
97
|
+
</Card>
|
|
98
|
+
))}
|
|
99
|
+
</Grid>
|
|
100
|
+
</Section>
|
|
101
|
+
<Section title="Webhook endpoints" description="We sign every request; retries back off for up to 24 hours.">
|
|
102
|
+
<Table className="min-w-[620px]">
|
|
103
|
+
<TableCaption srOnly>Webhook endpoints</TableCaption>
|
|
104
|
+
<TableHeader>
|
|
105
|
+
<TableRow>
|
|
106
|
+
<TableHead>Endpoint</TableHead>
|
|
107
|
+
<TableHead>Events</TableHead>
|
|
108
|
+
<TableHead>Status</TableHead>
|
|
109
|
+
<TableHead align="right">Last delivery</TableHead>
|
|
110
|
+
</TableRow>
|
|
111
|
+
</TableHeader>
|
|
112
|
+
<TableBody>
|
|
113
|
+
{endpoints.map((e) => (
|
|
114
|
+
<TableRow key={e.id}>
|
|
115
|
+
<TableCell className="font-mono text-[12px] text-foreground">{e.url}</TableCell>
|
|
116
|
+
<TableCell>
|
|
117
|
+
<div className="flex flex-wrap gap-1">
|
|
118
|
+
{e.events.map((ev) => (
|
|
119
|
+
<Badge key={ev} size="sm" className="font-mono">
|
|
120
|
+
{ev}
|
|
121
|
+
</Badge>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
</TableCell>
|
|
125
|
+
<TableCell>
|
|
126
|
+
<StatusTag status={e.status} statuses={ENDPOINT_STATUSES} />
|
|
127
|
+
</TableCell>
|
|
128
|
+
<TableCell align="right" className="text-muted-foreground">
|
|
129
|
+
{e.lastDeliveryAt ? <Timestamp value={e.lastDeliveryAt} tooltip={false} /> : 'Never'}
|
|
130
|
+
</TableCell>
|
|
131
|
+
</TableRow>
|
|
132
|
+
))}
|
|
133
|
+
</TableBody>
|
|
134
|
+
</Table>
|
|
135
|
+
</Section>
|
|
136
|
+
<Section
|
|
137
|
+
title="Recent deliveries"
|
|
138
|
+
actions={
|
|
139
|
+
<SimpleSelect
|
|
140
|
+
aria-label="Filter deliveries by endpoint"
|
|
141
|
+
size="sm"
|
|
142
|
+
items={[
|
|
143
|
+
{ value: 'all', label: 'All endpoints' },
|
|
144
|
+
...endpoints.map((e) => ({ value: e.id, label: e.url.replace('https://', '') })),
|
|
145
|
+
]}
|
|
146
|
+
value={endpointFilter}
|
|
147
|
+
onValueChange={(v) => setEndpointFilter(v ?? 'all')}
|
|
148
|
+
/>
|
|
149
|
+
}
|
|
150
|
+
>
|
|
151
|
+
<Table density="compact" className="min-w-[620px]">
|
|
152
|
+
<TableCaption srOnly>Webhook deliveries</TableCaption>
|
|
153
|
+
<TableHeader>
|
|
154
|
+
<TableRow>
|
|
155
|
+
<TableHead>Response</TableHead>
|
|
156
|
+
<TableHead>Event</TableHead>
|
|
157
|
+
<TableHead>Endpoint</TableHead>
|
|
158
|
+
<TableHead align="right">Duration</TableHead>
|
|
159
|
+
<TableHead align="right">Sent</TableHead>
|
|
160
|
+
</TableRow>
|
|
161
|
+
</TableHeader>
|
|
162
|
+
<TableBody>
|
|
163
|
+
{shown.map((d) => (
|
|
164
|
+
<TableRow key={d.id}>
|
|
165
|
+
<TableCell>
|
|
166
|
+
<Badge size="sm" tone={deliveryTone(d.statusCode)} dot className="font-mono">
|
|
167
|
+
{d.statusCode ?? 'Timeout'}
|
|
168
|
+
</Badge>
|
|
169
|
+
</TableCell>
|
|
170
|
+
<TableCell className="font-mono text-[12px]">{d.event}</TableCell>
|
|
171
|
+
<TableCell className="font-mono text-[12px] text-muted-foreground">{urlOf(d.endpointId)}</TableCell>
|
|
172
|
+
<TableCell align="right" className="tabular-nums">
|
|
173
|
+
{d.durationMs} ms
|
|
174
|
+
</TableCell>
|
|
175
|
+
<TableCell align="right" className="text-muted-foreground">
|
|
176
|
+
<Timestamp value={d.at} tooltip={false} />
|
|
177
|
+
</TableCell>
|
|
178
|
+
</TableRow>
|
|
179
|
+
))}
|
|
180
|
+
</TableBody>
|
|
181
|
+
</Table>
|
|
182
|
+
</Section>
|
|
183
|
+
</SettingsFrame>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { BellRing, Database, GitBranch, HardDrive, ListTodo, MessageSquare, type LucideIcon } from '@gntik-ai/icons';
|
|
2
|
+
|
|
3
|
+
const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000).toISOString();
|
|
4
|
+
|
|
5
|
+
export interface Connector {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
icon: LucideIcon;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WebhookEndpoint {
|
|
14
|
+
id: string;
|
|
15
|
+
url: string;
|
|
16
|
+
events: string[];
|
|
17
|
+
status: 'active' | 'paused' | 'failing';
|
|
18
|
+
/** ISO date of the last delivery attempt. */
|
|
19
|
+
lastDeliveryAt: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WebhookDelivery {
|
|
23
|
+
id: string;
|
|
24
|
+
endpointId: string;
|
|
25
|
+
event: string;
|
|
26
|
+
/** HTTP status code returned by the endpoint; null on timeout. */
|
|
27
|
+
statusCode: number | null;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
/** ISO date. */
|
|
30
|
+
at: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const connectors: Connector[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'chat',
|
|
36
|
+
name: 'Team chat',
|
|
37
|
+
description: 'Post deployment and incident updates to a channel.',
|
|
38
|
+
icon: MessageSquare,
|
|
39
|
+
enabled: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'source',
|
|
43
|
+
name: 'Source control',
|
|
44
|
+
description: 'Build previews for every pull request.',
|
|
45
|
+
icon: GitBranch,
|
|
46
|
+
enabled: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'issues',
|
|
50
|
+
name: 'Issue tracker',
|
|
51
|
+
description: 'Link deployments to the issues they close.',
|
|
52
|
+
icon: ListTodo,
|
|
53
|
+
enabled: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'paging',
|
|
57
|
+
name: 'Incident paging',
|
|
58
|
+
description: 'Page the on-call member when a deployment fails.',
|
|
59
|
+
icon: BellRing,
|
|
60
|
+
enabled: false,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'warehouse',
|
|
64
|
+
name: 'Data warehouse',
|
|
65
|
+
description: 'Export usage and audit events every night.',
|
|
66
|
+
icon: Database,
|
|
67
|
+
enabled: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'storage',
|
|
71
|
+
name: 'Object storage',
|
|
72
|
+
description: 'Archive build logs and artifacts to your bucket.',
|
|
73
|
+
icon: HardDrive,
|
|
74
|
+
enabled: false,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export const webhookEndpoints: WebhookEndpoint[] = [
|
|
79
|
+
{
|
|
80
|
+
id: 'wh1',
|
|
81
|
+
url: 'https://hooks.example.com/deployments',
|
|
82
|
+
events: ['deployment.succeeded', 'deployment.failed'],
|
|
83
|
+
status: 'active',
|
|
84
|
+
lastDeliveryAt: minutesAgo(3),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'wh2',
|
|
88
|
+
url: 'https://ops.example.net/alerts',
|
|
89
|
+
events: ['incident.opened'],
|
|
90
|
+
status: 'failing',
|
|
91
|
+
lastDeliveryAt: minutesAgo(18),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'wh3',
|
|
95
|
+
url: 'https://billing.example.org/usage',
|
|
96
|
+
events: ['usage.threshold'],
|
|
97
|
+
status: 'paused',
|
|
98
|
+
lastDeliveryAt: minutesAgo(60 * 24 * 6),
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
export const webhookDeliveries: WebhookDelivery[] = [
|
|
103
|
+
{ id: 'd1', endpointId: 'wh1', event: 'deployment.succeeded', statusCode: 200, durationMs: 142, at: minutesAgo(3) },
|
|
104
|
+
{ id: 'd2', endpointId: 'wh2', event: 'incident.opened', statusCode: 503, durationMs: 2010, at: minutesAgo(18) },
|
|
105
|
+
{ id: 'd3', endpointId: 'wh1', event: 'deployment.failed', statusCode: 200, durationMs: 188, at: minutesAgo(46) },
|
|
106
|
+
{ id: 'd4', endpointId: 'wh2', event: 'incident.opened', statusCode: null, durationMs: 10000, at: minutesAgo(52) },
|
|
107
|
+
{ id: 'd5', endpointId: 'wh1', event: 'deployment.succeeded', statusCode: 204, durationMs: 97, at: minutesAgo(130) },
|
|
108
|
+
{
|
|
109
|
+
id: 'd6',
|
|
110
|
+
endpointId: 'wh3',
|
|
111
|
+
event: 'usage.threshold',
|
|
112
|
+
statusCode: 200,
|
|
113
|
+
durationMs: 311,
|
|
114
|
+
at: minutesAgo(60 * 24 * 6),
|
|
115
|
+
},
|
|
116
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TemplateMeta } from '../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: TemplateMeta = {
|
|
4
|
+
name: 'Integrations settings',
|
|
5
|
+
family: 'Settings',
|
|
6
|
+
priority: 'P2',
|
|
7
|
+
status: 'beta',
|
|
8
|
+
description: 'Connector cards with on/off switches, webhook endpoints and a filterable delivery log.',
|
|
9
|
+
layout: 'SettingsLayout',
|
|
10
|
+
blocks: ['app-sidebar', 'app-topbar', 'page-header', 'settings-row'],
|
|
11
|
+
};
|