@stackone/hub 0.2.1 → 0.4.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.
Files changed (53) hide show
  1. package/dist/index.d.ts +19 -0
  2. package/dist/index.esm.js +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/webcomponent.js +11 -0
  5. package/dist/webcomponent.js.map +1 -0
  6. package/package.json +16 -3
  7. package/.github/workflows/node-ci.yml +0 -20
  8. package/.github/workflows/release-please.yml +0 -37
  9. package/.github/workflows/semantic-pull-request.yml +0 -31
  10. package/.nvmrc +0 -1
  11. package/.release-please-manifest.json +0 -1
  12. package/CHANGELOG.md +0 -57
  13. package/biome.json +0 -77
  14. package/dev/index.html +0 -11
  15. package/dev/main.css +0 -80
  16. package/dev/main.tsx +0 -98
  17. package/dev/vite-env.d.ts +0 -15
  18. package/index.html +0 -14
  19. package/release-please-config.json +0 -5
  20. package/rollup.config.mjs +0 -72
  21. package/src/Hub.tsx +0 -50
  22. package/src/StackOneHub.tsx +0 -138
  23. package/src/WebComponentWrapper.tsx +0 -14
  24. package/src/index.ts +0 -1
  25. package/src/modules/csv-importer.tsx/CsvImporter.tsx +0 -35
  26. package/src/modules/integration-picker/IntegrationPicker.tsx +0 -98
  27. package/src/modules/integration-picker/components/IntegrationFields.tsx +0 -129
  28. package/src/modules/integration-picker/components/IntegrationList.tsx +0 -71
  29. package/src/modules/integration-picker/components/IntegrationPickerContent.tsx +0 -107
  30. package/src/modules/integration-picker/components/cardFooter.tsx +0 -92
  31. package/src/modules/integration-picker/components/cardTitle.tsx +0 -53
  32. package/src/modules/integration-picker/components/views/ErrorView.tsx +0 -9
  33. package/src/modules/integration-picker/components/views/IntegrationFormView.tsx +0 -22
  34. package/src/modules/integration-picker/components/views/IntegrationListView.tsx +0 -19
  35. package/src/modules/integration-picker/components/views/LoadingView.tsx +0 -11
  36. package/src/modules/integration-picker/components/views/SuccessView.tsx +0 -10
  37. package/src/modules/integration-picker/components/views/index.ts +0 -5
  38. package/src/modules/integration-picker/hooks/useIntegrationPicker.ts +0 -394
  39. package/src/modules/integration-picker/queries.ts +0 -78
  40. package/src/modules/integration-picker/types.ts +0 -70
  41. package/src/shared/categories.ts +0 -55
  42. package/src/shared/components/error.tsx +0 -32
  43. package/src/shared/components/errorBoundary.tsx +0 -31
  44. package/src/shared/components/loading.tsx +0 -30
  45. package/src/shared/components/success.tsx +0 -40
  46. package/src/shared/contexts/featureFlagContext.tsx +0 -26
  47. package/src/shared/hooks/useFeatureFlags.ts +0 -24
  48. package/src/shared/httpClient.ts +0 -79
  49. package/src/shared/queries.ts +0 -12
  50. package/src/shared/types/featureFlags.ts +0 -1
  51. package/src/types/types.ts +0 -1
  52. package/tsconfig.json +0 -19
  53. package/vite.config.ts +0 -11
