tide-commander 1.165.1 → 1.166.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 (50) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/assets/{BossLogsModal-IqVqHwz-.js → BossLogsModal-CsMSjCZp.js} +1 -1
  3. package/dist/assets/{BossSpawnModal-Dj9ayWfR.js → BossSpawnModal-Z6ZZuraP.js} +1 -1
  4. package/dist/assets/{ControlsModal-ki85jvDA.js → ControlsModal-UAWrBPnS.js} +1 -1
  5. package/dist/assets/{DockerLogsModal-CeIuKlvk.js → DockerLogsModal-Dvk09sbP.js} +1 -1
  6. package/dist/assets/{EmbeddedEditor-BA9mX_Lg.js → EmbeddedEditor-n2QnK4hA.js} +1 -1
  7. package/dist/assets/{GmailOAuthSetup-7nj6aLot.js → GmailOAuthSetup-DhKifdH0.js} +1 -1
  8. package/dist/assets/GoogleOAuthSetup-D14lS0lT.js +270 -0
  9. package/dist/assets/{IframeModal-DkkCx6nq.js → IframeModal-c2Go5TIq.js} +1 -1
  10. package/dist/assets/{IntegrationsPanel-DHjpWEFF.js → IntegrationsPanel-CkZyLCWN.js} +2 -2
  11. package/dist/assets/{LogViewerModal-DKYB8dHo.js → LogViewerModal-onBz0hK3.js} +1 -1
  12. package/dist/assets/{MonitoringModal-DOKMIa5U.js → MonitoringModal-D58Oka-L.js} +1 -1
  13. package/dist/assets/{PM2LogsModal-XlbT9uoY.js → PM2LogsModal-DtEHMB6v.js} +1 -1
  14. package/dist/assets/{RestoreArchivedAreaModal-Du1f8Urv.js → RestoreArchivedAreaModal-BKgjpTYA.js} +1 -1
  15. package/dist/assets/{Scene2DCanvas-BTnpyyK6.js → Scene2DCanvas-BSe1eM97.js} +1 -1
  16. package/dist/assets/{SceneManager-owoMSHKT.js → SceneManager-Bv_J6Asr.js} +1 -1
  17. package/dist/assets/{SkillsPanel-BKjpKM1N.js → SkillsPanel-aK_Vhyhx.js} +1 -1
  18. package/dist/assets/{SlackMultiInstanceSetup-C0k60ioO.js → SlackMultiInstanceSetup-CR9tuDCR.js} +1 -1
  19. package/dist/assets/{SpawnModal-Cww_6dcz.js → SpawnModal-BGoN5C1p.js} +1 -1
  20. package/dist/assets/{StatisticsModal-Dl5loLLI.js → StatisticsModal-BY9pHl-m.js} +1 -1
  21. package/dist/assets/{SubordinateAssignmentModal-DefQL38f.js → SubordinateAssignmentModal-2bO3Y-zS.js} +1 -1
  22. package/dist/assets/{TriggerManagerPanel-RWrID12k.js → TriggerManagerPanel-CtDALyfM.js} +1 -1
  23. package/dist/assets/{WorkflowEditorPanel-Ccwm4H5t.js → WorkflowEditorPanel-CEYKw625.js} +1 -1
  24. package/dist/assets/{area-logos-DnrrrkFh.js → area-logos-HooY4aHb.js} +1 -1
  25. package/dist/assets/{index-D-MKKw7S.js → index-BSPc0AE-.js} +3 -3
  26. package/dist/assets/{index-Fcs4x2Vj.js → index-BTaesMkc.js} +1 -1
  27. package/dist/assets/{index-Cjgy2CnG.js → index-BxPFO5EJ.js} +1 -1
  28. package/dist/assets/{index-B5iakYkB.js → index-CCZsNuvk.js} +1 -1
  29. package/dist/assets/{index-CcHBNDP_.js → index-CUfnGcms.js} +2 -2
  30. package/dist/assets/{index-DG7HgtQn.js → index-CwjljR04.js} +1 -1
  31. package/dist/assets/{index-CbJ-R04c.js → index-DPJj90PK.js} +1 -1
  32. package/dist/assets/{index-DZ7i37eV.js → index-DdCk239w.js} +1 -1
  33. package/dist/assets/{index-g0yAcpoy.js → index-I8PSiTLl.js} +1 -1
  34. package/dist/assets/main-BzGnAbiA.css +1 -0
  35. package/dist/assets/{main-DIf9R6Jx.js → main-D6IBl2Xu.js} +8 -8
  36. package/dist/assets/{web-DhZ0XCan.js → web-B6Lyhp8u.js} +1 -1
  37. package/dist/assets/{web-DDxnPoTK.js → web-BVrXaF9j.js} +1 -1
  38. package/dist/assets/{web-Dlc8Nyra.js → web-DQi4Jy_X.js} +1 -1
  39. package/dist/build-info.json +1 -1
  40. package/dist/index.html +2 -2
  41. package/dist/src/packages/server/integrations/gmail/gmail-client.js +73 -5
  42. package/dist/src/packages/server/integrations/gmail/gmail-routes.js +9 -4
  43. package/dist/src/packages/server/integrations/google-auth/token-health.js +196 -0
  44. package/dist/src/packages/server/integrations/google-calendar/calendar-client.js +61 -6
  45. package/dist/src/packages/server/integrations/google-calendar/calendar-routes.js +9 -4
  46. package/dist/src/packages/server/integrations/google-drive/drive-client.js +61 -6
  47. package/dist/src/packages/server/integrations/google-drive/drive-routes.js +9 -4
  48. package/package.json +1 -1
  49. package/dist/assets/GoogleOAuthSetup-Bh-Cy-dE.js +0 -236
  50. package/dist/assets/main-Dnp-3iMX.css +0 -1
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { google } from 'googleapis';
8
8
  import { loadConfig, updateConfig } from './drive-config.js';
