diff --git a/setup-wizard/src/app/app-routing.module.ts b/setup-wizard/src/app/app-routing.module.ts index 1bef74a7f..2e98c1993 100644 --- a/setup-wizard/src/app/app-routing.module.ts +++ b/setup-wizard/src/app/app-routing.module.ts @@ -21,6 +21,10 @@ const routes: Routes = [ path: 'home', loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule), }, + { + path: 'success', + loadChildren: () => import('./pages/success/success.module').then( m => m.SuccessPageModule), + }, ]; @NgModule({ diff --git a/setup-wizard/src/app/app.component.ts b/setup-wizard/src/app/app.component.ts index 376d485f5..34b46b548 100644 --- a/setup-wizard/src/app/app.component.ts +++ b/setup-wizard/src/app/app.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core' import { NavController } from '@ionic/angular' -import { StateService } from './services/state.service' @Component({ selector: 'app-root', @@ -11,12 +10,9 @@ export class AppComponent implements OnInit { constructor( private readonly navCtrl: NavController, - private stateService: StateService - ) {} async ngOnInit() { - this.stateService.reset() - await this.navCtrl.navigateForward(`/home`) + await this.navCtrl.navigateForward(`/product-key`) } } diff --git a/setup-wizard/src/app/pages/embassy/embassy.page.html b/setup-wizard/src/app/pages/embassy/embassy.page.html index 73986f7ac..3998ed891 100644 --- a/setup-wizard/src/app/pages/embassy/embassy.page.html +++ b/setup-wizard/src/app/pages/embassy/embassy.page.html @@ -1,6 +1,6 @@ - +
diff --git a/setup-wizard/src/app/pages/embassy/embassy.page.ts b/setup-wizard/src/app/pages/embassy/embassy.page.ts index ed24c1824..bf3b904a5 100644 --- a/setup-wizard/src/app/pages/embassy/embassy.page.ts +++ b/setup-wizard/src/app/pages/embassy/embassy.page.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { AlertController, ModalController, NavController } from '@ionic/angular' +import { AlertController, LoadingController, ModalController, NavController } from '@ionic/angular' import { ApiService, EmbassyDrive } from 'src/app/services/api/api.service' import { StateService } from 'src/app/services/state.service' import { PasswordPage } from '../password/password.page' @@ -20,6 +20,7 @@ export class EmbassyPage { private modalController: ModalController, private stateService: StateService, private readonly alertCtrl: AlertController, + private loadingCtrl: LoadingController ) {} async ngOnInit() { @@ -35,26 +36,28 @@ export class EmbassyPage { } }) modal.onDidDismiss().then(async ret => { - if (!ret.data && !ret.data.success) return + if (!ret.data || !ret.data.password) return - if(!!this.stateService.recoveryDrive) { - await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward' }) - } else { - const alert = await this.alertCtrl.create({ - cssClass: 'success-alert', - header: 'Success!', - subHeader: `Your Embassy is set up and ready to go.`, - backdropDismiss: false, - buttons: [ - { - text: 'Go To Embassy', - handler: () => { - window.location.reload() - } - } - ] - }) - await alert.present() + const loader = await this.loadingCtrl.create({ + message: 'Setting up your Embassy!' + }) + + await loader.present() + + this.stateService.embassyDrive = drive + this.stateService.embassyPassword = ret.data.password + + try { + this.stateService.torAddress = (await this.stateService.setupEmbassy()).torAddress + } catch (e) { + console.log(e.message) + } finally { + loader.dismiss() + if(!!this.stateService.recoveryDrive) { + await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward' }) + } else { + await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward' }) + } } }) await modal.present(); diff --git a/setup-wizard/src/app/pages/home/home.page.html b/setup-wizard/src/app/pages/home/home.page.html index ef021ae44..9a4fd828a 100644 --- a/setup-wizard/src/app/pages/home/home.page.html +++ b/setup-wizard/src/app/pages/home/home.page.html @@ -1,6 +1,6 @@ - +
diff --git a/setup-wizard/src/app/pages/loading/loading.page.html b/setup-wizard/src/app/pages/loading/loading.page.html index a63cbd3d3..c34d32494 100644 --- a/setup-wizard/src/app/pages/loading/loading.page.html +++ b/setup-wizard/src/app/pages/loading/loading.page.html @@ -1,6 +1,6 @@ - +
@@ -9,7 +9,7 @@ - Loading Embassy + Recovering From Backup Progress: {{(stateService.dataProgress * 100).toFixed(0) }}% @@ -21,16 +21,4 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/setup-wizard/src/app/pages/password/password.page.html b/setup-wizard/src/app/pages/password/password.page.html index 538bfdc5e..9ea310dad 100644 --- a/setup-wizard/src/app/pages/password/password.page.html +++ b/setup-wizard/src/app/pages/password/password.page.html @@ -1,51 +1,74 @@ - Verify Recovery Drive Password + Unlock Drive Set Password -
- -

Password:

- -
- -
-

Warning: After submit, any data currently stored on {{ embassyDrive.labels.length ? embassyDrive.labels.join(' / ') : embassyDrive.logicalname }} will be wiped.

-

Password:

- +
+
+

Warning: After submit, any data currently stored on {{ embassyDrive.labels.length ? embassyDrive.labels.join(' / ') : embassyDrive.logicalname }} will be wiped.

-

Verify Password:

- +

+ Password: +

+ + + + + + + +

{{pwError}}

+ +

+ Verify Password: +

+ + + + + + + +

{{ verError }}

+
-

{{error}}

-
+ + - + Cancel - - {{ !recoveryDrive ? 'Verify Password' : 'Submit' }} + + {{ !!recoveryDrive ? 'Unlock' : 'Submit' }} diff --git a/setup-wizard/src/app/pages/password/password.page.scss b/setup-wizard/src/app/pages/password/password.page.scss index 20c5e6fb9..f095edb41 100644 --- a/setup-wizard/src/app/pages/password/password.page.scss +++ b/setup-wizard/src/app/pages/password/password.page.scss @@ -3,7 +3,26 @@ margin-inline-end: 0; margin: 6px; height: 48px; - --background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%); +} + +.input-label { + color: var(--ion-color-dark); + // padding-top: 10px; + margin-bottom: 6px; + font-size: medium; + font-weight: 500; + * { + display: inline-block; + vertical-align: middle; + } +} + +.error-border { + border: 2px solid var(--ion-color-danger); +} + +.success-border { + border: 2px solid var(--ion-color-success); } ion-input { diff --git a/setup-wizard/src/app/pages/password/password.page.ts b/setup-wizard/src/app/pages/password/password.page.ts index f2c3c57be..4b3af3f56 100644 --- a/setup-wizard/src/app/pages/password/password.page.ts +++ b/setup-wizard/src/app/pages/password/password.page.ts @@ -12,9 +12,13 @@ export class PasswordPage { @Input() recoveryDrive: RecoveryDrive @Input() embassyDrive: EmbassyDrive - error = '' + pwError = '' password = '' + unmasked1 = false + + verError = '' passwordVer = '' + unmasked2 = false constructor( private modalController: ModalController, @@ -27,7 +31,7 @@ export class PasswordPage { async verifyPw () { - if(!this.recoveryDrive) this.error = 'No recovery drive' // unreachable + if(!this.recoveryDrive) this.pwError = 'No recovery drive' // unreachable const loader = await this.loadingCtrl.create({ message: 'Verifying Password' }) @@ -38,10 +42,10 @@ export class PasswordPage { if(isCorrectPassword) { this.modalController.dismiss({ password: this.password }) } else { - this.error = "Incorrect password provided" + this.pwError = "Incorrect password provided" } } catch (e) { - this.error = 'Error connecting to Embassy' + this.pwError = 'Error connecting to Embassy' } finally { loader.dismiss() } @@ -49,37 +53,31 @@ export class PasswordPage { async submitPw () { this.validate() - if (!this.error && this.password !== this.passwordVer) { - this.error="*passwords dont match" + console.log('here') + if (this.password !== this.passwordVer) { + this.verError="*passwords do not match" } - if(this.error) return - const loader = await this.loadingCtrl.create({ - message: 'Setting up your Embassy!' - }) - - await loader.present() - - - this.stateService.embassyDrive = this.embassyDrive - this.stateService.embassyPassword = this.password - - try { - await this.stateService.setupEmbassy() - this.modalController.dismiss({ success: true }) - } catch (e) { - this.error = e.message - } finally { - loader.dismiss() - } + if(this.pwError || this.verError) return + this.modalController.dismiss({ password: this.password }) } validate () { - if (this.password.length < 12) { - this.error="*password must be 12 characters or greater" - } else { - this.error = '' + if(!!this.recoveryDrive) return this.pwError = '' + + if (this.passwordVer) { + this.checkVer() } + + if (this.password.length < 12) { + this.pwError="*password must be 12 characters or greater" + } else { + this.pwError = '' + } + } + + checkVer () { + this.verError = this.password !== this.passwordVer ? "*passwords do not match" : '' } diff --git a/setup-wizard/src/app/pages/product-key/product-key.page.html b/setup-wizard/src/app/pages/product-key/product-key.page.html index f4b81554b..e86f56252 100644 --- a/setup-wizard/src/app/pages/product-key/product-key.page.html +++ b/setup-wizard/src/app/pages/product-key/product-key.page.html @@ -1,6 +1,6 @@ - +
@@ -11,16 +11,20 @@ Enter Product Key - -
-

Product Key

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

*{{ error }}

+
+
+ Submit diff --git a/setup-wizard/src/app/pages/product-key/product-key.page.scss b/setup-wizard/src/app/pages/product-key/product-key.page.scss index e69de29bb..506e177dd 100644 --- a/setup-wizard/src/app/pages/product-key/product-key.page.scss +++ b/setup-wizard/src/app/pages/product-key/product-key.page.scss @@ -0,0 +1,43 @@ +.selected { + border: 4px solid gray; +} +ion-card-title { + margin: 24px 0; + font-family: 'Montserrat'; + font-size: x-large; + --color: var(--ion-color-light); +} + +ion-item { + --border-radius: 4px; + --border-style: solid; + --border-width: 1px; + --border-color: var(--ion-color-light); +} + +.input-label { + text-align: left; + padding-bottom: 2px; + font-size: small; + color: var(--ion-color-light); + font-weight: bold; +} + +.claim-button { + margin-inline-start: 0; + margin-inline-end: 0; + margin-top: 24px; + height: 48px; + --background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%); +} + +.card-footer { + text-align: left; + --background: rgb(222, 222, 222); + border-top: solid; + border-width: 1px; + border-color: var(--ion-color-medium); + ion-item { + --border-color: var(--ion-color-medium); + } +} \ No newline at end of file diff --git a/setup-wizard/src/app/pages/product-key/product-key.page.ts b/setup-wizard/src/app/pages/product-key/product-key.page.ts index 4bbd81acc..0a1582238 100644 --- a/setup-wizard/src/app/pages/product-key/product-key.page.ts +++ b/setup-wizard/src/app/pages/product-key/product-key.page.ts @@ -1,5 +1,7 @@ import { Component } from '@angular/core' -import { NavController } from '@ionic/angular' +import { LoadingController, NavController } from '@ionic/angular' +import { ApiService } from 'src/app/services/api/api.service' +import { StateService } from 'src/app/services/state.service' @Component({ selector: 'app-product-key', @@ -7,10 +9,42 @@ import { NavController } from '@ionic/angular' styleUrls: ['product-key.page.scss'], }) export class ProductKeyPage { + productKey: string + error: string + constructor( private readonly navCtrl: NavController, + private stateService: StateService, + private apiService: ApiService, + private loadingCtrl: LoadingController, ) {} + async submit () { + if(!this.productKey) return this.error = "Must enter product key" + + const loader = await this.loadingCtrl.create({ + message: 'Verifying Product Key' + }) + await loader.present() + + try { + const state = await this.apiService.verifyProductKey(this.productKey) + this.stateService.productKey = this.productKey + if(state['is-recovering']) { + await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward' }) + } else if (!!state['tor-address']) { + this.stateService.torAddress = state['tor-address'] + await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward' }) + } else { + await this.navCtrl.navigateForward(`/home`, { animationDirection: 'forward' }) + } + } catch (e) { + this.error = e.message + } finally { + loader.dismiss() + } + } + async recoverNav () { await this.navCtrl.navigateForward(`/recover`, { animationDirection: 'forward' }) } diff --git a/setup-wizard/src/app/pages/recover/recover.page.html b/setup-wizard/src/app/pages/recover/recover.page.html index 59db3c6d7..e88b34651 100644 --- a/setup-wizard/src/app/pages/recover/recover.page.html +++ b/setup-wizard/src/app/pages/recover/recover.page.html @@ -1,6 +1,6 @@ - +
diff --git a/setup-wizard/src/app/pages/success/success-routing.module.ts b/setup-wizard/src/app/pages/success/success-routing.module.ts new file mode 100644 index 000000000..60971b5f8 --- /dev/null +++ b/setup-wizard/src/app/pages/success/success-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { SuccessPage } from './success.page'; + +const routes: Routes = [ + { + path: '', + component: SuccessPage, + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class SuccessPageRoutingModule {} diff --git a/setup-wizard/src/app/pages/success/success.module.ts b/setup-wizard/src/app/pages/success/success.module.ts new file mode 100644 index 000000000..488252fe6 --- /dev/null +++ b/setup-wizard/src/app/pages/success/success.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { SuccessPage } from './success.page'; +import { PasswordPageModule } from '../password/password.module'; + +import { SuccessPageRoutingModule } from './success-routing.module'; + + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + SuccessPageRoutingModule, + PasswordPageModule, + ], + declarations: [SuccessPage] +}) +export class SuccessPageModule {} diff --git a/setup-wizard/src/app/pages/success/success.page.html b/setup-wizard/src/app/pages/success/success.page.html new file mode 100644 index 000000000..df702d7ac --- /dev/null +++ b/setup-wizard/src/app/pages/success/success.page.html @@ -0,0 +1,42 @@ + + + + + +
+ +
+ + + + Setup Complete! + +
+ +

