@auxiora/dashboard 1.0.0 → 1.3.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 (82) hide show
  1. package/dist/router.d.ts.map +1 -1
  2. package/dist/router.js +195 -49
  3. package/dist/router.js.map +1 -1
  4. package/dist/types.d.ts +66 -0
  5. package/dist/types.d.ts.map +1 -1
  6. package/dist/types.js.map +1 -1
  7. package/package.json +10 -4
  8. package/dist-ui/assets/index-BfY0i5jw.css +0 -1
  9. package/dist-ui/assets/index-CXpk9mvw.js +0 -60
  10. package/dist-ui/icon.svg +0 -59
  11. package/dist-ui/index.html +0 -20
  12. package/src/auth.ts +0 -83
  13. package/src/cloud-types.ts +0 -63
  14. package/src/index.ts +0 -5
  15. package/src/router.ts +0 -2494
  16. package/src/types.ts +0 -269
  17. package/tests/auth.test.ts +0 -51
  18. package/tests/cloud-router.test.ts +0 -249
  19. package/tests/desktop-router.test.ts +0 -151
  20. package/tests/router.test.ts +0 -388
  21. package/tests/trust-router.test.ts +0 -170
  22. package/tsconfig.json +0 -12
  23. package/tsconfig.tsbuildinfo +0 -1
  24. package/ui/index.html +0 -19
  25. package/ui/node_modules/.bin/browserslist +0 -17
  26. package/ui/node_modules/.bin/tsc +0 -17
  27. package/ui/node_modules/.bin/tsserver +0 -17
  28. package/ui/node_modules/.bin/vite +0 -17
  29. package/ui/package.json +0 -23
  30. package/ui/public/icon.svg +0 -59
  31. package/ui/src/App.tsx +0 -63
  32. package/ui/src/api.ts +0 -238
  33. package/ui/src/components/ActivityFeed.tsx +0 -123
  34. package/ui/src/components/BehaviorHealth.tsx +0 -105
  35. package/ui/src/components/DataTable.tsx +0 -39
  36. package/ui/src/components/Layout.tsx +0 -160
  37. package/ui/src/components/PasswordStrength.tsx +0 -31
  38. package/ui/src/components/SetupProgress.tsx +0 -26
  39. package/ui/src/components/StatusBadge.tsx +0 -12
  40. package/ui/src/components/ThemeSelector.tsx +0 -39
  41. package/ui/src/contexts/ThemeContext.tsx +0 -58
  42. package/ui/src/hooks/useApi.ts +0 -19
  43. package/ui/src/hooks/usePolling.ts +0 -8
  44. package/ui/src/main.tsx +0 -16
  45. package/ui/src/pages/AuditLog.tsx +0 -36
  46. package/ui/src/pages/Behaviors.tsx +0 -426
  47. package/ui/src/pages/Chat.tsx +0 -688
  48. package/ui/src/pages/Login.tsx +0 -64
  49. package/ui/src/pages/Overview.tsx +0 -56
  50. package/ui/src/pages/Sessions.tsx +0 -26
  51. package/ui/src/pages/SettingsAmbient.tsx +0 -185
  52. package/ui/src/pages/SettingsConnections.tsx +0 -201
  53. package/ui/src/pages/SettingsNotifications.tsx +0 -241
  54. package/ui/src/pages/SetupAppearance.tsx +0 -45
  55. package/ui/src/pages/SetupChannels.tsx +0 -143
  56. package/ui/src/pages/SetupComplete.tsx +0 -31
  57. package/ui/src/pages/SetupConnections.tsx +0 -80
  58. package/ui/src/pages/SetupDashboardPassword.tsx +0 -50
  59. package/ui/src/pages/SetupIdentity.tsx +0 -68
  60. package/ui/src/pages/SetupPersonality.tsx +0 -78
  61. package/ui/src/pages/SetupProvider.tsx +0 -65
  62. package/ui/src/pages/SetupVault.tsx +0 -50
  63. package/ui/src/pages/SetupWelcome.tsx +0 -19
  64. package/ui/src/pages/UnlockVault.tsx +0 -56
  65. package/ui/src/pages/Webhooks.tsx +0 -158
  66. package/ui/src/pages/settings/Appearance.tsx +0 -63
  67. package/ui/src/pages/settings/Channels.tsx +0 -138
  68. package/ui/src/pages/settings/Identity.tsx +0 -61
  69. package/ui/src/pages/settings/Personality.tsx +0 -54
  70. package/ui/src/pages/settings/PersonalityEditor.tsx +0 -577
  71. package/ui/src/pages/settings/Provider.tsx +0 -537
  72. package/ui/src/pages/settings/Security.tsx +0 -111
  73. package/ui/src/styles/global.css +0 -2308
  74. package/ui/src/styles/themes/index.css +0 -7
  75. package/ui/src/styles/themes/monolith.css +0 -125
  76. package/ui/src/styles/themes/nebula.css +0 -90
  77. package/ui/src/styles/themes/neon.css +0 -149
  78. package/ui/src/styles/themes/polar.css +0 -151
  79. package/ui/src/styles/themes/signal.css +0 -163
  80. package/ui/src/styles/themes/terra.css +0 -146
  81. package/ui/tsconfig.json +0 -14
  82. package/ui/vite.config.ts +0 -20
