From 0bf474cebc79679a514474f3b00c35ffbf1acd4e Mon Sep 17 00:00:00 2001 From: ptkach Date: Mon, 5 Feb 2024 16:50:32 -0500 Subject: [PATCH] Restyle console based on the new design proposal --- console-webapp/angular.json | 6 +- console-webapp/build.gradle | 2 +- console-webapp/src/app/app-routing.module.ts | 97 ++-- console-webapp/src/app/app.component.html | 25 +- console-webapp/src/app/app.component.scss | 24 +- console-webapp/src/app/app.component.spec.ts | 2 +- console-webapp/src/app/app.component.ts | 41 +- console-webapp/src/app/app.module.ts | 71 ++- .../billingInfo/billingInfo.component.html | 14 + .../billingInfo/billingInfo.component.scss | 22 + .../billingInfo.component.ts} | 20 +- .../src/app/domains/domainList.component.html | 120 ++--- .../src/app/domains/domainList.component.scss | 21 + .../app/domains/domainList.component.spec.ts | 2 +- .../src/app/domains/domainList.component.ts | 2 +- .../src/app/domains/domainList.service.ts | 2 +- .../src/app/header/header.component.html | 68 ++- .../src/app/header/header.component.scss | 36 +- .../src/app/header/header.component.spec.ts | 2 +- .../src/app/header/header.component.ts | 5 +- .../src/app/home/home.component.html | 54 ++- .../src/app/home/home.component.scss | 30 +- .../src/app/home/home.component.spec.ts | 2 +- console-webapp/src/app/home/home.component.ts | 10 +- .../home/widgets/billingWidget.component.html | 22 - .../home/widgets/contactWidget.component.html | 29 -- .../home/widgets/contactWidget.component.ts | 24 - .../home/widgets/domainsWidget.component.html | 30 -- .../home/widgets/domainsWidget.component.ts | 29 -- .../app/home/widgets/eppWidget.component.html | 13 - .../widgets/promotionsWidget.component.html | 27 -- .../widgets/resourcesWidget.component.html | 17 - .../widgets/settingsWidget.component.html | 53 --- .../home/widgets/settingsWidget.component.ts | 44 -- .../home/widgets/tldsWidget.component.html | 13 - console-webapp/src/app/material.module.ts | 4 +- .../app/navigation/navigation.component.html | 44 ++ .../app/navigation/navigation.component.scss | 71 +++ .../app/navigation/navigation.component.ts | 100 ++++ .../registrar/emptyRegistrar.component.html | 7 - .../app/registrar/emptyRegistrar.component.ts | 37 -- .../src/app/registrar/registrar.guard.spec.ts | 68 --- .../src/app/registrar/registrar.guard.ts | 45 -- .../app/registrar/registrar.service.spec.ts | 2 +- .../src/app/registrar/registrar.service.ts | 215 ++++++++- .../registrar/registrarDetails.component.html | 126 +++-- .../registrar/registrarDetails.component.scss | 23 +- .../registrar/registrarDetails.component.ts | 52 ++- .../registrarSelector.component.html | 51 +-- .../registrarSelector.component.scss | 11 - .../registrarSelector.component.spec.ts | 2 +- .../registrar/registrarSelector.component.ts | 37 +- .../registrar/registrarsTable.component.html | 24 +- .../registrar/registrarsTable.component.scss | 1 - .../registrarsTable.component.spec.ts | 2 +- .../registrar/registrarsTable.component.ts | 122 +++-- .../app/resources/resources.component.html | 15 + .../app/resources/resources.component.scss | 22 + .../resources.component.ts} | 14 +- .../settings/contact/contact.component.html | 72 ++- .../settings/contact/contact.component.scss | 55 +-- .../contact/contact.component.spec.ts | 2 +- .../app/settings/contact/contact.component.ts | 203 +++------ .../app/settings/contact/contact.service.ts | 113 ++++- .../contact/contactDetails.component.html | 226 ++++++--- .../contact/contactDetails.component.scss | 23 + .../contact/contactDetails.component.ts | 64 +++ .../settings/security/security.component.html | 168 +++---- .../settings/security/security.component.scss | 42 +- .../security/security.component.spec.ts | 2 +- .../settings/security/security.component.ts | 55 +-- .../security/security.service.spec.ts | 2 +- .../app/settings/security/security.service.ts | 9 +- .../security/securityEdit.component.html | 51 +++ .../security/securityEdit.component.scss} | 25 +- .../security/securityEdit.component.ts | 65 +++ .../src/app/settings/settings.component.html | 47 +- .../src/app/settings/settings.component.scss | 5 +- .../app/settings/settings.component.spec.ts | 2 +- .../src/app/settings/settings.component.ts | 8 +- .../app/settings/users/users.component.scss | 2 +- .../settings/users/users.component.spec.ts | 2 +- .../src/app/settings/users/users.component.ts | 2 +- .../app/settings/whois/whois.component.html | 429 ++++++++---------- .../app/settings/whois/whois.component.scss | 68 +-- .../settings/whois/whois.component.spec.ts | 2 +- .../src/app/settings/whois/whois.component.ts | 42 +- .../src/app/settings/whois/whois.service.ts | 16 +- .../components/dialogBottomSheet.component.ts | 69 --- .../notifications.component.html | 16 + .../notifications.component.scss | 23 + .../notifications/notifications.component.ts} | 22 +- .../selectedRegistrarWrapper.component.scss} | 0 .../selectedRegistrarWrapper.component.ts | 37 ++ .../directives/locationBack.directive.ts | 14 + .../app/shared/services/backend.service.ts | 18 +- .../app/shared/services/breakPoint.service.ts | 45 ++ .../shared/services/globalLoader.service.ts | 2 +- .../app/shared/services/userData.service.ts | 2 +- console-webapp/src/app/snackbar.module.ts | 2 +- .../src/app/support/support.component.html | 45 ++ .../src/app/support/support.component.scss | 13 + .../support.component.ts} | 13 +- .../src/app/tlds/tlds.component.html | 48 +- .../src/app/tlds/tlds.component.scss | 2 +- .../src/app/tlds/tlds.component.spec.ts | 2 +- console-webapp/src/app/tlds/tlds.component.ts | 2 +- console-webapp/src/assets/billing.png | Bin 0 -> 78411 bytes console-webapp/src/assets/resources.png | Bin 0 -> 187326 bytes .../src/environments/environment.prod.ts | 2 +- .../src/environments/environment.ts | 2 +- console-webapp/src/index.html | 6 +- console-webapp/src/main.ts | 2 +- console-webapp/src/styles.scss | 82 ++-- console-webapp/src/theme.scss | 162 +++++-- 115 files changed, 2500 insertions(+), 2007 deletions(-) create mode 100644 console-webapp/src/app/billingInfo/billingInfo.component.html create mode 100644 console-webapp/src/app/billingInfo/billingInfo.component.scss rename console-webapp/src/app/{home/widgets/billingWidget.component.ts => billingInfo/billingInfo.component.ts} (66%) delete mode 100644 console-webapp/src/app/home/widgets/billingWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/contactWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/contactWidget.component.ts delete mode 100644 console-webapp/src/app/home/widgets/domainsWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/domainsWidget.component.ts delete mode 100644 console-webapp/src/app/home/widgets/eppWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/promotionsWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/resourcesWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/settingsWidget.component.html delete mode 100644 console-webapp/src/app/home/widgets/settingsWidget.component.ts delete mode 100644 console-webapp/src/app/home/widgets/tldsWidget.component.html create mode 100644 console-webapp/src/app/navigation/navigation.component.html create mode 100644 console-webapp/src/app/navigation/navigation.component.scss create mode 100644 console-webapp/src/app/navigation/navigation.component.ts delete mode 100644 console-webapp/src/app/registrar/emptyRegistrar.component.html delete mode 100644 console-webapp/src/app/registrar/emptyRegistrar.component.ts delete mode 100644 console-webapp/src/app/registrar/registrar.guard.spec.ts delete mode 100644 console-webapp/src/app/registrar/registrar.guard.ts create mode 100644 console-webapp/src/app/resources/resources.component.html create mode 100644 console-webapp/src/app/resources/resources.component.scss rename console-webapp/src/app/{home/widgets/resourcesWidget.component.ts => resources/resources.component.ts} (61%) create mode 100644 console-webapp/src/app/settings/contact/contactDetails.component.scss create mode 100644 console-webapp/src/app/settings/contact/contactDetails.component.ts create mode 100644 console-webapp/src/app/settings/security/securityEdit.component.html rename console-webapp/src/app/{home/widgets/eppWidget.component.ts => settings/security/securityEdit.component.scss} (64%) create mode 100644 console-webapp/src/app/settings/security/securityEdit.component.ts delete mode 100644 console-webapp/src/app/shared/components/dialogBottomSheet.component.ts create mode 100644 console-webapp/src/app/shared/components/notifications/notifications.component.html create mode 100644 console-webapp/src/app/shared/components/notifications/notifications.component.scss rename console-webapp/src/app/{home/widgets/tldsWidget.component.ts => shared/components/notifications/notifications.component.ts} (56%) rename console-webapp/src/app/{registrar/emptyRegistrar.component.scss => shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.scss} (100%) create mode 100644 console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.ts create mode 100644 console-webapp/src/app/shared/directives/locationBack.directive.ts create mode 100644 console-webapp/src/app/shared/services/breakPoint.service.ts create mode 100644 console-webapp/src/app/support/support.component.html create mode 100644 console-webapp/src/app/support/support.component.scss rename console-webapp/src/app/{home/widgets/promotionsWidget.component.ts => support/support.component.ts} (62%) create mode 100644 console-webapp/src/assets/billing.png create mode 100644 console-webapp/src/assets/resources.png diff --git a/console-webapp/angular.json b/console-webapp/angular.json index 562b5c020fa..a9f17c350af 100644 --- a/console-webapp/angular.json +++ b/console-webapp/angular.json @@ -73,10 +73,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "browserTarget": "console-webapp:build:production" + "buildTarget": "console-webapp:build:production" }, "development": { - "browserTarget": "console-webapp:build:development" + "buildTarget": "console-webapp:build:development" } }, "defaultConfiguration": "development" @@ -84,7 +84,7 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "console-webapp:build" + "buildTarget": "console-webapp:build" } }, "test": { diff --git a/console-webapp/build.gradle b/console-webapp/build.gradle index a5c5a96f6a5..a9fcbb0bc42 100644 --- a/console-webapp/build.gradle +++ b/console-webapp/build.gradle @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/app-routing.module.ts b/console-webapp/src/app/app-routing.module.ts index 62eca80f8d3..a9075c5b58b 100644 --- a/console-webapp/src/app/app-routing.module.ts +++ b/console-webapp/src/app/app-routing.module.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,69 +13,106 @@ // limitations under the License. import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { TldsComponent } from './tlds/tlds.component'; +import { Route, RouterModule } from '@angular/router'; +import { BillingInfoComponent } from './billingInfo/billingInfo.component'; +import { DomainListComponent } from './domains/domainList.component'; import { HomeComponent } from './home/home.component'; -import { SettingsComponent } from './settings/settings.component'; -import SettingsContactComponent from './settings/contact/contact.component'; -import SettingsWhoisComponent from './settings/whois/whois.component'; -import SettingsUsersComponent from './settings/users/users.component'; -import SettingsSecurityComponent from './settings/security/security.component'; -import { RegistrarGuard } from './registrar/registrar.guard'; +import { RegistrarDetailsComponent } from './registrar/registrarDetails.component'; import { RegistrarComponent } from './registrar/registrarsTable.component'; -import { EmptyRegistrar } from './registrar/emptyRegistrar.component'; +import { ResourcesComponent } from './resources/resources.component'; import ContactComponent from './settings/contact/contact.component'; -import WhoisComponent from './settings/whois/whois.component'; import SecurityComponent from './settings/security/security.component'; +import { SettingsComponent } from './settings/settings.component'; import UsersComponent from './settings/users/users.component'; -import { DomainListComponent } from './domains/domainList.component'; +import WhoisComponent from './settings/whois/whois.component'; +import { SupportComponent } from './support/support.component'; -const routes: Routes = [ +export interface RouteWithIcon extends Route { + iconName?: string; +} + +export const routes: RouteWithIcon[] = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'registrars', component: RegistrarComponent }, - { path: 'empty-registrar', component: EmptyRegistrar }, - { path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] }, - { path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] }, + { + path: 'home', + component: HomeComponent, + title: 'Dashboard', + iconName: 'view_comfy_alt', + }, + // { path: 'tlds', component: TldsComponent, title: "TLDs", iconName: "event_list" }, { path: DomainListComponent.PATH, component: DomainListComponent, - canActivate: [RegistrarGuard], + title: 'Domains', + iconName: 'view_list', }, { path: SettingsComponent.PATH, component: SettingsComponent, + title: 'Settings', + iconName: 'settings', children: [ { path: '', - redirectTo: 'registrars', + redirectTo: ContactComponent.PATH, pathMatch: 'full', }, { path: ContactComponent.PATH, - component: SettingsContactComponent, - canActivate: [RegistrarGuard], + component: ContactComponent, + title: 'Contacts', }, { path: WhoisComponent.PATH, - component: SettingsWhoisComponent, - canActivate: [RegistrarGuard], + component: WhoisComponent, + title: 'WHOIS Info', }, { path: SecurityComponent.PATH, - component: SettingsSecurityComponent, - canActivate: [RegistrarGuard], + component: SecurityComponent, + title: 'Security', }, { path: UsersComponent.PATH, - component: SettingsUsersComponent, - canActivate: [RegistrarGuard], - }, - { - path: RegistrarComponent.PATH, - component: RegistrarComponent, + component: UsersComponent, }, ], }, + // { + // path: DomainListComponent.PATH, + // component: DomainListComponent, + // title: "EPP Console", + // iconName: "upgrade" + // }, + { + path: RegistrarComponent.PATH, + component: RegistrarComponent, + title: 'Registrars', + iconName: 'account_circle', + }, + { + path: RegistrarDetailsComponent.PATH, + component: RegistrarDetailsComponent, + }, + { + path: BillingInfoComponent.PATH, + component: BillingInfoComponent, + title: 'Billing Info', + iconName: 'credit_card', + }, + { + path: ResourcesComponent.PATH, + component: ResourcesComponent, + title: 'Resources', + iconName: 'description', + }, + { + path: SupportComponent.PATH, + component: SupportComponent, + title: 'Support', + iconName: 'help', + }, ]; @NgModule({ diff --git a/console-webapp/src/app/app.component.html b/console-webapp/src/app/app.component.html index db688b2dd5b..5334de30844 100644 --- a/console-webapp/src/app/app.component.html +++ b/console-webapp/src/app/app.component.html @@ -1,24 +1,19 @@ -
- +
+ - - - - Home page - - - TLDS - - - Settings - - + +
-
+
diff --git a/console-webapp/src/app/app.component.scss b/console-webapp/src/app/app.component.scss index a3a6b7ec698..642dfc5f8b7 100644 --- a/console-webapp/src/app/app.component.scss +++ b/console-webapp/src/app/app.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ // limitations under the License. :host { - font-family: Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol" !important; + font-family: "Google Sans", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; font-size: 14px; color: #333; box-sizing: border-box; @@ -26,26 +26,18 @@ display: flex; flex-direction: column; height: 100%; + background: #fff; &__container { flex: 1; - margin-top: -12px; padding-bottom: 36px; + background: transparent; } &__sidebar { - min-width: 300px; - a::before { - background-color: transparent; - } - .active { - background-color: var(--secondary); - } - } - &__content-wrapper { - margin: 12px 24px; + min-width: 240px; + border: 0; } &__content { - max-width: 1340px; - margin: 0 auto; + padding: 0 16px; } &__global-spinner { margin-bottom: 2rem; diff --git a/console-webapp/src/app/app.component.spec.ts b/console-webapp/src/app/app.component.spec.ts index 8ac87543af3..55f9248cf3a 100644 --- a/console-webapp/src/app/app.component.spec.ts +++ b/console-webapp/src/app/app.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/app.component.ts b/console-webapp/src/app/app.component.ts index 4c69f91f2a4..6531fe73f25 100644 --- a/console-webapp/src/app/app.component.ts +++ b/console-webapp/src/app/app.component.ts @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AfterViewInit, Component, ViewChild, effect } from '@angular/core'; +import { AfterViewInit, Component, ViewChild } from '@angular/core'; +import { MatSidenav } from '@angular/material/sidenav'; +import { NavigationEnd, Router } from '@angular/router'; import { RegistrarService } from './registrar/registrar.service'; -import { UserDataService } from './shared/services/userData.service'; +import { BreakPointObserverService } from './shared/services/breakPoint.service'; import { GlobalLoaderService } from './shared/services/globalLoader.service'; -import { NavigationEnd, Router } from '@angular/router'; -import { MatSidenav } from '@angular/material/sidenav'; +import { UserDataService } from './shared/services/userData.service'; @Component({ selector: 'app-root', @@ -25,32 +26,32 @@ import { MatSidenav } from '@angular/material/sidenav'; styleUrls: ['./app.component.scss'], }) export class AppComponent implements AfterViewInit { - renderRouter: boolean = true; - - @ViewChild('sidenav') + @ViewChild(MatSidenav) sidenav!: MatSidenav; constructor( protected registrarService: RegistrarService, protected userDataService: UserDataService, protected globalLoader: GlobalLoaderService, - protected router: Router - ) { - effect(() => { - if (registrarService.registrarId()) { - this.renderRouter = false; - setTimeout(() => { - this.renderRouter = true; - }, 400); - } - }); - } + protected breakpointObserver: BreakPointObserverService, + private router: Router + ) {} ngAfterViewInit() { this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { - this.sidenav.close(); + if (this.breakpointObserver.isMobileView()) { + this.sidenav.close(); + } } }); } + + toggleSidenav() { + if (this.sidenav.opened) { + this.sidenav.close(); + } else { + this.sidenav.open(); + } + } } diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts index 83aac71a2f9..6b89ca7b943 100644 --- a/console-webapp/src/app/app.module.ts +++ b/console-webapp/src/app/app.module.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,70 +13,65 @@ // limitations under the License. import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MaterialModule } from './material.module'; import { BackendService } from './shared/services/backend.service'; -import { HomeComponent } from './home/home.component'; -import { TldsComponent } from './tlds/tlds.component'; -import { HeaderComponent } from './header/header.component'; -import { SettingsComponent } from './settings/settings.component'; -import SettingsContactComponent, { - ContactDetailsDialogComponent, -} from './settings/contact/contact.component'; import { HttpClientModule } from '@angular/common/http'; -import { RegistrarComponent } from './registrar/registrarsTable.component'; -import { RegistrarGuard } from './registrar/registrar.guard'; -import SecurityComponent from './settings/security/security.component'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; -import { EmptyRegistrar } from './registrar/emptyRegistrar.component'; +import { BillingInfoComponent } from './billingInfo/billingInfo.component'; +import { DomainListComponent } from './domains/domainList.component'; +import { HeaderComponent } from './header/header.component'; +import { HomeComponent } from './home/home.component'; +import { NavigationComponent } from './navigation/navigation.component'; +import { RegistrarDetailsComponent } from './registrar/registrarDetails.component'; import { RegistrarSelectorComponent } from './registrar/registrarSelector.component'; +import { RegistrarComponent } from './registrar/registrarsTable.component'; +import { ResourcesComponent } from './resources/resources.component'; +import SettingsContactComponent from './settings/contact/contact.component'; +import { ContactDetailsComponent } from './settings/contact/contactDetails.component'; +import SecurityComponent from './settings/security/security.component'; +import SecurityEditComponent from './settings/security/securityEdit.component'; +import { SettingsComponent } from './settings/settings.component'; +import WhoisComponent from './settings/whois/whois.component'; +import { NotificationsComponent } from './shared/components/notifications/notifications.component'; +import { SelectedRegistrarWrapper } from './shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component'; +import { LocationBackDirective } from './shared/directives/locationBack.directive'; +import { BreakPointObserverService } from './shared/services/breakPoint.service'; import { GlobalLoaderService } from './shared/services/globalLoader.service'; -import { ContactWidgetComponent } from './home/widgets/contactWidget.component'; -import { PromotionsWidgetComponent } from './home/widgets/promotionsWidget.component'; -import { TldsWidgetComponent } from './home/widgets/tldsWidget.component'; -import { ResourcesWidgetComponent } from './home/widgets/resourcesWidget.component'; -import { EppWidgetComponent } from './home/widgets/eppWidget.component'; -import { BillingWidgetComponent } from './home/widgets/billingWidget.component'; -import { DomainsWidgetComponent } from './home/widgets/domainsWidget.component'; -import { SettingsWidgetComponent } from './home/widgets/settingsWidget.component'; import { UserDataService } from './shared/services/userData.service'; -import WhoisComponent from './settings/whois/whois.component'; import { SnackBarModule } from './snackbar.module'; -import { RegistrarDetailsComponent } from './registrar/registrarDetails.component'; -import { DomainListComponent } from './domains/domainList.component'; -import { DialogBottomSheetWrapper } from './shared/components/dialogBottomSheet.component'; +import { SupportComponent } from './support/support.component'; +import { TldsComponent } from './tlds/tlds.component'; @NgModule({ declarations: [ AppComponent, - DialogBottomSheetWrapper, - BillingWidgetComponent, - ContactDetailsDialogComponent, - ContactWidgetComponent, + BillingInfoComponent, + ContactDetailsComponent, DomainListComponent, - DomainsWidgetComponent, - EmptyRegistrar, - EppWidgetComponent, HeaderComponent, HomeComponent, - PromotionsWidgetComponent, + LocationBackDirective, + NavigationComponent, + NotificationsComponent, RegistrarComponent, RegistrarDetailsComponent, RegistrarSelectorComponent, - ResourcesWidgetComponent, + ResourcesComponent, SecurityComponent, + SecurityEditComponent, + SelectedRegistrarWrapper, SettingsComponent, SettingsContactComponent, - SettingsWidgetComponent, + SupportComponent, TldsComponent, - TldsWidgetComponent, WhoisComponent, ], imports: [ @@ -90,8 +85,8 @@ import { DialogBottomSheetWrapper } from './shared/components/dialogBottomSheet. ], providers: [ BackendService, + BreakPointObserverService, GlobalLoaderService, - RegistrarGuard, UserDataService, { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, diff --git a/console-webapp/src/app/billingInfo/billingInfo.component.html b/console-webapp/src/app/billingInfo/billingInfo.component.html new file mode 100644 index 00000000000..c292df16059 --- /dev/null +++ b/console-webapp/src/app/billingInfo/billingInfo.component.html @@ -0,0 +1,14 @@ +

Billing Info

+
+
+
+ Billing records and information +
+ View on Google Drive +
+
+ +
+
diff --git a/console-webapp/src/app/billingInfo/billingInfo.component.scss b/console-webapp/src/app/billingInfo/billingInfo.component.scss new file mode 100644 index 00000000000..4cae50e574c --- /dev/null +++ b/console-webapp/src/app/billingInfo/billingInfo.component.scss @@ -0,0 +1,22 @@ +.console-app__billing { + display: flex; + flex-wrap: wrap-reverse; + > div { + flex: 1; + display: flex; + justify-content: center; + flex-direction: column; + text-align: left; + max-width: 300px; + min-width: 200px; + margin-top: -40px; + } + img { + aspect-ratio: 1 / 1; + width: 100%; + } + &-subhead { + font-size: 20px; + margin-bottom: 20px; + } +} diff --git a/console-webapp/src/app/home/widgets/billingWidget.component.ts b/console-webapp/src/app/billingInfo/billingInfo.component.ts similarity index 66% rename from console-webapp/src/app/home/widgets/billingWidget.component.ts rename to console-webapp/src/app/billingInfo/billingInfo.component.ts index 23682bcc090..b3bb8f01ef6 100644 --- a/console-webapp/src/app/home/widgets/billingWidget.component.ts +++ b/console-webapp/src/app/billingInfo/billingInfo.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,27 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; -import { RegistrarService } from 'src/app/registrar/registrar.service'; +import { Component, computed } from '@angular/core'; +import { RegistrarService } from '../registrar/registrar.service'; @Component({ - selector: '[app-billing-widget]', - templateUrl: './billingWidget.component.html', + selector: 'app-billingInfo', + templateUrl: './billingInfo.component.html', + styleUrls: ['./billingInfo.component.scss'], }) -export class BillingWidgetComponent { +export class BillingInfoComponent { + public static PATH = 'billingInfo'; constructor(public registrarService: RegistrarService) {} - public get driveFolderUrl(): string { + driveFolderUrl = computed(() => { if (this.registrarService.registrar()?.driveFolderId) { return `https://drive.google.com/drive/folders/${ this.registrarService.registrar()?.driveFolderId }`; } return ''; - } + }); openBillingDetails(e: MouseEvent) { - if (!this.driveFolderUrl) { + if (!this.driveFolderUrl()) { e.preventDefault(); } } diff --git a/console-webapp/src/app/domains/domainList.component.html b/console-webapp/src/app/domains/domainList.component.html index f2cd6b95f91..617ffbbd638 100644 --- a/console-webapp/src/app/domains/domainList.component.html +++ b/console-webapp/src/app/domains/domainList.component.html @@ -1,60 +1,72 @@ -
- - Filter - - + +

console-app__domains-table

+
+ + Filter + + -
- -
- - - - - - +
+ +
+ + + + Domain Name + {{ + element.domainName + }} + - - - - + + Creation Time + + {{ element.creationTime.creationTime }} + + - - - - + + Expiration Time + + {{ element.registrationExpirationTime }} + + - - - - + + Statuses + {{ element.statuses }} + - - + + - - - - -
Domain Name{{ element.domainName }}Creation Time - {{ element.creationTime.creationTime }} - Expiration Time - {{ element.registrationExpirationTime }} - Statuses{{ element.statuses }}
No domains found
- -
-
+ + + No domains found + + + + +
+ diff --git a/console-webapp/src/app/domains/domainList.component.scss b/console-webapp/src/app/domains/domainList.component.scss index e69de29bb2d..f1f35a9583a 100644 --- a/console-webapp/src/app/domains/domainList.component.scss +++ b/console-webapp/src/app/domains/domainList.component.scss @@ -0,0 +1,21 @@ +.console-app { + $min-width: 756px; + + &__domains { + width: 100%; + overflow: auto; + } + + &__domains-filter { + min-width: $min-width !important; + width: 100%; + } + + &__domains-table { + min-width: $min-width !important; + } + + .mat-mdc-paginator { + min-width: $min-width !important; + } +} diff --git a/console-webapp/src/app/domains/domainList.component.spec.ts b/console-webapp/src/app/domains/domainList.component.spec.ts index 3dfd85780f7..3852a56768d 100644 --- a/console-webapp/src/app/domains/domainList.component.spec.ts +++ b/console-webapp/src/app/domains/domainList.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/domains/domainList.component.ts b/console-webapp/src/app/domains/domainList.component.ts index c6557e5393b..705bbb38c33 100644 --- a/console-webapp/src/app/domains/domainList.component.ts +++ b/console-webapp/src/app/domains/domainList.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/domains/domainList.service.ts b/console-webapp/src/app/domains/domainList.service.ts index 6a53f1504dc..996bbaf9a69 100644 --- a/console-webapp/src/app/domains/domainList.service.ts +++ b/console-webapp/src/app/domains/domainList.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/header/header.component.html b/console-webapp/src/app/header/header.component.html index 502c2caeeec..27fb35a4903 100644 --- a/console-webapp/src/app/header/header.component.html +++ b/console-webapp/src/app/header/header.component.html @@ -1,6 +1,12 @@

- - - - + +

diff --git a/console-webapp/src/app/header/header.component.scss b/console-webapp/src/app/header/header.component.scss index 3b6f07b19b1..a0e52456e3f 100644 --- a/console-webapp/src/app/header/header.component.scss +++ b/console-webapp/src/app/header/header.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,25 +16,27 @@ &__logo { color: inherit; text-decoration: none; + margin-left: -10px; + } + &__menu-btn { + width: 25px; + height: 25px; + padding: 0; } &__header { margin-top: 0; - margin-bottom: 10px; - @media (max-width: 599px) { - .mat-toolbar { - padding: 0; - } - .console-app__logo { - font-size: 16px; - } - button { - padding-left: 0; - padding-right: 0; - width: 30px; - } + margin-bottom: 15px; + + .mat-toolbar { + background: transparent; + margin-bottom: 10px; + } + + @media (max-width: 480px) { + } + + &-user-icon { + margin-left: 20px; } } } -.spacer { - flex: 1; -} diff --git a/console-webapp/src/app/header/header.component.spec.ts b/console-webapp/src/app/header/header.component.spec.ts index 579b7166e8c..d7e15b5ccdc 100644 --- a/console-webapp/src/app/header/header.component.spec.ts +++ b/console-webapp/src/app/header/header.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/header/header.component.ts b/console-webapp/src/app/header/header.component.ts index 3be0014c2e1..edfd1f8d707 100644 --- a/console-webapp/src/app/header/header.component.ts +++ b/console-webapp/src/app/header/header.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ // limitations under the License. import { Component, EventEmitter, Output } from '@angular/core'; +import { BreakPointObserverService } from '../shared/services/breakPoint.service'; @Component({ selector: 'app-header', @@ -22,6 +23,8 @@ import { Component, EventEmitter, Output } from '@angular/core'; export class HeaderComponent { private isNavOpen = false; + constructor(protected breakpointObserver: BreakPointObserverService) {} + @Output() toggleNavOpen = new EventEmitter(); toggleNavPane() { diff --git a/console-webapp/src/app/home/home.component.html b/console-webapp/src/app/home/home.component.html index 0d90720d2b1..c74dd9b2d13 100644 --- a/console-webapp/src/app/home/home.component.html +++ b/console-webapp/src/app/home/home.component.html @@ -1,13 +1,47 @@ -
-

Welcome to the Google Registry Console

+
+

Dashboard

+
-
-
-
-
-
-
-
-
+ + +

+ view_list + DUMs +

+

Some text here

+
+ + + +
+ + + +

+ settings + EPP Passwords +

+

Some text here

+
+ + + +
+ + + +

+ account_circle + Registrars +

+

Some text here

+
+ + + +
diff --git a/console-webapp/src/app/home/home.component.scss b/console-webapp/src/app/home/home.component.scss index f591fdf0fdd..97ce0794222 100644 --- a/console-webapp/src/app/home/home.component.scss +++ b/console-webapp/src/app/home/home.component.scss @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,25 +13,23 @@ // limitations under the License. .console-app { - &__home { - margin-top: 1rem; - } &__home-widgets { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - grid-gap: 20px; - grid-auto-flow: dense; - + display: flex; + gap: 1rem; + h3 { + padding: 0; + margin: 0; + display: flex; + align-items: center; + gap: 10px; + } mat-card { - height: 100%; - h1 { - margin-top: 1rem; - } + flex: 1; } } - @media (max-width: 510px) { - .console-app__widget-wrapper__wide { - grid-column: initial; + &__home_tablet { + .console-app__home-widgets { + flex-direction: column; } } } diff --git a/console-webapp/src/app/home/home.component.spec.ts b/console-webapp/src/app/home/home.component.spec.ts index 8527db3c832..2201cb4bb9e 100644 --- a/console-webapp/src/app/home/home.component.spec.ts +++ b/console-webapp/src/app/home/home.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/home/home.component.ts b/console-webapp/src/app/home/home.component.ts index 6ce4e2b538c..66bf09d746e 100644 --- a/console-webapp/src/app/home/home.component.ts +++ b/console-webapp/src/app/home/home.component.ts @@ -1,4 +1,4 @@ -// Copyright 2022 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewEncapsulation } from '@angular/core'; +import { Component } from '@angular/core'; +import { BreakPointObserverService } from '../shared/services/breakPoint.service'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], - encapsulation: ViewEncapsulation.None, }) -export class HomeComponent {} +export class HomeComponent { + constructor(protected breakPointObserverService: BreakPointObserverService) {} +} diff --git a/console-webapp/src/app/home/widgets/billingWidget.component.html b/console-webapp/src/app/home/widgets/billingWidget.component.html deleted file mode 100644 index 5d3352b0d9b..00000000000 --- a/console-webapp/src/app/home/widgets/billingWidget.component.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/console-webapp/src/app/home/widgets/contactWidget.component.html b/console-webapp/src/app/home/widgets/contactWidget.component.html deleted file mode 100644 index a42f21124a2..00000000000 --- a/console-webapp/src/app/home/widgets/contactWidget.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
- call -

Contact Support

-

- Let us know if you have any questions -

-
-
-
Give us a Call
-

- Call {{ userDataService.userData?.productName }} support at - {{ - userDataService.userData?.supportPhoneNumber - }} -

-
Send us an Email
-

- Email {{ userDataService.userData?.productName }} at - {{ - userDataService.userData?.supportEmail - }} -

-
-
-
-
diff --git a/console-webapp/src/app/home/widgets/contactWidget.component.ts b/console-webapp/src/app/home/widgets/contactWidget.component.ts deleted file mode 100644 index 750221b1ea9..00000000000 --- a/console-webapp/src/app/home/widgets/contactWidget.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component } from '@angular/core'; -import { UserDataService } from 'src/app/shared/services/userData.service'; - -@Component({ - selector: '[app-contact-widget]', - templateUrl: './contactWidget.component.html', -}) -export class ContactWidgetComponent { - constructor(public userDataService: UserDataService) {} -} diff --git a/console-webapp/src/app/home/widgets/domainsWidget.component.html b/console-webapp/src/app/home/widgets/domainsWidget.component.html deleted file mode 100644 index f30dcdf9be4..00000000000 --- a/console-webapp/src/app/home/widgets/domainsWidget.component.html +++ /dev/null @@ -1,30 +0,0 @@ - - -
-
- view_list -

Domains

-

- Manage domain names and registry lock settings. -

-
-
- -

Register a new domain name

- -

- Download a csv of all domains under management -

-
-
-
-
diff --git a/console-webapp/src/app/home/widgets/domainsWidget.component.ts b/console-webapp/src/app/home/widgets/domainsWidget.component.ts deleted file mode 100644 index aaa1e0a5412..00000000000 --- a/console-webapp/src/app/home/widgets/domainsWidget.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { DomainListComponent } from 'src/app/domains/domainList.component'; - -@Component({ - selector: '[app-domains-widget]', - templateUrl: './domainsWidget.component.html', -}) -export class DomainsWidgetComponent { - constructor(private router: Router) {} - - openDomainsPage() { - this.router.navigate([DomainListComponent.PATH]); - } -} diff --git a/console-webapp/src/app/home/widgets/eppWidget.component.html b/console-webapp/src/app/home/widgets/eppWidget.component.html deleted file mode 100644 index c014b514d13..00000000000 --- a/console-webapp/src/app/home/widgets/eppWidget.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
-
- computer -

EPP Console

-

- Test run and execute EPP commands using XML files. -

-
-
-
-
diff --git a/console-webapp/src/app/home/widgets/promotionsWidget.component.html b/console-webapp/src/app/home/widgets/promotionsWidget.component.html deleted file mode 100644 index cb0c5b8ba0a..00000000000 --- a/console-webapp/src/app/home/widgets/promotionsWidget.component.html +++ /dev/null @@ -1,27 +0,0 @@ - - -
-
- subject -

Promotions

-

- Manage Google Registry promotions and view onboarding process. -

-
-
- -

- Onboard or view current preferred partner status -

- -

- Onboard or view current registry lock status -

-
-
-
-
diff --git a/console-webapp/src/app/home/widgets/resourcesWidget.component.html b/console-webapp/src/app/home/widgets/resourcesWidget.component.html deleted file mode 100644 index 6caf5335eca..00000000000 --- a/console-webapp/src/app/home/widgets/resourcesWidget.component.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - diff --git a/console-webapp/src/app/home/widgets/settingsWidget.component.html b/console-webapp/src/app/home/widgets/settingsWidget.component.html deleted file mode 100644 index d75f01104a3..00000000000 --- a/console-webapp/src/app/home/widgets/settingsWidget.component.html +++ /dev/null @@ -1,53 +0,0 @@ - - -
-
- settings -

Settings

-

- Configure registrar settings, manage console users, and view activity - log. -

-
-
- -

Manage Primary, Technical, etc contacts.

- -

- Manage IP allow lists and SSL certificates. -

- -

Reset your Nomulus password.

- -

Create and manage console user accounts

- -

Create and manage registrar accounts

-
-
-
-
diff --git a/console-webapp/src/app/home/widgets/settingsWidget.component.ts b/console-webapp/src/app/home/widgets/settingsWidget.component.ts deleted file mode 100644 index 425cb975721..00000000000 --- a/console-webapp/src/app/home/widgets/settingsWidget.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; -import { RegistrarComponent } from 'src/app/registrar/registrarsTable.component'; -import ContactComponent from 'src/app/settings/contact/contact.component'; -import SecurityComponent from 'src/app/settings/security/security.component'; -import { SettingsComponent } from 'src/app/settings/settings.component'; - -@Component({ - selector: '[app-settings-widget]', - templateUrl: './settingsWidget.component.html', -}) -export class SettingsWidgetComponent { - constructor(private router: Router) {} - - openRegistrarsPage() { - this.navigate(RegistrarComponent.PATH); - } - - openSecurityPage() { - this.navigate(SecurityComponent.PATH); - } - - openContactsPage() { - this.navigate(ContactComponent.PATH); - } - - private navigate(route: string) { - this.router.navigate([SettingsComponent.PATH, route]); - } -} diff --git a/console-webapp/src/app/home/widgets/tldsWidget.component.html b/console-webapp/src/app/home/widgets/tldsWidget.component.html deleted file mode 100644 index 99324ed7fcb..00000000000 --- a/console-webapp/src/app/home/widgets/tldsWidget.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
-
- list_alt -

TLDs

-

- View launch phase information for all onboarded and available TLDs. -

-
-
-
-
diff --git a/console-webapp/src/app/material.module.ts b/console-webapp/src/app/material.module.ts index bc5b6b089da..cc8fbbf0d54 100644 --- a/console-webapp/src/app/material.module.ts +++ b/console-webapp/src/app/material.module.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatChipsModule } from '@angular/material/chips'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; @NgModule({ exports: [ @@ -83,6 +84,7 @@ import { MatChipsModule } from '@angular/material/chips'; MatSnackBarModule, MatPaginatorModule, MatChipsModule, + MatAutocompleteModule, ], }) export class MaterialModule {} diff --git a/console-webapp/src/app/navigation/navigation.component.html b/console-webapp/src/app/navigation/navigation.component.html new file mode 100644 index 00000000000..745372fdb86 --- /dev/null +++ b/console-webapp/src/app/navigation/navigation.component.html @@ -0,0 +1,44 @@ + + + + {{ node.iconName }} + + {{ node.title }} + + +
+ + + {{ node.iconName }} + + {{ node.title }} +
+
+ +
+
+
diff --git a/console-webapp/src/app/navigation/navigation.component.scss b/console-webapp/src/app/navigation/navigation.component.scss new file mode 100644 index 00000000000..aa73db9b2eb --- /dev/null +++ b/console-webapp/src/app/navigation/navigation.component.scss @@ -0,0 +1,71 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +$expand-icon-size: 26px; + +.console-app { + &__sidebar { + min-width: 300px; + border: 0; + } + + &__nav-icon { + width: $expand-icon-size; + height: $expand-icon-size; + color: var(--secondary) !important; + margin-right: $expand-icon-size; + + &_expand { + position: absolute; + left: 0; + margin: auto; + padding: 0px; + width: $expand-icon-size; + height: $expand-icon-size; + } + } + + &__nav-tree { + .mat-tree-node { + cursor: pointer; + padding-left: $expand-icon-size; + position: relative; + + &:hover { + background-color: var(--light-highlight); + border-radius: 0 25px 25px 0; + } + &.active { + border-radius: 0 25px 25px 0; + background-color: var(--lightest); + } + } + + div[role="group"] > .mat-tree-node { + // expand icon + regular icon + spacing = 3 * $expand-icon-size + padding-left: calc($expand-icon-size * 3); + } + + ul, + li { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; + } + + &_invisible { + display: none; + } + } +} diff --git a/console-webapp/src/app/navigation/navigation.component.ts b/console-webapp/src/app/navigation/navigation.component.ts new file mode 100644 index 00000000000..b8d3b84d660 --- /dev/null +++ b/console-webapp/src/app/navigation/navigation.component.ts @@ -0,0 +1,100 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NestedTreeControl } from '@angular/cdk/tree'; +import { Component } from '@angular/core'; +import { MatTreeNestedDataSource } from '@angular/material/tree'; +import { NavigationEnd, Router } from '@angular/router'; +import { RouteWithIcon, routes } from '../app-routing.module'; + +interface NavMenuNode extends RouteWithIcon { + parentRoute?: RouteWithIcon; +} + +@Component({ + selector: 'app-navigation', + templateUrl: './navigation.component.html', + styleUrls: ['./navigation.component.scss'], +}) +export class NavigationComponent { + renderRouter: boolean = true; + treeControl = new NestedTreeControl((node) => node.children); + dataSource = new MatTreeNestedDataSource(); + private subscription: any; + hasChild = (_: number, node: RouteWithIcon) => + !!node.children && node.children.length > 0; + + constructor(protected router: Router) { + this.dataSource.data = this.ngRoutesToNavMenuNodes(routes); + } + + ngOnInit() { + this.subscription = this.router.events.subscribe((navigationParams) => { + if (navigationParams instanceof NavigationEnd) { + this.syncExpandedNavigationWithRoute(navigationParams.url); + } + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + syncExpandedNavigationWithRoute(url: string) { + const maybeComponentWithChildren = this.dataSource.data.find((menuNode) => { + return ( + // @ts-ignore - optional function added to components with children, + // there's no availble tools to get current active router component + typeof menuNode.component?.matchesUrl === 'function' && + // @ts-ignore + menuNode.component?.matchesUrl(url) + ); + }); + if (maybeComponentWithChildren) { + this.treeControl.expand(maybeComponentWithChildren); + } + } + + onClick(node: NavMenuNode) { + if (node.parentRoute) { + this.router.navigate([node.parentRoute.path + '/' + node.path]); + } else { + this.router.navigate([node.path]); + } + } + + /** + * We only want to use routes with titles and we want to provide easy reference to parent node + */ + ngRoutesToNavMenuNodes(routes: RouteWithIcon[]): NavMenuNode[] { + return routes + .filter((r) => r.title) + .map((r) => { + if (r.children) { + return { + ...r, + children: r.children + .filter((r) => r.title) + .map((childRoute) => { + return { + ...childRoute, + parentRoute: r, + }; + }), + }; + } + return r; + }); + } +} diff --git a/console-webapp/src/app/registrar/emptyRegistrar.component.html b/console-webapp/src/app/registrar/emptyRegistrar.component.html deleted file mode 100644 index e77cdc8b3ab..00000000000 --- a/console-webapp/src/app/registrar/emptyRegistrar.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-

- block -

-

No registrar selected

-

Please select a registrar

-
diff --git a/console-webapp/src/app/registrar/emptyRegistrar.component.ts b/console-webapp/src/app/registrar/emptyRegistrar.component.ts deleted file mode 100644 index 02521b104eb..00000000000 --- a/console-webapp/src/app/registrar/emptyRegistrar.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Component, effect } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; - -import { RegistrarService } from './registrar.service'; - -@Component({ - selector: 'app-empty-registrar', - templateUrl: './emptyRegistrar.component.html', - styleUrls: ['./emptyRegistrar.component.scss'], -}) -export class EmptyRegistrar { - constructor( - private route: ActivatedRoute, - protected registrarService: RegistrarService, - private router: Router - ) { - effect(() => { - if (registrarService.registrarId()) { - this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]); - } - }); - } -} diff --git a/console-webapp/src/app/registrar/registrar.guard.spec.ts b/console-webapp/src/app/registrar/registrar.guard.spec.ts deleted file mode 100644 index ec4618c6bf1..00000000000 --- a/console-webapp/src/app/registrar/registrar.guard.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { TestBed } from '@angular/core/testing'; - -import { RegistrarGuard } from './registrar.guard'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { RegistrarService } from './registrar.service'; - -describe('RegistrarGuard', () => { - let guard: RegistrarGuard; - let dummyRegistrarService: RegistrarService; - let routeSpy: Router; - let dummyRoute: RouterStateSnapshot; - - beforeEach(() => { - routeSpy = jasmine.createSpyObj('Router', ['navigate']); - dummyRegistrarService = { activeRegistrarId: '' } as RegistrarService; - dummyRoute = { url: '/value' } as RouterStateSnapshot; - - TestBed.configureTestingModule({ - providers: [ - RegistrarGuard, - { provide: Router, useValue: routeSpy }, - { provide: RegistrarService, useValue: dummyRegistrarService }, - ], - }); - }); - - it('should not be able to activate when activeRegistrarId is empty', () => { - guard = TestBed.inject(RegistrarGuard); - const res = guard.canActivate(new ActivatedRouteSnapshot(), dummyRoute); - expect(res).toBeFalsy(); - }); - - it('should be able to activate when activeRegistrarId is not empty', () => { - TestBed.overrideProvider(RegistrarService, { - useValue: { activeRegistrarId: 'value' }, - }); - guard = TestBed.inject(RegistrarGuard); - const res = guard.canActivate(new ActivatedRouteSnapshot(), dummyRoute); - expect(res).toBeTrue(); - }); - - it('should navigate to empty-registrar screen when activeRegistrarId is empty', () => { - guard = TestBed.inject(RegistrarGuard); - guard.canActivate(new ActivatedRouteSnapshot(), dummyRoute); - expect(routeSpy.navigate).toHaveBeenCalledOnceWith([ - '/empty-registrar', - { nextUrl: dummyRoute.url }, - ]); - }); -}); diff --git a/console-webapp/src/app/registrar/registrar.guard.ts b/console-webapp/src/app/registrar/registrar.guard.ts deleted file mode 100644 index c79b59aa8e3..00000000000 --- a/console-webapp/src/app/registrar/registrar.guard.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; - -import { RegistrarService } from './registrar.service'; - -@Injectable({ - providedIn: 'root', -}) -export class RegistrarGuard { - constructor( - private router: Router, - private registrarService: RegistrarService - ) {} - - canActivate( - _: ActivatedRouteSnapshot, - state: RouterStateSnapshot - ): Promise | boolean { - if (this.registrarService.registrarId()) { - return true; - } - return this.router.navigate([ - `/empty-registrar`, - { nextUrl: state.url || '' }, - ]); - } -} diff --git a/console-webapp/src/app/registrar/registrar.service.spec.ts b/console-webapp/src/app/registrar/registrar.service.spec.ts index 49f1408993c..85c1ed95f34 100644 --- a/console-webapp/src/app/registrar/registrar.service.spec.ts +++ b/console-webapp/src/app/registrar/registrar.service.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/registrar/registrar.service.ts b/console-webapp/src/app/registrar/registrar.service.ts index 8beb662b50b..fe83506e29c 100644 --- a/console-webapp/src/app/registrar/registrar.service.ts +++ b/console-webapp/src/app/registrar/registrar.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ import { Injectable, computed, signal } from '@angular/core'; import { Observable, tap } from 'rxjs'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { BackendService } from '../shared/services/backend.service'; import { GlobalLoader, GlobalLoaderService, } from '../shared/services/globalLoader.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; export interface Address { street?: string[]; @@ -30,22 +30,26 @@ export interface Address { state?: string; } -export interface Registrar { +export interface WhoisRegistrarFields { + ianaIdentifier?: number; + icannReferralEmail?: string; + localizedAddress?: Address; + registrarId?: string; + url?: string; + whoisServer?: string; +} + +export interface Registrar extends WhoisRegistrarFields { allowedTlds?: string[]; + faxNumber?: string; billingAccountMap?: object; driveFolderId?: string; emailAddress?: string; - faxNumber?: string; - ianaIdentifier?: number; - icannReferralEmail?: string; ipAddressAllowList?: string[]; - localizedAddress?: Address; phoneNumber?: string; registrarId: string; registrarName: string; registryLockAllowed?: boolean; - url?: string; - whoisServer?: string; } @Injectable({ @@ -53,7 +57,181 @@ export interface Registrar { }) export class RegistrarService implements GlobalLoader { registrarId = signal(''); - registrars = signal([]); + registrars = signal([ + { + registrarId: 'larrytest2', + registrarName: 'larrytest2', + allowedTlds: ['com'], + ipAddressAllowList: [ + '123.123.123.123/32', + '1.0.0.1/32', + '1.0.0.4/32', + '1.0.0.5/32', + '1.0.0.6/32', + ], + localizedAddress: { + street: ['none'], + city: 'nowhere', + state: 'NA', + zip: '12345', + countryCode: 'us', + }, + emailAddress: 'larryruili@google.com', + ianaIdentifier: 43211, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'larryruili@google.com', + registryLockAllowed: false, + }, + { + registrarId: 'larrytest', + registrarName: 'larrytest', + allowedTlds: ['app', 'com', 'how', 'dev'], + ipAddressAllowList: [ + '123.123.123.123/32', + '1.0.0.1/32', + '1.0.0.2/32', + '1.0.0.3/32', + ], + localizedAddress: { + street: ['e-street'], + city: 'citysville', + state: 'NY', + zip: '12345', + countryCode: 'US', + }, + emailAddress: 'larryruili@google.com', + ianaIdentifier: 77788, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'larryruili@google.com', + registryLockAllowed: false, + }, + { + registrarId: 'charlestonroad22', + registrarName: 'CRR TEST STUFF', + allowedTlds: ['how-12.test'], + localizedAddress: { + street: ['a'], + city: 'b', + state: 'ee', + zip: '12345', + countryCode: 'dd', + }, + emailAddress: 'fake@fake.com', + ianaIdentifier: 4499, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'example@google.com', + registryLockAllowed: false, + }, + { + registrarId: 'NewRegistrar', + registrarName: 'NewRegistrar', + ipAddressAllowList: ['123.123.123.123/32', '1.0.0.1/32'], + localizedAddress: { + street: ['address details'], + city: 'Sunnyplace', + countryCode: 'US', + }, + ianaIdentifier: 12345, + billingAccountMap: { USD: '474184cc-ebf0-44d3-b622-269d819d3667' }, + icannReferralEmail: 'admin@registrar.com', + driveFolderId: '342', + registryLockAllowed: false, + }, + { + registrarId: 'guybentest', + registrarName: 'guyben test', + ipAddressAllowList: ['123.0.0.1/32'], + localizedAddress: { + street: ['XXX'], + city: 'XXX', + state: 'XX', + zip: '00000', + countryCode: 'XX', + }, + ianaIdentifier: 44444, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'icann@example.com', + registryLockAllowed: false, + }, + { + registrarId: 'jianglai-test3', + registrarName: 'Test registrar3', + localizedAddress: { + street: ['123 Main Street'], + city: 'New York', + state: 'New York', + zip: '11111', + countryCode: 'US', + }, + ianaIdentifier: 12346, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'test3@test.test', + registryLockAllowed: false, + }, + { + registrarId: 'jianglai-test', + registrarName: 'Test registrar', + localizedAddress: { + street: ['123 Main Street'], + city: 'New York', + state: 'New York', + zip: '11111', + countryCode: 'US', + }, + ianaIdentifier: 12345, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'test@test.test', + registryLockAllowed: false, + }, + { + registrarId: 'gbrodman', + registrarName: 'gbrodman', + allowedTlds: ['app', 'dev', 'test'], + localizedAddress: { + street: ['asdf'], + city: 'NYC', + state: 'NY', + zip: '10011', + countryCode: 'us', + }, + emailAddress: 'legina@google.com', + ianaIdentifier: 12387, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'test@test.test', + registryLockAllowed: true, + }, + { + registrarId: 'contacts-alpha', + registrarName: 'contacts-alpha', + ipAddressAllowList: ['123.123.123.123/32'], + localizedAddress: { + street: ['76 9th Avenue'], + city: 'New York', + state: 'NY', + zip: '10011', + countryCode: 'US', + }, + ianaIdentifier: 7788, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'weiminyu@google.com', + registryLockAllowed: false, + }, + { + registrarId: 'jianglai-test2', + registrarName: 'Test registrar2', + localizedAddress: { + street: ['123 Main Street'], + city: 'New York', + state: 'New York', + zip: '11111', + countryCode: 'US', + }, + ianaIdentifier: 12345, + billingAccountMap: { JPY: '123', USD: '456' }, + icannReferralEmail: 'test@test.test', + registryLockAllowed: false, + }, + ]); registrar = computed(() => this.registrars().find((r) => r.registrarId === this.registrarId()) ); @@ -83,6 +261,23 @@ export class RegistrarService implements GlobalLoader { ); } + saveRegistrar(registrar: Registrar) { + return this.backend.postRegistrar(registrar).pipe( + tap((registrar) => { + if (registrar) { + this.registrars.set( + this.registrars().map((r) => { + if (r.registrarId === registrar.registrarId) { + return registrar; + } + return r; + }) + ); + } + }) + ); + } + loadingTimeout() { this._snackBar.open('Timeout loading registrars'); } diff --git a/console-webapp/src/app/registrar/registrarDetails.component.html b/console-webapp/src/app/registrar/registrarDetails.component.html index c1c9a4df7ed..96b5e00106d 100644 --- a/console-webapp/src/app/registrar/registrarDetails.component.html +++ b/console-webapp/src/app/registrar/registrarDetails.component.html @@ -1,40 +1,96 @@ -
-

Edit Registrar: {{ registrarInEdit.registrarId }}

-
+
+

Registrars

+ +
+
+ +
+ @if(!inEdit) { + + + } +
+

{{ registrarInEdit.registrarId }}

+

+ {{ registrarInEdit.registrarName }} +

+ @if(inEdit) {
- - Registry Lock: - - True - False - - - - Onboarded TLDs: - - + + Registry Lock: + - {{ tld }} - - - - - - - - - + True + False + + +
+
+ + Onboarded TLDs: + + + {{ tld }} + + + + + +
+ + } @else { + + + + +

Registrar details

+
+ + @for (column of columns; track column.columnDef) { + + {{ column.header }} + + + + } +
+
+
+ }
diff --git a/console-webapp/src/app/registrar/registrarDetails.component.scss b/console-webapp/src/app/registrar/registrarDetails.component.scss index 132f0d566f3..4e4c59a181f 100644 --- a/console-webapp/src/app/registrar/registrarDetails.component.scss +++ b/console-webapp/src/app/registrar/registrarDetails.component.scss @@ -1,8 +1,19 @@ -.registrarDetails { - min-width: 30vw; - - &__input { - display: block; - margin-top: 0.5rem; +.console-app { + &__registrar-view { + &-controls { + display: flex; + align-items: center; + margin: 20px 0; + } + } + &__registrar-view-content { + max-width: 616px; + mat-divider:last-child { + display: none; + } + mat-form-field { + margin-bottom: 20px; + width: 100%; + } } } diff --git a/console-webapp/src/app/registrar/registrarDetails.component.ts b/console-webapp/src/app/registrar/registrarDetails.component.ts index 47b0aaf2bec..d9e1c162bad 100644 --- a/console-webapp/src/app/registrar/registrarDetails.component.ts +++ b/console-webapp/src/app/registrar/registrarDetails.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,38 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Registrar, RegistrarService } from './registrar.service'; import { MatChipInputEvent } from '@angular/material/chips'; -import { DialogBottomSheetContent } from '../shared/components/dialogBottomSheet.component'; - -type RegistrarDetailsParams = { - close: Function; - data: { - registrar: Registrar; - }; -}; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { columns } from './registrarsTable.component'; @Component({ selector: 'app-registrar-details', templateUrl: './registrarDetails.component.html', styleUrls: ['./registrarDetails.component.scss'], }) -export class RegistrarDetailsComponent implements DialogBottomSheetContent { +export class RegistrarDetailsComponent implements OnInit { + public static PATH = 'registrars/:id'; + inEdit: boolean = false; registrarInEdit!: Registrar; - params?: RegistrarDetailsParams; + columns = columns.filter((c) => !c.hiddenOnDetailsCard); + private subscription: any; - constructor(protected registrarService: RegistrarService) {} + constructor( + protected registrarService: RegistrarService, + private route: ActivatedRoute + ) {} - init(params: RegistrarDetailsParams) { - this.params = params; - this.registrarInEdit = JSON.parse( - JSON.stringify(this.params.data.registrar) - ); - } - - saveAndClose() { - this.params?.close(); + ngOnInit(): void { + this.subscription = this.route.paramMap.subscribe((params: ParamMap) => { + this.registrarInEdit = structuredClone( + this.registrarService + .registrars() + .filter((r) => r.registrarId === params.get('id'))[0] + ); + }); } addTLD(e: MatChipInputEvent) { @@ -58,4 +57,13 @@ export class RegistrarDetailsComponent implements DialogBottomSheetContent { (v) => v != tld ); } + + saveAndClose() { + this.registrarService.saveRegistrar(this.registrarInEdit); + this.inEdit = false; + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } } diff --git a/console-webapp/src/app/registrar/registrarSelector.component.html b/console-webapp/src/app/registrar/registrarSelector.component.html index 4b8cf525051..ff3bd6437c0 100644 --- a/console-webapp/src/app/registrar/registrarSelector.component.html +++ b/console-webapp/src/app/registrar/registrarSelector.component.html @@ -1,29 +1,28 @@
- - - - Registrar - - - {{ registrar.registrarId }} - - - - + Registrar + + + @for (registrarId of filteredOptions; track registrarId) { + {{ registrarId }} + } + +
diff --git a/console-webapp/src/app/registrar/registrarSelector.component.scss b/console-webapp/src/app/registrar/registrarSelector.component.scss index ca1ba08f4fb..fc2cf2a6bf3 100644 --- a/console-webapp/src/app/registrar/registrarSelector.component.scss +++ b/console-webapp/src/app/registrar/registrarSelector.component.scss @@ -3,16 +3,5 @@ display: flex; justify-content: center; align-items: baseline; - // Fix for angular v15 label issue - // https://github.com/angular/components/issues/26579 - .mat-mdc-floating-label { - display: inline !important; - } - .mat-mdc-select-arrow-wrapper { - transform: translateY(0) !important; - } - } - &__title { - margin-right: 1rem; } } diff --git a/console-webapp/src/app/registrar/registrarSelector.component.spec.ts b/console-webapp/src/app/registrar/registrarSelector.component.spec.ts index 0080b3142ff..297e88ac1fe 100644 --- a/console-webapp/src/app/registrar/registrarSelector.component.spec.ts +++ b/console-webapp/src/app/registrar/registrarSelector.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/registrar/registrarSelector.component.ts b/console-webapp/src/app/registrar/registrarSelector.component.ts index ea6d942a920..48b8c0acdbb 100644 --- a/console-webapp/src/app/registrar/registrarSelector.component.ts +++ b/console-webapp/src/app/registrar/registrarSelector.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,35 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, effect, signal } from '@angular/core'; import { RegistrarService } from './registrar.service'; -import { BreakpointObserver } from '@angular/cdk/layout'; -import { distinctUntilChanged } from 'rxjs'; - -const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)'; @Component({ selector: 'app-registrar-selector', templateUrl: './registrarSelector.component.html', styleUrls: ['./registrarSelector.component.scss'], }) -export class RegistrarSelectorComponent implements OnInit { - protected isMobile: boolean = false; - - readonly breakpoint$ = this.breakpointObserver - .observe([MOBILE_LAYOUT_BREAKPOINT]) - .pipe(distinctUntilChanged()); - - constructor( - protected registrarService: RegistrarService, - protected breakpointObserver: BreakpointObserver - ) {} +export class RegistrarSelectorComponent { + registrarInput = signal(''); + filteredOptions?: string[]; - ngOnInit(): void { - this.breakpoint$.subscribe(() => this.breakpointChanged()); + constructor(protected registrarService: RegistrarService) { + effect(() => { + const filterValue = this.registrarInput().toLowerCase(); + this.filteredOptions = this.registrarService + .registrars() + .map((r) => r.registrarId) + .filter((option) => option.toLowerCase().includes(filterValue)); + }); } - private breakpointChanged() { - this.isMobile = this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT); + onSelect(registrarId: string) { + this.registrarService.updateSelectedRegistrar(registrarId); + this.registrarInput.set(''); } } diff --git a/console-webapp/src/app/registrar/registrarsTable.component.html b/console-webapp/src/app/registrar/registrarsTable.component.html index 26feb6cd3cb..8922f06f5fb 100644 --- a/console-webapp/src/app/registrar/registrarsTable.component.html +++ b/console-webapp/src/app/registrar/registrarsTable.component.html @@ -1,4 +1,5 @@
+

Registrars

Search - - - - - - - -
diff --git a/console-webapp/src/app/registrar/registrarsTable.component.scss b/console-webapp/src/app/registrar/registrarsTable.component.scss index 21db917aefc..e8ab32ce11b 100644 --- a/console-webapp/src/app/registrar/registrarsTable.component.scss +++ b/console-webapp/src/app/registrar/registrarsTable.component.scss @@ -2,7 +2,6 @@ $min-width: 756px; &__registrars { - margin-top: 1.5rem; width: 100%; overflow: auto; } diff --git a/console-webapp/src/app/registrar/registrarsTable.component.spec.ts b/console-webapp/src/app/registrar/registrarsTable.component.spec.ts index 2ee085cbad7..5c1fcc68454 100644 --- a/console-webapp/src/app/registrar/registrarsTable.component.spec.ts +++ b/console-webapp/src/app/registrar/registrarsTable.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/registrar/registrarsTable.component.ts b/console-webapp/src/app/registrar/registrarsTable.component.ts index 9dfb2c38a2e..44630fd9bcb 100644 --- a/console-webapp/src/app/registrar/registrarsTable.component.ts +++ b/console-webapp/src/app/registrar/registrarsTable.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,8 +17,56 @@ import { Registrar, RegistrarService } from './registrar.service'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; -import { RegistrarDetailsComponent } from './registrarDetails.component'; -import { DialogBottomSheetWrapper } from '../shared/components/dialogBottomSheet.component'; +import { Router } from '@angular/router'; + +export const columns = [ + { + columnDef: 'registrarId', + header: 'Registrar Id', + cell: (record: Registrar) => `${record.registrarId || ''}`, + hiddenOnDetailsCard: true, + }, + { + columnDef: 'registrarName', + header: 'Name', + cell: (record: Registrar) => `${record.registrarName || ''}`, + hiddenOnDetailsCard: true, + }, + { + columnDef: 'allowedTlds', + header: 'TLDs', + cell: (record: Registrar) => `${(record.allowedTlds || []).join(', ')}`, + }, + { + columnDef: 'emailAddress', + header: 'Username', + cell: (record: Registrar) => `${record.emailAddress || ''}`, + }, + { + columnDef: 'ianaIdentifier', + header: 'IANA ID', + cell: (record: Registrar) => `${record.ianaIdentifier || ''}`, + }, + { + columnDef: 'billingAccountMap', + header: 'Billing Accounts', + cell: (record: Registrar) => + // @ts-ignore - completely legit line, but TS keeps complaining + `${Object.entries(record.billingAccountMap).reduce((acc, [key, val]) => { + return `${acc}${key}=${val}
`; + }, '')}`, + }, + { + columnDef: 'registryLockAllowed', + header: 'Registry Lock', + cell: (record: Registrar) => `${record.registryLockAllowed}`, + }, + { + columnDef: 'driveId', + header: 'Drive ID', + cell: (record: Registrar) => `${record.driveFolderId || ''}`, + }, +]; @Component({ selector: 'app-registrar', @@ -29,63 +77,17 @@ import { DialogBottomSheetWrapper } from '../shared/components/dialogBottomSheet export class RegistrarComponent { public static PATH = 'registrars'; dataSource: MatTableDataSource; - columns = [ - { - columnDef: 'registrarId', - header: 'Registrar Id', - cell: (record: Registrar) => `${record.registrarId || ''}`, - }, - { - columnDef: 'registrarName', - header: 'Name', - cell: (record: Registrar) => `${record.registrarName || ''}`, - }, - { - columnDef: 'allowedTlds', - header: 'TLDs', - cell: (record: Registrar) => `${(record.allowedTlds || []).join(', ')}`, - }, - { - columnDef: 'emailAddress', - header: 'Username', - cell: (record: Registrar) => `${record.emailAddress || ''}`, - }, - { - columnDef: 'ianaIdentifier', - header: 'IANA ID', - cell: (record: Registrar) => `${record.ianaIdentifier || ''}`, - }, - { - columnDef: 'billingAccountMap', - header: 'Billing Accounts', - cell: (record: Registrar) => - // @ts-ignore - completely legit line, but TS keeps complaining - `${Object.entries(record.billingAccountMap).reduce( - (acc, [key, val]) => { - return `${acc}${key}=${val}
`; - }, - '' - )}`, - }, - { - columnDef: 'registryLockAllowed', - header: 'Registry Lock', - cell: (record: Registrar) => `${record.registryLockAllowed}`, - }, - { - columnDef: 'driveId', - header: 'Drive ID', - cell: (record: Registrar) => `${record.driveFolderId || ''}`, - }, - ]; - displayedColumns = ['edit'].concat(this.columns.map((c) => c.columnDef)); + columns = columns; + + displayedColumns = this.columns.map((c) => c.columnDef); @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; - @ViewChild('registrarDetailsView') - detailsComponentWrapper!: DialogBottomSheetWrapper; - constructor(protected registrarService: RegistrarService) { + constructor( + protected registrarService: RegistrarService, + private router: Router + ) { this.dataSource = new MatTableDataSource( registrarService.registrars() ); @@ -96,12 +98,8 @@ export class RegistrarComponent { this.dataSource.sort = this.sort; } - openDetails(event: MouseEvent, registrar: Registrar) { - event.stopPropagation(); - this.detailsComponentWrapper.open( - RegistrarDetailsComponent, - { registrar } - ); + openDetails(registrarId: string) { + this.router.navigate(['registrars/', registrarId]); } applyFilter(event: Event) { diff --git a/console-webapp/src/app/resources/resources.component.html b/console-webapp/src/app/resources/resources.component.html new file mode 100644 index 00000000000..90a0bc7e12c --- /dev/null +++ b/console-webapp/src/app/resources/resources.component.html @@ -0,0 +1,15 @@ +

Resource

+
+
+
Technical resources
+ View on Google Drive +
+
+ +
+
diff --git a/console-webapp/src/app/resources/resources.component.scss b/console-webapp/src/app/resources/resources.component.scss new file mode 100644 index 00000000000..1e0ae4c17b5 --- /dev/null +++ b/console-webapp/src/app/resources/resources.component.scss @@ -0,0 +1,22 @@ +.console-app__resources { + display: flex; + flex-wrap: wrap-reverse; + > div { + flex: 1; + display: flex; + justify-content: center; + flex-direction: column; + text-align: left; + max-width: 300px; + min-width: 200px; + margin-top: 30px; + } + img { + aspect-ratio: 1 / 1; + width: 100%; + } + &-subhead { + font-size: 20px; + margin-bottom: 20px; + } +} diff --git a/console-webapp/src/app/home/widgets/resourcesWidget.component.ts b/console-webapp/src/app/resources/resources.component.ts similarity index 61% rename from console-webapp/src/app/home/widgets/resourcesWidget.component.ts rename to console-webapp/src/app/resources/resources.component.ts index 7aad3d3e7b7..f9d62e1adf3 100644 --- a/console-webapp/src/app/home/widgets/resourcesWidget.component.ts +++ b/console-webapp/src/app/resources/resources.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,12 +13,14 @@ // limitations under the License. import { Component } from '@angular/core'; -import { UserDataService } from 'src/app/shared/services/userData.service'; +import { UserDataService } from '../shared/services/userData.service'; @Component({ - selector: '[app-resources-widget]', - templateUrl: './resourcesWidget.component.html', + selector: 'app-resources', + templateUrl: './resources.component.html', + styleUrls: ['./resources.component.scss'], }) -export class ResourcesWidgetComponent { - constructor(public userDataService: UserDataService) {} +export class ResourcesComponent { + public static PATH = 'resources'; + constructor(protected userDataService: UserDataService) {} } diff --git a/console-webapp/src/app/settings/contact/contact.component.html b/console-webapp/src/app/settings/contact/contact.component.html index a034e69b64e..11801847224 100644 --- a/console-webapp/src/app/settings/contact/contact.component.html +++ b/console-webapp/src/app/settings/contact/contact.component.html @@ -1,48 +1,40 @@ -@if (loading) { -
- -
+@if(contactService.isContactDetailsView || contactService.isContactNewView) { + } @else { -
+
+ +
+
@if (contactService.contacts().length === 0) { -
- + apps_outage

No contacts found

- } @else { @for (group of groupedContacts(); track group.emailAddress) { -
-

{{ group.label }}s

- -
- - {{ contact.name }} -

{{ contact.phoneNumber }}

-

{{ contact.emailAddress }}

- - - - -
-
-
- } } -
- -
- + } @else { + + + {{ column.header }} + + + + + + }
} diff --git a/console-webapp/src/app/settings/contact/contact.component.scss b/console-webapp/src/app/settings/contact/contact.component.scss index 5a962213ccf..0f9456e5211 100644 --- a/console-webapp/src/app/settings/contact/contact.component.scss +++ b/console-webapp/src/app/settings/contact/contact.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,30 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -.contact { - &__cards-wrapper { - margin-top: 22px; - } - &__card { - width: 400px; - padding: 15px; - } - &__card-actions { - padding: 0; - } - &__loading { - margin: 2rem 0; - } - &__cards { - display: flex; - flex-wrap: wrap; - gap: 20px; - margin-top: 20px; - } - &__actions { - display: flex; - justify-content: flex-start; - margin: 2rem 0; +.console-app { + &__contacts { + margin-top: -10px; + width: 100%; + overflow: auto; + mat-table { + min-width: 800px; + } } &__empty-contacts { display: flex; @@ -49,22 +33,9 @@ font-size: 4rem; margin-top: 1.5rem; } -} -.contact-details { - &__input { - width: 100%; - } - &__group { - margin: 20px 0; - display: flex; - align-items: baseline; - } - &__group-content { - display: flex; - flex-wrap: wrap; - max-width: 450px; - * { - min-width: 200px; - } + &__contacts-controls { + position: absolute; + right: 20px; + top: 5px; } } diff --git a/console-webapp/src/app/settings/contact/contact.component.spec.ts b/console-webapp/src/app/settings/contact/contact.component.spec.ts index 26726ef51df..70a2be2d22f 100644 --- a/console-webapp/src/app/settings/contact/contact.component.spec.ts +++ b/console-webapp/src/app/settings/contact/contact.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/contact/contact.component.ts b/console-webapp/src/app/settings/contact/contact.component.ts index b5e7bef2fde..ba51f6f1349 100644 --- a/console-webapp/src/app/settings/contact/contact.component.ts +++ b/console-webapp/src/app/settings/contact/contact.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,101 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewChild, computed } from '@angular/core'; -import { Contact, ContactService } from './contact.service'; -import { HttpErrorResponse } from '@angular/common/http'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { Component, effect } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { take } from 'rxjs'; +import { RegistrarService } from 'src/app/registrar/registrar.service'; import { - DialogBottomSheetContent, - DialogBottomSheetWrapper, -} from 'src/app/shared/components/dialogBottomSheet.component'; - -enum Operations { - DELETE, - ADD, - UPDATE, -} - -interface GroupedContacts { - value: string; - label: string; - contacts: Array; -} - -type ContactDetailsParams = { - close: Function; - data: { - contact: Contact; - operation: Operations; - }; -}; - -const contactTypes: Array = [ - { value: 'ADMIN', label: 'Primary contact', contacts: [] }, - { value: 'ABUSE', label: 'Abuse contact', contacts: [] }, - { value: 'BILLING', label: 'Billing contact', contacts: [] }, - { value: 'LEGAL', label: 'Legal contact', contacts: [] }, - { value: 'MARKETING', label: 'Marketing contact', contacts: [] }, - { value: 'TECH', label: 'Technical contact', contacts: [] }, - { value: 'WHOIS', label: 'WHOIS-Inquiry contact', contacts: [] }, -]; - -@Component({ - selector: 'app-contact-details-dialog', - templateUrl: 'contactDetails.component.html', - styleUrls: ['./contact.component.scss'], -}) -export class ContactDetailsDialogComponent implements DialogBottomSheetContent { - contact?: Contact; - contactTypes = contactTypes; - contactIndex?: number; - - params?: ContactDetailsParams; - - constructor( - public contactService: ContactService, - private _snackBar: MatSnackBar - ) {} - - init(params: ContactDetailsParams) { - this.params = params; - this.contactIndex = this.contactService - .contacts() - .findIndex((c) => c === params.data.contact); - this.contact = JSON.parse(JSON.stringify(params.data.contact)); - } - - close() { - this.params?.close(); - } - - saveAndClose(e: SubmitEvent) { - e.preventDefault(); - if (!this.contact || this.contactIndex === undefined) return; - if (!(e.target as HTMLFormElement).checkValidity()) { - return; - } - const operation = this.params?.data.operation; - let operationObservable; - if (operation === Operations.ADD) { - operationObservable = this.contactService.addContact(this.contact); - } else if (operation === Operations.UPDATE) { - operationObservable = this.contactService.updateContact( - this.contactIndex, - this.contact - ); - } else { - throw 'Unknown operation type'; - } - - operationObservable.subscribe({ - complete: () => this.close(), - error: (err: HttpErrorResponse) => { - this._snackBar.open(err.error); - }, - }); - } -} + Contact, + ContactService, + ViewReadyContact, + contactTypeToViewReadyContact, +} from './contact.service'; @Component({ selector: 'app-contact', @@ -115,61 +30,67 @@ export class ContactDetailsDialogComponent implements DialogBottomSheetContent { }) export default class ContactComponent { public static PATH = 'contact'; - public groupedContacts = computed(() => { - return this.contactService.contacts().reduce((acc, contact) => { - contact.types.forEach((contactType) => { - acc - .find((group: GroupedContacts) => group.value === contactType) - ?.contacts.push(contact); - }); - return acc; - }, JSON.parse(JSON.stringify(contactTypes))); - }); - @ViewChild('contactDetailsWrapper') - detailsComponentWrapper!: DialogBottomSheetWrapper; + dataSource: MatTableDataSource = + new MatTableDataSource([]); + columns = [ + { + columnDef: 'name', + header: 'Name', + cell: (contact: ViewReadyContact) => ` +
+
${contact.name}
+
${contact.userFriendlyTypes.join( + ' • ' + )}
+
+ `, + }, + { + columnDef: 'emailAddress', + header: 'Email', + cell: (contact: ViewReadyContact) => `${contact.emailAddress || ''}`, + }, + { + columnDef: 'phoneNumber', + header: 'Phone', + cell: (contact: ViewReadyContact) => `${contact.phoneNumber || ''}`, + }, + { + columnDef: 'faxNumber', + header: 'Fax', + cell: (contact: ViewReadyContact) => `${contact.faxNumber || ''}`, + }, + ]; + displayedColumns = this.columns.map((c) => c.columnDef); - loading: boolean = false; constructor( public contactService: ContactService, - private _snackBar: MatSnackBar + private registrarService: RegistrarService ) { - // TODO: Refactor to registrarId service - this.loading = true; - this.contactService.fetchContacts().subscribe(() => { - this.loading = false; + effect(() => { + if (this.registrarService.registrarId()) { + this.contactService.isContactDetailsView = false; + this.contactService.isContactNewView = false; + this.contactService + .fetchContacts() + .pipe(take(1)) + .subscribe((contacts) => { + this.dataSource = new MatTableDataSource( + contacts.map(contactTypeToViewReadyContact) + ); + }); + } }); } - deleteContact(contact: Contact) { - if (confirm(`Please confirm contact ${contact.name} delete`)) { - this.contactService.deleteContact(contact).subscribe({ - error: (err: HttpErrorResponse) => { - this._snackBar.open(err.error); - }, - }); - } + openDetails(contact: Contact) { + this.contactService.setEditableContact(contact); + this.contactService.isContactDetailsView = true; } - openCreateNew(e: MouseEvent) { - const newContact: Contact = { - name: '', - phoneNumber: '', - emailAddress: '', - types: [contactTypes[0].value], - }; - this.openDetails(e, newContact, Operations.ADD); - } - - openDetails( - e: MouseEvent, - contact: Contact, - operation: Operations = Operations.UPDATE - ) { - e.preventDefault(); - this.detailsComponentWrapper.open( - ContactDetailsDialogComponent, - { contact, operation } - ); + openNewContact() { + this.contactService.setEditableContact(); + this.contactService.isContactNewView = true; } } diff --git a/console-webapp/src/app/settings/contact/contact.service.ts b/console-webapp/src/app/settings/contact/contact.service.ts index 617e2fab66c..de67b67ed59 100644 --- a/console-webapp/src/app/settings/contact/contact.service.ts +++ b/console-webapp/src/app/settings/contact/contact.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,43 +13,132 @@ // limitations under the License. import { Injectable, signal } from '@angular/core'; -import { Observable, tap } from 'rxjs'; +import { Observable, map, tap } from 'rxjs'; import { RegistrarService } from 'src/app/registrar/registrar.service'; import { BackendService } from 'src/app/shared/services/backend.service'; +type contactType = + | 'ADMIN' + | 'ABUSE' + | 'BILLING' + | 'LEGAL' + | 'MARKETING' + | 'TECH' + | 'WHOIS'; + +type contactTypesToUserFriendlyTypes = { [type in contactType]: string }; + +export const contactTypeToTextMap: contactTypesToUserFriendlyTypes = { + ADMIN: 'Primary contact', + ABUSE: 'Abuse contact', + BILLING: 'Billing contact', + LEGAL: 'Legal contact', + MARKETING: 'Marketing contact', + TECH: 'Technical contact', + WHOIS: 'WHOIS-Inquiry contact', +}; + +type UserFriendlyType = (typeof contactTypeToTextMap)[contactType]; + export interface Contact { name: string; - phoneNumber: string; + phoneNumber?: string; emailAddress: string; registrarId?: string; faxNumber?: string; - types: Array; + types: Array; visibleInWhoisAsAdmin?: boolean; visibleInWhoisAsTech?: boolean; visibleInDomainWhoisAsAbuse?: boolean; } +export interface ViewReadyContact extends Contact { + userFriendlyTypes: Array; +} + +const mockContacts: Array = [ + { + name: 'test', + emailAddress: 'test@google.com', + registrarId: 'contacts-alpha', + phoneNumber: '+1.2125650000', + types: ['ADMIN', 'BILLING'], + visibleInWhoisAsAdmin: true, + visibleInWhoisAsTech: true, + visibleInDomainWhoisAsAbuse: true, + }, + { + name: 'Test test', + emailAddress: 'testtest@google.com', + registrarId: 'contacts-alpha', + types: ['ADMIN'], + visibleInWhoisAsAdmin: false, + visibleInWhoisAsTech: false, + visibleInDomainWhoisAsAbuse: false, + }, + { + name: 'nobody', + emailAddress: 'nobody@google.com', + registrarId: 'contacts-alpha', + types: ['ADMIN'], + visibleInWhoisAsAdmin: false, + visibleInWhoisAsTech: false, + visibleInDomainWhoisAsAbuse: false, + }, +]; + +export function contactTypeToViewReadyContact(c: Contact): ViewReadyContact { + return { + ...c, + userFriendlyTypes: c.types?.map((cType) => contactTypeToTextMap[cType]), + }; +} + @Injectable({ providedIn: 'root', }) export class ContactService { - contacts = signal([]); + contacts = signal([]); + contactInEdit!: ViewReadyContact; + isContactDetailsView: boolean = false; + isContactNewView: boolean = false; constructor( private backend: BackendService, private registrarService: RegistrarService ) {} - // TODO: Come up with a better handling for registrarId + setEditableContact(contact?: Contact) { + this.contactInEdit = contactTypeToViewReadyContact( + contact + ? contact + : { + emailAddress: '', + name: '', + types: ['ADMIN'], + faxNumber: '', + phoneNumber: '', + registrarId: '', + } + ); + } + fetchContacts(): Observable { return this.backend.getContacts(this.registrarService.registrarId()).pipe( - tap((contacts = []) => { - this.contacts.set(contacts); + // TODO: REMOVE mockContacts + map((contacts) => { + if (!contacts) { + return mockContacts; + } + return contacts; + }), + tap((contacts) => { + this.contacts.set(contacts.map(contactTypeToViewReadyContact)); }) ); } - saveContacts(contacts: Contact[]): Observable { + saveContacts(contacts: ViewReadyContact[]): Observable { return this.backend .postContacts(this.registrarService.registrarId(), contacts) .pipe( @@ -59,19 +148,19 @@ export class ContactService { ); } - updateContact(index: number, contact: Contact) { + updateContact(index: number, contact: ViewReadyContact) { const newContacts = this.contacts().map((c, i) => i === index ? contact : c ); return this.saveContacts(newContacts); } - addContact(contact: Contact) { + addContact(contact: ViewReadyContact) { const newContacts = this.contacts().concat([contact]); return this.saveContacts(newContacts); } - deleteContact(contact: Contact) { + deleteContact(contact: ViewReadyContact) { const newContacts = this.contacts().filter((c) => c !== contact); return this.saveContacts(newContacts); } diff --git a/console-webapp/src/app/settings/contact/contactDetails.component.html b/console-webapp/src/app/settings/contact/contactDetails.component.html index 7b024804601..e1147a7b1fa 100644 --- a/console-webapp/src/app/settings/contact/contactDetails.component.html +++ b/console-webapp/src/app/settings/contact/contactDetails.component.html @@ -1,80 +1,84 @@ -

Contact details

-
-
-

- - Name: - - -

+
+
+ +
+ @if(!isEditing && !contactService.isContactNewView) { + + + } +
-

- - Primary account email: - - -

+ @if(isEditing || contactService.isContactNewView) { +

Contact Details

+ + + Name: + + -

- - Phone: - - -

+ + Primary account email: + + -

- - Fax: - - -

+ + Phone: + + + + + Phone: + + -
- -
- - {{ contactType.label }} - -
+

Contact Type

+

errorRequired to select at least one

+
+ + {{ contactType.value }} +
+

WHOIS Preferences

Show in Registrar WHOIS record as admin contact @@ -82,7 +86,7 @@

Contact details

Show in Registrar WHOIS record as technical contact @@ -90,15 +94,87 @@

Contact details

Show Phone and Email in Domain WHOIS Record as registrar abuse contact (per CL&D requirements)
- - - - + + } @else { +

{{ contactService.contactInEdit.name }}

+ + + + +

Contact details

+
+ + + Email + {{ + contactService.contactInEdit.emailAddress + }} + + + + Phone + {{ + contactService.contactInEdit.phoneNumber + }} + + + + Fax + {{ + contactService.contactInEdit.faxNumber + }} + + + + Type: + {{ + contactService.contactInEdit.userFriendlyTypes + }} + +
+
+
+ + + + +

WHOIS Preferences

+
+ + + Show in Registrar WHOIS record as admin contact + Show in Registrar WHOIS record as technical contact + Show Phone and Email in Domain WHOIS Record as registrar abuse + contact (per CL&D requirements) + +
+
+
+ }
diff --git a/console-webapp/src/app/settings/contact/contactDetails.component.scss b/console-webapp/src/app/settings/contact/contactDetails.component.scss new file mode 100644 index 00000000000..4f376bd615a --- /dev/null +++ b/console-webapp/src/app/settings/contact/contactDetails.component.scss @@ -0,0 +1,23 @@ +.console-app__contact { + max-width: 616px; + + &-submit { + margin: 30px 0; + } + + &-controls { + display: flex; + align-items: center; + margin: 20px 0; + } + + mat-card { + margin-bottom: 30px; + } + + mat-form-field { + display: block; + width: 100%; + margin-bottom: 30px; + } +} diff --git a/console-webapp/src/app/settings/contact/contactDetails.component.ts b/console-webapp/src/app/settings/contact/contactDetails.component.ts new file mode 100644 index 00000000000..aae9af894be --- /dev/null +++ b/console-webapp/src/app/settings/contact/contactDetails.component.ts @@ -0,0 +1,64 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { HttpErrorResponse } from '@angular/common/http'; +import { Component } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { first } from 'rxjs'; +import { ContactService, contactTypeToTextMap } from './contact.service'; + +@Component({ + selector: 'app-contact-details', + templateUrl: './contactDetails.component.html', + styleUrls: ['./contactDetails.component.scss'], +}) +export class ContactDetailsComponent { + protected contactTypeToTextMap = contactTypeToTextMap; + isEditing: boolean = false; + + constructor( + protected contactService: ContactService, + private _snackBar: MatSnackBar + ) {} + + deleteContact() { + if ( + confirm( + `Please confirm contact ${this.contactService.contactInEdit.name} delete` + ) + ) { + this.contactService + .deleteContact(this.contactService.contactInEdit) + .pipe(first()) + .subscribe({ + error: (err: HttpErrorResponse) => { + this._snackBar.open(err.error); + }, + }); + } + } + + goBack() { + if (this.isEditing) { + this.isEditing = false; + } else { + this.contactService.isContactNewView = false; + this.contactService.isContactDetailsView = false; + } + } + + save(e: SubmitEvent) { + e.preventDefault(); + } +} diff --git a/console-webapp/src/app/settings/security/security.component.html b/console-webapp/src/app/settings/security/security.component.html index a517e8eb4b9..1acdbfd875d 100644 --- a/console-webapp/src/app/settings/security/security.component.html +++ b/console-webapp/src/app/settings/security/security.component.html @@ -1,98 +1,78 @@ -
- -
-
-
-
-

IP Allowlist

-

- Restrict access to EPP production servers to the following IP/IPv6 - addresses, or ranges like 1.1.1.0/24 -

-
-
-
-
- - +@if(securityService.isEditingSecurity) { + +} @else { +
+ + + + + +
+

IP Allowlist

- -
-
- -
-
-
-
-

SSL Certificate

-

X.509 PEM certificate for EPP production access.

-
-
- -
-
-
-
-

Failover SSL Certificate

-

X.509 PEM backup certificate for EPP Production Access.

-
-
- -
-
-
- - - - - - - -
+
+ + + Restrict access to EPP production servers to the following IP/IPv6 + addresses, or ranges like 1.1.1.0/24 + + + @for (item of dataSource.ipAddressAllowList; track item.value) { + + {{ item.value }} + + + } + + + + + +

SSL Certificate

+
+ + X.509 PEM certificate for EPP production access + + + + {{ + dataSource.clientCertificate || "No client certificate on file." + }} + + + + + + +

Failover SSL Certificate

+
+ + X.509 PEM backup certificate for EPP production access + + + + {{ + dataSource.failoverClientCertificate || + "No failover certificate on file." + }} + + + + +
+} diff --git a/console-webapp/src/app/settings/security/security.component.scss b/console-webapp/src/app/settings/security/security.component.scss index 674ae37d671..745b5822147 100644 --- a/console-webapp/src/app/settings/security/security.component.scss +++ b/console-webapp/src/app/settings/security/security.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,41 +13,13 @@ // limitations under the License. .settings-security { - margin-top: 1.5rem; - h1 { - margin: 0; + max-width: 616px; + mat-card { + margin-bottom: 40px; } - &__ipRecord { - margin-bottom: 1rem; - } - &__section { - display: flex; - align-items: stretch; - margin-bottom: 3rem; - flex-wrap: wrap; - } - &__section-description { - flex: 1; - min-width: 300px; - } - &__section-form { - flex: 1; - min-width: 300px; - textarea { - min-height: 100%; - min-width: 100%; - box-sizing: border-box; - min-height: 100px; - } - } - &__actions { + &__section-header { display: flex; - justify-content: flex-end; - button { - margin-left: 20px; - } - } - &__loading { - margin: 2rem 0; + justify-content: space-between; + align-items: center; } } diff --git a/console-webapp/src/app/settings/security/security.component.spec.ts b/console-webapp/src/app/settings/security/security.component.spec.ts index 468d3a25aff..d26b93b2d94 100644 --- a/console-webapp/src/app/settings/security/security.component.spec.ts +++ b/console-webapp/src/app/settings/security/security.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/security/security.component.ts b/console-webapp/src/app/settings/security/security.component.ts index d9d35262745..770f5c56777 100644 --- a/console-webapp/src/app/settings/security/security.component.ts +++ b/console-webapp/src/app/settings/security/security.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,70 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, effect } from '@angular/core'; +import { RegistrarService } from 'src/app/registrar/registrar.service'; import { SecurityService, SecuritySettings, apiToUiConverter, } from './security.service'; -import { HttpErrorResponse } from '@angular/common/http'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { RegistrarService } from 'src/app/registrar/registrar.service'; @Component({ selector: 'app-security', templateUrl: './security.component.html', styleUrls: ['./security.component.scss'], - providers: [SecurityService], }) export default class SecurityComponent { public static PATH = 'security'; - - loading: boolean = false; - inEdit: boolean = false; dataSource: SecuritySettings = {}; - constructor( public securityService: SecurityService, - private _snackBar: MatSnackBar, public registrarService: RegistrarService ) { - this.dataSource = apiToUiConverter(this.registrarService.registrar()); - } - - enableEdit() { - this.inEdit = true; - } - - cancel() { - this.inEdit = false; - this.resetDataSource(); - } - - createIpEntry() { - this.dataSource.ipAddressAllowList?.push({ value: '' }); - } - - save() { - this.loading = true; - this.securityService.saveChanges(this.dataSource).subscribe({ - complete: () => { - this.loading = false; - this.resetDataSource(); - }, - error: (err: HttpErrorResponse) => { - this._snackBar.open(err.error); - }, + effect(() => { + if (this.registrarService.registrar()) { + this.dataSource = apiToUiConverter(this.registrarService.registrar()); + this.securityService.isEditingSecurity = false; + } }); - this.cancel(); - } - - removeIpEntry(index: number) { - this.dataSource.ipAddressAllowList = - this.dataSource.ipAddressAllowList?.filter((_, i) => i != index); } - resetDataSource() { - this.dataSource = apiToUiConverter(this.registrarService.registrar()); + editSecurity() { + this.securityService.isEditingSecurity = true; } } diff --git a/console-webapp/src/app/settings/security/security.service.spec.ts b/console-webapp/src/app/settings/security/security.service.spec.ts index 578423e78bf..c71354a28ff 100644 --- a/console-webapp/src/app/settings/security/security.service.spec.ts +++ b/console-webapp/src/app/settings/security/security.service.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/security/security.service.ts b/console-webapp/src/app/settings/security/security.service.ts index a6c05798095..16e96b298ce 100644 --- a/console-webapp/src/app/settings/security/security.service.ts +++ b/console-webapp/src/app/settings/security/security.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { switchMap } from 'rxjs'; import { RegistrarService } from 'src/app/registrar/registrar.service'; import { BackendService } from 'src/app/shared/services/backend.service'; -interface ipAllowListItem { +export interface ipAllowListItem { value: string; } export interface SecuritySettings { @@ -52,9 +52,12 @@ export function uiToApiConverter( }); } -@Injectable() +@Injectable({ + providedIn: 'root', +}) export class SecurityService { securitySettings: SecuritySettings = {}; + isEditingSecurity: boolean = false; constructor( private backend: BackendService, diff --git a/console-webapp/src/app/settings/security/securityEdit.component.html b/console-webapp/src/app/settings/security/securityEdit.component.html new file mode 100644 index 00000000000..399bce2e853 --- /dev/null +++ b/console-webapp/src/app/settings/security/securityEdit.component.html @@ -0,0 +1,51 @@ +
+

IP Allowlist

+

+ Restrict access to EPP production servers to the following IP/IPv6 + addresses, or ranges like 1.1.1.0/24 +

+
+ @for (ip of dataSource.ipAddressAllowList; track ip.value) { +
+ + + + +
+ } + + +

SSL Certificate

+

X.509 PEM certificate for EPP production access.

+ + + +

Failover SSL Certificate

+

X.509 PEM backup certificate for EPP Production Access.

+ + + +
+
diff --git a/console-webapp/src/app/home/widgets/eppWidget.component.ts b/console-webapp/src/app/settings/security/securityEdit.component.scss similarity index 64% rename from console-webapp/src/app/home/widgets/eppWidget.component.ts rename to console-webapp/src/app/settings/security/securityEdit.component.scss index 36bb7b0f8ab..6b468f303b6 100644 --- a/console-webapp/src/app/home/widgets/eppWidget.component.ts +++ b/console-webapp/src/app/settings/security/securityEdit.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; - -@Component({ - selector: '[app-epp-widget]', - templateUrl: './eppWidget.component.html', -}) -export class EppWidgetComponent { - constructor() {} +.settings-security__edit { + max-width: 616px; + h1 { + margin-top: 30px; + } + mat-form-field { + width: 100%; + flex: 1; + } + &-ip { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; + } } diff --git a/console-webapp/src/app/settings/security/securityEdit.component.ts b/console-webapp/src/app/settings/security/securityEdit.component.ts new file mode 100644 index 00000000000..e2f8deb47e3 --- /dev/null +++ b/console-webapp/src/app/settings/security/securityEdit.component.ts @@ -0,0 +1,65 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { HttpErrorResponse } from '@angular/common/http'; +import { Component } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { RegistrarService } from 'src/app/registrar/registrar.service'; +import { + SecurityService, + SecuritySettings, + apiToUiConverter, + ipAllowListItem, +} from './security.service'; + +@Component({ + selector: 'app-security-edit', + templateUrl: './securityEdit.component.html', + styleUrls: ['./securityEdit.component.scss'], +}) +export default class SecurityEditComponent { + dataSource: SecuritySettings = {}; + + constructor( + public securityService: SecurityService, + private _snackBar: MatSnackBar, + public registrarService: RegistrarService + ) { + this.dataSource = apiToUiConverter(registrarService.registrar()); + } + + createIpEntry() { + this.dataSource.ipAddressAllowList?.push({ value: '' }); + } + + save() { + this.securityService.saveChanges(this.dataSource).subscribe({ + complete: () => { + this.goBack(); + }, + error: (err: HttpErrorResponse) => { + this._snackBar.open(err.error); + }, + }); + } + + goBack() { + this.securityService.isEditingSecurity = false; + } + + removeIpEntry(ip: ipAllowListItem) { + this.dataSource.ipAddressAllowList = + this.dataSource.ipAddressAllowList?.filter((item) => item !== ip); + } +} diff --git a/console-webapp/src/app/settings/settings.component.html b/console-webapp/src/app/settings/settings.component.html index 540e27eeff5..13b0a9f25ec 100644 --- a/console-webapp/src/app/settings/settings.component.html +++ b/console-webapp/src/app/settings/settings.component.html @@ -1,24 +1,25 @@ -
-

Settings

- + + + + +
+ diff --git a/console-webapp/src/app/settings/settings.component.scss b/console-webapp/src/app/settings/settings.component.scss index afae0f19364..a530022eb51 100644 --- a/console-webapp/src/app/settings/settings.component.scss +++ b/console-webapp/src/app/settings/settings.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,9 @@ // limitations under the License. .console-settings { + > mat-divider { + margin-bottom: 40px; + } .mdc-tab { &.active-link { border-bottom: 2px solid var(--primary); diff --git a/console-webapp/src/app/settings/settings.component.spec.ts b/console-webapp/src/app/settings/settings.component.spec.ts index 128d15192c0..976bc584549 100644 --- a/console-webapp/src/app/settings/settings.component.spec.ts +++ b/console-webapp/src/app/settings/settings.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/settings.component.ts b/console-webapp/src/app/settings/settings.component.ts index 5e7cdbd6724..ad0c77d4845 100644 --- a/console-webapp/src/app/settings/settings.component.ts +++ b/console-webapp/src/app/settings/settings.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,4 +22,10 @@ import { Component, ViewEncapsulation } from '@angular/core'; }) export class SettingsComponent { public static PATH = 'settings'; + + public static matchesUrl(url: string): boolean { + return url[0] === '/' + ? url.startsWith(`/${this.PATH}`) + : url.startsWith(this.PATH); + } } diff --git a/console-webapp/src/app/settings/users/users.component.scss b/console-webapp/src/app/settings/users/users.component.scss index 5b5b6437117..c5e3ba12240 100644 --- a/console-webapp/src/app/settings/users/users.component.scss +++ b/console-webapp/src/app/settings/users/users.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/users/users.component.spec.ts b/console-webapp/src/app/settings/users/users.component.spec.ts index 95b89f54c8a..6ab79ebe60d 100644 --- a/console-webapp/src/app/settings/users/users.component.spec.ts +++ b/console-webapp/src/app/settings/users/users.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/users/users.component.ts b/console-webapp/src/app/settings/users/users.component.ts index 5828ce4ee7a..6c97691b6bf 100644 --- a/console-webapp/src/app/settings/users/users.component.ts +++ b/console-webapp/src/app/settings/users/users.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/whois/whois.component.html b/console-webapp/src/app/settings/whois/whois.component.html index c14215e5c6f..8bb6a7310d0 100644 --- a/console-webapp/src/app/settings/whois/whois.component.html +++ b/console-webapp/src/app/settings/whois/whois.component.html @@ -1,244 +1,191 @@ -
-

WHOIS settings

-

- General registrar information for your WHOIS record. This information is - always visible in WHOIS. -

-
- +
+ @if(inEdit) { + + } + +
+ + General registrar information for your WHOIS record. This information is + always visible in WHOIS. + +
+ @if(!inEdit) { + + }
-
-
-

Name:

-
-
- - - -
-
-
-
-

IANA Identifier:

-
-
- - - -
-
-
-
-

ICANN Referral Email:

-
-
- - - -
-
-
-
-

WHOIS server:

-
-
- - - -
-
-
-
-

Referral URL:

-
-
- - - -
-
-
-
-

Email:

-
-
- - - -
-
-
-
-

Phone::

-
-
- - - -
-
-
-
-

Fax:

-
-
- - - -
-
-
-
-
-

Address Line 1:

-
-
- - - -
-
-
-
-

City:

-
-
- - - -
-
-
-
-
-
-

Address Line 2:

-
-
- - - -
-
-
-
-

State/Region:

-
-
- - - -
-
-
-
-
-
-

Address Line 3:

-
-
- - - -
-
-
-
-

Country Code:

-
-
- - - -
-
-
-
- - - - - - - + + @if(inEdit) { +
+

Personal info

+ + + Email: + + + + + Phone: + + + + + Fax: + + + + + Street Address: + + + + + City: + + + + + State or Province: + + + + + Country: + + + + + Postal code: + + + +

Technical info

+ + + WHOIS server: + + + + + Referral URL: + + + +
+ } @else { + + + + +

Personal Info

+
+ + + Company name + {{ + registrar.registrarName + }} + + + + Referral email + {{ + registrar.emailAddress + }} + + + + Phone + {{ + registrar.phoneNumber + }} + + + + Fax + {{ registrar.faxNumber }} + + + + Address + {{ formattedAddress() }} + +
+
+
+ + + + + +

Technical Info

+
+ + + IANA Identifier + {{ + registrar.ianaIdentifier + }} + + + +
+ ICANN Referral Email + {{ + registrar.icannReferralEmail + }} +
+
+ + + WHOIS server + {{ + registrar.whoisServer + }} + + + + Referral URL + {{ registrar.url }} + +
+
+
+ + }
diff --git a/console-webapp/src/app/settings/whois/whois.component.scss b/console-webapp/src/app/settings/whois/whois.component.scss index 6d08212c888..dfae4d9d03e 100644 --- a/console-webapp/src/app/settings/whois/whois.component.scss +++ b/console-webapp/src/app/settings/whois/whois.component.scss @@ -1,59 +1,23 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +.console-app__whois { + max-width: 616px; -.settings-whois { - margin-top: 1.5rem; - &__section { + &-controls { display: flex; - flex-wrap: wrap; - margin-bottom: 10px; - min-width: 400px; - } - &__section-address { - display: flex; - flex-wrap: wrap; - margin-bottom: 5px; - min-width: 400px; - width: 50%; - max-width: 50%; - } - &__section-description { - display: inline-block; - margin-block-start: 1em; - width: 160px; - } - &__section-form { - display: inline-block; - width: 70%; - mat-form-field { - width: 90%; - min-width: 300px; - } - input:disabled { - border: 0; + align-items: center; + gap: 1rem; + margin: 20px 0; + button { + flex-shrink: 0; } } - &__loading { - margin: 2rem 0; + + mat-card { + margin-bottom: 30px; } - &__actions { - margin-top: 50px; - display: flex; - justify-content: flex-end; - margin-right: 50px; - button { - margin-left: 20px; - } + + mat-form-field { + display: block; + width: 100%; + margin-bottom: 30px; } } diff --git a/console-webapp/src/app/settings/whois/whois.component.spec.ts b/console-webapp/src/app/settings/whois/whois.component.spec.ts index 994b44777fe..00434b3d650 100644 --- a/console-webapp/src/app/settings/whois/whois.component.spec.ts +++ b/console-webapp/src/app/settings/whois/whois.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/settings/whois/whois.component.ts b/console-webapp/src/app/settings/whois/whois.component.ts index a0891a6b206..59f60a9b7ba 100644 --- a/console-webapp/src/app/settings/whois/whois.component.ts +++ b/console-webapp/src/app/settings/whois/whois.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ // limitations under the License. import { HttpErrorResponse } from '@angular/common/http'; -import { Component } from '@angular/core'; +import { Component, computed, effect } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Registrar, @@ -32,28 +32,39 @@ export default class WhoisComponent { public static PATH = 'whois'; loading = false; inEdit = false; - registrar: Registrar; + registrar!: Registrar; + formattedAddress = computed(() => { + let result = ''; + const registrar = this.registrarService.registrar(); + if (registrar?.localizedAddress?.street) { + result += `${registrar?.localizedAddress?.street?.join(' ')} `; + } + if (registrar?.localizedAddress?.state) { + result += `${registrar?.localizedAddress?.state} `; + } + if (registrar?.localizedAddress?.street) { + result += registrar?.localizedAddress?.countryCode; + } + return result; + }); constructor( public whoisService: WhoisService, public registrarService: RegistrarService, private _snackBar: MatSnackBar ) { - this.registrar = JSON.parse( - JSON.stringify(this.registrarService.registrar) - ); + effect(() => { + if (this.registrarService.registrar()) this.resetDataSource(); + }); } enableEdit() { this.inEdit = true; } - cancel() { - this.inEdit = false; - this.resetDataSource(); - } - save() { + if (!this.registrar) return; + this.loading = true; this.whoisService.saveChanges(this.registrar).subscribe({ complete: () => { @@ -65,12 +76,13 @@ export default class WhoisComponent { this.loading = false; }, }); - this.cancel(); } resetDataSource() { - this.registrar = JSON.parse( - JSON.stringify(this.registrarService.registrar) - ); + const maybeRegistrar = this.registrarService.registrar(); + this.inEdit = false; + if (maybeRegistrar) { + this.registrar = structuredClone(maybeRegistrar); + } } } diff --git a/console-webapp/src/app/settings/whois/whois.service.ts b/console-webapp/src/app/settings/whois/whois.service.ts index 54e99202f86..a85797253f0 100644 --- a/console-webapp/src/app/settings/whois/whois.service.ts +++ b/console-webapp/src/app/settings/whois/whois.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,18 +14,12 @@ import { Injectable } from '@angular/core'; import { switchMap } from 'rxjs'; -import { Address, RegistrarService } from 'src/app/registrar/registrar.service'; +import { + RegistrarService, + WhoisRegistrarFields, +} from 'src/app/registrar/registrar.service'; import { BackendService } from 'src/app/shared/services/backend.service'; -export interface WhoisRegistrarFields { - ianaIdentifier?: number; - icannReferralEmail?: string; - localizedAddress?: Address; - registrarId?: string; - url?: string; - whoisServer?: string; -} - @Injectable() export class WhoisService { whoisRegistrarFields: WhoisRegistrarFields = {}; diff --git a/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts b/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts deleted file mode 100644 index 73ad95afd18..00000000000 --- a/console-webapp/src/app/shared/components/dialogBottomSheet.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { BreakpointObserver } from '@angular/cdk/layout'; -import { ComponentType } from '@angular/cdk/portal'; -import { Component } from '@angular/core'; -import { - MatBottomSheet, - MatBottomSheetRef, -} from '@angular/material/bottom-sheet'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; - -const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 599px)'; - -export interface DialogBottomSheetContent { - init(data: Object): void; -} - -/** - * Wraps up a child component in an Angular Material Dalog for desktop or a Bottom Sheet - * component for mobile depending on a screen resolution, with Breaking Point being 599px. - * Child component is required to implement @see DialogBottomSheetContent interface - */ -@Component({ - selector: 'app-dialog-bottom-sheet-wrapper', - template: '', -}) -export class DialogBottomSheetWrapper { - private elementRef?: MatBottomSheetRef | MatDialogRef; - - constructor( - private dialog: MatDialog, - private bottomSheet: MatBottomSheet, - protected breakpointObserver: BreakpointObserver - ) {} - - open( - component: ComponentType, - data: any - ) { - const config = { data, close: () => this.close() }; - if (this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT)) { - this.elementRef = this.bottomSheet.open(component); - this.elementRef.instance.init(config); - } else { - this.elementRef = this.dialog.open(component); - this.elementRef.componentInstance.init(config); - } - } - - close() { - if (this.elementRef instanceof MatBottomSheetRef) { - this.elementRef.dismiss(); - } else if (this.elementRef instanceof MatDialogRef) { - this.elementRef.close(); - } - } -} diff --git a/console-webapp/src/app/shared/components/notifications/notifications.component.html b/console-webapp/src/app/shared/components/notifications/notifications.component.html new file mode 100644 index 00000000000..29c7fa90279 --- /dev/null +++ b/console-webapp/src/app/shared/components/notifications/notifications.component.html @@ -0,0 +1,16 @@ +
+ @for (notification of mockNotifications; track notification.id) { +
+
+ +

{{ notification.text }}

+
+ + +
+ } +
diff --git a/console-webapp/src/app/shared/components/notifications/notifications.component.scss b/console-webapp/src/app/shared/components/notifications/notifications.component.scss new file mode 100644 index 00000000000..06ad7ecc509 --- /dev/null +++ b/console-webapp/src/app/shared/components/notifications/notifications.component.scss @@ -0,0 +1,23 @@ +.console-app { + &__notifications { + margin-bottom: 40px; + } + &__notification { + padding: 20px; + background-color: var(--lightest); + border-radius: 10px; + margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; + &-content { + display: flex; + align-items: center; + gap: 10px; + } + h4 { + margin: 0; + padding: 0; + } + } +} diff --git a/console-webapp/src/app/home/widgets/tldsWidget.component.ts b/console-webapp/src/app/shared/components/notifications/notifications.component.ts similarity index 56% rename from console-webapp/src/app/home/widgets/tldsWidget.component.ts rename to console-webapp/src/app/shared/components/notifications/notifications.component.ts index 8f560632a30..69b89be3278 100644 --- a/console-webapp/src/app/home/widgets/tldsWidget.component.ts +++ b/console-webapp/src/app/shared/components/notifications/notifications.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,10 +14,24 @@ import { Component } from '@angular/core'; +interface Notification { + id: string; + date: Date; + text: string; +} + @Component({ - selector: '[app-tlds-widget]', - templateUrl: './tldsWidget.component.html', + selector: 'app-notifications', + templateUrl: './notifications.component.html', + styleUrls: ['./notifications.component.scss'], }) -export class TldsWidgetComponent { +export class NotificationsComponent { + protected mockNotifications: Notification[] = [ + { + id: '0', + date: new Date(), + text: 'Registry Downtime Planned on June 9, 2024 due to maintenance.', + }, + ]; constructor() {} } diff --git a/console-webapp/src/app/registrar/emptyRegistrar.component.scss b/console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.scss similarity index 100% rename from console-webapp/src/app/registrar/emptyRegistrar.component.scss rename to console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.scss diff --git a/console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.ts b/console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.ts new file mode 100644 index 00000000000..87ebb4e51fe --- /dev/null +++ b/console-webapp/src/app/shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component.ts @@ -0,0 +1,37 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { RegistrarService } from 'src/app/registrar/registrar.service'; + +@Component({ + selector: 'app-selected-registrar-wrapper', + styleUrls: ['./selectedRegistrarWrapper.component.scss'], + template: ` + @if(registrarService.registrarId()){ + + } @else { +
+

+ block +

+

No registrar selected

+

Please select a registrar

+
+ } + `, +}) +export class SelectedRegistrarWrapper { + constructor(protected registrarService: RegistrarService) {} +} diff --git a/console-webapp/src/app/shared/directives/locationBack.directive.ts b/console-webapp/src/app/shared/directives/locationBack.directive.ts new file mode 100644 index 00000000000..daf604f6169 --- /dev/null +++ b/console-webapp/src/app/shared/directives/locationBack.directive.ts @@ -0,0 +1,14 @@ +import { Directive, HostListener } from '@angular/core'; +import { Location } from '@angular/common'; + +@Directive({ + selector: '[backButton]', +}) +export class LocationBackDirective { + constructor(private location: Location) {} + + @HostListener('click') + onClick() { + this.location.back(); + } +} diff --git a/console-webapp/src/app/shared/services/backend.service.ts b/console-webapp/src/app/shared/services/backend.service.ts index c6c977a6abe..053f2376fba 100644 --- a/console-webapp/src/app/shared/services/backend.service.ts +++ b/console-webapp/src/app/shared/services/backend.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,14 +14,16 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { catchError, Observable, of } from 'rxjs'; +import { Observable, catchError, of } from 'rxjs'; import { SecuritySettingsBackendModel } from 'src/app/settings/security/security.service'; +import { DomainListResult } from 'src/app/domains/domainList.service'; +import { + Registrar, + WhoisRegistrarFields, +} from '../../registrar/registrar.service'; import { Contact } from '../../settings/contact/contact.service'; -import { Registrar } from '../../registrar/registrar.service'; import { UserData } from './userData.service'; -import { WhoisRegistrarFields } from 'src/app/settings/whois/whois.service'; -import { DomainListResult } from 'src/app/domains/domainList.service'; @Injectable() export class BackendService { @@ -99,6 +101,12 @@ export class BackendService { .pipe(catchError((err) => this.errorCatcher(err))); } + postRegistrar(registrar: Registrar): Observable { + return this.http + .post('/console-api/registrar', registrar) + .pipe(catchError((err) => this.errorCatcher(err))); + } + getSecuritySettings( registrarId: string ): Observable { diff --git a/console-webapp/src/app/shared/services/breakPoint.service.ts b/console-webapp/src/app/shared/services/breakPoint.service.ts new file mode 100644 index 00000000000..57afbb116fc --- /dev/null +++ b/console-webapp/src/app/shared/services/breakPoint.service.ts @@ -0,0 +1,45 @@ +// Copyright 2024 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { BreakpointObserver } from '@angular/cdk/layout'; +import { Injectable, signal } from '@angular/core'; +import { distinctUntilChanged } from 'rxjs'; + +const MOBILE_LAYOUT_BREAKPOINT = '(max-width: 560px)'; +const TABLET_LAYOUT_BREAKPOINT = '(max-width: 768px)'; + +@Injectable({ + providedIn: 'root', +}) +export class BreakPointObserverService { + isMobileView = signal(false); + isTabletView = signal(false); + + readonly breakpoint$ = this.breakpointObserver + .observe([MOBILE_LAYOUT_BREAKPOINT, TABLET_LAYOUT_BREAKPOINT]) + .pipe(distinctUntilChanged()); + + constructor(protected breakpointObserver: BreakpointObserver) { + this.breakpoint$.subscribe(() => this.breakpointChanged()); + } + + private breakpointChanged() { + this.isMobileView.set( + this.breakpointObserver.isMatched(MOBILE_LAYOUT_BREAKPOINT) + ); + this.isTabletView.set( + this.breakpointObserver.isMatched(TABLET_LAYOUT_BREAKPOINT) + ); + } +} diff --git a/console-webapp/src/app/shared/services/globalLoader.service.ts b/console-webapp/src/app/shared/services/globalLoader.service.ts index d8b0d721536..ed5354c3925 100644 --- a/console-webapp/src/app/shared/services/globalLoader.service.ts +++ b/console-webapp/src/app/shared/services/globalLoader.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/shared/services/userData.service.ts b/console-webapp/src/app/shared/services/userData.service.ts index 0930cafbdc6..20d998a619f 100644 --- a/console-webapp/src/app/shared/services/userData.service.ts +++ b/console-webapp/src/app/shared/services/userData.service.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/snackbar.module.ts b/console-webapp/src/app/snackbar.module.ts index 72fc4219ee1..81993c4084f 100644 --- a/console-webapp/src/app/snackbar.module.ts +++ b/console-webapp/src/app/snackbar.module.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/support/support.component.html b/console-webapp/src/app/support/support.component.html new file mode 100644 index 00000000000..ee75170cc93 --- /dev/null +++ b/console-webapp/src/app/support/support.component.html @@ -0,0 +1,45 @@ +
+

Support

+

+ Our support team can assist you with any technical or operational questions + you may have regarding our registry services. +

+ +

Email

+

+ For help with OT&E sandbox and certification, or new technical requirements + for any of our new TLD launches. +

+ {{ userDataService.userData?.supportEmail || "TODO_REMOVE@gmail.com" }} +

+ For general purpose questions once you are integrated with our registry + system. If the issue is urgent, please put "Urgent" in the email title. +

+ {{ userDataService.userData?.supportEmail || "TODO_REMOVE@gmail.com" }} +

+ Note: You may receive occasional service announcements via + registrar-announcement@google.com. You will not be able to reply to + those messages. +

+

Phone

+

For general support inquiries 24/7:

+ {{ userDataService.userData?.supportPhoneNumber || "123123123" }} +

Your telephone passcode:

+

5555

+

+ Note: Please be ready with your account name and telephone passcode when + contacting us by phone. +

+
diff --git a/console-webapp/src/app/support/support.component.scss b/console-webapp/src/app/support/support.component.scss new file mode 100644 index 00000000000..3645b2f3a57 --- /dev/null +++ b/console-webapp/src/app/support/support.component.scss @@ -0,0 +1,13 @@ +.console-app { + &__support { + &-passcode { + color: #1e8e3e; + } + > * { + margin-bottom: 20px; + } + .secondary-text { + margin-bottom: 50px; + } + } +} diff --git a/console-webapp/src/app/home/widgets/promotionsWidget.component.ts b/console-webapp/src/app/support/support.component.ts similarity index 62% rename from console-webapp/src/app/home/widgets/promotionsWidget.component.ts rename to console-webapp/src/app/support/support.component.ts index 43752d5c0a6..f51dd85ae80 100644 --- a/console-webapp/src/app/home/widgets/promotionsWidget.component.ts +++ b/console-webapp/src/app/support/support.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,11 +13,14 @@ // limitations under the License. import { Component } from '@angular/core'; +import { UserDataService } from '../shared/services/userData.service'; @Component({ - selector: '[app-promotions-widget]', - templateUrl: './promotionsWidget.component.html', + selector: 'app-support', + templateUrl: './support.component.html', + styleUrls: ['./support.component.scss'], }) -export class PromotionsWidgetComponent { - constructor() {} +export class SupportComponent { + public static PATH = 'support'; + constructor(protected userDataService: UserDataService) {} } diff --git a/console-webapp/src/app/tlds/tlds.component.html b/console-webapp/src/app/tlds/tlds.component.html index 6eb9d3b31cc..fcb7066c3bf 100644 --- a/console-webapp/src/app/tlds/tlds.component.html +++ b/console-webapp/src/app/tlds/tlds.component.html @@ -1,47 +1 @@ -
- - .how - A place for thinkers, tinkerers, and knowledge seekers - - Onboarding Now - Marketing Materials - Visit get.how for more information - - -
- -
- - .soy - A place for thinkers, tinkerers, and knowledge seekers - - Onboarding Now - Marketing Materials - Visit iam.soy for more information - - -
+
diff --git a/console-webapp/src/app/tlds/tlds.component.scss b/console-webapp/src/app/tlds/tlds.component.scss index 3b51df472b1..6753521df2c 100644 --- a/console-webapp/src/app/tlds/tlds.component.scss +++ b/console-webapp/src/app/tlds/tlds.component.scss @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/tlds/tlds.component.spec.ts b/console-webapp/src/app/tlds/tlds.component.spec.ts index dbec7069702..e90adf08aa9 100644 --- a/console-webapp/src/app/tlds/tlds.component.spec.ts +++ b/console-webapp/src/app/tlds/tlds.component.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/app/tlds/tlds.component.ts b/console-webapp/src/app/tlds/tlds.component.ts index 0569536cb3e..a2e8dc9c1fd 100644 --- a/console-webapp/src/app/tlds/tlds.component.ts +++ b/console-webapp/src/app/tlds/tlds.component.ts @@ -1,4 +1,4 @@ -// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// Copyright 2024 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/console-webapp/src/assets/billing.png b/console-webapp/src/assets/billing.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce4e0f6d130796feeed1b7646f836f0864eae5b GIT binary patch literal 78411 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

^V1p8n+q2I3{7VJlRf5wnS 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 literal 0 HcmV?d00001 diff --git a/console-webapp/src/assets/resources.png b/console-webapp/src/assets/resources.png new file mode 100644 index 0000000000000000000000000000000000000000..8b249b5393c772bd94c16d0bb86e202f1dfb1cdd GIT binary patch literal 187326 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