Tor Address:

+ + +

{{ stateService.torAddress }}

+
+ + + + + +
+ + +

We suggest you copy and save your tor adress in a safe place. If you happen to loose it, no worries! You can always retrieve it by coming back to {{ window.location.hostname }}.

+
+
+ + + Go To Embassy! + + +
+
+
+
+
+
diff --git a/setup-wizard/src/app/pages/success/success.page.scss b/setup-wizard/src/app/pages/success/success.page.scss new file mode 100644 index 000000000..2deb423f8 --- /dev/null +++ b/setup-wizard/src/app/pages/success/success.page.scss @@ -0,0 +1,49 @@ +.selected { + border: 4px solid gray; +} +ion-card-title { + margin: 24px 0; + font-family: 'Montserrat'; + font-size: x-large; + --color: var(--ion-color-light); +} + +ion-item { + --border-radius: 4px; + --border-style: solid; + --border-width: 1px; + --border-color: var(--ion-color-light); +} + +.input-label { + text-align: left; + padding-bottom: 2px; + font-size: medium; + color: var(--ion-color-light); + font-weight: bold; +} + +.claim-button { + margin-inline-start: 0; + margin-inline-end: 0; + margin-top: 24px; + height: 48px; + --background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%); +} + +.card-footer { + text-align: left; + --background: rgb(222, 222, 222); + border-top: solid; + border-width: 1px; + border-color: var(--ion-color-medium); + ion-item { + --border-color: var(--ion-color-medium); + } +} + +.divider { + margin: 0; + background: linear-gradient(90deg,var(--ion-color-dark) 0,var(--ion-color-medium) 50%,var(--ion-color-dark) 100%); + height: 1px; +} \ No newline at end of file diff --git a/setup-wizard/src/app/pages/success/success.page.ts b/setup-wizard/src/app/pages/success/success.page.ts new file mode 100644 index 000000000..837c35cbd --- /dev/null +++ b/setup-wizard/src/app/pages/success/success.page.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core' +import { NavController, ToastController } from '@ionic/angular' +import { StateService } from 'src/app/services/state.service' + +@Component({ + selector: 'success', + templateUrl: 'success.page.html', + styleUrls: ['success.page.scss'], +}) +export class SuccessPage { + constructor( + public stateService: StateService, + private readonly navCtrl: NavController, + private toastCtrl: ToastController + ) { + this.stateService.torAddress = 'asdfasdfasdf.onion' + } + + window = window + + async copy (): Promise { + let message = '' + await this.copyToClipboard(this.stateService.torAddress) + .then(success => message = success ? 'copied to clipboard!' : 'failed to copy') + + const toast = await this.toastCtrl.create({ + header: message, + position: 'bottom', + duration: 1000, + }) + await toast.present() + } + + async goToEmbassy () { + window.location.reload() + // await this.navCtrl.navigateForward(`/recover`, { animationDirection: 'forward' }) + } + + async copyToClipboard (str: string): Promise { + if (window.isSecureContext) { + return navigator.clipboard.writeText(str) + .then(() => { + return true + }) + .catch(err => { + return false + }) + } else { + const el = document.createElement('textarea') + el.value = str + el.setAttribute('readonly', '') + el.style.position = 'absolute' + el.style.left = '-9999px' + document.body.appendChild(el) + el.select() + const copy = document.execCommand('copy') + document.body.removeChild(el) + return copy + } + } +} + diff --git a/setup-wizard/src/app/services/api/api.service.ts b/setup-wizard/src/app/services/api/api.service.ts index fa371189f..fff7247fd 100644 --- a/setup-wizard/src/app/services/api/api.service.ts +++ b/setup-wizard/src/app/services/api/api.service.ts @@ -3,6 +3,7 @@ import { Subject } from 'rxjs' export abstract class ApiService { protected error$: Subject = new Subject(); watchError$ = this.error$.asObservable(); + abstract verifyProductKey (key: string): Promise<{ "is-recovering": boolean, "tor-address": string }>; abstract getEmbassyDrives (): Promise; abstract getRecoveryDrives (): Promise; abstract getDataTransferProgress (): Promise; @@ -12,7 +13,7 @@ export abstract class ApiService { embassyPassword: string recoveryLogicalname?: string, recoveryPassword?: string - }): Promise + }): Promise<{ "tor-address": string }> } export interface TransferProgress { diff --git a/setup-wizard/src/app/services/api/mock-api.service.ts b/setup-wizard/src/app/services/api/mock-api.service.ts index 475bf6f3d..f288bff9d 100644 --- a/setup-wizard/src/app/services/api/mock-api.service.ts +++ b/setup-wizard/src/app/services/api/mock-api.service.ts @@ -11,24 +11,11 @@ export class MockApiService extends ApiService { super() } - async getState() { + async verifyProductKey(key) { await pauseFor(2000) - return { - 'embassy-drive': - null, - // { - // logicalname: 'name1', - // labels: ['label 1', 'label 2'], - // capacity: 1600, - // used: 200, - // }, - 'recovery-drive': - null, - // { - // logicalname: 'name1', - // version: '0.3.3', - // name: 'My Embassy' - // } + return { + "is-recovering": false, + "tor-address": null } } @@ -41,7 +28,6 @@ export class MockApiService extends ApiService { } async getEmbassyDrives() { - await pauseFor(2000) return [ { logicalname: 'Name1', @@ -79,9 +65,9 @@ export class MockApiService extends ApiService { return password.length > 8 } - async setupEmbassy (setupInfo){ + async setupEmbassy (setupInfo) { await pauseFor(2000) - return + return { "tor-address": 'asdfasdfasdf.onion' } } } diff --git a/setup-wizard/src/app/services/state.service.ts b/setup-wizard/src/app/services/state.service.ts index 87d563c37..526d96a1c 100644 --- a/setup-wizard/src/app/services/state.service.ts +++ b/setup-wizard/src/app/services/state.service.ts @@ -8,6 +8,8 @@ import { ApiService, EmbassyDrive, RecoveryDrive } from './api/api.service' export class StateService { polling = false + productKey: string + embassyDrive: EmbassyDrive; embassyPassword: string recoveryDrive: RecoveryDrive; @@ -15,21 +17,13 @@ export class StateService { dataTransferProgress: { bytesTransfered: number; totalBytes: number } | null; dataProgress = 0; dataProgSubject = new BehaviorSubject(this.dataProgress) + + torAddress: string + constructor( private readonly apiService: ApiService ) {} - reset() { - this.polling = false - - this.embassyDrive = null - this.embassyPassword = null - this.recoveryDrive = null - this.recoveryPassword = null - this.dataTransferProgress = null - this.dataProgress = 0 - } - async pollDataTransferProgress(callback?: () => void) { this.polling = true await pauseFor(1000) @@ -51,13 +45,15 @@ export class StateService { this.pollDataTransferProgress(callback) } - async setupEmbassy () { - await this.apiService.setupEmbassy({ + async setupEmbassy () : Promise<{ torAddress: string }> { + const ret = await this.apiService.setupEmbassy({ embassyLogicalname: this.embassyDrive.logicalname, embassyPassword: this.embassyPassword, recoveryLogicalname: this.recoveryDrive?.logicalname, recoveryPassword: this.recoveryPassword }) + + return { torAddress: ret['tor-address'] } } }