-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature in app referral program #1594
Changes from 8 commits
41eac36
3e9e166
cdbb06a
bb8dcb4
06ba533
9a00a43
a1b0eaa
f19b462
73253dc
43eb0b2
a170c3a
d3aa29e
6beb2e1
e92afde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { Routes, RouterModule } from '@angular/router'; | ||
|
||
import { InvitationPage } from './invitation.page'; | ||
|
||
const routes: Routes = [ | ||
{ | ||
path: '', | ||
component: InvitationPage, | ||
}, | ||
]; | ||
|
||
@NgModule({ | ||
imports: [RouterModule.forChild(routes)], | ||
exports: [RouterModule], | ||
}) | ||
export class InvitationPageRoutingModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { SharedModule } from '../../shared/shared.module'; | ||
import { InvitationPageRoutingModule } from './invitation-routing.module'; | ||
import { InvitationPage } from './invitation.page'; | ||
|
||
@NgModule({ | ||
imports: [SharedModule, InvitationPageRoutingModule], | ||
declarations: [InvitationPage], | ||
}) | ||
export class InvitationPageModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<mat-toolbar *transloco="let t"> | ||
<button routerLink=".." routerDirection="back" mat-icon-button> | ||
<mat-icon>arrow_back</mat-icon> | ||
</button> | ||
<span>{{ t('invitation.invitation') }}</span> | ||
</mat-toolbar> | ||
|
||
<ion-content *transloco="let t"> | ||
<ng-container *ngIf="referralCode$ | ngrxPush as referralCode"> | ||
<ion-card> | ||
<ion-grid> | ||
<ion-row> | ||
<ion-col sizeMd="6" offsetMd="3" sizeXs="10" offsetXs="1"> | ||
<div class="title">Share invitation code</div> | ||
<div class="subtitle">Share to get rewarded</div> | ||
|
||
<div class="social-icons-container"> | ||
<ion-avatar> | ||
<img src="/assets/images/share-icons/share-fb.png" /> | ||
</ion-avatar> | ||
<ion-avatar> | ||
<img src="/assets/images/share-icons/share-line.png" /> | ||
</ion-avatar> | ||
<ion-avatar> | ||
<img src="/assets/images/share-icons/share-email.jpg" /> | ||
</ion-avatar> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these icons supposed to be view only? As a user it seems pretty tempting to press these icons to share but then user will find out they are not buttons. |
||
<div class="share-more">更多</div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Translation should be applied here. |
||
</div> | ||
|
||
<div class="title">{{ referralCode }}</div> | ||
|
||
<div class="spacer-12"></div> | ||
|
||
<ion-button (click)="shareReferralCode()" expand="block"> | ||
Share | ||
</ion-button> | ||
</ion-col> | ||
</ion-row> | ||
</ion-grid> | ||
</ion-card> | ||
</ng-container> | ||
|
||
<div class="spacer-24"></div> | ||
</ion-content> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
ion-card { | ||
padding-top: 8px; | ||
padding-bottom: 8px; | ||
background: white; | ||
border-radius: 15px; | ||
} | ||
|
||
.title { | ||
text-align: center; | ||
font-style: normal; | ||
font-weight: 500; | ||
font-size: 24px; | ||
color: black; | ||
} | ||
|
||
.subtitle { | ||
text-align: center; | ||
font-size: 14px; | ||
margin-bottom: 12px; | ||
color: black; | ||
} | ||
|
||
.social-icons-container { | ||
display: flex; | ||
justify-content: center; | ||
margin: 24px 8px 32px; | ||
} | ||
|
||
ion-avatar { | ||
width: 42px; | ||
height: 42px; | ||
margin: 0 6px; | ||
} | ||
|
||
.share-more { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
height: 42px; | ||
width: 42px; | ||
background-color: #fcecd4; | ||
border-radius: 50%; | ||
margin: 0 6px; | ||
} | ||
|
||
ion-button { | ||
--border-radius: 15px; | ||
--color: white; | ||
--background: #00c9ff; | ||
|
||
font-style: normal; | ||
font-weight: 500; | ||
font-size: 18px; | ||
} | ||
|
||
.spacer-12 { | ||
height: 12px; | ||
} | ||
|
||
.spacer-24 { | ||
height: 24px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; | ||
import { IonicModule } from '@ionic/angular'; | ||
import { SharedTestingModule } from '../../shared/shared-testing.module'; | ||
import { InvitationPage } from './invitation.page'; | ||
|
||
describe('InvitationPage', () => { | ||
let component: InvitationPage; | ||
let fixture: ComponentFixture<InvitationPage>; | ||
|
||
beforeEach( | ||
waitForAsync(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [InvitationPage], | ||
imports: [IonicModule.forRoot(), SharedTestingModule], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(InvitationPage); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}) | ||
); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,32 @@ | ||||||
import { Component, OnInit } from '@angular/core'; | ||||||
import { DiaBackendAuthService } from '../../shared/dia-backend/auth/dia-backend-auth.service'; | ||||||
import { ShareService } from '../../shared/share/share.service'; | ||||||
|
||||||
@Component({ | ||||||
selector: 'app-invitation', | ||||||
templateUrl: './invitation.page.html', | ||||||
styleUrls: ['./invitation.page.scss'], | ||||||
}) | ||||||
export class InvitationPage implements OnInit { | ||||||
readonly referralCode$ = this.diaBackendAuthService.referralCode$; | ||||||
|
||||||
constructor( | ||||||
private readonly diaBackendAuthService: DiaBackendAuthService, | ||||||
private readonly shareService: ShareService | ||||||
) {} | ||||||
|
||||||
ngOnInit(): void { | ||||||
this.refetchReferralCode(); | ||||||
} | ||||||
|
||||||
async refetchReferralCode() { | ||||||
// return; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line should be removed. |
||||||
const referralCode = await this.diaBackendAuthService.getReferralCode(); | ||||||
if (!referralCode) this.diaBackendAuthService.syncProfile$().toPromise(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The promise should be awaited |
||||||
} | ||||||
|
||||||
async shareReferralCode() { | ||||||
const referralCode = await this.diaBackendAuthService.getReferralCode(); | ||||||
this.shareService.shareReferralCode(referralCode); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ export class SignupPage { | |
username: '', | ||
password: '', | ||
confirmPassword: '', | ||
referralCodeOptional: '', | ||
}; | ||
|
||
fields: FormlyFieldConfig[] = []; | ||
|
@@ -42,6 +43,7 @@ export class SignupPage { | |
this.translocoService.selectTranslate('username'), | ||
this.translocoService.selectTranslate('password'), | ||
this.translocoService.selectTranslate('confirmPassword'), | ||
this.translocoService.selectTranslate('referralCodeOptional'), | ||
]) | ||
.pipe( | ||
tap( | ||
|
@@ -50,12 +52,14 @@ export class SignupPage { | |
usernameTranlation, | ||
passwordTranslation, | ||
confirmPasswordTranslation, | ||
referralCodeOptionalTranslation, | ||
]) => | ||
this.createFormFields( | ||
emailTranslation, | ||
usernameTranlation, | ||
passwordTranslation, | ||
confirmPasswordTranslation | ||
confirmPasswordTranslation, | ||
referralCodeOptionalTranslation | ||
) | ||
), | ||
untilDestroyed(this) | ||
|
@@ -67,7 +71,8 @@ export class SignupPage { | |
emailTranslation: string, | ||
usernameTranlation: string, | ||
passwordTranslation: string, | ||
confirmPasswordTranslation: string | ||
confirmPasswordTranslation: string, | ||
referralCodeOptionalTranslation: string | ||
) { | ||
this.fields = [ | ||
{ | ||
|
@@ -87,6 +92,21 @@ export class SignupPage { | |
), | ||
errorPath: 'confirmPassword', | ||
}, | ||
referralCodeValidator: { | ||
expression: (control: FormGroup) => { | ||
const alphanumeric = /^[A-Z0-9]{6}$/g; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm worrying that doing form validation for lowercase letters would have negative UX impact for users that aren't aware of the fact that the referral code is case-sensitive. It's possible for some users to not understand why the referral code is not working when they type the code in lowercase. In my opinion converting user's input to uppercase automatically would be a more ideal way. Let me know what do you think. |
||
const { referralCodeOptional } = control.value; | ||
|
||
if (referralCodeOptional?.length === 0) return true; | ||
if (referralCodeOptional?.match(alphanumeric)) return true; | ||
|
||
return false; | ||
}, | ||
message: this.translocoService.translate( | ||
'message.invalidReferralCode' | ||
), | ||
errorPath: 'referralCodeOptional', | ||
}, | ||
}, | ||
fieldGroup: [ | ||
{ | ||
|
@@ -152,14 +172,29 @@ export class SignupPage { | |
hideRequiredMarker: true, | ||
}, | ||
}, | ||
{ | ||
key: 'referralCodeOptional', | ||
type: 'input', | ||
templateOptions: { | ||
type: 'text', | ||
placeholder: referralCodeOptionalTranslation, | ||
required: false, | ||
hideRequiredMarker: true, | ||
}, | ||
}, | ||
], | ||
}, | ||
]; | ||
} | ||
|
||
onSubmit() { | ||
const action$ = this.diaBackendAuthService | ||
.createUser$(this.model.username, this.model.email, this.model.password) | ||
.createUser$( | ||
this.model.username, | ||
this.model.email, | ||
this.model.password, | ||
this.model.referralCodeOptional | ||
) | ||
.pipe( | ||
first(), | ||
concatMapTo( | ||
|
@@ -174,6 +209,14 @@ export class SignupPage { | |
) | ||
), | ||
catchError((err: unknown) => { | ||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers | ||
if (err instanceof HttpErrorResponse && err.status === 400) { | ||
return this.errorService.toastError$( | ||
this.translocoService.translate( | ||
'error.diaBackend.invalid_referral_code' | ||
) | ||
); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers | ||
if (err instanceof HttpErrorResponse && err.status === 401) | ||
return this.errorService.toastError$( | ||
|
@@ -197,4 +240,5 @@ interface SignupFormModel { | |
username: string; | ||
password: string; | ||
confirmPassword: string; | ||
referralCodeOptional: string; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translation should be applied.