@plutonhq/core-frontend 0.1.21 → 0.1.23

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 (70) hide show
  1. package/dist-lib/@types/plans.d.ts +11 -20
  2. package/dist-lib/@types/plans.d.ts.map +1 -1
  3. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +49 -47
  4. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
  5. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts +7 -4
  6. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.d.ts.map +1 -1
  7. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js +177 -52
  8. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettings.js.map +1 -1
  9. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts +9 -0
  10. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.d.ts.map +1 -0
  11. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js +62 -0
  12. package/dist-lib/components/Plan/PlanSettings/PlanNotificationSettingsTester.js.map +1 -0
  13. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js +2 -2
  14. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js.map +1 -1
  15. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +56 -40
  16. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
  17. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts.map +1 -1
  18. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +16 -8
  19. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
  20. package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js +42 -26
  21. package/dist-lib/components/Storage/AddStorage/AddStorage.module.scss.js.map +1 -1
  22. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.d.ts.map +1 -1
  23. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js +119 -53
  24. package/dist-lib/components/Storage/StorageAuthSettings/StorageAuthSettings.js.map +1 -1
  25. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  26. package/dist-lib/components/common/Icon/Icon.js +15 -1
  27. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  28. package/dist-lib/components/index.d.ts +1 -0
  29. package/dist-lib/components/index.d.ts.map +1 -1
  30. package/dist-lib/components.js +86 -84
  31. package/dist-lib/components.js.map +1 -1
  32. package/dist-lib/providers/s3compatible.png +0 -0
  33. package/dist-lib/services/plans.d.ts +13 -1
  34. package/dist-lib/services/plans.d.ts.map +1 -1
  35. package/dist-lib/services/plans.js +88 -61
  36. package/dist-lib/services/plans.js.map +1 -1
  37. package/dist-lib/services/settings.d.ts.map +1 -1
  38. package/dist-lib/services/settings.js +25 -24
  39. package/dist-lib/services/settings.js.map +1 -1
  40. package/dist-lib/services/storage.d.ts +10 -0
  41. package/dist-lib/services/storage.d.ts.map +1 -1
  42. package/dist-lib/services/storage.js +55 -20
  43. package/dist-lib/services/storage.js.map +1 -1
  44. package/dist-lib/services.js +106 -101
  45. package/dist-lib/styles/core-frontend.css +1 -1
  46. package/dist-lib/utils/constants.d.ts.map +1 -1
  47. package/dist-lib/utils/constants.js +28 -5
  48. package/dist-lib/utils/constants.js.map +1 -1
  49. package/dist-lib/utils/helpers.d.ts +1 -0
  50. package/dist-lib/utils/helpers.d.ts.map +1 -1
  51. package/dist-lib/utils/helpers.js +17 -10
  52. package/dist-lib/utils/helpers.js.map +1 -1
  53. package/dist-lib/utils.js +28 -27
  54. package/package.json +1 -1
  55. package/src/@types/plans.ts +11 -20
  56. package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +4 -4
  57. package/src/components/Plan/PlanSettings/PlanNotificationSettings.tsx +179 -47
  58. package/src/components/Plan/PlanSettings/PlanNotificationSettingsTester.tsx +85 -0
  59. package/src/components/Plan/PlanSettings/PlanPruneSettings.tsx +2 -2
  60. package/src/components/Plan/PlanSettings/PlanSettings.module.scss +71 -0
  61. package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +6 -1
  62. package/src/components/Storage/AddStorage/AddStorage.module.scss +74 -0
  63. package/src/components/Storage/StorageAuthSettings/StorageAuthSettings.tsx +136 -3
  64. package/src/components/common/Icon/Icon.tsx +16 -0
  65. package/src/components/index.ts +1 -0
  66. package/src/services/plans.ts +38 -1
  67. package/src/services/settings.ts +2 -2
  68. package/src/services/storage.ts +49 -0
  69. package/src/utils/constants.ts +23 -0
  70. package/src/utils/helpers.ts +9 -0
@@ -2,75 +2,207 @@ import classes from './PlanSettings.module.scss';
2
2
  import Select from '../../common/form/Select/Select';
3
3
  import Input from '../../common/form/Input/Input';
