diff --git a/app/settings/addons/controller.ts b/app/settings/addons/controller.ts
new file mode 100644
index 00000000000..488b2e85900
--- /dev/null
+++ b/app/settings/addons/controller.ts
@@ -0,0 +1,8 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+import CurrentUser from 'ember-osf-web/services/current-user';
+
+export default class SettingsAddonsController extends Controller {
+ @service currentUser!: CurrentUser;
+}
diff --git a/app/settings/addons/route.ts b/app/settings/addons/route.ts
new file mode 100644
index 00000000000..ce5c7df8022
--- /dev/null
+++ b/app/settings/addons/route.ts
@@ -0,0 +1,4 @@
+import Route from '@ember/routing/route';
+
+export default class SettingsAddonsRoute extends Route {
+}
diff --git a/app/settings/addons/template.hbs b/app/settings/addons/template.hbs
index e69de29bb2d..6987cbe114d 100644
--- a/app/settings/addons/template.hbs
+++ b/app/settings/addons/template.hbs
@@ -0,0 +1,16 @@
+
+ {{#if manager.currentListIsLoading}}
+
+ {{else}}
+ {{#each manager.filteredAddonProviders as |provider|}}
+
+ {{provider.name}}
+
+ {{else}}
+ {{t 'addons.list.no-results'}}
+ {{/each}}
+ {{/if}}
+
diff --git a/lib/osf-components/addon/components/addons-service/manager/component.ts b/lib/osf-components/addon/components/addons-service/manager/component.ts
index a533e6ed0d3..30544712bb1 100644
--- a/lib/osf-components/addon/components/addons-service/manager/component.ts
+++ b/lib/osf-components/addon/components/addons-service/manager/component.ts
@@ -31,7 +31,7 @@ enum PageMode {
CONFIGURE = 'configure',
}
-enum FilterTypes {
+export enum FilterTypes {
STORAGE = 'additional-storage',
CITATION_MANAGER = 'citation-manager',
CLOUD_COMPUTING = 'cloud-computing',
diff --git a/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts b/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
new file mode 100644
index 00000000000..0d2f446e9e1
--- /dev/null
+++ b/lib/osf-components/addon/components/addons-service/user-addons-manager/component.ts
@@ -0,0 +1,138 @@
+import EmberArray, { A } from '@ember/array';
+import { inject as service } from '@ember/service';
+import { waitFor } from '@ember/test-waiters';
+import Store from '@ember-data/store';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { task } from 'ember-concurrency';
+import { taskFor } from 'ember-concurrency-ts';
+import IntlService from 'ember-intl/services/intl';
+
+import UserReferenceModel from 'ember-osf-web/models/user-reference';
+import Provider from 'ember-osf-web/packages/addons-service/provider';
+import CurrentUserService from 'ember-osf-web/services/current-user';
+import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
+import UserModel from 'ember-osf-web/models/user';
+
+import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
+import CloudComputingServiceModel from 'ember-osf-web/models/cloud-computing-service';
+import CitationServiceModel from 'ember-osf-web/models/citation-service';
+
+import { FilterTypes } from '../manager/component';
+
+type AllProviderTypes = ExternalStorageServiceModel | CloudComputingServiceModel | CitationServiceModel;
+
+interface Args {
+ user: UserModel;
+}
+
+export default class UserAddonManagerComponent extends Component {
+ @service store!: Store;
+ @service currentUser!: CurrentUserService;
+ @service intl!: IntlService;
+
+ user = this.args.user;
+ @tracked userReference?: UserReferenceModel;
+
+ filterTypeMapper = {
+ [FilterTypes.STORAGE]: {
+ modelName: 'external-storage-service',
+ userRelationshipName: 'authorizedStorageAccounts',
+ fetchProvidersTask: taskFor(this.getStorageAddonProviders),
+ list: A([]) as EmberArray,
+ authorizedAccounts: A([]) as EmberArray,
+ },
+ [FilterTypes.CITATION_MANAGER]: {
+ modelName: 'citation-service',
+ fetchProvidersTask: taskFor(this.getCitationAddonProviders),
+ list: A([]) as EmberArray,
+ // TODO: add authorizedAccounts for citation manager
+ },
+ [FilterTypes.CLOUD_COMPUTING]: {
+ modelName: 'cloud-service',
+ fetchProvidersTask: taskFor(this.getCloudComputingProviders),
+ list: A([]) as EmberArray,
+ // TODO: add authorizedAccounts for cloud computing
+ },
+ };
+ @tracked activeFilterType = FilterTypes.STORAGE;
+
+ @tracked selectedProvider?: Provider;
+ @tracked selectedAccount?: AuthorizedStorageAccountModel;
+
+
+ get filteredAddonProviders() {
+ return this.filterTypeMapper[this.activeFilterType].list;
+ }
+
+ get currentListIsLoading() {
+ const activeFilterObject = this.filterTypeMapper[this.activeFilterType];
+ return activeFilterObject.fetchProvidersTask.isRunning;
+ }
+
+
+ constructor(owner: unknown, args: Args) {
+ super(owner, args);
+ taskFor(this.getUserReference).perform();
+ taskFor(this.getStorageAddonProviders).perform();
+ taskFor(this.getAuthorizedStorageAccounts).perform();
+ }
+
+ @task
+ @waitFor
+ async getUserReference() {
+ const { user } = this;
+ const userReference = await this.store.findRecord('user-reference', user.id);
+ this.userReference = userReference;
+ }
+
+ @task
+ @waitFor
+ async getAuthorizedStorageAccounts() {
+ const { userReference } = this;
+ const mappedObject = this.filterTypeMapper[FilterTypes.STORAGE];
+ if (userReference) {
+ const accounts = (await userReference.authorizedStorageAccounts).toArray();
+ mappedObject.authorizedAccounts = accounts;
+ }
+ }
+
+ @task
+ @waitFor
+ async getStorageAddonProviders() {
+ const activeFilterObject = this.filterTypeMapper[FilterTypes.STORAGE];
+ const serviceStorageProviders: AllProviderTypes[] =
+ await taskFor(this.getExternalProviders).perform(activeFilterObject.modelName);
+ const sortedList = serviceStorageProviders.sort(this.providerSorter);
+ activeFilterObject.list = sortedList;
+ }
+
+ @task
+ @waitFor
+ async getCloudComputingProviders() {
+ const activeFilterObject = this.filterTypeMapper[FilterTypes.CLOUD_COMPUTING];
+ const cloudComputingProviders: AllProviderTypes[] =
+ await taskFor(this.getExternalProviders).perform(activeFilterObject.modelName);
+ activeFilterObject.list = cloudComputingProviders.sort(this.providerSorter);
+ }
+
+ @task
+ @waitFor
+ async getCitationAddonProviders() {
+ const activeFilterObject = this.filterTypeMapper[FilterTypes.CITATION_MANAGER];
+ const serviceCloudComputingProviders: AllProviderTypes[] =
+ await taskFor(this.getExternalProviders).perform(activeFilterObject.modelName);
+ activeFilterObject.list = A(serviceCloudComputingProviders.sort(this.providerSorter));
+ }
+
+ providerSorter(a: AllProviderTypes, b: AllProviderTypes) {
+ return a.name.localeCompare(b.name);
+ }
+
+ @task
+ @waitFor
+ async getExternalProviders(providerType: string) {
+ const serviceProviderModels: AllProviderTypes[] = (await this.store.findAll(providerType)).toArray();
+ return serviceProviderModels;
+ }
+}
diff --git a/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs b/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs
new file mode 100644
index 00000000000..ee973fc6fea
--- /dev/null
+++ b/lib/osf-components/addon/components/addons-service/user-addons-manager/template.hbs
@@ -0,0 +1,6 @@
+{{yield (hash
+ user=this.user
+ authorizedStorageAccounts=this.authorizedStorageAccounts
+ filteredAddonProviders=this.filteredAddonProviders
+ currentListIsLoading=this.currentListIsLoading
+)}}
diff --git a/lib/osf-components/app/components/addons-service/user-addons-manager/component.js b/lib/osf-components/app/components/addons-service/user-addons-manager/component.js
new file mode 100644
index 00000000000..316330463f0
--- /dev/null
+++ b/lib/osf-components/app/components/addons-service/user-addons-manager/component.js
@@ -0,0 +1 @@
+export { default } from 'osf-components/components/addons-service/user-addons-manager/component';
diff --git a/lib/osf-components/app/components/addons-service/user-addons-manager/template.js b/lib/osf-components/app/components/addons-service/user-addons-manager/template.js
new file mode 100644
index 00000000000..fc10cf5159f
--- /dev/null
+++ b/lib/osf-components/app/components/addons-service/user-addons-manager/template.js
@@ -0,0 +1 @@
+export { default } from 'osf-components/components/addons-service/user-addons-manager/template';