Replace less with scss, add homepage widgets, add registrars selector and registrars page (#2114)

* Replace less compiler with scss

* Replace less with scss, add homepage widgets, add registrars selector and registrars page
This commit is contained in:
Pavlo Tkach 2023-09-01 11:48:30 -04:00 committed by GitHub
parent 6b5ec36eed
commit 1929654f8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 11927 additions and 730 deletions

View file

@ -7,7 +7,7 @@
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "less" "style": "scss"
} }
}, },
"root": "", "root": "",
@ -22,15 +22,18 @@
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "less", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/theme.scss",
"src/styles.less" "src/styles.scss"
], ],
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": [] "scripts": []
}, },
"configurations": { "configurations": {
@ -91,15 +94,18 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "less", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/theme.scss",
"src/styles.less" "src/styles.scss"
], ],
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": [] "scripts": []
} }
}, },

File diff suppressed because it is too large Load diff

View file

@ -18,51 +18,56 @@ import { TldsComponent } from './tlds/tlds.component';
import { HomeComponent } from './home/home.component'; import { HomeComponent } from './home/home.component';
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from './settings/settings.component';
import SettingsContactComponent from './settings/contact/contact.component'; import SettingsContactComponent from './settings/contact/contact.component';
import SettingsRegistrarsComponent from './settings/registrars/registrars.component';
import SettingsWhoisComponent from './settings/whois/whois.component'; import SettingsWhoisComponent from './settings/whois/whois.component';
import SettingsUsersComponent from './settings/users/users.component'; import SettingsUsersComponent from './settings/users/users.component';
import SettingsSecurityComponent from './settings/security/security.component'; import SettingsSecurityComponent from './settings/security/security.component';
import { RegistrarGuard } from './registrar/registrar.guard'; import { RegistrarGuard } from './registrar/registrar.guard';
import { RegistrarComponent } from './registrar/registrar.component'; import { RegistrarComponent } from './registrar/registrarsTable.component';
import { EmptyRegistrar } from './registrar/emptyRegistrar.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'registrars', component: RegistrarComponent }, { path: 'registrars', component: RegistrarComponent },
{ path: 'empty-registrar', component: EmptyRegistrar },
{ path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] }, { path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] },
{ path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] }, { path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] },
{ {
path: 'settings', path: 'settings',
component: SettingsComponent, component: SettingsComponent,
canActivate: [RegistrarGuard],
children: [ children: [
{ {
path: '', path: '',
redirectTo: 'contact', redirectTo: 'registrars',
pathMatch: 'full', pathMatch: 'full',
}, },
{ {
path: 'contact', path: 'contact',
component: SettingsContactComponent, component: SettingsContactComponent,
canActivate: [RegistrarGuard],
}, },
{ {
path: 'whois', path: 'whois',
component: SettingsWhoisComponent, component: SettingsWhoisComponent,
canActivate: [RegistrarGuard],
}, },
{ {
path: 'security', path: 'security',
component: SettingsSecurityComponent, component: SettingsSecurityComponent,
canActivate: [RegistrarGuard],
}, },
{ {
path: 'epp-password', path: 'epp-password',
component: SettingsSecurityComponent, component: SettingsSecurityComponent,
canActivate: [RegistrarGuard],
}, },
{ {
path: 'users', path: 'users',
component: SettingsUsersComponent, component: SettingsUsersComponent,
canActivate: [RegistrarGuard],
}, },
{ {
path: 'registrars', path: 'registrars',
component: SettingsRegistrarsComponent, component: RegistrarComponent,
}, },
], ],
}, },

View file

@ -12,13 +12,13 @@
<a mat-list-item [routerLink]="'/settings'" routerLinkActive="active"> <a mat-list-item [routerLink]="'/settings'" routerLinkActive="active">
Settings Settings
</a> </a>
<a mat-list-item [routerLink]="'/registrars'" routerLinkActive="active">
Select Registrar
</a>
</mat-nav-list> </mat-nav-list>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content class="console-app__content-wrapper"> <mat-sidenav-content class="console-app__content-wrapper">
<div class="console-app__content"> <div *ngIf="globalLoader.isLoading" class="console-app__global-spinner">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<div class="console-app__content" *ngIf="renderRouter">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</mat-sidenav-content> </mat-sidenav-content>

