@if (showDangerButton()) {
@@ -18,6 +19,7 @@
{{ card()?.displayName }}
[label]="'settings.addons.form.buttons.disable' | translate"
severity="danger"
(onClick)="showDisableDialog()"
+ data-test-addon-card-disconnect
>
}
diff --git a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html
index dbcb2445c..9f27832d6 100644
--- a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html
+++ b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html
@@ -13,9 +13,10 @@
{{ 'settings.addons.form.fields.secretKey' | translate }}
-
@@ -32,9 +33,10 @@
{{ 'settings.addons.form.fields.apiToken' | translate }}
-
@@ -79,9 +81,10 @@
{{ 'settings.addons.form.fields.personalAccessToken' | translate }}
-
@@ -104,6 +107,7 @@
class="w-10rem btn-full-width"
(onClick)="handleBack()"
[disabled]="isSubmitting()"
+ data-test-addon-back-button
>
type="submit"
[disabled]="!isFormValid || isSubmitting()"
[loading]="isSubmitting()"
+ data-test-addon-authorize-button
>
} @else {
class="w-10rem btn-full-width"
routerLink="/settings/addons"
[disabled]="isSubmitting()"
+ data-test-addon-cancel-button
>
[label]="'settings.addons.form.buttons.reconnect' | translate"
type="submit"
[disabled]="!isFormValid || isSubmitting()"
+ data-test-addon-reconnect-button
>
}
diff --git a/src/app/shared/models/addons/index.ts b/src/app/shared/models/addons/index.ts
index 5d227d4cf..e0ae0ab42 100644
--- a/src/app/shared/models/addons/index.ts
+++ b/src/app/shared/models/addons/index.ts
@@ -3,6 +3,7 @@ export * from './addon-terms.model';
export * from './addons.models';
export * from './authorized-account.model';
export * from './configured-addon.model';
+export * from './oauth-callbacks.model';
export * from './operation-invocation.models';
export * from './operation-invoke-data.model';
export * from './strorage-item.model';
diff --git a/src/app/shared/models/addons/oauth-callbacks.model.ts b/src/app/shared/models/addons/oauth-callbacks.model.ts
new file mode 100644
index 000000000..2d72e41ad
--- /dev/null
+++ b/src/app/shared/models/addons/oauth-callbacks.model.ts
@@ -0,0 +1,7 @@
+import { AuthorizedAccountModel } from '@shared/models';
+
+export interface OAuthCallbacks {
+ onSuccess: (addon: AuthorizedAccountModel) => void;
+ onError?: () => void;
+ onCleanup?: () => void;
+}
diff --git a/src/app/shared/services/addons/addon-form.service.ts b/src/app/shared/services/addons/addon-form.service.ts
index a2e0c79da..33a534f1c 100644
--- a/src/app/shared/services/addons/addon-form.service.ts
+++ b/src/app/shared/services/addons/addon-form.service.ts
@@ -86,7 +86,7 @@ export class AddonFormService {
credentials,
initiate_oauth: initiateOAuth,
auth_url: null,
- credentials_available: false,
+ credentials_available: ('credentialsAvailable' in addon && addon.credentialsAvailable) || false,
},
relationships: {
account_owner: {
diff --git a/src/app/shared/services/addons/addon-oauth.service.ts b/src/app/shared/services/addons/addon-oauth.service.ts
new file mode 100644
index 000000000..2d341e032
--- /dev/null
+++ b/src/app/shared/services/addons/addon-oauth.service.ts
@@ -0,0 +1,105 @@
+import { createDispatchMap, select } from '@ngxs/store';
+
+import { DestroyRef, inject, Injectable, signal } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+
+import { AuthorizedAccountModel, OAuthCallbacks } from '@shared/models';
+import { AddonsSelectors, DeleteAuthorizedAddon, GetAuthorizedStorageOauthToken } from '@shared/stores/addons';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AddonOAuthService {
+ private destroyRef = inject(DestroyRef);
+
+ private pendingOauth = signal