From 39471eb54861629eccfc6b4445c3a9f7e32253cc Mon Sep 17 00:00:00 2001 From: Amy Sorto <8575252+amysorto@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:22:38 -0700 Subject: [PATCH] Add terms of service page (#1970) Co-authored-by: Gino Miceli <228050+gino-m@users.noreply.github.com> --- .../components/header/header.component.html | 2 +- .../app/components/header/header.component.ts | 4 ++ .../sign-in-page/sign-in-page.component.ts | 2 +- web/src/app/pages/about/about.component.ts | 4 +- .../pages/terms/_terms.component-theme.scss | 40 ++++++++++++++ web/src/app/pages/terms/terms.component.html | 49 +++++++++++++++++ web/src/app/pages/terms/terms.component.scss | 49 +++++++++++++++++ web/src/app/pages/terms/terms.component.ts | 55 +++++++++++++++++++ web/src/app/pages/terms/terms.module.ts | 41 ++++++++++++++ web/src/app/routing.module.ts | 6 ++ web/src/app/services/auth/auth.guard.ts | 9 +++ web/src/app/services/auth/auth.service.ts | 9 +++ .../services/data-store/data-store.service.ts | 5 ++ .../services/navigation/navigation.service.ts | 8 +++ web/src/ground-theme.scss | 2 + 15 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 web/src/app/pages/terms/_terms.component-theme.scss create mode 100644 web/src/app/pages/terms/terms.component.html create mode 100644 web/src/app/pages/terms/terms.component.scss create mode 100644 web/src/app/pages/terms/terms.component.ts create mode 100644 web/src/app/pages/terms/terms.module.ts diff --git a/web/src/app/components/header/header.component.html b/web/src/app/components/header/header.component.html index 2f2c157ed..239025b1d 100644 --- a/web/src/app/components/header/header.component.html +++ b/web/src/app/components/header/header.component.html @@ -70,7 +70,7 @@ - + diff --git a/web/src/app/components/header/header.component.ts b/web/src/app/components/header/header.component.ts index 78cbda530..da71edaef 100644 --- a/web/src/app/components/header/header.component.ts +++ b/web/src/app/components/header/header.component.ts @@ -80,6 +80,10 @@ export class HeaderComponent { this.router.navigate([NavigationService.ABOUT]); } + onTermsOfServiceClick() { + this.navigationService.navigateToTermsOfService(); + } + onCancelEditSurveyClick() { if (!this.draftSurveyService.dirty) { this.navigationService.selectSurvey(this.surveyId); diff --git a/web/src/app/components/sign-in-page/sign-in-page.component.ts b/web/src/app/components/sign-in-page/sign-in-page.component.ts index cdde43f1a..2ef0bea22 100644 --- a/web/src/app/components/sign-in-page/sign-in-page.component.ts +++ b/web/src/app/components/sign-in-page/sign-in-page.component.ts @@ -40,7 +40,7 @@ export class SignInPageComponent implements OnInit, OnDestroy { this.authService .isAuthenticated$() .pipe(filter(isAuth => isAuth || environment.useEmulators)) - .subscribe(() => this.navigationService.navigateToSurveyList()) + .subscribe(() => this.navigationService.navigateToTermsOfService()) ); } diff --git a/web/src/app/pages/about/about.component.ts b/web/src/app/pages/about/about.component.ts index 96e9228df..660940d34 100644 --- a/web/src/app/pages/about/about.component.ts +++ b/web/src/app/pages/about/about.component.ts @@ -23,9 +23,9 @@ import {Component} from '@angular/core'; styleUrls: ['./about.component.scss'], }) export class AboutComponent { - constructor(private _location: Location) {} + constructor(private location: Location) {} onBackButtonClick() { - this._location.back(); + this.location.back(); } } diff --git a/web/src/app/pages/terms/_terms.component-theme.scss b/web/src/app/pages/terms/_terms.component-theme.scss new file mode 100644 index 000000000..e137edf18 --- /dev/null +++ b/web/src/app/pages/terms/_terms.component-theme.scss @@ -0,0 +1,40 @@ +/** + * Copyright 2024 The Ground Authors. + * + * 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 + * + * https://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 'sass:map'; +@use '@angular/material' as mat; + +@mixin color($theme) { + .terms-text-container { + background-color: mat.get-theme-color($theme, surface-container-low); + border: 1px solid mat.get-theme-color($theme, outline-variant); + } + + .page ::-webkit-scrollbar-thumb { + background: mat.get-theme-color($theme, surface-dim); + } +} + +@mixin typography($theme) { + .terms-text-container { + font: mat.get-theme-typography($theme, title-medium, font); + } +} + +@mixin theme($theme) { + @include color($theme); + @include typography($theme); +} diff --git a/web/src/app/pages/terms/terms.component.html b/web/src/app/pages/terms/terms.component.html new file mode 100644 index 000000000..3477f219e --- /dev/null +++ b/web/src/app/pages/terms/terms.component.html @@ -0,0 +1,49 @@ + + +
+ +
+

Terms Of Service

+
+ {{termsOfServiceText}} +
+
+ @if(!hasAcceptedTos) { + + I have read and agree with terms of service. + + + } + @else { + + } +
+
+
\ No newline at end of file diff --git a/web/src/app/pages/terms/terms.component.scss b/web/src/app/pages/terms/terms.component.scss new file mode 100644 index 000000000..2b82f6102 --- /dev/null +++ b/web/src/app/pages/terms/terms.component.scss @@ -0,0 +1,49 @@ +/** + * Copyright 2024 The Ground Authors. + * + * 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 + * + * https://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. + */ + +.page { + min-height: 100%; + padding-bottom: 40px; + + .page-content { + display: flex; + flex-direction: column; + margin: 30px 160px 24px 160px; + height: calc(100vh - 220px); + } + + .terms-text-container { + padding: 24px; + overflow-y: scroll; + } + + .action-buttons { + display: flex; + justify-content: space-between; + padding-top: 24px; + } + + ::-webkit-scrollbar { + width: 8px; + background: transparent; + border-radius: 10px; + } + + ::-webkit-scrollbar-thumb { + border-radius: 10px; + width: 8px; + } +} diff --git a/web/src/app/pages/terms/terms.component.ts b/web/src/app/pages/terms/terms.component.ts new file mode 100644 index 000000000..3183ae1cb --- /dev/null +++ b/web/src/app/pages/terms/terms.component.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2024 The Ground Authors. + * + * 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 + * + * https://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 {Location} from '@angular/common'; +import {Component, OnInit} from '@angular/core'; + +import {AuthService} from 'app/services/auth/auth.service'; +import {DataStoreService} from 'app/services/data-store/data-store.service'; +import {NavigationService} from 'app/services/navigation/navigation.service'; + +@Component({ + selector: 'ground-terms-page', + templateUrl: './terms.component.html', + styleUrls: ['./terms.component.scss'], +}) +export class TermsComponent implements OnInit { + hasAcceptedTos: boolean; + isTermsOfServiceChecked = false; + termsOfServiceText = ''; + + constructor( + private authService: AuthService, + private dataStore: DataStoreService, + private navigationService: NavigationService, + private location: Location + ) { + this.hasAcceptedTos = this.authService.getHasAcceptedTos(); + } + + async ngOnInit() { + this.termsOfServiceText = await this.dataStore.getTermsOfService(); + } + + onContinueButtonClick() { + this.authService.approveTos(); + this.navigationService.navigateToSurveyList(); + } + + onBackButtonClick() { + this.location.back(); + } +} diff --git a/web/src/app/pages/terms/terms.module.ts b/web/src/app/pages/terms/terms.module.ts new file mode 100644 index 000000000..de9aa62be --- /dev/null +++ b/web/src/app/pages/terms/terms.module.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2024 The Ground Authors. + * + * 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 + * + * https://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 {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatIcon} from '@angular/material/icon'; +import {RouterModule} from '@angular/router'; + +import {HeaderModule} from 'app/components/header/header.module'; +import {TermsComponent} from 'app/pages/terms/terms.component'; + +@NgModule({ + declarations: [TermsComponent], + imports: [ + CommonModule, + FormsModule, + HeaderModule, + RouterModule, + MatButton, + MatCheckboxModule, + MatIcon, + ], + exports: [TermsComponent], +}) +export class TermsModule {} diff --git a/web/src/app/routing.module.ts b/web/src/app/routing.module.ts index a3215eac0..f40eff679 100644 --- a/web/src/app/routing.module.ts +++ b/web/src/app/routing.module.ts @@ -37,6 +37,7 @@ import {EditSurveyModule} from './pages/edit-survey/edit-survey.module'; import {SurveyJsonComponent} from './pages/edit-survey/survey-json/survey-json.component'; import {ErrorComponent} from './pages/error/error.component'; import {ErrorModule} from './pages/error/error.module'; +import {TermsComponent} from './pages/terms/terms.component'; const routes: Routes = [ { @@ -90,6 +91,11 @@ const routes: Routes = [ component: AboutComponent, canActivate: [AuthGuard], }, + { + path: NavigationService.TERMS, + component: TermsComponent, + canActivate: [AuthGuard], + }, ]; const config = RouterModule.forRoot(routes, {}); diff --git a/web/src/app/services/auth/auth.guard.ts b/web/src/app/services/auth/auth.guard.ts index 621e30ea1..0339f77e6 100644 --- a/web/src/app/services/auth/auth.guard.ts +++ b/web/src/app/services/auth/auth.guard.ts @@ -56,6 +56,15 @@ export class AuthGuard { this.navigationService.navigateToSurveyList(); return false; } + + if (url.includes(NavigationService.TERMS)) { + if (user.isAuthenticated) { + return true; + } + this.navigationService.signIn(); + return false; + } + if (user.isAuthenticated) { return true; } diff --git a/web/src/app/services/auth/auth.service.ts b/web/src/app/services/auth/auth.service.ts index b2416e4b7..96c4e6e28 100644 --- a/web/src/app/services/auth/auth.service.ts +++ b/web/src/app/services/auth/auth.service.ts @@ -53,6 +53,7 @@ export const ROLE_OPTIONS = [ export class AuthService { private user$: Observable; private currentUser!: User; + private hasAcceptedTos = false; constructor( private afAuth: AngularFireAuth, @@ -119,6 +120,14 @@ export class AuthService { return this.currentUser; } + getHasAcceptedTos(): boolean { + return this.hasAcceptedTos; + } + + approveTos(): void { + this.hasAcceptedTos = true; + } + async signIn() { const provider = new GoogleAuthProvider(); await this.afAuth.signInWithPopup(provider); diff --git a/web/src/app/services/data-store/data-store.service.ts b/web/src/app/services/data-store/data-store.service.ts index 53b1525fb..ca1930ac9 100644 --- a/web/src/app/services/data-store/data-store.service.ts +++ b/web/src/app/services/data-store/data-store.service.ts @@ -554,6 +554,11 @@ export class DataStoreService { return getDownloadURL(ref(getStorage(), path)); } + async getTermsOfService(): Promise { + const tos = await this.db.collection('config').doc('tos').ref.get(); + return tos.get('text'); + } + addOrUpdateTasks( surveyId: string, job: Job, diff --git a/web/src/app/services/navigation/navigation.service.ts b/web/src/app/services/navigation/navigation.service.ts index 4dbe9bf6d..8e610d7ef 100644 --- a/web/src/app/services/navigation/navigation.service.ts +++ b/web/src/app/services/navigation/navigation.service.ts @@ -50,6 +50,7 @@ export class NavigationService { static readonly JOB_SEGMENT = 'job'; static readonly ERROR = 'error'; static readonly ABOUT = 'about'; + static readonly TERMS = 'terms'; private sidePanelExpanded = true; @@ -280,6 +281,13 @@ export class NavigationService { ]); } + /** + * Navigate to the terms of service page + */ + navigateToTermsOfService() { + this.router.navigate([NavigationService.TERMS]); + } + /** * Navigate to the URL for viewing a list of available surveys. */ diff --git a/web/src/ground-theme.scss b/web/src/ground-theme.scss index 3b751d556..122fb0703 100644 --- a/web/src/ground-theme.scss +++ b/web/src/ground-theme.scss @@ -49,6 +49,7 @@ @use 'app/pages/main-page-container/main-page/survey-header/_survey-header.component-theme' as survey-header; @use 'app/pages/main-page-container/main-page/title-dialog/_title-dialog.component-theme' as title-dialog; @use 'app/pages/main-page-container/main-page/secondary-side-panel/_secondary-side-panel.component-theme' as secondary-side-panel; +@use 'app/pages/terms/_terms.component-theme.scss' as terms; @import 'firebaseui/dist/firebaseui.css'; @@ -92,6 +93,7 @@ html { @include survey-header.theme($theme); @include title-dialog.theme($theme); @include secondary-side-panel.theme($theme); + @include terms.theme($theme); } // Global styles