9
+ import { getTokenHealth, needsReauth, notifyTokenRotated, onTokenRotated, probeTokenHealth, reportGoogleApiError, reportGoogleApiSuccess, startTokenHealthMonitor, } from '../google-auth/token-health.js';
9
10
  import { Readable } from 'stream';
10
11
  // ─── State ───
11
12
  let ctx = null;
@@ -22,6 +23,30 @@ const SCOPES = [
22
23
  ];
23
24
  const REDIRECT_PATH = '/api/drive/auth/callback';
24
25
  let oauth2Client = null;
26
+ /**
27
+ * Build an OAuth2 client whose every outbound call updates shared token health.
28
+ *
29
+ * Every Drive and Docs call made with `auth: oauth2Client` funnels through this one
30
+ * `request()` method, so wrapping it here means a revoked token is detected the moment
31
+ * an agent actually touches Drive — no waiting for the next background probe — without
32
+ * instrumenting each of the ~15 API call sites.
33
+ */
34
+ function createOAuthClient(clientId, clientSecret, redirectUri) {
35
+ const client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
36
+ const originalRequest = client.request.bind(client);
37
+ client.request = (async (opts) => {
38
+ try {
39
+ const result = await originalRequest(opts);
40
+ reportGoogleApiSuccess();
41
+ return result;
42
+ }
43
+ catch (err) {
44
+ reportGoogleApiError(err);
45
+ throw err;
46
+ }
47
+ });
48
+ return client;
49
+ }
25
50
  function getRedirectUri() {
26
51
  if (!ctx)
27
52
  throw new Error('Google Drive not initialized');
@@ -44,10 +69,16 @@ export async function init(integrationCtx) {
44
69
  ctx.log.info('Google Drive missing OAuth credentials, skipping init');
45
70
  return;
46
71
  }
47
- oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
72
+ oauth2Client = createOAuthClient(clientId, clientSecret, getRedirectUri());
48
73
  oauth2Client.setCredentials({ refresh_token: refreshToken });
49
74
  driveApi = google.drive({ version: 'v3', auth: oauth2Client });
50
75
  docsApi = google.docs({ version: 'v1', auth: oauth2Client });
76
+ // Constructing the client above proves nothing — it just wraps the token string.
77
+ // Probe Google (and keep probing) so an expired token is reflected in getStatus().
78
+ startTokenHealthMonitor(ctx);
79
+ // Re-read the shared refresh token if the user re-consents via Gmail or Calendar,
80
+ // otherwise this client keeps using the token it was seeded with here.
81
+ onTokenRotated('google-drive', () => init(integrationCtx));
51
82
  ctx.log.info('Google Drive initialized');
52
83
  }