View file

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
:host { :host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, font-family: Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; "Segoe UI Emoji", "Segoe UI Symbol" !important;
font-size: 14px; font-size: 14px;
color: #333; color: #333;
box-sizing: border-box; box-sizing: border-box;
@ -47,4 +47,7 @@
max-width: 1340px; max-width: 1340px;
margin: 0 auto; margin: 0 auto;
} }
&__global-spinner {
margin-bottom: 2rem;
}
} }

View file

@ -13,10 +13,25 @@
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RegistrarService } from './registrar/registrar.service';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.less'], styleUrls: ['./app.component.scss'],
}) })
export class AppComponent {} export class AppComponent {
renderRouter: boolean = true;
constructor(
protected registrarService: RegistrarService,
protected globalLoader: GlobalLoaderService
) {
registrarService.activeRegistrarIdChange.subscribe(() => {
this.renderRouter = false;
setTimeout(() => {
this.renderRouter = true;
}, 400);
});
}
}

View file

@ -31,9 +31,20 @@ import SettingsContactComponent, {
ContactDetailsDialogComponent, ContactDetailsDialogComponent,
} from './settings/contact/contact.component'; } from './settings/contact/contact.component';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { RegistrarComponent } from './registrar/registrar.component'; import { RegistrarComponent } from './registrar/registrarsTable.component';
import { RegistrarGuard } from './registrar/registrar.guard'; import { RegistrarGuard } from './registrar/registrar.guard';
import SecurityComponent from './settings/security/security.component'; 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 { RegistrarSelectorComponent } from './registrar/registrar-selector.component';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
import { ContactWidgetComponent } from './home/widgets/contact-widget.component';
import { PromotionsWidgetComponent } from './home/widgets/promotions-widget.component';
import { TldsWidgetComponent } from './home/widgets/tlds-widget.component';
import { ResourcesWidgetComponent } from './home/widgets/resources-widget.component';
import { EppWidgetComponent } from './home/widgets/epp-widget.component';
import { BillingWidgetComponent } from './home/widgets/billing-widget.component';
import { DomainsWidgetComponent } from './home/widgets/domains-widget.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -46,6 +57,15 @@ import SecurityComponent from './settings/security/security.component';
ContactDetailsDialogComponent, ContactDetailsDialogComponent,
RegistrarComponent, RegistrarComponent,
SecurityComponent, SecurityComponent,
EmptyRegistrar,
RegistrarSelectorComponent,
ContactWidgetComponent,
DomainsWidgetComponent,
PromotionsWidgetComponent,
TldsWidgetComponent,
ResourcesWidgetComponent,
EppWidgetComponent,
BillingWidgetComponent,
], ],
imports: [ imports: [
HttpClientModule, HttpClientModule,
@ -55,7 +75,17 @@ import SecurityComponent from './settings/security/security.component';
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
], ],
providers: [BackendService, RegistrarGuard], providers: [
GlobalLoaderService,
BackendService,
RegistrarGuard,
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: {
subscriptSizing: 'dynamic',
},
},
],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule {} export class AppModule {}

View file

@ -5,6 +5,7 @@
</button> </button>
<span>Google Registry</span> <span>Google Registry</span>
<span class="spacer"></span> <span class="spacer"></span>
<app-registrar-selector />
<button mat-icon-button aria-label="Open FAQ"> <button mat-icon-button aria-label="Open FAQ">
<mat-icon>question_mark</mat-icon> <mat-icon>question_mark</mat-icon>
</button> </button>

View file

@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
.console-app__header {
margin-top: 0;
}
.spacer { .spacer {
flex: 1; flex: 1;
} }

View file