4
4
  import Toggle from '../../common/form/Toggle/Toggle';
5
- import { NewPlanSettings, PlanNotificationCase } from '../../../@types/plans';
5
+ import { PlanNotification, PlanNotificationCase } from '../../../@types/plans';
6
6
  import { NavLink } from 'react-router';
7
+ import PlanNotificationSettingsTester from './PlanNotificationSettingsTester';
8
+ import { Icon } from '../..';
7
9
 
8
10
  interface PlanNotificationSettingsProps {
9
- plan: NewPlanSettings;
10
11
  types: string[];
12
+ isSync: boolean;
13
+ notificationType?: 'backup' | 'integrity';
11
14
  admin_email?: string;
12
- onUpdate: (notificationSettings: NewPlanSettings['settings']['notification']) => void;
15
+ planID?: string;
16
+ notificationSettings: PlanNotification;
17
+ onUpdate: (notificationSettings: PlanNotification) => void;
13
18
  }
14
19
 
15
- const PlanNotificationSettings = ({ plan, types = [], admin_email = '', onUpdate }: PlanNotificationSettingsProps) => {
16
- const notificationSettings = plan.settings?.notification || {};
20
+ const PlanNotificationSettings = ({
21
+ planID,
22
+ isSync,
23
+ types = [],
24
+ admin_email = '',
25
+ notificationSettings,
26
+ notificationType = 'backup',
27
+ onUpdate,
28
+ }: PlanNotificationSettingsProps) => {
29
+ const hasConnectedIntegrations = types.length > 0;
30
+ const defaultEmail = !hasConnectedIntegrations && admin_email ? admin_email : '';
17
31
 
18
32
  const updateNotificationEmails = (emails: string) => {
19
33
  onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, emails } });
20
34
  };
21
35
 
22
- const hasConnectedIntegrations = types.length > 0;
23
- const defaultEmail = !hasConnectedIntegrations && admin_email ? admin_email : '';
36
+ const caseOptions = [
37
+ { label: 'On Start', value: 'start' },
38
+ { label: 'On End', value: 'end' },
39
+ { label: 'On Both Start & End', value: 'both' },
40
+ { label: 'On Success Only', value: 'success' },
41
+ { label: 'On Failure Only', value: 'failure' },
42
+ ];
24
43
 
25
44
  return (
26
45
  <>
27
- <div className={classes.field}>
28
- <label className={classes.label}>Notification</label>
29
- <Toggle
30
- fieldValue={notificationSettings?.email?.enabled || false}
31
- onUpdate={(val: boolean) => onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, enabled: val } })}
32
- description={plan.method === 'sync' ? 'Notify Me when Sync fails' : 'Notify Me when Backup fails or succeeds'}
33
- />
46
+ <div className={classes.notificationSettingsSection}>
47
+ <div className={`${classes.field} ${classes.notificationToggle}`}>
48
+ <Icon type="email" size={14} />
49
+ <Toggle
50
+ label="Enable Email Notifications"
51
+ fieldValue={notificationSettings?.email?.enabled || false}
52
+ onUpdate={(val: boolean) => onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, enabled: val } })}
53
+ hint={'Notify me via email when Backup fails or succeeds'}
54
+ inline={true}
55
+ />
56
+ </div>
57
+ {notificationSettings?.email?.enabled && (
58
+ <div className={classes.notificationSettings}>
59
+ {!isSync && (
60
+ <div className={classes.field}>
61
+ <Select
62
+ label="Send Notification"
63
+ fieldValue={notificationSettings?.email?.case || 'failure'}
64
+ options={caseOptions}
65
+ onUpdate={(val: string) =>
66
+ onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, case: val as PlanNotificationCase } })
67
+ }
68
+ inline={true}
69
+ />
70
+ </div>
71
+ )}
72
+ <div className={classes.field}>
73
+ <Input
74
+ label="Email Addresses"
75
+ fieldValue={notificationSettings?.email?.emails || defaultEmail}
76
+ onUpdate={(val) => updateNotificationEmails(val)}
77
+ placeholder="john@gmail.com, chris@icloud.com"
78
+ full={true}
79
+ inline={true}
80
+ required={true}
81
+ disabled={!hasConnectedIntegrations}
82
+ error={!notificationSettings?.email?.emails ? 'Required' : undefined}
83
+ />
84
+ {!hasConnectedIntegrations && (
85
+ <div className={classes.fieldNotice}>
86
+ ⚠️ SMTP has not been setup yet. Set it up in <NavLink to={`/settings?t=integration`}>Settings</NavLink> to enable Email
87
+ Notification.
88
+ </div>
89
+ )}
90
+ </div>
91
+ </div>
92
+ )}
34
93
  </div>
