From 2d64295506639b9e4cd02d1134e8144575d1f37f Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 14 Mar 2025 17:03:26 +0200 Subject: [PATCH 01/14] refactor(developer-apps): Changed structure of developer apps component hierarchy --- .../developer-app-details.component.html | 1 + .../developer-app-details.component.scss | 0 .../developer-app-details.component.spec.ts | 22 ++++++++++++++++++ .../developer-app-details.component.ts | 10 ++++++++ .../developer-apps-container.component.html | 6 +++++ ...> developer-apps-container.component.scss} | 18 --------------- ...eveloper-apps-container.component.spec.ts} | 10 ++++---- .../developer-apps-container.component.ts | 12 ++++++++++ .../developer-apps-list.component.html} | 15 +++++------- .../developer-apps-list.component.scss | 22 ++++++++++++++++++ .../developer-apps-list.component.spec.ts | 22 ++++++++++++++++++ .../developer-apps-list.component.ts} | 11 +++++---- .../developer-apps/developer-apps.route.ts | 23 +++++++++++++++++++ src/app/features/settings/settings.routes.ts | 9 ++------ 14 files changed, 137 insertions(+), 44 deletions(-) create mode 100644 src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html create mode 100644 src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss create mode 100644 src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts create mode 100644 src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts create mode 100644 src/app/features/settings/developer-apps/developer-apps-container.component.html rename src/app/features/settings/developer-apps/{developer-apps.component.scss => developer-apps-container.component.scss} (61%) rename src/app/features/settings/developer-apps/{developer-apps.component.spec.ts => developer-apps-container.component.spec.ts} (52%) create mode 100644 src/app/features/settings/developer-apps/developer-apps-container.component.ts rename src/app/features/settings/developer-apps/{developer-apps.component.html => developer-apps-list/developer-apps-list.component.html} (65%) create mode 100644 src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss create mode 100644 src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts rename src/app/features/settings/developer-apps/{developer-apps.component.ts => developer-apps-list/developer-apps-list.component.ts} (76%) create mode 100644 src/app/features/settings/developer-apps/developer-apps.route.ts diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html new file mode 100644 index 000000000..6aea59f16 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html @@ -0,0 +1 @@ +

developer-application-details works!

diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts new file mode 100644 index 000000000..bebf34b45 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeveloperAppDetailsComponent } from './developer-app-details.component'; + +describe('DeveloperApplicationDetailsComponent', () => { + let component: DeveloperAppDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DeveloperAppDetailsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DeveloperAppDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts new file mode 100644 index 000000000..c6f8e1fde --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts @@ -0,0 +1,10 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'osf-developer-application-details', + imports: [], + templateUrl: './developer-app-details.component.html', + styleUrl: './developer-app-details.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DeveloperAppDetailsComponent {} diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.html b/src/app/features/settings/developer-apps/developer-apps-container.component.html new file mode 100644 index 000000000..2a254cdd7 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.html @@ -0,0 +1,6 @@ +
+ +

Developer apps

+ +
+ diff --git a/src/app/features/settings/developer-apps/developer-apps.component.scss b/src/app/features/settings/developer-apps/developer-apps-container.component.scss similarity index 61% rename from src/app/features/settings/developer-apps/developer-apps.component.scss rename to src/app/features/settings/developer-apps/developer-apps-container.component.scss index a4448def7..0bf7ef49e 100644 --- a/src/app/features/settings/developer-apps/developer-apps.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.scss @@ -24,22 +24,4 @@ font-size: 2.6rem; } } - - .content { - margin: 1.7rem; - color: var.$dark-blue-1; - - p { - margin-bottom: 1.7rem; - } - - .applications-container { - @include mix.flex-column; - gap: 0.85rem; - - .card-body { - @include mix.flex-center-between; - } - } - } } diff --git a/src/app/features/settings/developer-apps/developer-apps.component.spec.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts similarity index 52% rename from src/app/features/settings/developer-apps/developer-apps.component.spec.ts rename to src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts index 877f51a84..e481631a6 100644 --- a/src/app/features/settings/developer-apps/developer-apps.component.spec.ts +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DeveloperAppsComponent } from './developer-apps.component'; +import { DeveloperAppsContainerComponent } from './developer-apps-container.component'; describe('DeveloperAppsComponent', () => { - let component: DeveloperAppsComponent; - let fixture: ComponentFixture; + let component: DeveloperAppsContainerComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [DeveloperAppsComponent], + imports: [DeveloperAppsContainerComponent], }).compileComponents(); - fixture = TestBed.createComponent(DeveloperAppsComponent); + fixture = TestBed.createComponent(DeveloperAppsContainerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.ts new file mode 100644 index 000000000..164748cee --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'osf-developer-apps', + imports: [Button, RouterOutlet], + templateUrl: './developer-apps-container.component.html', + styleUrl: './developer-apps-container.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DeveloperAppsContainerComponent {} diff --git a/src/app/features/settings/developer-apps/developer-apps.component.html b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html similarity index 65% rename from src/app/features/settings/developer-apps/developer-apps.component.html rename to src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html index 7ab621ed7..269647be7 100644 --- a/src/app/features/settings/developer-apps/developer-apps.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html @@ -1,9 +1,4 @@ -
- -

Developer apps

- Create Developer App -
-
+

Third-party web applications can connect to the OSF on behalf of users via the OAuth 2.0 web application flow. @@ -13,12 +8,14 @@

Developer apps

@for (developerApp of developerApplications; track $index) {
-

{{ developerApp }}

+ +

{{ developerApp }}

+
Delete + />
} diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss new file mode 100644 index 000000000..890c1dc17 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss @@ -0,0 +1,22 @@ +@use "assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.content-container { + margin: 1.7rem; + color: var.$dark-blue-1; + + p { + margin-bottom: 1.7rem; + } + + .applications-container { + @include mix.flex-column; + gap: 0.85rem; + + p-card { + .card-body { + @include mix.flex-center-between; + } + } + } +} diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts new file mode 100644 index 000000000..11b7fe2fb --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeveloperAppsListComponent } from './developer-apps-list.component'; + +describe('DeveloperApplicationsListComponent', () => { + let component: DeveloperAppsListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DeveloperAppsListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DeveloperAppsListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/developer-apps/developer-apps.component.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts similarity index 76% rename from src/app/features/settings/developer-apps/developer-apps.component.ts rename to src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts index a9d59f78c..01aabb5fc 100644 --- a/src/app/features/settings/developer-apps/developer-apps.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts @@ -1,15 +1,16 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; +import { RouterLink } from '@angular/router'; @Component({ - selector: 'osf-developer-apps', - imports: [Button, Card], - templateUrl: './developer-apps.component.html', - styleUrl: './developer-apps.component.scss', + selector: 'osf-developer-applications-list', + imports: [Button, Card, RouterLink], + templateUrl: './developer-apps-list.component.html', + styleUrl: './developer-apps-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DeveloperAppsComponent { +export class DeveloperAppsListComponent { developerApplications: string[] = [ 'Developer app name example', 'Developer app name example', diff --git a/src/app/features/settings/developer-apps/developer-apps.route.ts b/src/app/features/settings/developer-apps/developer-apps.route.ts new file mode 100644 index 000000000..0e3c87d52 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-apps.route.ts @@ -0,0 +1,23 @@ +import { Route } from '@angular/router'; +import { DeveloperAppsContainerComponent } from '@osf/features/settings/developer-apps/developer-apps-container.component'; + +export const developerAppsRoute: Route = { + path: 'developer-apps', + component: DeveloperAppsContainerComponent, + children: [ + { + path: '', + loadComponent: () => + import('./developer-apps-list/developer-apps-list.component').then( + (c) => c.DeveloperAppsListComponent, + ), + }, + { + path: ':id/details', + loadComponent: () => + import('./developer-app-details/developer-app-details.component').then( + (c) => c.DeveloperAppDetailsComponent, + ), + }, + ], +}; diff --git a/src/app/features/settings/settings.routes.ts b/src/app/features/settings/settings.routes.ts index 6922327a2..12d4fc4f9 100644 --- a/src/app/features/settings/settings.routes.ts +++ b/src/app/features/settings/settings.routes.ts @@ -1,5 +1,6 @@ import { Routes } from '@angular/router'; import { SettingsContainerComponent } from '@osf/features/settings/settings-container.component'; +import { developerAppsRoute } from '@osf/features/settings/developer-apps/developer-apps.route'; export const settingsRoutes: Routes = [ { @@ -20,13 +21,7 @@ export const settingsRoutes: Routes = [ (c) => c.AccountSettingsComponent, ), }, - { - path: 'developer-apps', - loadComponent: () => - import('./developer-apps/developer-apps.component').then( - (mod) => mod.DeveloperAppsComponent, - ), - }, + developerAppsRoute, ], }, ]; From 9016dd88d115a8aec3656f5511b88fbec5623007 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 18 Mar 2025 15:25:40 +0200 Subject: [PATCH 02/14] feat(developer-apps): Finished developer Apps List page, started working on details page --- .../breadcrumb/breadcrumb.component.ts | 16 +++- .../developer-app-details.component.html | 94 ++++++++++++++++++- .../developer-app-details.component.scss | 41 ++++++++ .../developer-app-details.component.ts | 50 +++++++++- .../developer-apps/developer-app.entity.ts | 4 + .../developer-apps-container.component.html | 13 ++- .../developer-apps-container.component.scss | 21 ----- .../developer-apps-container.component.ts | 10 +- .../developer-apps-list.component.html | 21 +++-- .../developer-apps-list.component.scss | 20 +++- .../developer-apps-list.component.ts | 56 ++++++----- src/assets/styles/overrides/iconfield.scss | 18 ++++ src/assets/styles/styles.scss | 1 + 13 files changed, 297 insertions(+), 68 deletions(-) create mode 100644 src/app/features/settings/developer-apps/developer-app.entity.ts create mode 100644 src/assets/styles/overrides/iconfield.scss diff --git a/src/app/core/components/breadcrumb/breadcrumb.component.ts b/src/app/core/components/breadcrumb/breadcrumb.component.ts index 06af64b2d..21ace7a94 100644 --- a/src/app/core/components/breadcrumb/breadcrumb.component.ts +++ b/src/app/core/components/breadcrumb/breadcrumb.component.ts @@ -1,5 +1,6 @@ -import { Component, computed, inject, signal } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, computed, DestroyRef, inject, signal } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'osf-breadcrumb', @@ -9,8 +10,19 @@ import { Router } from '@angular/router'; }) export class BreadcrumbComponent { #router = inject(Router); + #destroyRef = inject(DestroyRef); protected readonly url = signal(this.#router.url); protected readonly parsedUrl = computed(() => { return this.url().split('/').filter(Boolean); }); + + constructor() { + this.#router.events + .pipe(takeUntilDestroyed(this.#destroyRef)) + .subscribe((event) => { + if (event instanceof NavigationEnd) { + this.url.set(this.#router.url); + } + }); + } } diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html index 6aea59f16..561ac0d44 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html @@ -1 +1,93 @@ -

developer-application-details works!

+
+ + +
+

{{ developerApp().name + developerAppId() }}

+ +
+ +
+ +
+

Client ID

+

+ The client ID is the developer app's unique identifier and is safe to + share publicly. +

+ + + + + + + +
+
+ + +
+

Client Secret

+ +

+ The client secret is available only to you. Keep it private and do not + share it. +

+ +
+ + + + + + + + +
+ +
+ +
+
+
+ + +
+

Edit app

+ +
+ +
+
+
+
+
diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss index e69de29bb..1a8f012ed 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss @@ -0,0 +1,41 @@ +@use "assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.content-container { + padding: 1.7rem; + color: var.$dark-blue-1; + background-color: var.$white; + + .navigation-bar-container { + margin-bottom: 24px; + } + + .tittle-container { + @include mix.flex-center-between; + margin-bottom: 48px; + } + + .cards-container { + @include mix.flex-column; + gap: 24px; + + .card-body { + h2 { + margin-bottom: 24px; + } + + p { + margin-bottom: 12px; + } + + .client-secret-container { + @include mix.flex-align-center; + gap: 12px; + } + + .card-actions { + @include mix.flex-center-right; + } + } + } +} diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts index c6f8e1fde..9e8f802b5 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts @@ -1,10 +1,54 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + inject, + OnInit, + signal, +} from '@angular/core'; +import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entity'; +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { InputText } from 'primeng/inputtext'; +import { IconField } from 'primeng/iconfield'; +import { InputIcon } from 'primeng/inputicon'; @Component({ selector: 'osf-developer-application-details', - imports: [], + imports: [Button, Card, RouterLink, InputText, IconField, InputIcon], templateUrl: './developer-app-details.component.html', styleUrl: './developer-app-details.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DeveloperAppDetailsComponent {} +export class DeveloperAppDetailsComponent implements OnInit { + private readonly destroyRef = inject(DestroyRef); + private readonly activatedRoute = inject(ActivatedRoute); + developerAppId = signal(null); + developerApp = signal({ + id: '1', + name: 'Example name', + }); + isClientSecretVisible = signal(false); + clientSecret = signal('asdfasdfasdfasdfasdf'); + hiddenClientSecret = computed(() => + '*'.repeat(this.clientSecret().length), + ); + + ngOnInit(): void { + this.developerAppId.set(this.activatedRoute.snapshot.params['id']); + } + + onDeleteAppBtnClicked(): void { + //TODO integrate API + } + + onResetClientSecretBtnClicked(): void { + //TODO integrate API + } + + copyClientId(): void { + console.log('copy client id'); + } +} diff --git a/src/app/features/settings/developer-apps/developer-app.entity.ts b/src/app/features/settings/developer-apps/developer-app.entity.ts new file mode 100644 index 000000000..c4ad86de6 --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-app.entity.ts @@ -0,0 +1,4 @@ +export interface DeveloperApp { + id: string; + name: string; +} diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.html b/src/app/features/settings/developer-apps/developer-apps-container.component.html index 2a254cdd7..7c46eb6b1 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.html @@ -1,6 +1,9 @@ -
- -

Developer apps

- -
+ + diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.scss b/src/app/features/settings/developer-apps/developer-apps-container.component.scss index 0bf7ef49e..43b3dda99 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.scss @@ -3,25 +3,4 @@ :host { @include mix.flex-column; - flex: 1; - - .header { - @include mix.flex-center-between; - width: 100%; - padding: 7.14rem 1.71rem 3.43rem 1.71rem; - background: var.$gradient-1; - - h1 { - margin-left: 0.85rem; - } - - p-button { - margin-left: auto; - } - - i { - color: var.$dark-blue-1; - font-size: 2.6rem; - } - } } diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.ts index 164748cee..89c2fe49a 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.ts @@ -1,12 +1,16 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { Button } from 'primeng/button'; import { RouterOutlet } from '@angular/router'; +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; @Component({ selector: 'osf-developer-apps', - imports: [Button, RouterOutlet], + imports: [RouterOutlet, SubHeaderComponent], templateUrl: './developer-apps-container.component.html', styleUrl: './developer-apps-container.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DeveloperAppsContainerComponent {} +export class DeveloperAppsContainerComponent { + onCreateDeveloperAppBtnClicked(): void { + //TODO integrate API + } +} diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html index 269647be7..e98b425b9 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html @@ -5,17 +5,20 @@

- @for (developerApp of developerApplications; track $index) { + @for (developerApp of developerApplications(); track $index) { -
- -

{{ developerApp }}

+
+ +

{{ developerApp.name }}

- +
+ +
} diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss index 890c1dc17..392eb7da6 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss @@ -2,8 +2,9 @@ @use "assets/styles/mixins" as mix; .content-container { - margin: 1.7rem; + padding: 1.7rem; color: var.$dark-blue-1; + background-color: var.$white; p { margin-bottom: 1.7rem; @@ -15,7 +16,22 @@ p-card { .card-body { - @include mix.flex-center-between; + &.mobile { + @include mix.flex-column; + + a { + align-self: flex-start; + } + + .button-container { + align-self: flex-end; + width: 50%; + } + } + + &:not(.mobile) { + @include mix.flex-center-between; + } } } } diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts index 01aabb5fc..07e5b7e4f 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts @@ -1,37 +1,49 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + inject, + signal, +} from '@angular/core'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { RouterLink } from '@angular/router'; +import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entity'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { NgClass } from '@angular/common'; @Component({ selector: 'osf-developer-applications-list', - imports: [Button, Card, RouterLink], + imports: [Button, Card, RouterLink, NgClass], templateUrl: './developer-apps-list.component.html', styleUrl: './developer-apps-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class DeveloperAppsListComponent { - developerApplications: string[] = [ - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - 'Developer app name example', - ]; + #isXSmall$ = inject(IS_XSMALL); + isXSmall = toSignal(this.#isXSmall$); - onDeleteDeveloperApp(developerApp: string): void { - console.log('delete', developerApp); + developerApplications = signal([ + { + id: '1', + name: 'Developer app name example', + }, + { + id: '2', + name: 'Developer app name example', + }, + { + id: '3', + name: 'Developer app name example', + }, + { + id: '4', + name: 'Developer app name example', + }, + ]); + + onDeleteAppBtnClicked(developerAppId: string): void { + console.log('delete', developerAppId); //TODO implement api integration } } diff --git a/src/assets/styles/overrides/iconfield.scss b/src/assets/styles/overrides/iconfield.scss new file mode 100644 index 000000000..91c63a85f --- /dev/null +++ b/src/assets/styles/overrides/iconfield.scss @@ -0,0 +1,18 @@ +@use "assets/styles/variables" as var; + +.p-iconfield { + display: inline-block; + + .p-inputtext { + width: inherit !important; + } + + .p-inputicon { + cursor: pointer; + color: var.$dark-blue-1; + + i { + font-size: 1.2rem; + } + } +} diff --git a/src/assets/styles/styles.scss b/src/assets/styles/styles.scss index 33aa0c946..ae0e511c7 100644 --- a/src/assets/styles/styles.scss +++ b/src/assets/styles/styles.scss @@ -9,6 +9,7 @@ @use "./overrides/message"; @use "./overrides/drawer"; @use "./overrides/card"; +@use "./overrides/iconfield"; @layer base, primeng, reset; From 9a8beff0b61f5fefed7a2d1392908a3d27755387 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 13:25:30 +0200 Subject: [PATCH 03/14] style(primeng-override): Added styles for button, card and iconfield --- src/assets/styles/overrides/button.scss | 8 ++++++++ src/assets/styles/overrides/card.scss | 4 ++++ src/assets/styles/overrides/iconfield.scss | 4 ---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/assets/styles/overrides/button.scss b/src/assets/styles/overrides/button.scss index 49b9e98ef..861231cfc 100644 --- a/src/assets/styles/overrides/button.scss +++ b/src/assets/styles/overrides/button.scss @@ -142,6 +142,14 @@ } .btn-full-width { + width: 100%; + .p-button { + width: 100%; + } +} + +.btn-half-width { + width: 50%; .p-button { width: 100%; } diff --git a/src/assets/styles/overrides/card.scss b/src/assets/styles/overrides/card.scss index 84c630308..2fa01d8af 100644 --- a/src/assets/styles/overrides/card.scss +++ b/src/assets/styles/overrides/card.scss @@ -11,3 +11,7 @@ padding: 1.7rem; } } + +.mobile .p-card .p-card-body { + padding: 0.85rem; +} diff --git a/src/assets/styles/overrides/iconfield.scss b/src/assets/styles/overrides/iconfield.scss index 91c63a85f..7781e8f4b 100644 --- a/src/assets/styles/overrides/iconfield.scss +++ b/src/assets/styles/overrides/iconfield.scss @@ -3,10 +3,6 @@ .p-iconfield { display: inline-block; - .p-inputtext { - width: inherit !important; - } - .p-inputicon { cursor: pointer; color: var.$dark-blue-1; From 8e49dd22933c87e0d728efc641df967bb33ad383 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 13:38:30 +0200 Subject: [PATCH 04/14] feat(developer-apps): Added create developer app form component --- src/app/core/helpers/link-validator.helper.ts | 16 +++++ .../create-developer-app.component.html | 64 +++++++++++++++++ .../create-developer-app.component.scss | 0 .../create-developer-app.component.spec.ts | 22 ++++++ .../create-developer-app.component.ts | 71 +++++++++++++++++++ .../developer-apps/developer-app.entities.ts | 24 +++++++ .../developer-apps/developer-app.entity.ts | 4 -- 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 src/app/core/helpers/link-validator.helper.ts create mode 100644 src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html create mode 100644 src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.scss create mode 100644 src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.spec.ts create mode 100644 src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.ts create mode 100644 src/app/features/settings/developer-apps/developer-app.entities.ts delete mode 100644 src/app/features/settings/developer-apps/developer-app.entity.ts diff --git a/src/app/core/helpers/link-validator.helper.ts b/src/app/core/helpers/link-validator.helper.ts new file mode 100644 index 000000000..a7c406224 --- /dev/null +++ b/src/app/core/helpers/link-validator.helper.ts @@ -0,0 +1,16 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; + +export function linkValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (!value) { + return null; + } + + const urlPattern = /^(https?):\/\/.+/i; + + const isValid = urlPattern.test(value); + + return isValid ? null : { link: true }; + }; +} diff --git a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html new file mode 100644 index 000000000..b80a456e8 --- /dev/null +++ b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html @@ -0,0 +1,64 @@ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
diff --git a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.scss b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.spec.ts b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.spec.ts new file mode 100644 index 000000000..06cae3a5e --- /dev/null +++ b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateDeveloperAppComponent } from './create-developer-app.component'; + +describe('CreateDeveloperAppComponent', () => { + let component: CreateDeveloperAppComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CreateDeveloperAppComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CreateDeveloperAppComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.ts b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.ts new file mode 100644 index 000000000..063a2199d --- /dev/null +++ b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.ts @@ -0,0 +1,71 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { Button } from 'primeng/button'; +import { InputText } from 'primeng/inputtext'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { + DeveloperAppForm, + DeveloperAppFormFormControls, +} from '@osf/features/settings/developer-apps/developer-app.entities'; +import { linkValidator } from '@core/helpers/link-validator.helper'; + +@Component({ + selector: 'osf-create-developer-app', + imports: [Button, InputText, ReactiveFormsModule], + templateUrl: './create-developer-app.component.html', + styleUrl: './create-developer-app.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CreateDeveloperAppComponent { + readonly dialogRef = inject(DynamicDialogRef); + protected readonly DeveloperAppFormFormControls = + DeveloperAppFormFormControls; + + readonly createAppForm: DeveloperAppForm = new FormGroup({ + [DeveloperAppFormFormControls.AppName]: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + [DeveloperAppFormFormControls.ProjectHomePageUrl]: new FormControl('', { + nonNullable: true, + validators: [Validators.required, linkValidator()], + }), + [DeveloperAppFormFormControls.AppDescription]: new FormControl('', { + nonNullable: false, + }), + [DeveloperAppFormFormControls.AuthorizationCallbackUrl]: new FormControl( + '', + { + nonNullable: true, + validators: [Validators.required, linkValidator()], + }, + ), + }); + + submitForm(): void { + if (!this.createAppForm.valid) { + this.createAppForm.markAllAsTouched(); + this.createAppForm + .get([DeveloperAppFormFormControls.AppName]) + ?.markAsDirty(); + this.createAppForm + .get(DeveloperAppFormFormControls.ProjectHomePageUrl) + ?.markAsDirty(); + this.createAppForm + .get(DeveloperAppFormFormControls.AppDescription) + ?.markAsDirty(); + this.createAppForm + .get(DeveloperAppFormFormControls.AuthorizationCallbackUrl) + ?.markAsDirty(); + return; + } + + //TODO integrate API + this.dialogRef.close(); + } +} diff --git a/src/app/features/settings/developer-apps/developer-app.entities.ts b/src/app/features/settings/developer-apps/developer-app.entities.ts new file mode 100644 index 000000000..200a7ff5e --- /dev/null +++ b/src/app/features/settings/developer-apps/developer-app.entities.ts @@ -0,0 +1,24 @@ +import { FormControl, FormGroup } from '@angular/forms'; +import { StringOrNull } from '@core/helpers/types.helper'; + +export interface DeveloperApp { + id: string; + appName: string; + projHomePageUrl: string; + appDescription: StringOrNull; + authorizationCallbackUrl: string; +} + +export enum DeveloperAppFormFormControls { + AppName = 'appName', + ProjectHomePageUrl = 'projHomePageUrl', + AppDescription = 'appDescription', + AuthorizationCallbackUrl = 'authorizationCallbackUrl', +} + +export type DeveloperAppForm = FormGroup<{ + [DeveloperAppFormFormControls.AppName]: FormControl; + [DeveloperAppFormFormControls.ProjectHomePageUrl]: FormControl; + [DeveloperAppFormFormControls.AppDescription]: FormControl; + [DeveloperAppFormFormControls.AuthorizationCallbackUrl]: FormControl; +}>; diff --git a/src/app/features/settings/developer-apps/developer-app.entity.ts b/src/app/features/settings/developer-apps/developer-app.entity.ts deleted file mode 100644 index c4ad86de6..000000000 --- a/src/app/features/settings/developer-apps/developer-app.entity.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface DeveloperApp { - id: string; - name: string; -} From 882d8d8f99e25704751b0e043866a82df4968b21 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 13:42:07 +0200 Subject: [PATCH 05/14] style(developer-apps): Adapted developer apps component for mobile --- .../developer-apps-list/developer-apps-list.component.html | 4 ++-- .../developer-apps-list/developer-apps-list.component.scss | 5 +++++ .../developer-apps-list/developer-apps-list.component.ts | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html index e98b425b9..551bf2717 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html @@ -1,4 +1,4 @@ -
+

Third-party web applications can connect to the OSF on behalf of users via the OAuth 2.0 web application flow. @@ -7,7 +7,7 @@

@for (developerApp of developerApplications(); track $index) { -
+

{{ developerApp.name }}

diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss index 392eb7da6..e327dbce6 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss @@ -6,6 +6,10 @@ color: var.$dark-blue-1; background-color: var.$white; + &.mobile { + padding: 1rem; + } + p { margin-bottom: 1.7rem; } @@ -18,6 +22,7 @@ .card-body { &.mobile { @include mix.flex-column; + gap: 0.85rem; a { align-self: flex-start; diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts index 07e5b7e4f..d98fcf987 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts @@ -10,11 +10,10 @@ import { RouterLink } from '@angular/router'; import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entity'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; -import { NgClass } from '@angular/common'; @Component({ selector: 'osf-developer-applications-list', - imports: [Button, Card, RouterLink, NgClass], + imports: [Button, Card, RouterLink], templateUrl: './developer-apps-list.component.html', styleUrl: './developer-apps-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, From 88d995c58405ef5256d0f2e5a8364bca8b242b28 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 13:49:14 +0200 Subject: [PATCH 06/14] feat(developer-apps): Added create app dialog --- .../developer-apps-container.component.html | 2 +- .../developer-apps-container.component.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.html b/src/app/features/settings/developer-apps/developer-apps-container.component.html index 7c46eb6b1..622c11cec 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.html @@ -3,7 +3,7 @@ [buttonLabel]="'Create Developer App'" [title]="'Developer apps'" [icon]="'settings'" - (buttonClick)="onCreateDeveloperAppBtnClicked()" + (buttonClick)="createDeveloperApp()" /> diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.ts index 89c2fe49a..50c64aa9d 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.ts @@ -1,16 +1,28 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; +import { DialogService } from 'primeng/dynamicdialog'; +import { CreateDeveloperAppComponent } from '@osf/features/settings/developer-apps/create-developer-app/create-developer-app.component'; @Component({ selector: 'osf-developer-apps', imports: [RouterOutlet, SubHeaderComponent], templateUrl: './developer-apps-container.component.html', styleUrl: './developer-apps-container.component.scss', + providers: [DialogService], changeDetection: ChangeDetectionStrategy.OnPush, }) export class DeveloperAppsContainerComponent { - onCreateDeveloperAppBtnClicked(): void { - //TODO integrate API + private readonly dialogService = inject(DialogService); + + createDeveloperApp(): void { + this.dialogService.open(CreateDeveloperAppComponent, { + width: '448px', + focusOnShow: false, + header: 'Create Developer App', + closeOnEscape: true, + modal: true, + closable: true, + }); } } From bab0d9b447e40b2ce1e34ae5eaeed5c8aba5706c Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 13:54:04 +0200 Subject: [PATCH 07/14] refactor(developer-apps): Refactored developer apps component --- .../developer-apps-list.component.html | 4 ++-- .../developer-apps-list.component.ts | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html index 551bf2717..5b3b79394 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html @@ -9,14 +9,14 @@
-

{{ developerApp.name }}

+

{{ developerApp.appName }}

diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts index d98fcf987..5ab2aabad 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts @@ -7,7 +7,7 @@ import { import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { RouterLink } from '@angular/router'; -import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entity'; +import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entities'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; @@ -25,23 +25,21 @@ export class DeveloperAppsListComponent { developerApplications = signal([ { id: '1', - name: 'Developer app name example', + appName: 'Developer app name example', + projHomePageUrl: 'https://example.com', + appDescription: 'Example description', + authorizationCallbackUrl: 'https://example.com/callback', }, { id: '2', - name: 'Developer app name example', - }, - { - id: '3', - name: 'Developer app name example', - }, - { - id: '4', - name: 'Developer app name example', + appName: 'Developer app name example', + projHomePageUrl: 'https://example.com', + appDescription: 'Example description', + authorizationCallbackUrl: 'https://example.com/callback', }, ]); - onDeleteAppBtnClicked(developerAppId: string): void { + deleteApp(developerAppId: string): void { console.log('delete', developerAppId); //TODO implement api integration } From 76a000501a0bcb78cf055d18f2505648752174e7 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 19 Mar 2025 17:22:13 +0200 Subject: [PATCH 08/14] feat(developer-apps): Added edit app form, adapted for mobile --- .../developer-app-details.component.html | 100 ++++++++++++++--- .../developer-app-details.component.scss | 28 ++++- .../developer-app-details.component.ts | 106 ++++++++++++++++-- 3 files changed, 202 insertions(+), 32 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html index 561ac0d44..b888610fd 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html @@ -1,36 +1,37 @@ -
+
-
-

{{ developerApp().name + developerAppId() }}

+
+

{{ developerApp().appName + developerAppId() }}

-
+

Client ID

+

The client ID is the developer app's unique identifier and is safe to share publicly.

- - + + @@ -46,7 +47,7 @@

Client Secret

share it.

-
+
Client Secret " readonly /> - + @@ -74,7 +78,8 @@

Client Secret

@@ -84,9 +89,68 @@

Client Secret

Edit app

-
- -
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss index 1a8f012ed..6b9899f4a 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.scss @@ -6,31 +6,47 @@ color: var.$dark-blue-1; background-color: var.$white; + &.mobile { + padding: 1rem; + } + .navigation-bar-container { - margin-bottom: 24px; + margin-bottom: 1.7rem; } .tittle-container { @include mix.flex-center-between; - margin-bottom: 48px; + margin-bottom: 3.4rem; + + &.mobile { + @include mix.flex-column; + align-items: inherit; + gap: 1.7rem; + } } .cards-container { @include mix.flex-column; - gap: 24px; + gap: 1.7rem; .card-body { h2 { - margin-bottom: 24px; + margin-bottom: 1.7rem; } p { - margin-bottom: 12px; + margin-bottom: 0.85rem; } .client-secret-container { @include mix.flex-align-center; - gap: 12px; + gap: 0.85rem; + margin-bottom: 1.7rem; + + &.mobile { + @include mix.flex-column; + align-items: start; + } } .card-actions { diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts index 9e8f802b5..3557de1e4 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts @@ -7,17 +7,42 @@ import { OnInit, signal, } from '@angular/core'; -import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entity'; +import { + DeveloperApp, + DeveloperAppFormFormControls, + DeveloperAppForm, +} from '@osf/features/settings/developer-apps/developer-app.entities'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { InputText } from 'primeng/inputtext'; import { IconField } from 'primeng/iconfield'; import { InputIcon } from 'primeng/inputicon'; +import { CdkCopyToClipboard } from '@angular/cdk/clipboard'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { linkValidator } from '@core/helpers/link-validator.helper'; @Component({ selector: 'osf-developer-application-details', - imports: [Button, Card, RouterLink, InputText, IconField, InputIcon], + imports: [ + Button, + Card, + RouterLink, + InputText, + IconField, + InputIcon, + CdkCopyToClipboard, + FormsModule, + ReactiveFormsModule, + ], templateUrl: './developer-app-details.component.html', styleUrl: './developer-app-details.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -25,30 +50,95 @@ import { InputIcon } from 'primeng/inputicon'; export class DeveloperAppDetailsComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly activatedRoute = inject(ActivatedRoute); + private readonly isXSmall$ = inject(IS_XSMALL); + protected readonly DeveloperAppFormFormControls = + DeveloperAppFormFormControls; + + isXSmall = toSignal(this.isXSmall$); developerAppId = signal(null); developerApp = signal({ id: '1', - name: 'Example name', + appName: 'Example name', + projHomePageUrl: 'https://example.com', + appDescription: 'Example description', + authorizationCallbackUrl: 'https://example.com/callback', }); isClientSecretVisible = signal(false); - clientSecret = signal('asdfasdfasdfasdfasdf'); + clientSecret = signal( + 'clientsecretclientsecretclientsecretclientsecret', + ); + clientId = signal('clientid'); hiddenClientSecret = computed(() => '*'.repeat(this.clientSecret().length), ); + readonly editAppForm: DeveloperAppForm = new FormGroup({ + [DeveloperAppFormFormControls.AppName]: new FormControl( + this.developerApp().appName, + { + nonNullable: true, + validators: [Validators.required], + }, + ), + [DeveloperAppFormFormControls.ProjectHomePageUrl]: new FormControl( + this.developerApp().projHomePageUrl, + { + nonNullable: true, + validators: [Validators.required, linkValidator()], + }, + ), + [DeveloperAppFormFormControls.AppDescription]: new FormControl( + this.developerApp().appDescription, + { + nonNullable: false, + }, + ), + [DeveloperAppFormFormControls.AuthorizationCallbackUrl]: new FormControl( + this.developerApp().authorizationCallbackUrl, + { + nonNullable: true, + validators: [Validators.required, linkValidator()], + }, + ), + }); + ngOnInit(): void { this.developerAppId.set(this.activatedRoute.snapshot.params['id']); } - onDeleteAppBtnClicked(): void { + deleteApp(): void { + //TODO confirmation dialog //TODO integrate API } - onResetClientSecretBtnClicked(): void { + resetClientSecret(): void { //TODO integrate API } - copyClientId(): void { - console.log('copy client id'); + clientIdCopiedToClipboard(): void { + //TODO maybe show message + } + + clientSecretCopiedToClipboard(): void { + //TODO maybe show message + } + + submitForm(): void { + if (!this.editAppForm.valid) { + this.editAppForm.markAllAsTouched(); + this.editAppForm.get(DeveloperAppFormFormControls.AppName)?.markAsDirty(); + this.editAppForm + .get(DeveloperAppFormFormControls.ProjectHomePageUrl) + ?.markAsDirty(); + this.editAppForm + .get(DeveloperAppFormFormControls.AppDescription) + ?.markAsDirty(); + this.editAppForm + .get(DeveloperAppFormFormControls.AuthorizationCallbackUrl) + ?.markAsDirty(); + return; + } + + //TODO integrate API } } From 140e1d671a8592b965fcb411d08e25e5b8fab251 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 11:34:11 +0200 Subject: [PATCH 09/14] feat(developer-apps): Confirmation dialog before deleting app and resetting secret --- .../developer-app-details.component.ts | 36 +++++++++++++++++-- .../developer-apps-list.component.html | 2 +- .../developer-apps-list.component.ts | 21 +++++++++-- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts index 3557de1e4..1e710eff9 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.ts @@ -29,6 +29,8 @@ import { Validators, } from '@angular/forms'; import { linkValidator } from '@core/helpers/link-validator.helper'; +import { ConfirmationService } from 'primeng/api'; +import { defaultConfirmationConfig } from '@shared/helpers/default-confirmation-config.helper'; @Component({ selector: 'osf-developer-application-details', @@ -50,6 +52,7 @@ import { linkValidator } from '@core/helpers/link-validator.helper'; export class DeveloperAppDetailsComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly activatedRoute = inject(ActivatedRoute); + private readonly confirmationService = inject(ConfirmationService); private readonly isXSmall$ = inject(IS_XSMALL); protected readonly DeveloperAppFormFormControls = DeveloperAppFormFormControls; @@ -107,12 +110,39 @@ export class DeveloperAppDetailsComponent implements OnInit { } deleteApp(): void { - //TODO confirmation dialog - //TODO integrate API + this.confirmationService.confirm({ + ...defaultConfirmationConfig, + message: + "Are you sure you want to delete this developer app? All users' access tokens will be revoked. This cannot be reversed.", + header: `Delete App ${this.developerApp().appName}?`, + acceptButtonProps: { + ...defaultConfirmationConfig.acceptButtonProps, + severity: 'danger', + label: 'Delete', + }, + accept: () => { + //TODO integrate API + }, + }); } resetClientSecret(): void { - //TODO integrate API + this.confirmationService.confirm({ + ...defaultConfirmationConfig, + message: + 'Resetting the client secret will render your application unusable until it is updated with the new client secret,' + + ' and all users must reauthorize access. Previously issued access tokens will no longer work.' + + '

Are you sure you want to reset the client secret? This cannot be reversed.', + header: `Reset Client Secret?`, + acceptButtonProps: { + ...defaultConfirmationConfig.acceptButtonProps, + severity: 'danger', + label: 'Reset', + }, + accept: () => { + //TODO integrate API + }, + }); } clientIdCopiedToClipboard(): void { diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html index 5b3b79394..8c19106e4 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.html @@ -16,7 +16,7 @@

{{ developerApp.appName }}

class="btn-full-width" label="Delete" severity="danger" - (onClick)="deleteApp(developerApp.id)" + (onClick)="deleteApp(developerApp)" />
diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts index 5ab2aabad..cdf7ac554 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.ts @@ -10,6 +10,8 @@ import { RouterLink } from '@angular/router'; import { DeveloperApp } from '@osf/features/settings/developer-apps/developer-app.entities'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; +import { defaultConfirmationConfig } from '@shared/helpers/default-confirmation-config.helper'; +import { ConfirmationService } from 'primeng/api'; @Component({ selector: 'osf-developer-applications-list', @@ -19,6 +21,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class DeveloperAppsListComponent { + private readonly confirmationService = inject(ConfirmationService); #isXSmall$ = inject(IS_XSMALL); isXSmall = toSignal(this.#isXSmall$); @@ -39,8 +42,20 @@ export class DeveloperAppsListComponent { }, ]); - deleteApp(developerAppId: string): void { - console.log('delete', developerAppId); - //TODO implement api integration + deleteApp(developerApp: DeveloperApp): void { + this.confirmationService.confirm({ + ...defaultConfirmationConfig, + message: + "Are you sure you want to delete this developer app? All users' access tokens will be revoked. This cannot be reversed.", + header: `Delete App ${developerApp.appName}?`, + acceptButtonProps: { + ...defaultConfirmationConfig.acceptButtonProps, + severity: 'danger', + label: 'Delete', + }, + accept: () => { + //TODO integrate API + }, + }); } } From a5fd4c9becd24df3a260a9510de3fc535282cd39 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 11:34:50 +0200 Subject: [PATCH 10/14] feat(developer-apps): Renamed developer create form submit button's label --- .../create-developer-app.component.html | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html index b80a456e8..5aeaa6349 100644 --- a/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html +++ b/src/app/features/settings/developer-apps/create-developer-app/create-developer-app.component.html @@ -48,16 +48,10 @@
+ -
From 9dce87d85dbf5162ce57bfc3dd4af8777c06e2fc Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 11:35:42 +0200 Subject: [PATCH 11/14] feat(developer-apps): Adjusted create developer app dialog width for different layouts --- .../developer-apps-container.component.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.ts b/src/app/features/settings/developer-apps/developer-apps-container.component.ts index 50c64aa9d..fa654d3a8 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.ts +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.ts @@ -3,6 +3,8 @@ import { RouterOutlet } from '@angular/router'; import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; import { DialogService } from 'primeng/dynamicdialog'; import { CreateDeveloperAppComponent } from '@osf/features/settings/developer-apps/create-developer-app/create-developer-app.component'; +import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; +import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'osf-developer-apps', @@ -14,10 +16,21 @@ import { CreateDeveloperAppComponent } from '@osf/features/settings/developer-ap }) export class DeveloperAppsContainerComponent { private readonly dialogService = inject(DialogService); + isXSmall$ = inject(IS_XSMALL); + isMedium$ = inject(IS_MEDIUM); + isXSmall = toSignal(this.isXSmall$); + isMedium = toSignal(this.isMedium$); createDeveloperApp(): void { + let dialogWidth = '850px'; + if (this.isXSmall()) { + dialogWidth = '345px'; + } else if (this.isMedium()) { + dialogWidth = '500px'; + } + this.dialogService.open(CreateDeveloperAppComponent, { - width: '448px', + width: dialogWidth, focusOnShow: false, header: 'Create Developer App', closeOnEscape: true, From 1c9c7aa6876e7e2fa9f8aa8e1c3782250c64d726 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 12:00:45 +0200 Subject: [PATCH 12/14] style(developer-apps): Fixed styles so apps list takes full height --- .../developer-apps-container.component.html | 4 +- .../developer-apps-container.component.scss | 6 ++ .../developer-apps-list.component.scss | 60 ++++++++++--------- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.html b/src/app/features/settings/developer-apps/developer-apps-container.component.html index 622c11cec..17a12df66 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.html +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.html @@ -6,4 +6,6 @@ (buttonClick)="createDeveloperApp()" /> - +
+ +
diff --git a/src/app/features/settings/developer-apps/developer-apps-container.component.scss b/src/app/features/settings/developer-apps/developer-apps-container.component.scss index 43b3dda99..3a0294d96 100644 --- a/src/app/features/settings/developer-apps/developer-apps-container.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-container.component.scss @@ -3,4 +3,10 @@ :host { @include mix.flex-column; + flex: 1; + + section { + @include mix.flex-column; + flex: 1; + } } diff --git a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss index e327dbce6..13e3d1ff1 100644 --- a/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss +++ b/src/app/features/settings/developer-apps/developer-apps-list/developer-apps-list.component.scss @@ -1,42 +1,48 @@ @use "assets/styles/variables" as var; @use "assets/styles/mixins" as mix; -.content-container { - padding: 1.7rem; - color: var.$dark-blue-1; - background-color: var.$white; +:host { + @include mix.flex-column; + flex: 1; + + .content-container { + flex: 1; + padding: 1.7rem; + color: var.$dark-blue-1; + background-color: var.$white; + + &.mobile { + padding: 1rem; + } - &.mobile { - padding: 1rem; - } + p { + margin-bottom: 1.7rem; + } - p { - margin-bottom: 1.7rem; - } + .applications-container { + @include mix.flex-column; + gap: 0.85rem; - .applications-container { - @include mix.flex-column; - gap: 0.85rem; + p-card { + .card-body { + &.mobile { + @include mix.flex-column; + gap: 0.85rem; - p-card { - .card-body { - &.mobile { - @include mix.flex-column; - gap: 0.85rem; + a { + align-self: flex-start; + } - a { - align-self: flex-start; + .button-container { + align-self: flex-end; + width: 50%; + } } - .button-container { - align-self: flex-end; - width: 50%; + &:not(.mobile) { + @include mix.flex-center-between; } } - - &:not(.mobile) { - @include mix.flex-center-between; - } } } } From 5e32efb59426b95de80677eaddd3a143096cd046 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 12:37:56 +0200 Subject: [PATCH 13/14] feat(developer-apps): Added notification when client secret or id is copied --- .../developer-app-details.component.html | 62 ++++++++++++------- .../developer-app-details.component.scss | 29 +++++++++ .../developer-app-details.component.ts | 27 ++++++-- 3 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html index b888610fd..f229fbbfc 100644 --- a/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html +++ b/src/app/features/settings/developer-apps/developer-app-details/developer-app-details.component.html @@ -26,15 +26,23 @@

Client ID

share publicly.

- - - + - - - + Copied! + + + + + + + +
@@ -48,21 +56,31 @@

Client Secret

- - - + - - - + Copied! + + + + + + + + (null); @@ -70,11 +69,15 @@ export class DeveloperAppDetailsComponent implements OnInit { clientSecret = signal( 'clientsecretclientsecretclientsecretclientsecret', ); - clientId = signal('clientid'); hiddenClientSecret = computed(() => '*'.repeat(this.clientSecret().length), ); + clientSecretCopiedNotificationVisible = signal(false); + + clientId = signal('clientid'); + clientIdCopiedNotificationVisible = signal(false); + readonly DeveloperAppFormFormControls = DeveloperAppFormFormControls; readonly editAppForm: DeveloperAppForm = new FormGroup({ [DeveloperAppFormFormControls.AppName]: new FormControl( this.developerApp().appName, @@ -146,11 +149,23 @@ export class DeveloperAppDetailsComponent implements OnInit { } clientIdCopiedToClipboard(): void { - //TODO maybe show message + this.clientIdCopiedNotificationVisible.set(true); + + timer(2500) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.clientIdCopiedNotificationVisible.set(false); + }); } clientSecretCopiedToClipboard(): void { - //TODO maybe show message + this.clientSecretCopiedNotificationVisible.set(true); + + timer(2500) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.clientSecretCopiedNotificationVisible.set(false); + }); } submitForm(): void { From 6247699aa1124915bff90718297377082257f83d Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 21 Mar 2025 12:41:09 +0200 Subject: [PATCH 14/14] fix(styles): Fix after merging main --- src/assets/styles/styles.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/assets/styles/styles.scss b/src/assets/styles/styles.scss index 61d3ff4a2..10e18939d 100644 --- a/src/assets/styles/styles.scss +++ b/src/assets/styles/styles.scss @@ -17,7 +17,6 @@ @use "./overrides/radio"; @use "./overrides/dropdown"; @use "./overrides/confirmation-dialog"; -@use "./overrides/tabs"; @use "./overrides/iconfield"; @layer base, primeng, reset;