@ -17,7 +17,7 @@ import { Component, EventEmitter, Output } from '@angular/core';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrls: ['./header.component.less'], styleUrls: ['./header.component.scss'],
}) })
export class HeaderComponent { export class HeaderComponent {
private isNavOpen = false; private isNavOpen = false;

View file

@ -1,21 +1,13 @@
<h3>Recent Activity</h3> <div class="console-app__home">
<table <h1>Welcome to the Google Registry Console</h1>
mat-table <div class="console-app__home-widgets">
[dataSource]="dataSource" <div app-domains-widget class="console-app__widget-wrapper__wide"></div>
class="mat-elevation-z8 console-home__activity" <div app-contact-widget class="console-app__widget-wrapper__wide"></div>
> <div app-tlds-widget></div>
<ng-container <div app-promotions-widget class="console-app__widget-wrapper__wide"></div>
*ngFor="let column of columns" <div app-promotions-widget class="console-app__widget-wrapper__wide"></div>
[matColumnDef]="column.columnDef" <div app-resources-widget></div>
> <div app-billing-widget></div>
<th mat-header-cell *matHeaderCellDef> <div app-epp-widget></div>
{{ column.header }} </div>
</th> </div>
<td mat-cell *matCellDef="let row">
{{ column.cell(row) }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>

View file

@ -1,13 +0,0 @@
// Copyright 2022 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.

View file

@ -12,11 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
html, .console-app {
body { &__home {
height: 100%; margin-top: 1rem;
} }
body { &__home-widgets {
margin: 0; display: grid;
font-family: Roboto, "Helvetica Neue", sans-serif; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
grid-gap: 20px;
mat-card {
height: 100%;
h1 {
margin-top: 1rem;
}
}
}
} }

View file

@ -12,220 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component, ViewEncapsulation } from '@angular/core';
export interface ActivityRecord {
eventType: string;
userName: string;
registrarName: string;
timestamp: string;
details: string;
}
const MOCK_DATA: ActivityRecord[] = [
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Export DUMS',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Update Contact',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
{
eventType: 'Delete Domain',
userName: 'user3',
registrarName: 'registrar1',
timestamp: '2022-03-15T19:46:39.007',
details: 'All Domains under management exported as .csv file',
},
];
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.less'], styleUrls: ['./home.component.scss'],
encapsulation: ViewEncapsulation.None,
}) })
export class HomeComponent { export class HomeComponent {}
columns = [
{
columnDef: 'eventType',
header: 'Event Type',
cell: (record: ActivityRecord) => `${record.eventType}`,
},
{
columnDef: 'userName',
header: 'User',
cell: (record: ActivityRecord) => `${record.userName}`,
},
{
columnDef: 'registrarName',
header: 'Registrar',
cell: (record: ActivityRecord) => `${record.registrarName}`,
},
{
columnDef: 'timestamp',
header: 'Timestamp',
cell: (record: ActivityRecord) => `${record.timestamp}`,
},
{
columnDef: 'details',
header: 'Details',
cell: (record: ActivityRecord) => `${record.details}`,
},
];
dataSource = MOCK_DATA;
displayedColumns = this.columns.map((c) => c.columnDef);
}

View file