35
- {notificationSettings?.email?.enabled && (
36
- <>
37
- {plan.method !== 'sync' && (
94
+
95
+ {/* Slack Notification */}
96
+ <div className={classes.notificationSettingsSection}>
97
+ <div className={`${classes.field} ${classes.notificationToggle}`}>
98
+ <Icon type="slack" size={14} />
99
+ <Toggle
100
+ label="Enable Slack Notifications"
101
+ fieldValue={notificationSettings?.slack?.enabled || false}
102
+ onUpdate={(val: boolean) =>
103
+ onUpdate({
104
+ ...notificationSettings,
105
+ slack: { ...notificationSettings.slack, enabled: val, url: notificationSettings.slack?.url || '' },
106
+ })
107
+ }
108
+ hint="Send notifications to a Slack channel via webhook"
109
+ inline={true}
110
+ />
111
+ </div>
112
+ {notificationSettings?.slack?.enabled && (
113
+ <div className={classes.notificationSettings}>
114
+ {!isSync && (
115
+ <div className={classes.field}>
116
+ <Select
117
+ label="Send Slack Notification"
118
+ fieldValue={notificationSettings?.slack?.case || 'failure'}
119
+ options={caseOptions}
120
+ onUpdate={(val: string) =>
121
+ onUpdate({ ...notificationSettings, slack: { ...notificationSettings.slack, case: val as PlanNotificationCase } })
122
+ }
123
+ inline={true}
124
+ />
125
+ </div>
126
+ )}
38
127
  <div className={classes.field}>
39
- <label className={classes.label}>Send Notification</label>
40
- <Select
41
- fieldValue={notificationSettings?.email?.case || 'failure'}
42
- options={[
43
- { label: 'On Start', value: 'start' },
44
- { label: 'On End', value: 'end' },
45
- { label: 'On Both Start & End', value: 'both' },
46
- { label: 'On Success Only', value: 'success' },
47
- { label: 'On Failure Only', value: 'failure' },
48
- ]}
49
- onUpdate={(val: string) =>
50
- onUpdate({ ...notificationSettings, email: { ...notificationSettings.email, case: val as PlanNotificationCase } })
51
- }
128
+ <Input
129
+ label="Slack Webhook URL"
130
+ fieldValue={notificationSettings?.slack?.url || ''}
131
+ onUpdate={(val) => onUpdate({ ...notificationSettings, slack: { ...notificationSettings.slack, url: val } })}
132
+ placeholder="https://hooks.slack.com/services/T00/B00/xxxx"
133
+ required={true}
134
+ inline={true}
135
+ full={true}
136
+ error={notificationSettings?.slack?.enabled && !notificationSettings?.slack?.url ? 'Required' : undefined}
137
+ />
138
+ </div>
139
+ <div className={`${classes.field} ${classes.notificationTestField}`}>
140
+ <PlanNotificationSettingsTester
141
+ planId={planID || ''}
142
+ notificationChannel="slack"
143
+ notificationSettings={notificationSettings}
144
+ notificationType={notificationType}
52
145
  />
53
146
  </div>
54
- )}
55
- <div className={classes.field}>
56
- <label className={classes.label}>Email Addresses</label>
57
- <Input
58
- fieldValue={notificationSettings?.email?.emails || defaultEmail}
59
- onUpdate={(val) => updateNotificationEmails(val)}
60
- placeholder="john@gmail.com, chris@icloud.com"
61
- full={true}
62
- disabled={!hasConnectedIntegrations}
63
- error={!notificationSettings?.email?.emails ? 'Required' : undefined}
64
- />
65
- {!hasConnectedIntegrations && (
66
- <div className={classes.fieldNotice}>
67
- ⚠️ SMTP has not been setup yet. Set it up in <NavLink to={`/settings?t=integration`}>Settings</NavLink> to enable Email
68
- Notification.
147
+ </div>
148
+ )}
149
+ </div>
150
+
151
+ {/* Discord Notification */}
152
+ <div className={classes.notificationSettingsSection}>
153
+ <div className={`${classes.field} ${classes.notificationToggle}`}>
154
+ <Icon type="discord" size={14} />
155
+ <Toggle
156
+ label="Send Discord Notifications"
157
+ fieldValue={notificationSettings?.discord?.enabled || false}
158
+ onUpdate={(val: boolean) =>
159
+ onUpdate({
160
+ ...notificationSettings,
161
+ discord: { ...notificationSettings.discord, enabled: val, url: notificationSettings.discord?.url || '' },
162
+ })
163
+ }
164
+ hint="Send notifications to a Discord channel via webhook"
165
+ inline={true}
166
+ />
167
+ </div>
168
+ {notificationSettings?.discord?.enabled && (
169
+ <div className={classes.notificationSettings}>
170
+ {!isSync && (
171
+ <div className={classes.field}>
172
+ <Select
173
+ label="Send Discord Notification"
174
+ fieldValue={notificationSettings?.discord?.case || 'failure'}
175
+ options={caseOptions}
176
+ onUpdate={(val: string) =>
177
+ onUpdate({ ...notificationSettings, discord: { ...notificationSettings.discord, case: val as PlanNotificationCase } })
178
+ }
179
+ inline={true}
180
+ />
69
181
  </div>
70
182
  )}
183
+ <div className={classes.field}>
184
+ <Input
185
+ label="Discord Webhook URL"
186
+ fieldValue={notificationSettings?.discord?.url || ''}
187
+ onUpdate={(val) => onUpdate({ ...notificationSettings, discord: { ...notificationSettings.discord, url: val } })}
188
+ placeholder="https://discord.com/api/webhooks/xxxx/xxxx"
189
+ required={true}
190
+ inline={true}
191
+ full={true}
192
+ error={notificationSettings?.discord?.enabled && !notificationSettings?.discord?.url ? 'Required' : undefined}
193
+ />
194
+ </div>
195
+ <div className={`${classes.field} ${classes.notificationTestField}`}>
196
+ <PlanNotificationSettingsTester
197
+ planId={planID || ''}
198
+ notificationChannel="discord"
199
+ notificationType={notificationType}
200
+ notificationSettings={notificationSettings}
201
+ />
202
+ </div>
71
203
  </div>
72
- </>
73
- )}
204
+ )}
205
+ </div>
74
206
  </>
75
207
  );
76
208
  };
