mirror of
https://github.com/google/nomulus
synced 2026-01-06 21:47:31 +00:00
Add Console POC reminder front-end (#2754)
This commit is contained in:
@@ -14,30 +14,71 @@
|
||||
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MaterialModule } from './material.module';
|
||||
import { BackendService } from './shared/services/backend.service';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { routes } from './app-routing.module';
|
||||
import { AppModule } from './app.module';
|
||||
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { UserData, UserDataService } from './shared/services/userData.service';
|
||||
import { Registrar, RegistrarService } from './registrar/registrar.service';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { signal, WritableSignal } from '@angular/core';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
let mockRegistrarService: {
|
||||
registrar: WritableSignal<Partial<Registrar> | null | undefined>;
|
||||
registrarId: WritableSignal<string>;
|
||||
registrars: WritableSignal<Array<Partial<Registrar>>>;
|
||||
};
|
||||
let mockUserDataService: { userData: WritableSignal<Partial<UserData>> };
|
||||
let mockSnackBar: jasmine.SpyObj<MatSnackBar>;
|
||||
|
||||
const dummyPocReminderComponent = class {}; // Dummy class for type checking
|
||||
|
||||
beforeEach(async () => {
|
||||
mockRegistrarService = {
|
||||
registrar: signal<Registrar | null | undefined>(undefined),
|
||||
registrarId: signal('123'),
|
||||
registrars: signal([]),
|
||||
};
|
||||
|
||||
mockUserDataService = {
|
||||
userData: signal({
|
||||
globalRole: 'NONE',
|
||||
}),
|
||||
};
|
||||
|
||||
mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [
|
||||
MaterialModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
MatSidenavModule,
|
||||
NoopAnimationsModule,
|
||||
MatSnackBarModule,
|
||||
AppModule,
|
||||
RouterModule.forRoot(routes),
|
||||
],
|
||||
providers: [
|
||||
BackendService,
|
||||
{ provide: RegistrarService, useValue: mockRegistrarService },
|
||||
{ provide: UserDataService, useValue: mockUserDataService },
|
||||
{ provide: MatSnackBar, useValue: mockSnackBar },
|
||||
{ provide: PocReminderComponent, useClass: dummyPocReminderComponent },
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
@@ -46,4 +87,51 @@ describe('AppComponent', () => {
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('PoC Verification Reminder', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
});
|
||||
|
||||
it('should open snackbar if lastPocVerificationDate is older than one year', fakeAsync(() => {
|
||||
const MOCK_TODAY = new Date('2024-07-15T10:00:00.000Z');
|
||||
jasmine.clock().mockDate(MOCK_TODAY);
|
||||
|
||||
const twoYearsAgo = new Date(MOCK_TODAY);
|
||||
twoYearsAgo.setFullYear(MOCK_TODAY.getFullYear() - 2);
|
||||
|
||||
mockRegistrarService.registrar.set({
|
||||
lastPocVerificationDate: twoYearsAgo.toISOString(),
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
TestBed.flushEffects();
|
||||
|
||||
expect(mockSnackBar.openFromComponent).toHaveBeenCalledWith(
|
||||
PocReminderComponent,
|
||||
{
|
||||
horizontalPosition: 'center',
|
||||
verticalPosition: 'top',
|
||||
duration: 1000000000,
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
it('should NOT open snackbar if lastPocVerificationDate is within last year', fakeAsync(() => {
|
||||
const MOCK_TODAY = new Date('2024-07-15T10:00:00.000Z');
|
||||
jasmine.clock().mockDate(MOCK_TODAY);
|
||||
|
||||
const sixMonthsAgo = new Date(MOCK_TODAY);
|
||||
sixMonthsAgo.setMonth(MOCK_TODAY.getMonth() - 6);
|
||||
|
||||
mockRegistrarService.registrar.set({
|
||||
lastPocVerificationDate: sixMonthsAgo.toISOString(),
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
TestBed.flushEffects();
|
||||
|
||||
expect(mockSnackBar.openFromComponent).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,13 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, effect, ViewChild } from '@angular/core';
|
||||
import { MatSidenav } from '@angular/material/sidenav';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { RegistrarService } from './registrar/registrar.service';
|
||||
import { BreakPointObserverService } from './shared/services/breakPoint.service';
|
||||
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||
import { UserDataService } from './shared/services/userData.service';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -35,8 +37,28 @@ export class AppComponent implements AfterViewInit {
|
||||
protected userDataService: UserDataService,
|
||||
protected globalLoader: GlobalLoaderService,
|
||||
protected breakpointObserver: BreakPointObserverService,
|
||||
private router: Router
|
||||
) {}
|
||||
private router: Router,
|
||||
private _snackBar: MatSnackBar
|
||||
) {
|
||||
effect(() => {
|
||||
const registrar = this.registrarService.registrar();
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
oneYearAgo.setHours(0, 0, 0, 0);
|
||||
if (
|
||||
registrar &&
|
||||
registrar.lastPocVerificationDate &&
|
||||
new Date(registrar.lastPocVerificationDate) < oneYearAgo &&
|
||||
this.userDataService?.userData()?.globalRole === 'NONE'
|
||||
) {
|
||||
this._snackBar.openFromComponent(PocReminderComponent, {
|
||||
horizontalPosition: 'center',
|
||||
verticalPosition: 'top',
|
||||
duration: 1000000000,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.router.events.subscribe((event) => {
|
||||
|
||||
@@ -60,6 +60,7 @@ import { TldsComponent } from './tlds/tlds.component';
|
||||
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
|
||||
import RdapComponent from './settings/rdap/rdap.component';
|
||||
import RdapEditComponent from './settings/rdap/rdapEdit.component';
|
||||
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SelectedRegistrarWrapper],
|
||||
@@ -86,6 +87,7 @@ export class SelectedRegistrarModule {}
|
||||
RdapComponent,
|
||||
RdapEditComponent,
|
||||
ReasonDialogComponent,
|
||||
PocReminderComponent,
|
||||
RegistrarComponent,
|
||||
RegistrarDetailsComponent,
|
||||
RegistrarSelectorComponent,
|
||||
|
||||
@@ -57,7 +57,7 @@ export class NavigationComponent {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
getElementId(node: RouteWithIcon) {
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface Registrar
|
||||
registrarName: string;
|
||||
registryLockAllowed?: boolean;
|
||||
type?: string;
|
||||
lastPocVerificationDate?: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="console-app__pocReminder">
|
||||
<p class="">
|
||||
Please take a moment to complete annual review of
|
||||
<a routerLink="/settings">contacts</a>.
|
||||
</p>
|
||||
<span matSnackBarActions>
|
||||
<button mat-button matSnackBarAction (click)="confirmReviewed()">
|
||||
Confirm reviewed
|
||||
</button>
|
||||
<button mat-button matSnackBarAction (click)="snackBarRef.dismiss()">
|
||||
Close
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.console-app__pocReminder {
|
||||
a {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 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 { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';
|
||||
import { RegistrarService } from '../../../registrar/registrar.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-poc-reminder',
|
||||
templateUrl: './pocReminder.component.html',
|
||||
styleUrls: ['./pocReminder.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class PocReminderComponent {
|
||||
constructor(
|
||||
public snackBarRef: MatSnackBarRef<PocReminderComponent>,
|
||||
private registrarService: RegistrarService,
|
||||
private _snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
confirmReviewed() {
|
||||
if (this.registrarService.registrar()) {
|
||||
const todayMidnight = new Date();
|
||||
todayMidnight.setHours(0, 0, 0, 0);
|
||||
this.registrarService
|
||||
// @ts-ignore - if check above won't allow empty object to be submitted
|
||||
.updateRegistrar({
|
||||
...this.registrarService.registrar(),
|
||||
lastPocVerificationDate: todayMidnight.toISOString(),
|
||||
})
|
||||
.subscribe({
|
||||
error: (err: HttpErrorResponse) => {
|
||||
this._snackBar.open(err.error || err.message);
|
||||
},
|
||||
next: () => {
|
||||
this.snackBarRef.dismiss();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package google.registry.ui.server.console;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
@@ -37,6 +38,7 @@ import google.registry.util.RegistryEnvironment;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@Action(
|
||||
service = GaeService.DEFAULT,
|
||||
@@ -88,18 +90,35 @@ public class ConsoleUpdateRegistrarAction extends ConsoleApiAction {
|
||||
}
|
||||
}
|
||||
|
||||
Registrar updatedRegistrar =
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DateTime newLastPocVerificationDate =
|
||||
registrarParam.getLastPocVerificationDate() == null
|
||||
? START_OF_TIME
|
||||
: registrarParam.getLastPocVerificationDate();
|
||||
|
||||
checkArgument(
|
||||
newLastPocVerificationDate.isBefore(now),
|
||||
"Invalid value of LastPocVerificationDate - value is in the future");
|
||||
|
||||
var updatedRegistrarBuilder =
|
||||
existingRegistrar
|
||||
.get()
|
||||
.asBuilder()
|
||||
.setAllowedTlds(
|
||||
registrarParam.getAllowedTlds().stream()
|
||||
.map(DomainNameUtils::canonicalizeHostname)
|
||||
.collect(Collectors.toSet()))
|
||||
.setRegistryLockAllowed(registrarParam.isRegistryLockAllowed())
|
||||
.setLastPocVerificationDate(registrarParam.getLastPocVerificationDate())
|
||||
.build();
|
||||
.setLastPocVerificationDate(newLastPocVerificationDate);
|
||||
|
||||
if (user.getUserRoles()
|
||||
.hasGlobalPermission(ConsolePermission.EDIT_REGISTRAR_DETAILS)) {
|
||||
updatedRegistrarBuilder =
|
||||
updatedRegistrarBuilder
|
||||
.setAllowedTlds(
|
||||
registrarParam.getAllowedTlds().stream()
|
||||
.map(DomainNameUtils::canonicalizeHostname)
|
||||
.collect(Collectors.toSet()))
|
||||
.setRegistryLockAllowed(registrarParam.isRegistryLockAllowed())
|
||||
.setLastPocVerificationDate(newLastPocVerificationDate);
|
||||
}
|
||||
|
||||
var updatedRegistrar = updatedRegistrarBuilder.build();
|
||||
tm().put(updatedRegistrar);
|
||||
finishAndPersistConsoleUpdateHistory(
|
||||
new ConsoleUpdateHistory.Builder()
|
||||
|
||||
@@ -40,6 +40,7 @@ import google.registry.request.Action;
|
||||
import google.registry.request.RequestModule;
|
||||
import google.registry.request.auth.AuthResult;
|
||||
import google.registry.testing.ConsoleApiParamsUtils;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.SystemPropertyExtension;
|
||||
import google.registry.tools.GsonUtils;
|
||||
@@ -51,6 +52,7 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -59,7 +61,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
/** Tests for {@link google.registry.ui.server.console.ConsoleUpdateRegistrarAction}. */
|
||||
class ConsoleUpdateRegistrarActionTest {
|
||||
private static final Gson GSON = GsonUtils.provideGson();
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2025-01-01T00:00:00.000Z"));
|
||||
private ConsoleApiParams consoleApiParams;
|
||||
private FakeResponse response;
|
||||
|
||||
@@ -75,6 +77,10 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
@Order(Integer.MAX_VALUE)
|
||||
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
|
||||
|
||||
@RegisterExtension
|
||||
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
createTlds("app", "dev");
|
||||
@@ -95,10 +101,6 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
consoleApiParams = createParams();
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
final JpaTestExtensions.JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
@Test
|
||||
void testSuccess_updatesRegistrar() throws IOException {
|
||||
var action =
|
||||
@@ -108,7 +110,7 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2025-01-01T00:00:00.000Z\""));
|
||||
"\"2024-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
|
||||
@@ -119,6 +121,33 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
assertThat(history.getDescription()).hasValue("TheRegistrar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_updatesNullPocVerificationDate() throws IOException {
|
||||
var action =
|
||||
createAction(
|
||||
String.format(registrarPostData, "TheRegistrar", "app, dev", false, "\"null\""));
|
||||
action.run();
|
||||
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(newRegistrar.getLastPocVerificationDate())
|
||||
.isEqualTo(DateTime.parse("1970-01-01T00:00:00.000Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pocVerificationInTheFuture() throws IOException {
|
||||
var action =
|
||||
createAction(
|
||||
String.format(
|
||||
registrarPostData,
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2025-02-01T00:00:00.000Z\""));
|
||||
action.run();
|
||||
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat((String) ((FakeResponse) consoleApiParams.response()).getPayload())
|
||||
.contains("Invalid value of LastPocVerificationDate - value is in the future");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFails_missingWhoisContact() throws IOException {
|
||||
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
|
||||
@@ -129,7 +158,7 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2025-01-01T00:00:00.000Z\""));
|
||||
"\"2024-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
assertThat(((FakeResponse) consoleApiParams.response()).getStatus()).isEqualTo(SC_BAD_REQUEST);
|
||||
assertThat((String) ((FakeResponse) consoleApiParams.response()).getPayload())
|
||||
@@ -159,7 +188,7 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2025-01-01T00:00:00.000Z\""));
|
||||
"\"2024-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
Registrar newRegistrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
assertThat(newRegistrar.getAllowedTlds()).containsExactly("app", "dev");
|
||||
@@ -176,7 +205,7 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
"TheRegistrar",
|
||||
"app, dev",
|
||||
false,
|
||||
"\"2025-01-01T00:00:00.000Z\""));
|
||||
"\"2024-12-12T00:00:00.000Z\""));
|
||||
action.run();
|
||||
verify(consoleApiParams.sendEmailUtils().gmailClient, times(1))
|
||||
.sendEmail(
|
||||
@@ -190,7 +219,7 @@ class ConsoleUpdateRegistrarActionTest {
|
||||
+ "\n"
|
||||
+ "allowedTlds: null -> [app, dev]\n"
|
||||
+ "lastPocVerificationDate: 1970-01-01T00:00:00.000Z ->"
|
||||
+ " 2025-01-01T00:00:00.000Z\n")
|
||||
+ " 2024-12-12T00:00:00.000Z\n")
|
||||
.setRecipients(ImmutableList.of(new InternetAddress("notification@test.example")))
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@ package google.registry.webdriver;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.server.Fixture.BASIC;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.console.GlobalRole;
|
||||
import google.registry.model.console.RegistrarRole;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.server.RegistryTestServer;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
@@ -74,6 +78,10 @@ public class ConsoleScreenshotTest {
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER));
|
||||
Registrar registrar = Registrar.loadByRegistrarId("TheRegistrar").get();
|
||||
registrar =
|
||||
registrar.asBuilder().setLastPocVerificationDate(DateTime.now(DateTimeZone.UTC)).build();
|
||||
persistResource(registrar);
|
||||
loadHomePage();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user