@ -0,0 +1,13 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">account_balance</mat-icon>
<h1 class="console-app__widget-title">Billing Info</h1>
<h4 class="secondary-text text-center">
View important billing and payments information.
</h4>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -15,8 +15,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-registrars', selector: '[app-billing-widget]',
templateUrl: './registrars.component.html', templateUrl: './billing-widget.component.html',
styleUrls: ['./registrars.component.less'],
}) })
export default class RegistrarsComponent {} export class BillingWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,27 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">call</mat-icon>
<h1 class="console-app__widget-title">Contact Support</h1>
<h4 class="secondary-text text-center">
View Google Registry support email and phone information
</h4>
</div>
<div class="console-app__widget_right">
<button mat-button color="primary" class="console-app__widget-link">
Give us a Call
</button>
<p class="secondary-text">
Call Google Registry support at +1 (404) 978 8419
</p>
<button mat-button color="primary" class="console-app__widget-link">
Send us an Email
</button>
<p class="secondary-text">
Email Google Registry at support@google.com
</p>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +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.
import { Component } from '@angular/core';
@Component({
selector: '[app-contact-widget]',
templateUrl: './contact-widget.component.html',
})
export class ContactWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,25 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">view_list</mat-icon>
<h1 class="console-app__widget-title">Domains</h1>
<h4 class="secondary-text text-center">
Manage domain names and registry lock settings.
</h4>
</div>
<div class="console-app__widget_right">
<button mat-button color="primary" class="console-app__widget-link">
Create a Domain
</button>
<p class="secondary-text">Register a new domain name</p>
<button mat-button color="primary" class="console-app__widget-link">
View DUMs
</button>
<p class="secondary-text">
Download a csv of all domains under management
</p>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +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.
import { Component } from '@angular/core';
@Component({
selector: '[app-domains-widget]',
templateUrl: './domains-widget.component.html',
})
export class DomainsWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,13 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">computer</mat-icon>
<h1 class="console-app__widget-title">EPP Console</h1>
<h4 class="secondary-text text-center">
Test run and execute EPP commands using XML files.
</h4>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -11,3 +11,13 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core';
@Component({
selector: '[app-epp-widget]',
templateUrl: './epp-widget.component.html',
})
export class EppWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,27 @@
<mat-card class="console-app__widget-wrapper__wide">
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">subject</mat-icon>
<h1 class="console-app__widget-title">Promotions</h1>
<h4 class="secondary-text text-center">
Manage Google Registry promotions and view onboarding process.
</h4>
</div>
<div class="console-app__widget_right">
<button mat-button color="primary" class="console-app__widget-link">
Manage Preferred Partner Program
</button>
<p class="secondary-text">
Onboard or view current preferred partner status
</p>
<button mat-button color="primary" class="console-app__widget-link">
Manage Registry Lock Program
</button>
<p class="secondary-text">
Onboard or view current registry lock status
</p>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +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.
import { Component } from '@angular/core';
@Component({
selector: '[app-promotions-widget]',
templateUrl: './promotions-widget.component.html',
})
export class PromotionsWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,13 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">menu_book</mat-icon>
<h1 class="console-app__widget-title">Resources</h1>
<h4 class="secondary-text text-center">
Use Google Drive to view onboarding FAQs, and technical documentation.
</h4>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +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.
import { Component } from '@angular/core';
@Component({
selector: '[app-resources-widget]',
templateUrl: './resources-widget.component.html',
})
export class ResourcesWidgetComponent {
constructor() {}
}

View file

@ -0,0 +1,13 @@
<mat-card>
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">list_alt</mat-icon>
<h1 class="console-app__widget-title">TLDs</h1>
<h4 class="secondary-text text-center">
View launch phase information for all onboarded and available TLDs.
</h4>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +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.
import { Component } from '@angular/core';
@Component({
selector: '[app-tlds-widget]',
templateUrl: './tlds-widget.component.html',
})
export class TldsWidgetComponent {
constructor() {}
}

View file

@ -44,6 +44,7 @@ import { CdkMenuModule } from '@angular/cdk/menu';
import { DialogModule } from '@angular/cdk/dialog'; import { DialogModule } from '@angular/cdk/dialog';
import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatPaginatorModule } from '@angular/material/paginator';
@NgModule({ @NgModule({
exports: [ exports: [
@ -79,6 +80,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
OverlayModule, OverlayModule,
DialogModule, DialogModule,
MatSnackBarModule, MatSnackBarModule,
MatPaginatorModule,
], ],
}) })
export class MaterialModule {} export class MaterialModule {}

View file

@ -0,0 +1,7 @@
<div class="console-app__emty-registrar">
<h1>
<mat-icon class="console-app__emty-registrar-icon">block</mat-icon>
</h1>
<h1>No registrar selected</h1>
<h4 class="mat-body-2">Please select a registrar</h4>
</div>

View file

@ -0,0 +1,17 @@
.console-app {
&__emty-registrar {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
&-icon {
transform: scale(3);
margin-bottom: 0.5rem;
}
}
}