@@ -0,0 +1,85 @@
1
+ import { toast } from 'react-toastify';
2
+ import { useState } from 'react';
3
+ import { useTestNotification } from '../../../services';
4
+ import { Icon } from '../..';
5
+ import { NewPlanSettings, PlanNotificationCase } from '../../..';
6
+ import classes from './PlanSettings.module.scss';
7
+ import { isValidURL } from '../../../utils';
8
+
9
+ const PlanNotificationSettingsTester = ({
10
+ planId,
11
+ notificationChannel,
12
+ notificationSettings,
13
+ notificationType = 'backup',
14
+ }: {
15
+ planId: string;
16
+ notificationType?: 'backup' | 'integrity';
17
+ notificationChannel: 'webhook' | 'slack' | 'discord';
18
+ notificationSettings: NewPlanSettings['settings']['notification'];
19
+ }) => {
20
+ const [showTestNotificationOptions, setShowTestNotificationOptions] = useState(false);
21
+ const testNotificationMutation = useTestNotification();
22
+ const channelSettings = notificationSettings[notificationChannel as 'webhook' | 'slack' | 'discord'];
23
+ const integrityNotification = notificationType === 'integrity';
24
+
25
+ const sendTestNotification = (notificationCase: PlanNotificationCase | 'integrity_failure') => {
26
+ setShowTestNotificationOptions(false);
27
+ if (!planId) {
28
+ return toast.error('Test notification can be sent after creating the Plan');
29
+ }
30
+ if (!channelSettings?.url || !isValidURL(channelSettings.url)) {
31
+ return toast.error(`Please enter a valid URL to send test notifications`);
32
+ }
33
+
34
+ toast.promise(
35
+ testNotificationMutation.mutateAsync({
36
+ planId,
37
+ notificationCase: notificationCase,
38
+ notificationChannel,
39
+ channelSettings: channelSettings,
40
+ }),
41
+ {
42
+ pending: `Sending Test Request to your ${notificationChannel}...`,
43
+ success: 'Test Request Sent Successfully!',
44
+ error: {
45
+ render({ data }: any) {
46
+ return data.message || 'Failed to send test request';
47
+ },
48
+ },
49
+ },
50
+ );
51
+ };
52
+
53
+ return (
54
+ <div className={classes.testNotificationContainer}>
55
+ <button
56
+ onClick={() =>
57
+ integrityNotification ? sendTestNotification('integrity_failure') : setShowTestNotificationOptions(!showTestNotificationOptions)
58
+ }
59
+ className={`${classes.testNotificationButton} ${!channelSettings?.url ? classes.disabled : ''}`}
60
+ disabled={!channelSettings?.url || !planId}
61
+ title={
62
+ !planId
63
+ ? 'Create the Plan first to send test notifications'
64
+ : !channelSettings?.url
65
+ ? `Enter ${notificationChannel === 'webhook' ? 'the' : notificationChannel.charAt(0).toUpperCase() + notificationChannel.slice(1)} Webhook URL to send test notifications`
66
+ : undefined
67
+ }
68
+ >
69
+ <Icon type="send" size={12} /> Send Test Request
70
+ </button>
71
+ {!integrityNotification && showTestNotificationOptions && (
72
+ <div className={classes.testNotificationOptions}>
73
+ <ul>
74
+ <li onClick={() => sendTestNotification('start')}>Start Notification</li>
75
+ <li onClick={() => sendTestNotification('end')}>End Notification</li>
76
+ <li onClick={() => sendTestNotification('success')}>Success Notification</li>
77
+ <li onClick={() => sendTestNotification('failure')}>Failure Notification</li>
78
+ </ul>
79
+ </div>
80
+ )}
81
+ </div>
82
+ );
83
+ };
84
+
85
+ export default PlanNotificationSettingsTester;
@@ -84,7 +84,7 @@ const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
84
84
  fieldValue={pruneSettings.keepWeeklySnaps || ''}