@@ -1,64 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useNavigate } from 'react-router-dom';
3
- import { api } from '../api';
4
-
5
- export function Login() {
6
- const [password, setPassword] = useState('');
7
- const [error, setError] = useState('');
8
- const [loading, setLoading] = useState(false);
9
- const [checking, setChecking] = useState(true);
10
- const [agentName, setAgentName] = useState('Auxiora');
11
- const navigate = useNavigate();
12
-
13
- // Check if vault needs unlocking before allowing login
14
- useEffect(() => {
15
- api.getSetupStatus()
16
- .then(status => {
17
- if (status.agentName) setAgentName(status.agentName);
18
- if (status.needsSetup) {
19
- navigate('/setup', { replace: true });
20
- } else if (!status.vaultUnlocked) {
21
- navigate('/unlock', { replace: true });
22
- }
23
- })
24
- .catch(() => {})
25
- .finally(() => setChecking(false));
26
- }, []);
27
-
28
- const handleSubmit = async (e: React.FormEvent) => {
29
- e.preventDefault();
30
- setLoading(true);
31
- setError('');
32
- try {
33
- await api.login(password);
34
- navigate('/');
35
- } catch (err: any) {
36
- setError(err.message || 'Login failed');
37
- } finally {
38
- setLoading(false);
39
- }
40
- };
41
-
42
- if (checking) return null;
43
-
44
- return (
45
- <div className="login-page">
46
- <div className="login-card">
47
- <h1>{agentName} Mission Control</h1>
48
- <form onSubmit={handleSubmit}>
49
- <input
50
- type="password"
51
- value={password}
52
- onChange={(e) => setPassword(e.target.value)}
53
- placeholder="Password"
54
- autoFocus
55
- />
56
- <button type="submit" disabled={loading}>
57
- {loading ? 'Logging in...' : 'Login'}
58
- </button>
59
- {error && <p className="error">{error}</p>}
60
- </form>
61
- </div>
62
- </div>
63
- );
64
- }
@@ -1,56 +0,0 @@
1
- import { useApi } from '../hooks/useApi';
2
- import { usePolling } from '../hooks/usePolling';
3
- import { api } from '../api';
4
- import { ActivityFeed } from '../components/ActivityFeed';
5
- import { BehaviorHealth } from '../components/BehaviorHealth';
6
-
7
- export function Overview() {
8
- const { data: status, refresh } = useApi(() => api.getStatus(), []);
9
- const { data: models, refresh: refreshModels } = useApi(() => api.getModels(), []);
10
- usePolling(() => { refresh(); refreshModels(); });
11
-
12
- const s = status?.data;
13
- const primaryProvider = models?.providers?.find((p: any) => p.available)?.displayName ?? 'None';
14
-
15
- return (
16
- <div className="page">
17
- <h2>Mission Control</h2>
18
-
19
- {/* Quick status strip */}
20
- <div className="status-grid">
21
- <div className="status-card">
22
- <h3>Connections</h3>
23
- <div className="value">{s?.connections ?? 0}</div>
24
- <div className="sub">Active sessions</div>
25
- </div>
26
- <div className="status-card">
27
- <h3>Provider</h3>
28
- <div className="value">{primaryProvider}</div>
29
- <div className="sub">{s?.activeModel?.model ?? 'unknown'}</div>
30
- </div>
31
- <div className="status-card">
32
- <h3>Uptime</h3>
33
- <div className="value">{s ? formatUptime(s.uptime) : '-'}</div>
34
- <div className="sub">Since last restart</div>
35
- </div>
36
- </div>
37
-
38
- {/* Main two-column layout */}
39
- <div className="mc-columns">
40
- <div className="mc-left">
41
- <BehaviorHealth />
42
- </div>
43
- <div className="mc-right">
44
- <ActivityFeed />
45
- </div>
46
- </div>
47
- </div>
48
- );
49
- }
50
-
51
- function formatUptime(seconds: number): string {
52
- const h = Math.floor(seconds / 3600);
53
- const m = Math.floor((seconds % 3600) / 60);
54
- if (h > 0) return `${h}h ${m}m`;
55
- return `${m}m`;
56
- }
@@ -1,26 +0,0 @@
1
- import { useApi } from '../hooks/useApi';
2
- import { usePolling } from '../hooks/usePolling';
3
- import { api } from '../api';
4
- import { DataTable } from '../components/DataTable';
5
-
6
- export function Sessions() {
7
- const { data, refresh } = useApi(() => api.getSessions(), []);
8
- usePolling(refresh);
9
-
10
- const sessions = data?.data ?? [];
11
-
12
- const columns = [
13
- { key: 'id', label: 'Session ID', render: (s: any) => s.id.slice(0, 8) + '...' },
14
- { key: 'channelType', label: 'Channel' },
15
- { key: 'authenticated', label: 'Auth', render: (s: any) => s.authenticated ? 'Yes' : 'No' },
16
- { key: 'voiceActive', label: 'Voice', render: (s: any) => s.voiceActive ? 'Active' : '-' },
17
- { key: 'lastActive', label: 'Last Active', render: (s: any) => new Date(s.lastActive).toLocaleString() },
18
- ];
19
-
20
- return (
21
- <div className="page">
22
- <h2>Active Sessions</h2>
23
- <DataTable columns={columns} rows={sessions} keyField="id" />
24
- </div>
25
- );
26
- }
@@ -1,185 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useApi } from '../hooks/useApi';
3
- import { api } from '../api';
4
-
5
- const CATEGORIES = ['Calendar', 'Email', 'Tasks', 'Patterns'] as const;
6
-
7
- interface AmbientConfig {
8
- morningBriefing: {
9
- enabled: boolean;
10
- time: string;
11
- categories: string[];
12
- };
13
- eveningSummary: {
14
- enabled: boolean;
15
- time: string;
16
- };
17
- deliveryChannel: string;
18
- }
19
-
20
- const DEFAULT_CONFIG: AmbientConfig = {
21
- morningBriefing: {
22
- enabled: true,
23
- time: '08:00',
24
- categories: ['Calendar', 'Email'],
25
- },
26
- eveningSummary: {
27
- enabled: false,
28
- time: '18:00',
29
- },
30
- deliveryChannel: 'all',
31
- };
32
-
33
- export function SettingsAmbient() {
34
- const { data, loading: fetching } = useApi(() => api.getAmbientConfig(), []);
35
- const [config, setConfig] = useState<AmbientConfig>(DEFAULT_CONFIG);
36
- const [saving, setSaving] = useState(false);
37
- const [success, setSuccess] = useState('');
38
- const [error, setError] = useState('');
39
-
40
- useEffect(() => {
41
- if (data?.data) {
42
- setConfig({
43
- morningBriefing: {
44
- enabled: data.data.morningBriefing?.enabled ?? DEFAULT_CONFIG.morningBriefing.enabled,
45
- time: data.data.morningBriefing?.time ?? DEFAULT_CONFIG.morningBriefing.time,
46
- categories: data.data.morningBriefing?.categories ?? DEFAULT_CONFIG.morningBriefing.categories,
47
- },
48
- eveningSummary: {
49
- enabled: data.data.eveningSummary?.enabled ?? DEFAULT_CONFIG.eveningSummary.enabled,
50
- time: data.data.eveningSummary?.time ?? DEFAULT_CONFIG.eveningSummary.time,
51
- },
52
- deliveryChannel: data.data.deliveryChannel ?? DEFAULT_CONFIG.deliveryChannel,
53
- });
54
- }
55
- }, [data]);
56
-
57
- const toggleCategory = (category: string) => {
58
- setConfig(prev => {
59
- const cats = prev.morningBriefing.categories;
60
- const next = cats.includes(category)
61
- ? cats.filter(c => c !== category)
62
- : [...cats, category];
63
- return {
64
- ...prev,
65
- morningBriefing: { ...prev.morningBriefing, categories: next },
66
- };
67
- });
68
- };
69
-
70
- const handleSave = async () => {
71
- setSaving(true);
72
- setError('');
73
- setSuccess('');
74
- try {
75
- await api.updateAmbientConfig(config);
76
- setSuccess('Ambient settings saved successfully');
77
- } catch (err: unknown) {
78
- setError(err instanceof Error ? err.message : 'Failed to save settings');
79
- } finally {
80
- setSaving(false);
81
- }
82
- };
83
-
84
- if (fetching) return null;
85
-
86
- return (
87
- <div className="page">
88
- <h2>Ambient Intelligence</h2>
89
- <div className="settings-form">
90
- <div className="settings-section">
91
- <h3>Morning Briefing</h3>
92
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1rem' }}>
93
- <div
94
- className={`toggle${config.morningBriefing.enabled ? ' active' : ''}`}
95
- onClick={() => setConfig(prev => ({
96
- ...prev,
97
- morningBriefing: { ...prev.morningBriefing, enabled: !prev.morningBriefing.enabled },
98
- }))}
99
- />
100
- <span style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
101
- {config.morningBriefing.enabled ? 'Enabled' : 'Disabled'}
102
- </span>
103
- </div>
104
- {config.morningBriefing.enabled && (
105
- <>
106
- <label>Delivery Time</label>
107
- <input
108
- type="time"
109
- value={config.morningBriefing.time}
110
- onChange={(e) => setConfig(prev => ({
111
- ...prev,
112
- morningBriefing: { ...prev.morningBriefing, time: e.target.value },
113
- }))}
114
- />
115
- <label>Categories</label>
116
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', marginBottom: '1rem' }}>
117
- {CATEGORIES.map(cat => (
118
- <label key={cat} style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', cursor: 'pointer', fontSize: '0.85rem' }}>
119
- <input
120
- type="checkbox"
121
- checked={config.morningBriefing.categories.includes(cat)}
122
- onChange={() => toggleCategory(cat)}
123
- style={{ width: 'auto', marginBottom: 0 }}
124
- />
125
- {cat}
126
- </label>
127
- ))}
128
- </div>
129
- </>
130
- )}
131
- </div>
132
-
133
- <div className="settings-section">
134
- <h3>Evening Summary</h3>
135
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1rem' }}>
136
- <div
137
- className={`toggle${config.eveningSummary.enabled ? ' active' : ''}`}
138
- onClick={() => setConfig(prev => ({
139
- ...prev,
140
- eveningSummary: { ...prev.eveningSummary, enabled: !prev.eveningSummary.enabled },
141
- }))}
142
- />
143
- <span style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
144
- {config.eveningSummary.enabled ? 'Enabled' : 'Disabled'}
145
- </span>
146
- </div>
147
- {config.eveningSummary.enabled && (
148
- <>
149
- <label>Delivery Time</label>
150
- <input
151
- type="time"
152
- value={config.eveningSummary.time}
153
- onChange={(e) => setConfig(prev => ({
154
- ...prev,
155
- eveningSummary: { ...prev.eveningSummary, time: e.target.value },
156
- }))}
157
- />
158
- </>
159
- )}
160
- </div>
161
-
162
- <div className="settings-section">
163
- <label>Delivery Channel</label>
164
- <select
165
- value={config.deliveryChannel}
166
- onChange={(e) => setConfig(prev => ({ ...prev, deliveryChannel: e.target.value }))}
167
- >
168
- <option value="all">All connected channels</option>
169
- <option value="webchat">Webchat only</option>
170
- <option value="discord">Discord</option>
171
- <option value="telegram">Telegram</option>
172
- <option value="slack">Slack</option>
173
- <option value="email">Email</option>
174
- </select>
175
- </div>
176
-
177
- <button className="settings-btn" onClick={handleSave} disabled={saving}>
178
- {saving ? 'Saving...' : 'Save Settings'}
179
- </button>
180
- {success && <div className="settings-success">{success}</div>}
181
- {error && <div className="error">{error}</div>}
182
- </div>
183
- </div>
184
- );
185
- }
@@ -1,201 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { api } from '../api';
3
-
4
- interface ConnectorState {
5
- hasCredentials: boolean;
6
- connected: boolean;
7
- expiresAt?: number;
8
- loading: boolean;
9
- }
10
-
11
- const CONNECTORS = [
12
- {
13
- id: 'google-workspace',
14
- name: 'Google Workspace',
15
- description: 'Gmail, Google Calendar, and Google Drive',
16
- scopes: 'Email summaries, calendar awareness, drive file access',
17
- setupUrl: 'https://console.cloud.google.com/apis/credentials',
18
- instructions: [
19
- 'Go to Google Cloud Console > APIs & Services > Credentials',
20
- 'Create an OAuth 2.0 Client ID (Web application type)',
21
- 'Add authorized redirect URI: {callbackUrl}',
22
- 'Enable Gmail API, Google Calendar API, and Google Drive API',
23
- ],
24
- },
25
- ];
26
-
27
- export function SettingsConnections() {
28
- const [states, setStates] = useState<Record<string, ConnectorState>>({});
29
- const [clientId, setClientId] = useState('');
30
- const [clientSecret, setClientSecret] = useState('');
31
- const [error, setError] = useState('');
32
- const [success, setSuccess] = useState('');
33
- const [showSetup, setShowSetup] = useState<string | null>(null);
34
-
35
- const callbackUrl = `${window.location.origin}/api/v1/dashboard/connectors/google-workspace/callback`;
36
-
37
- useEffect(() => {
38
- void loadStatuses();
39
- }, []);
40
-
41
- async function loadStatuses() {
42
- for (const connector of CONNECTORS) {
43
- setStates(prev => ({ ...prev, [connector.id]: { ...prev[connector.id]!, loading: true, hasCredentials: false, connected: false } }));
44
- try {
45
- const result = await api.getConnectorStatus(connector.id);
46
- setStates(prev => ({
47
- ...prev,
48
- [connector.id]: {
49
- hasCredentials: result.data.hasCredentials,
50
- connected: result.data.connected,
51
- expiresAt: result.data.expiresAt,
52
- loading: false,
53
- },
54
- }));
55
- } catch {
56
- setStates(prev => ({ ...prev, [connector.id]: { hasCredentials: false, connected: false, loading: false } }));
57
- }
58
- }
59
- }
60
-
61
- async function handleSaveCredentials(connectorId: string) {
62
- if (!clientId || !clientSecret) {
63
- setError('Both Client ID and Client Secret are required');
64
- return;
65
- }
66
- setError('');
67
- try {
68
- await api.saveConnectorCredentials(connectorId, clientId, clientSecret);
69
- setSuccess('Credentials saved. Click "Connect" to authorize.');
70
- setClientId('');
71
- setClientSecret('');
72
- setShowSetup(null);
73
- void loadStatuses();
74
- } catch (err: unknown) {
75
- setError(err instanceof Error ? err.message : 'Failed to save credentials');
76
- }
77
- }
78
-
79
- function handleStartOAuth(connectorId: string) {
80
- window.location.href = `/api/v1/dashboard/connectors/${connectorId}/auth`;
81
- }
82
-
83
- async function handleDisconnect(connectorId: string) {
84
- try {
85
- await api.disconnectConnector(connectorId);
86
- setSuccess('Disconnected successfully');
87
- void loadStatuses();
88
- } catch (err: unknown) {
89
- setError(err instanceof Error ? err.message : 'Failed to disconnect');
90
- }
91
- }
92
-
93
- return (
94
- <div className="page">
95
- <h2>Connections</h2>
96
- <div className="settings-form">
97
- {success && <div className="settings-success" onClick={() => setSuccess('')}>{success}</div>}
98
- {error && <div className="error">{error}</div>}
99
-
100
- {CONNECTORS.map(connector => {
101
- const state = states[connector.id];
102
- const isConnected = state?.connected;
103
- const hasCredentials = state?.hasCredentials;
104
-
105
- return (
106
- <div key={connector.id} className="settings-section">
107
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
108
- <div>
109
- <h3>{connector.name}</h3>
110
- <span style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
111
- {connector.description}
112
- </span>
113
- </div>
114
- <span className={isConnected ? 'badge badge-green' : 'badge badge-gray'}>
115
- {state?.loading ? 'Checking...' : isConnected ? 'Connected' : 'Not connected'}
116
- </span>
117
- </div>
118
-
119
- <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', margin: '0.5rem 0' }}>
120
- Enables: {connector.scopes}
121
- </p>
122
-
123
- {isConnected ? (
124
- <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem', alignItems: 'center' }}>
125
- {state?.expiresAt && (
126
- <span style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>
127
- Token expires: {new Date(state.expiresAt).toLocaleDateString()}
128
- </span>
129
- )}
130
- <button type="button" className="btn-sm" onClick={() => handleStartOAuth(connector.id)}>
131
- Reconnect
132
- </button>
133
- <button type="button" className="btn-sm btn-danger" onClick={() => handleDisconnect(connector.id)}>
134
- Disconnect
135
- </button>
136
- </div>
137
- ) : hasCredentials ? (
138
- <div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
139
- <button type="button" className="settings-btn" onClick={() => handleStartOAuth(connector.id)}>
140
- Connect
141
- </button>
142
- <button type="button" className="btn-sm" onClick={() => setShowSetup(connector.id)}>
143
- Update Credentials
144
- </button>
145
- </div>
146
- ) : (
147
- <div style={{ marginTop: '1rem' }}>
148
- {showSetup !== connector.id ? (
149
- <button type="button" className="settings-btn" onClick={() => setShowSetup(connector.id)}>
150
- Set Up
151
- </button>
152
- ) : null}
153
- </div>
154
- )}
155
-
156
- {showSetup === connector.id && (
157
- <div style={{ marginTop: '1rem', padding: '1rem', background: 'var(--bg-hover)', borderRadius: 'var(--radius)', border: '1px solid var(--border)' }}>
158
- <h4 style={{ margin: '0 0 0.75rem', fontFamily: 'var(--font-display)', fontWeight: 600 }}>Setup Instructions</h4>
159
- <ol style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', paddingLeft: '1.25rem', margin: '0 0 1rem' }}>
160
- {connector.instructions.map((step, i) => (
161
- <li key={i} style={{ marginBottom: '0.35rem' }}>
162
- {step.replace('{callbackUrl}', callbackUrl)}
163
- </li>
164
- ))}
165
- </ol>
166
-
167
- <div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginBottom: '1rem', wordBreak: 'break-all' }}>
168
- Redirect URI: <code style={{ fontFamily: 'var(--font-mono)', background: 'var(--accent-subtle)', color: 'var(--accent)', padding: '0.15rem 0.35rem', borderRadius: '4px', fontSize: '0.82em' }}>{callbackUrl}</code>
169
- </div>
170
-
171
- <label>Client ID</label>
172
- <input
173
- type="text"
174
- value={clientId}
175
- onChange={(e) => setClientId(e.target.value)}
176
- placeholder="your-client-id.apps.googleusercontent.com"
177
- />
178
- <label>Client Secret</label>
179
- <input
180
- type="password"
181
- value={clientSecret}
182
- onChange={(e) => setClientSecret(e.target.value)}
183
- placeholder="GOCSPX-..."
184
- />
185
- <div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.5rem' }}>
186
- <button type="button" className="settings-btn" onClick={() => handleSaveCredentials(connector.id)}>
187
- Save & Connect
188
- </button>
189
- <button type="button" className="btn-sm" onClick={() => { setShowSetup(null); setError(''); }}>
190
- Cancel
191
- </button>
192
- </div>
193
- </div>
194
- )}
195
- </div>
196
- );
197
- })}
198
- </div>
199
- </div>
200
- );
201
- }