View file

@ -13,33 +13,32 @@
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RegistrarService } from './registrar.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { RegistrarService } from './registrar.service';
import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'app-registrar', selector: 'app-empty-registrar',
templateUrl: './registrar.component.html', templateUrl: './emptyRegistrar.component.html',
styleUrls: ['./registrar.component.less'], styleUrls: ['./emptyRegistrar.component.scss'],
}) })
export class RegistrarComponent { export class EmptyRegistrar {
private lastActiveRegistrarId: string; private registrarIdChangeSubscription?: Subscription;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
protected registrarService: RegistrarService, protected registrarService: RegistrarService,
private router: Router private router: Router
) { ) {
this.lastActiveRegistrarId = registrarService.activeRegistrarId; this.registrarIdChangeSubscription =
registrarService.activeRegistrarIdChange.subscribe((newRegistrarId) => {
if (newRegistrarId) {
this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
}
});
} }
ngDoCheck() { ngOnDestroy() {
if ( this.registrarIdChangeSubscription?.unsubscribe();
this.registrarService.activeRegistrarId &&
this.registrarService.activeRegistrarId !== this.lastActiveRegistrarId &&
this.route.snapshot.paramMap.get('nextUrl')
) {
this.lastActiveRegistrarId = this.registrarService.activeRegistrarId;
this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
}
} }
} }

View file