85
85
  onUpdate={(val) => onUpdate({ ...pruneSettings, keepWeeklySnaps: val })}
86
86
  />
87
- <span>Days</span>
87
+ <span>Weeks</span>
88
88
  </div>
89
89
  <div className={classes.customPolicyOption}>
90
90
  <span>Keep Monthly Backups for </span>
@@ -92,7 +92,7 @@ const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
92
92
  fieldValue={pruneSettings.keepMonthlySnaps || ''}
93
93
  onUpdate={(val) => onUpdate({ ...pruneSettings, keepMonthlySnaps: val })}
94
94
  />
95
- <span>Days</span>
95
+ <span>Months</span>
96
96
  </div>
97
97
  </div>
98
98
  </>
@@ -462,3 +462,74 @@
462
462
  }
463
463
  }
464
464
  }
465
+
466
+ .notificationTestField {
467
+ display: flex;
468
+ justify-content: right;
469
+ margin-bottom: 0;
470
+ }
471
+
472
+ .notificationSettingsSection {
473
+ border-radius: 6px;
474
+ border: 1px solid var(--line-color);
475
+ margin-bottom: 15px;
476
+ .notificationToggle {
477
+ margin-bottom: 0;
478
+ padding: 10px;
479
+ box-sizing: border-box;
480
+ display: flex;
481
+ align-items: center;
482
+ gap: 5px;
483
+ div[class*='_fieldInner'] {
484
+ justify-content: space-between;
485
+ }
486
+ }
487
+ .notificationSettings {
488
+ padding: 15px;
489
+ border-top: 1px solid var(--line-color);
490
+ }
491
+ }
492
+
493
+ .testNotificationContainer {
494
+ display: inline-block;
495
+ position: relative;
496
+ .testNotificationButton {
497
+ padding: 6px 12px;
498
+ background-color: var(--primary-color-light);
499
+ color: var(--primary-color);
500
+ font-weight: 600;
501
+ font-size: 0.9em;
502
+ border-radius: 4px;
503
+ cursor: pointer;
504
+ &.disabled {
505
+ cursor: not-allowed;
506
+ opacity: 0.6;
507
+ }
508
+ }
509
+
510
+ .testNotificationOptions {
511
+ position: absolute;
512
+ width: 140px;
513
+ background-color: var(--content-background-color);
514
+ border-radius: 6px;
515
+ border: 1px solid var(--primary-color-mid);
516
+ bottom: 30px;
517
+ right: 0px;
518
+ z-index: 9;
519
+ ul {
520
+ list-style-type: none;
521
+ margin: 0;
522
+ padding: 0;
523
+ li {
524
+ font-size: 0.9em;
525
+ cursor: pointer;
526
+ padding: 6px 12px;
527
+
528
+ &:hover {
529
+ background-color: var(--primary-color-light);
530
+ color: var(--primary-color);
531
+ }
532
+ }
533
+ }
534
+ }
535
+ }
@@ -77,7 +77,12 @@ const GeneralSettings = ({ settings, settingsID, onUpdate }: GeneralSettingsProp
77
77
  />
78
78
  </div>
79
79
  <div className={classes.field}>
80
- <Toggle label="Enable 2FA" fieldValue={settings?.totp?.enabled || false} onUpdate={(val) => update2FASetting(val)} inline={true} />
80
+ <Toggle
81
+ label="Enable Two-Factor Authentication (2FA)"
82
+ fieldValue={settings?.totp?.enabled || false}
83
+ onUpdate={(val) => update2FASetting(val)}
84
+ inline={false}
85
+ />
81
86
  </div>
82
87
  {show2FASetupConfirm && (
83
88
  <ActionModal
@@ -166,3 +166,77 @@
166
166
  }
167
167
  }
168
168
  }