@@ -1,138 +0,0 @@
1
- import {
2
- Card,
3
- Flex,
4
- FlexAlign,
5
- FlexJustify,
6
- FooterLinks,
7
- MalachiteContext,
8
- PartialMalachiteTheme,
9
- Typography,
10
- applyDarkTheme,
11
- applyLightTheme,
12
- applyTheme,
13
- } from '@stackone/malachite';
14
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
15
- import { useEffect } from 'react';
16
- import { Hub } from './Hub';
17
- import { CsvImporter } from './modules/csv-importer.tsx/CsvImporter';
18
- import { IntegrationPicker } from './modules/integration-picker/IntegrationPicker';
19
- import ErrorContainer from './shared/components/error';
20
- import ErrorBoundary from './shared/components/errorBoundary';
21
- import { HubModes } from './types/types';
22
-
23
- interface StackOneHubProps {
24
- mode?: HubModes;
25
- token?: string;
26
- baseUrl?: string;
27
- appUrl?: string;
28
- height?: string;
29
- theme?: 'light' | 'dark' | PartialMalachiteTheme;
30
- accountId?: string;
31
- onSuccess?: () => void;
32
- onClose?: () => void;
33
- onCancel?: () => void;
34
- }
35
-
36
- export const StackOneHub: React.FC<StackOneHubProps> = ({
37
- mode,
38
- token,
39
- baseUrl,
40
- appUrl,
41
- height = '500px',
42
- theme = 'light',
43
- accountId,
44
- onSuccess,
45
- onClose,
46
- onCancel,
47
- }) => {
48
- const defaultBaseUrl = 'https://api.stackone.com';
49
- const apiUrl = baseUrl ?? defaultBaseUrl;
50
- const defaultDashboardUrl = 'https://app.stackone.com';
51
- const dashboardUrl = appUrl ?? defaultDashboardUrl;
52
- useEffect(() => {
53
- if (theme === 'dark') {
54
- applyDarkTheme();
55
- } else if (theme === 'light') {
56
- applyLightTheme();
57
- } else {
58
- applyTheme(theme);
59
- }
60
- }, [theme]);
61
-
62
- const queryClient = new QueryClient({
63
- defaultOptions: {
64
- queries: {
65
- refetchOnWindowFocus: false,
66
- retry: 1,
67
- retryDelay: 500,
68
- refetchOnMount: false,
69
- retryOnMount: false,
70
- },
71
- },
72
- });
73
-
74
- if (!token) {
75
- return (
76
- <MalachiteContext>
77
- <ErrorBoundary
78
- fallback={
79
- <Card height={height}>
80
- <ErrorContainer />
81
- </Card>
82
- }
83
- >
84
- <Card height={height} footer={<FooterLinks />}>
85
- <Flex justify={FlexJustify.Center} align={FlexAlign.Center} fullHeight>
86
- <Typography.PageTitle>No token provided</Typography.PageTitle>
87
- </Flex>
88
- </Card>
89
- </ErrorBoundary>
90
- </MalachiteContext>
91
- );
92
- }
93
- if (!mode) {
94
- return (
95
- <MalachiteContext>
96
- <ErrorBoundary
97
- fallback={
98
- <Card height={height}>
99
- <ErrorContainer />
100
- </Card>
101
- }
102
- >
103
- <Card height={height} footer={<FooterLinks />}>
104
- <Flex justify={FlexJustify.Center} align={FlexAlign.Center} fullHeight>
105
- <Typography.PageTitle>No mode selected</Typography.PageTitle>
106
- </Flex>
107
- </Card>
108
- </ErrorBoundary>
109
- </MalachiteContext>
110
- );
111
- }
112
-
113
- return (
114
- <MalachiteContext>
115
- <ErrorBoundary
116
- fallback={
117
- <Card height={height}>
118
- <ErrorContainer />
119
- </Card>
120
- }
121
- >
122
- <QueryClientProvider client={queryClient}>
123
- <Hub
124
- mode={mode}
125
- token={token}
126
- apiUrl={apiUrl}
127
- dashboardUrl={dashboardUrl}
128
- height={height}
129
- onSuccess={onSuccess}
130
- accountId={accountId}
131
- onClose={onClose}
132
- onCancel={onCancel}
133
- />
134
- </QueryClientProvider>
135
- </ErrorBoundary>
136
- </MalachiteContext>
137
- );
138
- };
@@ -1,14 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import reactToWebComponent from 'react-to-webcomponent';
4
- import { StackOneHub } from './StackOneHub';
5
-
6
- const WebComponent = reactToWebComponent(StackOneHub, React, ReactDOM, {
7
- props: {
8
- token: 'string',
9
- baseUrl: 'string',
10
- mode: 'string',
11
- },
12
- });
13
-
14
- customElements.define('stackone-hub', WebComponent);
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { StackOneHub } from './StackOneHub';
@@ -1,35 +0,0 @@
1
- import {
2
- Card,
3
- Flex,
4
- FlexDirection,
5
- FlexGapSize,
6
- FooterLinks,
7
- Typography,
8
- } from '@stackone/malachite';
9
-
10
- interface CsvImporterProps {
11
- height?: string;
12
- }
13
-
14
- export const CsvImporter: React.FC<CsvImporterProps> = ({ height }) => {
15
- return (
16
- <Card
17
- title={
18
- <Typography.Text fontWeight="semi-bold" size="large">
19
- CSV Importer
20
- </Typography.Text>
21
- }
22
- footer={<FooterLinks />}
23
- height={height}
24
- >
25
- <Flex direction={FlexDirection.Vertical} gapSize={FlexGapSize.Small} fullHeight>
26
- <Typography.Text fontWeight="bold" size="large">
27
- CSV Importer
28
- </Typography.Text>
29
- <Typography.SecondaryText>
30
- This is the CSV importer module.
31
- </Typography.SecondaryText>
32
- </Flex>
33
- </Card>
34
- );
35
- };
@@ -1,98 +0,0 @@
1
- import { Card } from '@stackone/malachite';
2
- import useFeatureFlags from '../../shared/hooks/useFeatureFlags';
3
- import { IntegrationPickerContent } from './components/IntegrationPickerContent';
4
- import CardFooter from './components/cardFooter';
5
- import CardTitle from './components/cardTitle';
6
- import { useIntegrationPicker } from './hooks/useIntegrationPicker';
7
-
8
- interface IntegrationPickerProps {
9
- token: string;
10
- baseUrl: string;
11
- height: string;
12
- accountId?: string;
13
- dashboardUrl?: string;
14
- onSuccess?: () => void;
15
- onClose?: () => void;
16
- onCancel?: () => void;
17
- }
18
-
19
- export const IntegrationPicker: React.FC<IntegrationPickerProps> = ({
20
- token,
21
- baseUrl,
22
- height,
23
- accountId,
24
- onSuccess,
25
- dashboardUrl,
26
- }) => {
27
- const isHubLinkAccountReleaseEnabled = useFeatureFlags('hub_link_account_release');
28
-
29
- const {
30
- // Data
31
- hubData,
32
- accountData,
33
- connectorData,
34
- selectedIntegration,
35
- fields,
36
- guide,
37
-
38
- // State
39
- connectionState,
40
- isLoading,
41
- hasError,
42
-
43
- // Errors
44
- errorHubData,
45
- errorConnectorData,
46
-
47
- // Actions
48
- setSelectedIntegration,
49
- setFormData,
50
- handleConnect,
51
- } = useIntegrationPicker({
52
- token,
53
- baseUrl,
54
- accountId,
55
- onSuccess,
56
- dashboardUrl,
57
- });
58
-
59
- return (
60
- <Card
61
- footer={
62
- <CardFooter
63
- selectedIntegration={selectedIntegration}
64
- isLoading={connectionState.loading}
65
- onBack={accountData ? undefined : () => setSelectedIntegration(null)}
66
- onNext={handleConnect}
67
- />
68
- }
69
- title={
70
- selectedIntegration && (
71
- <CardTitle
72
- selectedIntegration={selectedIntegration}
73
- onBack={accountData ? undefined : () => setSelectedIntegration(null)}
74
- guide={guide}
75
- />
76
- )
77
- }
78
- height={height}
79
- >
80
- {isHubLinkAccountReleaseEnabled && (
81
- <IntegrationPickerContent
82
- isLoading={isLoading}
83
- hasError={hasError}
84
- connectionState={connectionState}
85
- selectedIntegration={selectedIntegration}
86
- connectorData={connectorData?.config ?? null}
87
- hubData={hubData ?? null}
88
- fields={fields}
89
- guide={guide}
90
- errorHubData={errorHubData}
91
- errorConnectorData={errorConnectorData}
92
- onSelect={setSelectedIntegration}
93
- onChange={setFormData}
94
- />
95
- )}
96
- </Card>
97
- );
98
- };
@@ -1,129 +0,0 @@
1
- import { Alert, Dropdown, Form, Input, Spacer, TextArea, Typography } from '@stackone/malachite';
2
- import { useEffect, useState } from 'react';
3
- import { ConnectorConfigField } from '../types';
4
-
5
- interface IntegrationFieldsProps {
6
- fields: Array<ConnectorConfigField>;
7
- guide?: { supportLink?: string; description: string };
8
- error?: {
9
- message: string;
10
- provider_response: string;
11
- };
12
- onChange: (data: Record<string, string>) => void;
13
- }
14
-
15
- export const IntegrationForm: React.FC<IntegrationFieldsProps> = ({
16
- fields,
17
- guide,
18
- onChange,
19
- error,
20
- }) => {
21
- // Initialize formData with default values from fields
22
- const [formData, setFormData] = useState<Record<string, string>>(() => {
23
- const initialData: Record<string, string> = {};
24
- fields.forEach((field) => {
25
- if (field.value !== undefined) {
26
- initialData[field.key] = field.value.toString();
27
- }
28
- });
29
- return initialData;
30
- });
31
-
32
- // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
33
- useEffect(() => {
34
- const updatedData: Record<string, string> = {};
35
- fields.forEach((field) => {
36
- if (field.value !== undefined) {
37
- updatedData[field.key] = field.value.toString();
38
- }
39
- });
40
-
41
- setFormData((prev) => {
42
- const hasChanges =
43
- Object.keys(updatedData).some((key) => updatedData[key] !== prev[key]) ||
44
- Object.keys(prev).some((key) => !updatedData.hasOwnProperty(key));
45
-
46
- if (hasChanges) {
47
- return { ...prev, ...updatedData };
48
- }
49
- return prev;
50
- });
51
- }, [fields.length]);
52
-
53
- useEffect(() => {
54
- onChange(formData);
55
- }, [formData, onChange]);
56
-
57
- const handleFieldChange = (key: string, value: string) => {
58
- setFormData((prev) => ({
59
- ...prev,
60
- [key]: value,
61
- }));
62
- };
63
-
64
- return (
65
- <Spacer direction="vertical" size={8} fullWidth>
66
- {guide && <Alert type="info" message={guide?.description} hasMargin={false} />}
67
- {error && <Alert type="error" message={error.message} hasMargin={false} />}
68
- {error && <Typography.CodeText>{error.provider_response}</Typography.CodeText>}
69
- <Spacer direction="vertical" size={20} fullWidth>
70
- {fields.map((field) => {
71
- const key =
72
- typeof field.key === 'object'
73
- ? JSON.stringify(field.key)
74
- : String(field.key);
75
- return (
76
- <div key={key} style={{ width: '100%' }}>
77
- {(field.type === 'text' ||
78
- field.type === 'number' ||
79
- field.type === 'password') && (
80
- <Input
81
- name={key}
82
- required={field.required}
83
- placeholder={field.placeholder}
84
- defaultValue={field.value?.toString()}
85
- onChange={(value: string) => handleFieldChange(key, value)}
86
- disabled={field.readOnly}
87
- label={field.label}
88
- tooltip={field.guide?.tooltip}
89
- description={field.guide?.description}
90
- type={field.type}
91
- />
92
- )}
93
-
94
- {field.type === 'text_area' && (
95
- <TextArea
96
- name={key}
97
- required={field.required}
98
- defaultValue={formData[key] || ''}
99
- placeholder={field.placeholder}
100
- onChange={(value: string) => handleFieldChange(key, value)}
101
- disabled={field.readOnly}
102
- label={field.label}
103
- tooltip={field.guide?.tooltip}
104
- />
105
- )}
106
- {field.type === 'select' && (
107
- <Dropdown
108
- defaultValue={formData[key] || ''}
109
- disabled={field.readOnly}
110
- items={
111
- field.options?.map((option) => ({
112
- id: option.value,
113
- label: option.label,
114
- })) ?? []
115
- }
116
- onItemSelected={(value) => handleFieldChange(key, value ?? '')}
117
- name={key}
118
- label={field.label}
119
- tooltip={field.guide?.tooltip}
120
- description={field.guide?.description}
121
- />
122
- )}
123
- </div>
124
- );
125
- })}
126
- </Spacer>
127
- </Spacer>
128
- );
129
- };
@@ -1,71 +0,0 @@
1
- import {
2
- ButtonList,
3
- Flex,
4
- FlexAlign,
5
- FlexDirection,
6
- FlexGapSize,
7
- FlexJustify,
8
- Padded,
9
- Typography,
10
- } from '@stackone/malachite';
11
- import { CATEGORIES_WITH_LABELS } from '../../../shared/categories';
12
- import { Integration } from '../types';
13
-
14
- interface IntegrationRowProps {
15
- integration: Integration;
16
- }
17
-
18
- const IntegrationRow: React.FC<IntegrationRowProps> = ({ integration }) => {
19
- return (
20
- <Flex
21
- direction={FlexDirection.Horizontal}
22
- align={FlexAlign.Center}
23
- gapSize={FlexGapSize.Small}
24
- justify={FlexJustify.SpaceBetween}
25
- width="100%"
26
- >
27
- <Flex
28
- direction={FlexDirection.Horizontal}
29
- align={FlexAlign.Center}
30
- gapSize={FlexGapSize.Small}
31
- justify={FlexJustify.Left}
32
- width="100%"
33
- >
34
- <img
35
- src={`https://app.stackone.com/assets/logos/${integration.provider}.png`}
36
- alt={integration.provider}
37
- style={{ width: '24px', height: '24px' }}
38
- />
39
- <Typography.Text>{integration.name ?? 'N/A'}</Typography.Text>
40
- </Flex>
41
- <Typography.SecondaryText>
42
- {
43
- CATEGORIES_WITH_LABELS.find((category) => category.value === integration.type)
44
- ?.label
45
- }
46
- </Typography.SecondaryText>
47
- </Flex>
48
- );
49
- };
50
-
51
- export const IntegrationList: React.FC<{
52
- integrations: Integration[];
53
- onSelect: (integration: Integration) => void;
54
- }> = ({ integrations, onSelect }) => {
55
- return (
56
- <>
57
- <Padded vertical="medium" horizontal="small" fullHeight={false}>
58
- <Typography.SecondaryText>Select integration</Typography.SecondaryText>
59
- </Padded>
60
- <ButtonList
61
- buttons={integrations
62
- ?.filter((integration) => integration.active && integration.name)
63
- .map((integration) => ({
64
- key: integration.provider,
65
- children: <IntegrationRow integration={integration} />,
66
- onClick: () => onSelect(integration),
67
- }))}
68
- />
69
- </>
70
- );
71
- };
@@ -1,107 +0,0 @@
1
- import React from 'react';
2
- import { ConnectorConfig, ConnectorConfigField, HubData, Integration } from '../types';
3
- import { ErrorView } from './views/ErrorView';
4
- import { IntegrationFormView } from './views/IntegrationFormView';
5
- import { IntegrationListView } from './views/IntegrationListView';
6
- import { LoadingView } from './views/LoadingView';
7
- import { SuccessView } from './views/SuccessView';
8
-
9
- interface IntegrationPickerContentProps {
10
- // State flags
11
- isLoading: boolean;
12
- hasError: boolean;
13
- connectionState: {
14
- loading: boolean;
15
- success: boolean;
16
- error?: {
17
- message: string;
18
- provider_response: string;
19
- };
20
- };
21
-
22
- // Data
23
- selectedIntegration: Integration | null;
24
- connectorData: ConnectorConfig | null;
25
- hubData: HubData | null;
26
- fields: ConnectorConfigField[];
27
- guide?: { supportLink?: string; description: string };
28
-
29
- // Errors
30
- errorHubData: Error | null;
31
- errorConnectorData: Error | null;
32
-
33
- // Actions
34
- onSelect: (integration: Integration) => void;
35
- onChange: (data: Record<string, string>) => void;
36
- }
37
-
38
- export const IntegrationPickerContent: React.FC<IntegrationPickerContentProps> = ({
39
- isLoading,
40
- hasError,
41
- connectionState,
42
- selectedIntegration,
43
- connectorData,
44
- hubData,
45
- fields,
46
- guide,
47
- errorHubData,
48
- errorConnectorData,
49
- onSelect,
50
- onChange,
51
- }) => {
52
- // Loading states
53
- if (isLoading) {
54
- return (
55
- <LoadingView
56
- title="Loading integration data"
57
- description="Please wait, this may take a moment."
58
- />
59
- );
60
- }
61
-
62
- if (connectionState.loading && selectedIntegration) {
63
- return (
64
- <LoadingView
65
- title={`Connecting to ${selectedIntegration.name}`}
66
- description="Please wait, this may take a moment."
67
- />
68
- );
69
- }
70
-
71
- // Error states
72
- if (hasError) {
73
- return (
74
- <ErrorView
75
- message={errorHubData?.message || errorConnectorData?.message || 'Unknown error'}
76
- />
77
- );
78
- }
79
-
80
- // Success state
81
- if (connectionState.success && selectedIntegration) {
82
- return <SuccessView integrationName={selectedIntegration.name} />;
83
- }
84
-
85
- // Integration selection flow
86
- if (!selectedIntegration) {
87
- if (!hubData?.integrations.length) {
88
- return <ErrorView message="No integrations found." />;
89
- }
90
- return <IntegrationListView integrations={hubData.integrations} onSelect={onSelect} />;
91
- }
92
-
93
- // Form view (when integration is selected and connector data is available)
94
- if (connectorData) {
95
- return (
96
- <IntegrationFormView
97
- fields={fields}
98
- error={connectionState.error}
99
- onChange={onChange}
100
- guide={guide}
101
- />
102
- );
103
- }
104
-
105
- // Fallback
106
- return null;
107
- };
@@ -1,92 +0,0 @@
1
- import {
2
- Button,
3
- Flex,
4
- FlexDirection,
5
- FlexJustify,
6
- FooterLinks,
7
- Padded,
8
- Spacer,
9
- } from '@stackone/malachite';
10
- import { useMemo } from 'react';
11
- import { Integration } from '../types';
12
-
13
- interface CardFooterProps {
14
- selectedIntegration: Integration | null;
15
- fullWidth?: boolean;
16
- isLoading?: boolean;
17
- onBack?: () => void;
18
- onNext: () => void;
19
- }
20
-
21
- const CardFooter: React.FC<CardFooterProps> = ({
22
- fullWidth = true,
23
- selectedIntegration,
24
- isLoading,
25
- onBack,
26
- onNext,
27
- }) => {
28
- const buttons = useMemo(() => {
29
- if (!selectedIntegration || isLoading) {
30
- return [];
31
- }
32
-
33
- const buttons: Array<{
34
- label: string;
35
- type: 'outline' | 'filled';
36
- onClick: () => void;
37
- disabled: boolean;
38
- loading: boolean;
39
- }> = [];
40
-
41
- if (onBack) {
42
- buttons.push({
43
- label: 'Back',
44
- type: 'outline' as const,
45
- onClick: onBack,
46
- disabled: false,
47
- loading: false,
48
- });
49
- }
50
-
51
- buttons.push({
52
- label: 'Confirm',
53
- type: 'filled' as const,
54
- onClick: onNext,
55
- disabled: false,
56
- loading: false,
57
- });
58
-
59
- return buttons;
60
- }, [selectedIntegration, onBack, onNext, isLoading]);
61
-
62
- if (buttons.length === 0) {
63
- return <FooterLinks fullWidth={fullWidth} />;
64
- }
65
-
66
- return (
67
- <Spacer direction="horizontal" size={0} justifyContent="space-between">
68
- <FooterLinks fullWidth={fullWidth} />
69
- <Padded vertical="none" horizontal="medium" fullHeight={false}>
70
- <Flex direction={FlexDirection.Horizontal} justify={FlexJustify.Right}>
71
- <Spacer direction="horizontal" size={10}>
72
- {buttons.map((button) => (
73
- <Button
74
- key={button.label}
75
- size="small"
76
- variant={button.type}
77
- onClick={button.onClick}
78
- disabled={button.disabled}
79
- loading={button.loading}
80
- iconPosition="end"
81
- >
82
- {button.label}
83
- </Button>
84
- ))}
85
- </Spacer>
86
- </Flex>
87
- </Padded>
88
- </Spacer>
89
- );
90
- };
91
-
92
- export default CardFooter;