Skip to content
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

Merged
merged 14 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const routes: Routes = [
m => m.WalletsPageModule
),
},
{
path: 'invitation',
loadChildren: () =>
import('./features/invitation/invitation.module').then(
m => m.InvitationPageModule
),
},
];
@NgModule({
imports: [
Expand Down
5 changes: 5 additions & 0 deletions src/app/features/home/home.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
CaptureClub
</a>
</mat-list-item>
<mat-list-item>
<a routerLink="/invitation" (click)="sidenav.close()" mat-list-item>
{{ t('invitation.invitation') }}
</a>
</mat-list-item>
<mat-list-item>
<a routerLink="/privacy" (click)="sidenav.close()" mat-list-item>
{{ t('privacy') }}
Expand Down
17 changes: 17 additions & 0 deletions src/app/features/invitation/invitation-routing.module.ts
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 {}
10 changes: 10 additions & 0 deletions src/app/features/invitation/invitation.module.ts
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 {}
31 changes: 31 additions & 0 deletions src/app/features/invitation/invitation.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<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">{{ t('invitation.shareInvitationCode') }}</div>
<div class="subtitle">{{ t('invitation.shareToGetRewarded') }}</div>

<div class="title">{{ referralCode }}</div>

<div class="spacer-12"></div>

<ion-button (click)="shareReferralCode()" expand="block">
{{ t('share') }}
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ion-card>
</ng-container>

<div class="spacer-24"></div>
</ion-content>
39 changes: 39 additions & 0 deletions src/app/features/invitation/invitation.page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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;
}

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;
}
26 changes: 26 additions & 0 deletions src/app/features/invitation/invitation.page.spec.ts
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();
});
});
32 changes: 32 additions & 0 deletions src/app/features/invitation/invitation.page.ts
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() {
const referralCode = await this.diaBackendAuthService.getReferralCode();
if (!referralCode)
await this.diaBackendAuthService.syncProfile$().toPromise();
}

async shareReferralCode() {
const referralCode = await this.diaBackendAuthService.getReferralCode();
this.shareService.shareReferralCode(referralCode);
}
}
54 changes: 51 additions & 3 deletions src/app/features/signup/signup.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class SignupPage {
username: '',
password: '',
confirmPassword: '',
referralCodeOptional: '',
};

fields: FormlyFieldConfig[] = [];
Expand All @@ -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(
Expand All @@ -50,12 +52,14 @@ export class SignupPage {
usernameTranlation,
passwordTranslation,
confirmPasswordTranslation,
referralCodeOptionalTranslation,
]) =>
this.createFormFields(
emailTranslation,
usernameTranlation,
passwordTranslation,
confirmPasswordTranslation
confirmPasswordTranslation,
referralCodeOptionalTranslation
)
),
untilDestroyed(this)
Expand All @@ -67,7 +71,8 @@ export class SignupPage {
emailTranslation: string,
usernameTranlation: string,
passwordTranslation: string,
confirmPasswordTranslation: string
confirmPasswordTranslation: string,
referralCodeOptionalTranslation: string
) {
this.fields = [
{
Expand All @@ -87,6 +92,21 @@ export class SignupPage {
),
errorPath: 'confirmPassword',
},
referralCodeValidator: {
expression: (control: FormGroup) => {
const alphanumeric = /^[A-Z0-9]{6}$/g;
Copy link
Contributor

Choose a reason for hiding this comment

The 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: [
{
Expand Down Expand Up @@ -152,14 +172,33 @@ export class SignupPage {
hideRequiredMarker: true,
},
},
{
key: 'referralCodeOptional',
type: 'input',
templateOptions: {
type: 'text',
placeholder: referralCodeOptionalTranslation,
required: false,
hideRequiredMarker: true,
},
expressionProperties: {
'model.referralCodeOptional': 'model.referralCodeOptional',
},
parsers: [(value: any) => value?.toUpperCase()],
},
],
},
];
}

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(
Expand All @@ -174,6 +213,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$(
Expand All @@ -197,4 +244,5 @@ interface SignupFormModel {
username: string;
password: string;
confirmPassword: string;
referralCodeOptional: string;
}
Loading