53
84
  export async function shutdown() {
@@ -61,16 +92,36 @@ export function getStatus() {
61
92
  const hasCredentials = !!(ctx?.secrets.get('GOOGLE_CLIENT_ID') &&
62
93
  ctx?.secrets.get('GOOGLE_CLIENT_SECRET') &&
63
94
  ctx?.secrets.get('GOOGLE_REFRESH_TOKEN'));
95
+ const health = getTokenHealth();
96
+ const reauth = hasCredentials && needsReauth(health);
97
+ // Having credentials on disk is necessary but not sufficient — a revoked refresh
98
+ // token is still three non-empty strings. Only report connected when Google's last
99
+ // verdict wasn't a rejection. An 'unreachable' probe (offline host) is deliberately
100
+ // NOT treated as a rejection: it would push users into a pointless re-consent flow.
101
+ const wired = config.enabled && hasCredentials && driveApi !== null;
102
+ let error;
103
+ if (!hasCredentials && config.enabled)
104
+ error = 'Missing OAuth credentials';
105
+ else if (reauth)
106
+ error = health.error;
64
107
  return {
65
- authenticated: Boolean(driveApi && hasCredentials),
66
- connected: config.enabled && hasCredentials && driveApi !== null,
67
- lastChecked: Date.now(),
68
- error: !hasCredentials && config.enabled ? 'Missing OAuth credentials' : undefined,
108
+ authenticated: Boolean(driveApi && hasCredentials) && !reauth,
109
+ connected: wired && !reauth,
110
+ lastChecked: health.checkedAt || Date.now(),
111
+ error,
112
+ needsReauth: reauth,
113
+ tokenState: hasCredentials ? health.state : undefined,
69
114
  };
70
115
  }
71
116
  export function isConfigured() {
72
117
  return driveApi !== null;
73
118
  }
119
+ /** Force a live refresh_token exchange against Google and return the updated status. */
120
+ export async function probeToken() {
121
+ if (ctx)
122
+ await probeTokenHealth(ctx, { force: true });
123
+ return getStatus();
124
+ }
74
125
  // ─── OAuth ───
75
126
  export function getAuthUrl() {
76
127
  if (!oauth2Client) {
@@ -79,7 +130,7 @@ export function getAuthUrl() {
79
130
  if (!clientId || !clientSecret || !ctx) {
80
131
  throw new Error('Google Drive OAuth not configured');
81
132
  }
82
- oauth2Client = new google.auth.OAuth2(clientId, clientSecret, getRedirectUri());
133
+ oauth2Client = createOAuthClient(clientId, clientSecret, getRedirectUri());
83
134
  }
84
135
  return oauth2Client.generateAuthUrl({
85
136
  access_type: 'offline',
@@ -99,6 +150,10 @@ export async function handleAuthCallback(code) {
99
150
  docsApi = google.docs({ version: 'v1', auth: oauth2Client });
100
151
  // Auto-enable the integration after successful OAuth
101
152
  updateConfig({ enabled: true });
153
+ // Fresh consent just produced this token. Clears the 'expired' verdict AND re-arms
154
+ // Gmail and Calendar, whose clients still hold the old refresh token — all three
155
+ // share this one secret, so one consent must repair all three.
156
+ await notifyTokenRotated();
102
157
  ctx.log.info('Google Drive OAuth complete. Drive initialized.');
103
158
  }
104
159
  // ─── Files CRUD ───
@@ -283,10 +283,15 @@ router.get('/search', async (req, res) => {
283
283
  }
284
284
  });
285
285
  // ─── Status & Auth ───
286
- // GET /api/drive/status — Get Google Drive auth status
287
- router.get('/status', (_req, res) => {
288
- const status = driveClient.getStatus();
289
- res.json(status);
286
+ // GET /api/drive/status — Get Google Drive auth status.
287
+ // `?probe=1` forces a live refresh_token exchange against Google instead of reusing
288
+ // the cached verdict — the settings UI uses it to verify the token on open, so a
289
+ // revoked token can't keep rendering as "Connected".
290
+ router.get('/status', async (req, res) => {
291
+ if (req.query.probe === '1' || req.query.probe === 'true') {
292
+ await driveClient.probeToken();
293
+ }
294
+ res.json(driveClient.getStatus());
290
295
  });
291
296
  // GET /api/drive/auth/url — Get OAuth authorization URL
292
297
  router.get('/auth/url', (_req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.165.1",
3
+ "version": "1.166.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,236 +0,0 @@
1
- import{r as t,i as w,k as v,j as e}from"./main-DIf9R6Jx.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";function B({integration:o,onSave:j,onCancel:h}){const[l,C]=t.useState(o.values.GOOGLE_CLIENT_ID||""),[p,E]=t.useState(""),[b,S]=t.useState(o.values.GOOGLE_REDIRECT_BASE_URL||""),[y,G]=t.useState(null),[g,O]=t.useState(null),[c,u]=t.useState(!1),[N,s]=t.useState(null),[A,z]=t.useState(!1),I=o.values.GOOGLE_CLIENT_ID==="********"&&o.values.GOOGLE_CLIENT_SECRET==="********",[x,f]=t.useState(o.status.connected?"connected":"credentials"),r=t.useRef(null),m=t.useCallback(async()=>{try{const n={gmail:"/api/email/status","google-calendar":"/api/calendar/status","google-drive":"/api/drive/status"}[o.id]||"/api/calendar/status",i=await w(v(n));if(i.ok){const d=await i.json();O(d),d.authenticated&&(f("connected"),r.current&&(clearInterval(r.current),r.current=null))}}catch(a){console.error("Failed to fetch Google auth status:",a)}},[o.id]);t.useEffect(()=>(m(),()=>{r.current&&clearInterval(r.current)}),[m]);const k=async()=>{const n={gmail:"/api/email/auth/url","google-calendar":"/api/calendar/auth/url","google-drive":"/api/drive/auth/url"}[o.id]||"/api/calendar/auth/url",i=await w(v(n));if(!i.ok){const _=await i.json().catch(()=>({}));throw new Error(_.error||`HTTP ${i.status}`)}const d=await i.json();if(!d.url)throw new Error("OAuth URL is empty");G(d.url),s(null),f("authorize"),r.current&&clearInterval(r.current),r.current=setInterval(m,3e3)},L=async()=>{u(!0),s(null);try{await k()}catch(a){s(a instanceof Error?a.message:"Failed to get OAuth URL")}finally{u(!1)}},R=async()=>{if(!l.trim()){s("Client ID is required");return}u(!0),s(null);try{const a={GOOGLE_REDIRECT_BASE_URL:b.trim()};l.trim()&&l.trim()!=="********"&&(a.GOOGLE_CLIENT_ID=l.trim()),p.trim()&&(a.GOOGLE_CLIENT_SECRET=p.trim()),console.log("Saving Google OAuth config...",a),await j(a),console.log("Config saved successfully"),await k()}catch(a){const n=a instanceof Error?a.message:"Failed to get OAuth URL";console.error("Error in handleSaveCredentials:",n,a),s(n)}finally{u(!1)}};return e.jsxs("div",{className:"google-oauth-setup",children:[N&&e.jsx("div",{className:"google-oauth-error",children:N}),x==="credentials"&&e.jsxs("div",{className:"google-oauth-section",children:[e.jsx("h4",{className:"google-oauth-section-title",children:"Google OAuth Credentials"}),I&&!A?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"google-oauth-shared-banner",children:[e.jsxs("div",{className:"google-oauth-shared-title",children:[e.jsx("span",{children:"✓"}),e.jsx("span",{children:"Credentials already configured"})]}),e.jsx("div",{className:"google-oauth-shared-body",children:"Existing Google OAuth credentials (Client ID and Secret) are saved in your secrets store — they're shared between Gmail, Calendar, and Drive. You may still need to re-authorize once so the refresh token grants access to this service."})]}),e.jsxs("div",{className:"integration-form-actions",children:[e.jsx("button",{type:"button",className:"integration-btn cancel",onClick:h,children:"Cancel"}),e.jsx("button",{type:"button",className:"integration-btn secondary",onClick:()=>z(!0),disabled:c,children:"Edit credentials"}),e.jsx("button",{type:"button",className:"integration-btn save",onClick:L,disabled:c,children:c?"Loading...":"Authorize with existing credentials"})]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"google-oauth-help",children:["Create OAuth2 credentials in the"," ",e.jsx("a",{href:"https://console.cloud.google.com/apis/credentials",target:"_blank",rel:"noopener noreferrer",className:"google-oauth-link",children:"Google Cloud Console"}),". Enable the relevant API (Gmail, Calendar, or Drive). Set the redirect URI to:"," ",e.jsx("code",{className:"google-oauth-code",children:(()=>{const a=o.id==="gmail"?"/api/email/auth/callback":o.id==="google-drive"?"/api/drive/auth/callback":"/api/calendar/auth/callback",n=b.trim();return n?`${n.replace(/\/$/,"")}${a}`:v(a)})()})]}),e.jsxs("div",{className:"google-oauth-field",children:[e.jsxs("label",{className:"integration-field-label",children:["OAuth Client ID ",e.jsx("span",{className:"integration-field-required",children:"*"})]}),e.jsx("input",{type:"text",className:"integration-field-input",value:l,placeholder:"xxxx.apps.googleusercontent.com",onChange:a=>C(a.target.value)})]}),e.jsxs("div",{className:"google-oauth-field",children:[e.jsxs("label",{className:"integration-field-label",children:["OAuth Client Secret ",e.jsx("span",{className:"integration-field-required",children:"*"})]}),e.jsx("input",{type:"password",className:"integration-field-input",value:p,placeholder:o.values.GOOGLE_CLIENT_SECRET?"(saved)":"Enter client secret",onChange:a=>E(a.target.value),autoComplete:"off"})]}),e.jsxs("div",{className:"google-oauth-field",children:[e.jsx("label",{className:"integration-field-label",children:"OAuth Redirect Base URL"}),e.jsx("input",{type:"text",className:"integration-field-input",value:b,placeholder:"http://localhost:6200",onChange:a=>S(a.target.value),autoComplete:"off"}),e.jsxs("span",{className:"integration-field-description",style:{display:"block",marginTop:7,color:"#7f849c",fontSize:12},children:["Override the base URL used to build the OAuth redirect (e.g. ",e.jsx("code",{children:"http://commander.local:10003"}),"). Shared across Gmail, Calendar, and Drive. Leave empty to use the server's local address. Google does not accept raw IPs — use a domain (you can map one in ",e.jsx("code",{children:"/etc/hosts"}),")."]})]}),e.jsxs("div",{className:"integration-form-actions",children:[e.jsx("button",{type:"button",className:"integration-btn cancel",onClick:h,children:"Cancel"}),e.jsx("button",{type:"button",className:"integration-btn save",onClick:R,disabled:c||!l.trim(),children:c?"Saving...":"Save & Authorize"})]})]})]}),x==="authorize"&&e.jsxs("div",{className:"google-oauth-section",children:[e.jsx("h4",{className:"google-oauth-section-title",children:"Authorize Google Access"}),e.jsx("p",{className:"google-oauth-help",children:"Click the link below to authorize Tide Commander to access your Google account. After granting access, you will be redirected back and the connection will be established automatically."}),y&&e.jsx("a",{href:y,target:"_blank",rel:"noopener noreferrer",className:"google-oauth-authorize-btn",children:"Authorize with Google"}),e.jsxs("div",{className:"google-oauth-waiting",children:[e.jsx("span",{className:"google-oauth-spinner"}),e.jsx("span",{children:"Waiting for authorization to complete..."})]}),e.jsx("div",{className:"integration-form-actions",children:e.jsx("button",{type:"button",className:"integration-btn cancel",onClick:()=>{r.current&&(clearInterval(r.current),r.current=null),f("credentials")},children:"Back"})})]}),x==="connected"&&e.jsxs("div",{className:"google-oauth-section",children:[e.jsx("h4",{className:"google-oauth-section-title",children:"Google Connected"}),e.jsxs("div",{className:"google-oauth-connected-info",children:[e.jsx("span",{className:"google-oauth-connected-badge",children:"Connected"}),(g==null?void 0:g.emailAddress)&&e.jsx("span",{className:"google-oauth-email",children:g.emailAddress})]}),e.jsx("p",{className:"google-oauth-help",children:"Your Google account has been successfully connected."}),e.jsx("div",{className:"integration-form-actions",children:e.jsx("button",{type:"button",className:"integration-btn cancel",onClick:async()=>{await j({}),h()},children:"Close"})})]}),e.jsx("style",{children:`
2
- .google-oauth-setup {
3
- padding: 0;
4
- }
5
- .google-oauth-error {
6
- background: linear-gradient(135deg, rgba(243, 139, 168, 0.2) 0%, rgba(243, 139, 168, 0.08) 100%);
7
- border-left: 3px solid #f38ba8;
8
- color: #f38ba8;
9
- padding: 12px 16px;
10
- border-radius: 8px;
11
- margin-bottom: 18px;
12
- font-size: 13px;
13
- font-weight: 500;
14
- }
15
- .google-oauth-section {
16
- background: linear-gradient(180deg, rgba(45, 49, 69, 0.4) 0%, rgba(30, 30, 46, 0.2) 100%);
17
- border: 1px solid rgba(137, 180, 250, 0.15);
18
- border-radius: 12px;
19
- padding: 28px;
20
- backdrop-filter: blur(10px);
21
- }
22
- .google-oauth-section-title {
23
- margin: 0 0 16px 0;
24
- color: #cdd6f4;
25
- font-size: 18px;
26
- font-weight: 700;
27
- letter-spacing: -0.5px;
28
- }
29
- .google-oauth-help {
30
- color: #a6adc8;
31
- font-size: 13px;
32
- line-height: 1.6;
33
- margin: 0 0 20px 0;
34
- font-weight: 400;
35
- }
36
- .google-oauth-link {
37
- color: #89b4fa;
38
- text-decoration: none;
39
- border-bottom: 1.5px solid rgba(137, 180, 250, 0.4);
40
- transition: all 0.2s ease;
41
- font-weight: 500;
42
- }
43
- .google-oauth-link:hover {
44
- color: #a8c5ff;
45
- border-bottom-color: #89b4fa;
46
- }
47
- .google-oauth-code {
48
- background: rgba(137, 180, 250, 0.15);
49
- color: #89b4fa;
50
- padding: 4px 10px;
51
- border-radius: 6px;
52
- font-size: 12px;
53
- word-break: break-all;
54
- font-weight: 500;
55
- font-family: 'Monaco', 'Menlo', monospace;
56
- }
57
- .google-oauth-field {
58
- margin-bottom: 18px;
59
- }
60
- .integration-field-label {
61
- display: block;
62
- margin-bottom: 8px;
63
- color: #cdd6f4;
64
- font-size: 14px;
65
- font-weight: 600;
66
- letter-spacing: 0.3px;
67
- }
68
- .integration-field-required {
69
- color: #f38ba8;
70
- }
71
- .integration-field-input {
72
- width: 100%;
73
- background: rgba(30, 30, 46, 0.6);
74
- border: 1.5px solid rgba(137, 180, 250, 0.2);
75
- color: #cdd6f4;
76
- padding: 11px 14px;
77
- border-radius: 8px;
78
- font-size: 13px;
79
- transition: all 0.2s ease;
80
- font-weight: 500;
81
- }
82
- .integration-field-input:focus {
83
- outline: none;
84
- border-color: #89b4fa;
85
- background: rgba(30, 30, 46, 0.8);
86
- box-shadow: 0 0 0 3px rgba(137, 180, 250, 0.15);
87
- }
88
- .google-oauth-authorize-btn {
89
- display: inline-block;
90
- background: linear-gradient(135deg, #89b4fa 0%, #7aa3f0 100%);
91
- color: #1e1e2e;
92
- padding: 12px 32px;
93
- border-radius: 8px;
94
- text-decoration: none;
95
- font-weight: 700;
96
- font-size: 14px;
97
- margin-bottom: 20px;
98
- transition: all 0.25s ease;
99
- border: none;
100
- cursor: pointer;
101
- box-shadow: 0 4px 12px rgba(137, 180, 250, 0.25);
102
- letter-spacing: 0.3px;
103
- }
104
- .google-oauth-authorize-btn:hover {
105
- transform: translateY(-2px);
106
- box-shadow: 0 6px 16px rgba(137, 180, 250, 0.35);
107
- background: linear-gradient(135deg, #a8c5ff 0%, #8eb8ff 100%);
108
- }
109
- .google-oauth-waiting {
110
- display: flex;
111
- align-items: center;
112
- gap: 12px;
113
- color: #a6adc8;
114
- font-size: 13px;
115
- margin-bottom: 20px;
116
- background: rgba(137, 180, 250, 0.08);
117
- padding: 14px 16px;
118
- border-radius: 8px;
119
- border-left: 3px solid #89b4fa;
120
- font-weight: 500;
121
- }
122
- .google-oauth-spinner {
123
- display: inline-block;
124
- width: 16px;
125
- height: 16px;
126
- border: 2.5px solid rgba(137, 180, 250, 0.25);
127
- border-top-color: #89b4fa;
128
- border-radius: 50%;
129
- animation: google-spin 0.8s linear infinite;
130
- flex-shrink: 0;
131
- }
132
- @keyframes google-spin {
133
- to { transform: rotate(360deg); }
134
- }
135
- .google-oauth-connected-info {
136
- display: flex;
137
- align-items: center;
138
- gap: 14px;
139
- margin-bottom: 20px;
140
- background: rgba(166, 227, 161, 0.08);
141
- padding: 16px;
142
- border-radius: 10px;
143
- border-left: 3px solid #a6e3a1;
144
- }
145
- .google-oauth-connected-badge {
146
- background: linear-gradient(135deg, rgba(166, 227, 161, 0.3) 0%, rgba(166, 227, 161, 0.15) 100%);
147
- color: #a6e3a1;
148
- padding: 6px 14px;
149
- border-radius: 6px;
150
- font-size: 12px;
151
- font-weight: 700;
152
- letter-spacing: 0.4px;
153
- text-transform: uppercase;
154
- border: 1px solid rgba(166, 227, 161, 0.3);
155
- flex-shrink: 0;
156
- }
157
- .google-oauth-email {
158
- color: #cdd6f4;
159
- font-size: 14px;
160
- font-weight: 600;
161
- letter-spacing: 0.2px;
162
- }
163
- .integration-form-actions {
164
- display: flex;
165
- gap: 12px;
166
- justify-content: flex-end;
167
- margin-top: 24px;
168
- padding-top: 20px;
169
- border-top: 1px solid rgba(137, 180, 250, 0.1);
170
- }
171
- .integration-btn {
172
- padding: 10px 24px;
173
- border-radius: 8px;
174
- font-weight: 600;
175
- font-size: 13px;
176
- border: none;
177
- cursor: pointer;
178
- transition: all 0.2s ease;
179
- letter-spacing: 0.3px;
180
- }
181
- .integration-btn.save {
182
- background: linear-gradient(135deg, #89b4fa 0%, #7aa3f0 100%);
183
- color: #1e1e2e;
184
- box-shadow: 0 4px 12px rgba(137, 180, 250, 0.25);
185
- }
186
- .integration-btn.save:hover:not(:disabled) {
187
- transform: translateY(-2px);
188
- box-shadow: 0 6px 16px rgba(137, 180, 250, 0.35);
189
- }
190
- .integration-btn.cancel {
191
- background: rgba(137, 180, 250, 0.1);
192
- color: #89b4fa;
193
- border: 1.5px solid rgba(137, 180, 250, 0.2);
194
- }
195
- .integration-btn.cancel:hover:not(:disabled) {
196
- background: rgba(137, 180, 250, 0.15);
197
- border-color: #89b4fa;
198
- }
199
- .integration-btn.secondary {
200
- background: rgba(249, 226, 175, 0.1);
201
- color: #f9e2af;
202
- border: 1.5px solid rgba(249, 226, 175, 0.25);
203
- }
204
- .integration-btn.secondary:hover:not(:disabled) {
205
- background: rgba(249, 226, 175, 0.18);
206
- border-color: #f9e2af;
207
- }
208
- .integration-btn:disabled {
209
- opacity: 0.5;
210
- cursor: not-allowed;
211
- }
212
- .google-oauth-shared-banner {
213
- background: linear-gradient(135deg, rgba(166, 227, 161, 0.15) 0%, rgba(166, 227, 161, 0.05) 100%);
214
- border: 1px solid rgba(166, 227, 161, 0.3);
215
- border-left: 3px solid #a6e3a1;
216
- border-radius: 10px;
217
- padding: 16px 18px;
218
- margin-bottom: 8px;
219
- }
220
- .google-oauth-shared-title {
221
- display: flex;
222
- align-items: center;
223
- gap: 10px;
224
- color: #a6e3a1;
225
- font-size: 14px;
226
- font-weight: 700;
227
- letter-spacing: 0.2px;
228
- margin-bottom: 8px;
229
- }
230
- .google-oauth-shared-body {
231
- color: #a6adc8;
232
- font-size: 13px;
233
- line-height: 1.6;
234
- font-weight: 400;
235
- }
236
- `})]})}export{B as GoogleOAuthSetup};