@ -0,0 +1,18 @@
<div class="console-app__registrar">
<div>
<mat-form-field class="mat-form-field-density-5" appearance="fill">
<mat-label>Registrar</mat-label>
<mat-select
[ngModel]="registrarService.activeRegistrarId"
(selectionChange)="registrarService.updateRegistrar($event.value)"
>
<mat-option
*ngFor="let registrar of registrarService.registrars"
[value]="registrar.registrarName"
>
{{ registrar.registrarName }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>

View file

@ -0,0 +1,18 @@
.console-app {
&__registrar {
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;
}
}

View file

@ -14,18 +14,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import RegistrarsComponent from './registrars.component'; import { RegistrarSelectorComponent } from './registrar-selector.component';
describe('RegistrarsComponent', () => { describe('RegistrarSelectorComponent', () => {
let component: RegistrarsComponent; let component: RegistrarSelectorComponent;
let fixture: ComponentFixture<RegistrarsComponent>; let fixture: ComponentFixture<RegistrarSelectorComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [RegistrarsComponent], declarations: [RegistrarSelectorComponent],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(RegistrarsComponent); fixture = TestBed.createComponent(RegistrarSelectorComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View file

@ -0,0 +1,25 @@
// 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 { RegistrarService } from './registrar.service';
@Component({
selector: 'app-registrar-selector',
templateUrl: './registrar-selector.component.html',
styleUrls: ['./registrar-selector.component.scss'],
})
export class RegistrarSelectorComponent {
constructor(protected registrarService: RegistrarService) {}
}

View file

@ -1,17 +0,0 @@
<div class="console-app__registrar">
<h4 class="console-app__title">
{{ registrarService.activeRegistrarId === "" ? "Select" : "Switch" }}
registrar:
</h4>
<mat-form-field>
<mat-label>Registrar</mat-label>
<mat-select [(ngModel)]="registrarService.activeRegistrarId">
<mat-option
*ngFor="let registrar of registrarService.registrars"
[value]="registrar"
>
{{ registrar }}
</mat-option>
</mat-select>
</mat-form-field>
</div>

View file

@ -1,11 +0,0 @@
.console-app {
&__registrar {
display: flex;
justify-content: center;
align-items: center;
margin-top: 1rem;
}
&__title {
margin-right: 1rem;
}
}

View file

@ -29,6 +29,6 @@ export class RegistrarGuard {
if (this.registrarService.activeRegistrarId) { if (this.registrarService.activeRegistrarId) {
return true; return true;
} }
return this.router.navigate([`/registrars`, { nextUrl: state.url }]); return this.router.navigate([`/empty-registrar`, { nextUrl: state.url }]);
} }
} }

View file

@ -14,16 +14,59 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BackendService } from '../shared/services/backend.service'; import { BackendService } from '../shared/services/backend.service';
import { Subject } from 'rxjs';
import {
GlobalLoader,
GlobalLoaderService,
} from '../shared/services/globalLoader.service';
interface Address {
street?: string[];
city?: string;
countryCode?: string;
zip?: string;
state?: string;
}
export interface Registrar {
allowedTlds?: string[];
ipAddressAllowList?: string[];
emailAddress?: string;
billingAccountMap?: object;
driveFolderId?: string;
ianaIdentifier?: number;
icannReferralEmail?: string;
localizedAddress?: Address;
registrarId: string;
registrarName: string;
registryLockAllowed?: boolean;
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class RegistrarService { export class RegistrarService implements GlobalLoader {
activeRegistrarId: string = ''; activeRegistrarId: string = '';
registrars: string[] = []; registrars: Registrar[] = [];
constructor(private backend: BackendService) { activeRegistrarIdChange: Subject<string> = new Subject<string>();
constructor(
private backend: BackendService,
private globalLoader: GlobalLoaderService
) {
this.backend.getRegistrars().subscribe((r) => { this.backend.getRegistrars().subscribe((r) => {
this.globalLoader.stopGlobalLoader(this);
this.registrars = r; this.registrars = r;
}); });
this.globalLoader.startGlobalLoader(this);
}
public updateRegistrar(registrarId: string) {
this.activeRegistrarId = registrarId;
this.activeRegistrarIdChange.next(registrarId);
}
loadingTimeout() {
// TODO: Decide what to do when timeout happens
} }
} }

View file

@ -0,0 +1,25 @@
<div class="console-app__registrars">
<table
mat-table
[dataSource]="registrarService.registrars"
class="mat-elevation-z8"
>
<ng-container
*ngFor="let column of columns"
[matColumnDef]="column.columnDef"
>
<th mat-header-cell *matHeaderCellDef>
{{ column.header }}
</th>
<td mat-cell *matCellDef="let row" [innerHTML]="column.cell(row)"></td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator
class="mat-elevation-z8"
[pageSizeOptions]="[5, 10, 20]"
showFirstLastButtons
></mat-paginator>
</div>

View file

@ -0,0 +1,5 @@
.console-app {
&__registrars {
margin-top: 1.5rem;
}
}

View file

@ -14,7 +14,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistrarComponent } from './registrar.component'; import { RegistrarComponent } from './registrarsTable.component';
import { BackendService } from '../shared/services/backend.service'; import { BackendService } from '../shared/services/backend.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';

View file

@ -0,0 +1,75 @@
// 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 { Registrar, RegistrarService } from './registrar.service';
@Component({
selector: 'app-registrar',
templateUrl: './registrarsTable.component.html',
styleUrls: ['./registrarsTable.component.scss'],
})
export class RegistrarComponent {
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}<br/>`;
},
''
)}`,
},
{
columnDef: 'registryLockAllowed',
header: 'Registry Lock',
cell: (record: Registrar) => `${record.registryLockAllowed}`,
},
{
columnDef: 'driveId',
header: 'Drive ID',
cell: (record: Registrar) => `${record.driveFolderId || ''}`,
},
];
displayedColumns = this.columns.map((c) => c.columnDef);
constructor(protected registrarService: RegistrarService) {}
}

View file

@ -2,6 +2,15 @@
<mat-progress-bar mode="indeterminate"></mat-progress-bar> <mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div> </div>
<div *ngIf="!loading"> <div *ngIf="!loading">
<div
class="contact__empty-contacts"
*ngIf="contactService.contacts.length === 0"
>
<mat-icon class="contact__empty-contacts-icon secondary-text"
>apps_outage</mat-icon
>
<h1>No contacts found</h1>
</div>
<div *ngFor="let group of groupedData"> <div *ngFor="let group of groupedData">
<div class="contact__cards-wrapper" *ngIf="group.contacts.length"> <div class="contact__cards-wrapper" *ngIf="group.contacts.length">
<h3>{{ group.label }}s</h3> <h3>{{ group.label }}s</h3>