169
+
170
+ .oauthContainer {
171
+ background-color: var(--background-color);
172
+ border: 1px solid var(--line-color);
173
+ border-radius: 6px;
174
+ box-sizing: border-box;
175
+ padding: 12px;
176
+ word-break: break-all;
177
+ &.success {
178
+ background-color: var(--success-bg-color);
179
+ border-color: var(--success-bg-color);
180
+ color: var(--success-text-color);
181
+ }
182
+ &.error {
183
+ background-color: var(--error-bg-color);
184
+ border-color: var(--error-bg-color);
185
+ color: var(--error-text-color);
186
+ }
187
+ }
188
+
189
+ .oauthButton {
190
+ display: flex;
191
+ width: 100%;
192
+ flex-direction: column;
193
+ align-items: flex-end;
194
+ .oauthAuthorizeBtn {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 8px;
198
+ padding: 10px 18px;
199
+ font-size: 13px;
200
+ font-weight: 600;
201
+ color: #fff;
202
+ background-color: var(--primary-color);
203
+ border: none;
204
+ border-radius: 6px;
205
+ cursor: pointer;
206
+ transition: background-color 0.2s;
207
+ &:hover {
208
+ opacity: 0.9;
209
+ }
210
+ }
211
+ }
212
+
213
+ .oauthInnerBtn {
214
+ display: inline-flex;
215
+ align-items: center;
216
+ gap: 6px;
217
+ padding: 6px 14px;
218
+ font-size: 12px;
219
+ font-weight: 500;
220
+ color: var(--content-text-color);
221
+ background-color: var(--content-background-color);
222
+ border: none;
223
+ border-radius: 6px;
224
+ cursor: pointer;
225
+ margin-top: 8px;
226
+ &:hover {
227
+ background-color: var(--primary-color);
228
+ color: var(--primary-text-color);
229
+ }
230
+ }
231
+
232
+ .oauthProgress {
233
+ p {
234
+ margin: 6px 0;
235
+ font-size: 13px;
236
+ line-height: 1.5;
237
+ a {
238
+ color: var(--primary-color);
239
+ text-decoration: underline;
240
+ }
241
+ }
242
+ }