Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e151636
feat: add top up page structure
Guilhermeasper Feb 19, 2025
1c893c1
fix: remove template copy
Guilhermeasper Feb 20, 2025
345b874
feat: basic flow
Guilhermeasper Feb 23, 2025
30813f2
feat: pix transaction data
Guilhermeasper Mar 1, 2025
6fed803
feat: add error handling and pix timer
Guilhermeasper Mar 4, 2025
e540bab
fix: default timeout
Guilhermeasper Mar 4, 2025
1c4e133
refactor: cleanup of unnecessary code
Guilhermeasper Mar 4, 2025
c6a4572
refactor: remove unused import
Guilhermeasper Mar 4, 2025
c59d0b1
refactor: code structure and naming
Guilhermeasper Mar 4, 2025
4f18b24
refactor: code structure and naming
Guilhermeasper Mar 4, 2025
35c3ddc
feat: ui improvements
Guilhermeasper Mar 4, 2025
89b4adc
feat: ui improvements
Guilhermeasper Mar 5, 2025
1313f97
feat: add balance preview on calculator
Guilhermeasper Mar 5, 2025
02e8f14
feat: remove top up card for full grant students
Guilhermeasper Mar 5, 2025
10ebb79
fix: use explicit types and required
Guilhermeasper Mar 6, 2025
343bbcf
fix: add back mask directive
Guilhermeasper Mar 6, 2025
730be17
fix: check for NaN in calculator preview
Guilhermeasper Mar 6, 2025
eb3f5ad
fix: parse cpf only if it has 11 digits
Guilhermeasper Mar 6, 2025
1cbbc14
fix: remove on push from top up page
Guilhermeasper Mar 6, 2025
8054508
refactor: title text case
Guilhermeasper Mar 6, 2025
2ff6284
fix: correct spelling of FIFTEEN_MINUTES constant
Guilhermeasper Mar 6, 2025
6e34d27
fix: use enum instead of hardcoded values
Guilhermeasper Mar 6, 2025
19918fa
refactor: pix spelling
Guilhermeasper Mar 6, 2025
0dd088c
fix: check cpf length before parsing
Guilhermeasper Mar 6, 2025
225a023
fix: enhance balance preview
Guilhermeasper Mar 6, 2025
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
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"angular-schematics.schematicsDefaultOptions": {
"angular-*": {
"skipStyle": true,
"externalTemplate": true
}
}
}
15 changes: 15 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"date-fns": "^4.1.0",
"idb-keyval": "^6.2.1",
"jwt-decode": "^4.0.0",
"ngx-currency": "^19.0.0",
"ngx-mask": "^19.0.6",
"rxjs": "~7.8.2",
"superjson": "^2.2.2",
Expand Down
4 changes: 4 additions & 0 deletions public/assets/icons/bootstrapCash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/assets/icons/bootstrapCreditCard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/assets/icons/bootstrapDebitCard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/assets/icons/pix.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { provideHttpClient } from '@angular/common/http';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import {
ApplicationConfig,
inject,
Expand All @@ -25,6 +25,8 @@ import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { getAuth, provideAuth } from '@angular/fire/auth';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';

import { provideNgIconLoader } from '@ng-icons/core';

import { routes } from '@rusbe/app.routes';
import { environment } from '@rusbe/environments/environment';
import { version } from '@rusbe/environments/version';
Expand Down Expand Up @@ -69,5 +71,9 @@ export const appConfig: ApplicationConfig = {
}),
ScreenTrackingService,
UserTrackingService,
provideNgIconLoader((name) => {
const http = inject(HttpClient);
return http.get(`/assets/icons/${name}.svg`, { responseType: 'text' });
}),
],
};
6 changes: 6 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export const routes: Routes = [
(m) => m.LegalTermsPageComponent,
),
},
{
path: 'top-up',
loadComponent: () =>
import('./pages/top-up/top-up.component').then((m) => m.TopUpComponent),
...canActivate(redirectFirebaseUnauthorizedToLogin),
},
{
path: '404',
loadComponent: () =>
Expand Down
29 changes: 29 additions & 0 deletions src/app/components/cards/card-button/card-button.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div class="grouping-card-strip-button-inner">
@if (iconName()) {
<ng-icon
class="min-w-fit fill-accent"
[name]="iconName()"
aria-hidden="true"
></ng-icon>
} @else {
<div></div>
}
<div class="w-full text-overlay-contrast">
<h3><ng-content></ng-content></h3>
<p class="my-1 text-xs font-normal sm:text-base">
{{ subtitle() }}
</p>
</div>
@if (badgeText()) {
<p
class="transition-appear whitespace-pre rounded-md bg-accent px-2 py-1 font-geometric text-xs font-bold uppercase text-accent-contrast sm:mx-6 sm:mt-6"
>
{{ badgeText() }}
</p>
}
<ng-icon
class="min-w-fit"
name="lucideChevronRight"
aria-hidden="true"
></ng-icon>
</div>
17 changes: 17 additions & 0 deletions src/app/components/cards/card-button/card-button.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';

import { NgIcon } from '@ng-icons/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'button[rusbe-card-button]',
imports: [NgIcon],
templateUrl: './card-button.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'grouping-card-strip-button w-full disabled:opacity-50' },
})
export class CardButtonComponent {
subtitle = input.required<string>();
iconName = input.required<string>();
badgeText = input<string | undefined>();
}
2 changes: 2 additions & 0 deletions src/app/components/cards/card-group/card-group.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h2 class="grouping-card-title px-responsive">{{ title() }}</h2>
<ng-content></ng-content>
12 changes: 12 additions & 0 deletions src/app/components/cards/card-group/card-group.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'section[rusbe-card-group]',
templateUrl: './card-group.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'grouping-card' },
})
export class CardGroupComponent {
title = input.required();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ import { ButtonColorScheme } from '@rusbe/components/header/button-color-scheme'
})
export class BackButtonComponent {
colorScheme = input<ButtonColorScheme>(ButtonColorScheme.Page);
customAction = input<(() => boolean) | null>(null);
ButtonColorScheme = ButtonColorScheme;

router = inject(Router);
location = inject(Location);

goBack() {
const customAction = this.customAction()?.();
if (customAction) return;

if (this.router.lastSuccessfulNavigation?.previousNavigation != null) {
this.location.back();
} else {
Expand Down
1 change: 1 addition & 0 deletions src/app/components/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ <h1 class="h-6 text-base sm:h-8">

@if (hasBackButton()) {
<rusbe-back-button
[customAction]="customAction()"
[colorScheme]="buttonColorSchemeMap[type()]!"
></rusbe-back-button>
}
Expand Down
1 change: 1 addition & 0 deletions src/app/components/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class HeaderComponent {
HeaderType = HeaderType;

type = input<HeaderType>(HeaderType.LogoWithUserAccountButton);
customAction = input<(() => boolean) | null>(null);

readonly buttonColorSchemeMap: Partial<
Record<HeaderType, ButtonColorScheme>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<section class="reduced-page-container flex w-full flex-grow p-4">
<form
class="relative flex w-full flex-col gap-2"
(ngSubmit)="onSubmitValue()"
>
<span class="absolute start-6 top-[43px] font-grotesque text-4xl">R$</span>
<input
class="block w-full rounded-lg border-transparent bg-overlay p-10 pb-4 pl-[84px] font-geometric text-4xl font-bold shadow-lg placeholder:text-transparent autofill:pb-4 autofill:pt-10 focus:border-accent focus:pb-4 focus:pt-10 focus:ring-accent disabled:pointer-events-none disabled:opacity-50"
inputmode="numeric"
type="text"
name="top-up-value"
id="top-up-calculator-currency-input"
aria-errormessage="currency-input-error"
[currencyMask]="maskConfig"
[formControl]="topUpValue"
[disabled]="inputDisabled()"
[(ngModel)]="value"
/>
<label
for="top-up-calculator-currency-input"
class="text-md pointer-events-none absolute start-6 top-4 origin-[0_0] truncate border border-transparent transition duration-100 ease-in-out"
>Valor</label
>
@if (topUpValue.touched && topUpValue.hasError('lowTopUpValue')) {
<rusbe-warning-card class="transition-appear"
><p>
O valor da recarga deve ser no mínimo R$ 1,00
</p></rusbe-warning-card
>
} @else if (topUpValue.touched && topUpValue.hasError('highTopUpValue')) {
<rusbe-warning-card class="transition-appear"
><p>
O valor da recarga deve ser no máximo R$ 100,00
</p></rusbe-warning-card
>
}
</form>
</section>
<section *ngIf="false" class="grouping-card px-responsive">
<rusbe-meal-balance-breakdown></rusbe-meal-balance-breakdown>
</section>
<section
class="reduced-page-container px-responsive flex w-full flex-grow flex-col justify-end gap-5 p-4"
>
@if (currentBalance().value.toString() !== newBalance().toString()) {
<p>
Você tem R$ <strong>{{ currentBalance().value }} </strong> de saldo. Após
a transação, você ficará com R$ <strong> {{ newBalance() }}</strong
>.
</p>
} @else {
<p>
Você tem R$ <strong>{{ currentBalance().value }} </strong> de saldo.
</p>
}
<button
(click)="onSubmitValue()"
class="button-large button-split bg-accent text-accent-contrast focus:ring-offset-background"
>
Confirmar
<ng-icon
class="color-accent-contrast min-w-fit"
name="lucideMoveRight"
aria-hidden="true"
></ng-icon>
</button>
</section>
114 changes: 114 additions & 0 deletions src/app/components/top-up/calculator/top-up-calculator.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { NgIf } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
model,
output,
signal,
} from '@angular/core';
import {
AbstractControl,
FormControl,
FormsModule,
ReactiveFormsModule,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideMoveRight } from '@ng-icons/lucide';
import { NgxCurrencyDirective, NgxCurrencyInputMode } from 'ngx-currency';

import { MealBalanceBreakdownComponent } from '@rusbe/components/meal-balance-breakdown/meal-balance-breakdown.component';
import { WarningCardComponent } from '@rusbe/components/warning-card/warning-card.component';
import { GeneralGoodsPartialGrantBalance } from '@rusbe/services/general-goods/general-goods.service';
import { BrlCurrency } from '@rusbe/types/brl-currency';

@Component({
selector: 'rusbe-top-up-calculator',
imports: [
NgIf,
MealBalanceBreakdownComponent,
FormsModule,
NgxCurrencyDirective,
ReactiveFormsModule,
WarningCardComponent,
NgIcon,
],
templateUrl: './top-up-calculator.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
class: 'flex flex-grow flex-col items-start justify-start',
},
viewProviders: [
provideIcons({
lucideMoveRight,
}),
],
})
export class TopUpCalculatorComponent {
submitted = output<boolean>();

value = model.required<string>();
currentBalance = input.required<GeneralGoodsPartialGrantBalance>();

inputDisabled = signal(false);

newBalance = computed(() => {
const topUpValueFloat = parseFloat(this.value());
const currentBalance = this.currentBalance().value;

if (isNaN(topUpValueFloat)) return currentBalance;

return currentBalance.add(BrlCurrency.fromNumber(topUpValueFloat));
});

readonly topUpValue = new FormControl(
{ value: '0', disabled: this.inputDisabled() },
{
validators: [
Validators.required,
lowTopUpValueValidator,
highTopUpValueValidator,
],
},
);

readonly maskConfig = {
align: 'left',
allowNegative: false,
allowZero: true,
decimal: ',',
precision: 2,
prefix: '',
suffix: '',
thousands: '',
nullable: true,
min: null,
max: null,
inputMode: NgxCurrencyInputMode.Financial,
};

onSubmitValue() {
this.topUpValue.markAsTouched();
if (this.topUpValue.invalid) return;
this.submitted.emit(true);
}
}

const lowTopUpValueValidator: ValidatorFn = (
control: AbstractControl,
): ValidationErrors | null => {
const value = control.value;
return value < 1 ? { lowTopUpValue: true } : null;
};

const highTopUpValueValidator: ValidatorFn = (
control: AbstractControl,
): ValidationErrors | null => {
const value = control.value;
return value > 100 ? { highTopUpValue: true } : null;
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
selector: 'rusbe-top-up-credit-card',
imports: [],
templateUrl: './top-up-credit-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TopUpCreditCardComponent {}
Loading