View file

@ -37,6 +37,18 @@
justify-content: flex-start; justify-content: flex-start;
margin: 2rem 0; margin: 2rem 0;
} }
&__empty-contacts {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
&__empty-contacts-icon {
width: 4rem;
height: 4rem;
font-size: 4rem;
margin-top: 1.5rem;
}
} }
.contact-details { .contact-details {
&__input { &__input {

View file

@ -77,7 +77,7 @@ class ContactDetailsEventsResponder {
@Component({ @Component({
selector: 'app-contact-details-dialog', selector: 'app-contact-details-dialog',
templateUrl: 'contact-details.component.html', templateUrl: 'contact-details.component.html',
styleUrls: ['./contact.component.less'], styleUrls: ['./contact.component.scss'],
}) })
export class ContactDetailsDialogComponent { export class ContactDetailsDialogComponent {
contact: Contact; contact: Contact;
@ -140,7 +140,7 @@ export class ContactDetailsDialogComponent {
@Component({ @Component({
selector: 'app-contact', selector: 'app-contact',
templateUrl: './contact.component.html', templateUrl: './contact.component.html',
styleUrls: ['./contact.component.less'], styleUrls: ['./contact.component.scss'],
}) })
export default class ContactComponent { export default class ContactComponent {
loading: boolean = false; loading: boolean = false;
@ -158,7 +158,7 @@ export default class ContactComponent {
} }
public get groupedData() { public get groupedData() {
return this.contactService.contacts.reduce((acc, contact) => { return this.contactService.contacts?.reduce((acc, contact) => {
contact.types.forEach((type) => { contact.types.forEach((type) => {
acc acc
.find((group: GroupedContacts) => group.value === type) .find((group: GroupedContacts) => group.value === type)

View file

@ -1 +0,0 @@
<p>registrars component works!</p>

View file

@ -20,7 +20,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
@Component({ @Component({
selector: 'app-security', selector: 'app-security',
templateUrl: './security.component.html', templateUrl: './security.component.html',
styleUrls: ['./security.component.less'], styleUrls: ['./security.component.scss'],
providers: [SecurityService], providers: [SecurityService],
}) })
export default class SecurityComponent { export default class SecurityComponent {

View file

@ -17,7 +17,7 @@ import { Component, ViewEncapsulation } from '@angular/core';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
templateUrl: './settings.component.html', templateUrl: './settings.component.html',
styleUrls: ['./settings.component.less'], styleUrls: ['./settings.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class SettingsComponent {} export class SettingsComponent {}

View file

@ -17,6 +17,6 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-users', selector: 'app-users',
templateUrl: './users.component.html', templateUrl: './users.component.html',
styleUrls: ['./users.component.less'], styleUrls: ['./users.component.scss'],
}) })
export default class UsersComponent {} export default class UsersComponent {}

View file

@ -17,6 +17,6 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-whois', selector: 'app-whois',
templateUrl: './whois.component.html', templateUrl: './whois.component.html',
styleUrls: ['./whois.component.less'], styleUrls: ['./whois.component.scss'],
}) })
export default class WhoisComponent {} export default class WhoisComponent {}

View file

@ -18,6 +18,7 @@ import { catchError, Observable, of } from 'rxjs';
import { SecuritySettingsBackendModel } from 'src/app/settings/security/security.service'; import { SecuritySettingsBackendModel } from 'src/app/settings/security/security.service';
import { Contact } from '../../settings/contact/contact.service'; import { Contact } from '../../settings/contact/contact.service';
import { Registrar } from '../../registrar/registrar.service';
@Injectable() @Injectable()
export class BackendService { export class BackendService {
@ -60,10 +61,10 @@ export class BackendService {
); );
} }
getRegistrars(): Observable<string[]> { getRegistrars(): Observable<Registrar[]> {
return this.http return this.http
.get<string[]>('/console-api/registrars') .get<Registrar[]>('/console-api/registrars')
.pipe(catchError((err) => this.errorCatcher<string[]>(err))); .pipe(catchError((err) => this.errorCatcher<Registrar[]>(err)));
} }
getSecuritySettings( getSecuritySettings(

View file

@ -0,0 +1,60 @@
// 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 { Observable, Subscription, timeout } from 'rxjs';
export interface GlobalLoader {
loadingTimeout: () => void;
}
/**
* This class responsible for global application loading indicator
*
* <p>Only to be used for when the activity should indicate that the entire application is busy
* For instance - when initial user information is loading, which is crucial for any subsequent
* interaction with the application
*/
@Injectable({
providedIn: 'root',
})
export class GlobalLoaderService {
private static readonly TIMEOUT_MS = 3000;
private loaders = new Map<GlobalLoader, Subscription>();
public isLoading: boolean = false;
private syncLoading() {
this.isLoading = this.loaders.size > 0;
}
startGlobalLoader(c: GlobalLoader) {
const subscription = new Observable(() => {})
.pipe(timeout(GlobalLoaderService.TIMEOUT_MS))
.subscribe({
error: () => {
this.loaders.delete(c);
c.loadingTimeout();
this.syncLoading();
},
});
this.loaders.set(c, subscription);
this.syncLoading();
}
stopGlobalLoader(c: GlobalLoader) {
this.loaders.get(c)?.unsubscribe();
this.loaders.delete(c);
this.syncLoading();
}
}

View file

@ -17,6 +17,6 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-tlds', selector: 'app-tlds',
templateUrl: './tlds.component.html', templateUrl: './tlds.component.html',
styleUrls: ['./tlds.component.less'], styleUrls: ['./tlds.component.scss'],
}) })
export class TldsComponent {} export class TldsComponent {}

View file

@ -0,0 +1,63 @@
// Copyright 2022 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.
@use "@angular/material" as mat;
@import "app/registrar/registrar-selector.component.scss";
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
.text-center {
text-align: center;
}
.console-app {
&__widget {
display: flex;
gap: 10px;
&-wrapper__wide {
grid-column: span 2;
}
&-link {
padding: 0 !important;
text-align: left;
margin-bottom: 0.5rem;
}
&-title {
color: var(--primary) !important;
}
&-icon {
font-size: 4rem;
line-height: 4rem;
height: 4rem !important;
width: 4rem !important;
}
&_left {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
&_right {
flex: 1;
border-left: 1px solid var(--secondary);
padding-left: 20px;
}
}
}

View file

@ -0,0 +1,69 @@
@use "sass:map";
@use "@angular/material" as mat;
/** Copied from docs section **/
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat.core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$theme-primary: mat.define-palette(mat.$indigo-palette);
$theme-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
// The warn palette is optional (defaults to red).
$theme-warn: mat.define-palette(mat.$red-palette);
// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$theme: mat.define-light-theme(
(
color: (
primary: $theme-primary,
accent: $theme-accent,
warn: $theme-warn,
),
density: 0,
)
);
/** Application specific section **/
@mixin form-field-density($density) {
$field-typography: mat.define-typography-config(
$body-1: mat.define-typography-level(12px, 24px, 400),
);
@include mat.typography-level($field-typography, "body-1");
@include mat.form-field-density($density);
}
// Define lowest possible density class to be used in application
// In the same manner -1...-5 classes can be defined
.mat-form-field-density-5 {
@include form-field-density(-5);
}
$foreground: map.merge($theme, mat.$light-theme-foreground-palette);
// Access and define a class with secondary color exposed
.secondary-text {
color: map.get($foreground, "secondary-text");
}
:root {
--primary: #{mat.get-color-from-palette($theme-primary, 500)};
--secondary: #{map.get($foreground, "secondary-text")};
}
@include mat.all-component-themes($theme);
@import "@angular/material/theming";
// Define application specific typography settings, font-family, etc
$typography-configuration: mat-typography-config(
$font-family: 'Roboto, "Helvetica Neue", sans-serif',
);
@include angular-material-typography($typography-configuration);