-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/7 add new form to send transactions (#9)
* update dependencies and add form component for transfer funds * add new account transfer form for sending transactions with solana * add token info endpoint for loading the token decimals * fix rpc url for devnet * fix transaction issues
- Loading branch information
1 parent
a91bbc8
commit a50614e
Showing
21 changed files
with
689 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
apps/webapp/src/app/components/account/send/send.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<webapp-forms-transfer | ||
*ngIf="(publicKey$ | async) as publicKey; else elsePublicKey" | ||
[isLoading]="!!(isSendTransactionLoading && isSendTransactionLoading())" | ||
(submitForm)="onSubmitForm($event, publicKey)" | ||
/> | ||
|
||
<ng-template #elsePublicKey> | ||
<div *ngIf="(connected$ | async) as connected; else disconnected" class="mt-4 flex justify-center"> | ||
<mat-spinner diameter="32"></mat-spinner> | ||
</div> | ||
<ng-template #disconnected> | ||
<p class="text-gray-500 p-5"> | ||
Please connect your wallet to be able to send transactions. | ||
</p> | ||
</ng-template> | ||
</ng-template> | ||
|
||
<swal | ||
#errorSwal | ||
title="Ops! Something went wrong" | ||
[text]="errorMessage" | ||
icon="error" | ||
[showCancelButton]="false" | ||
[focusCancel]="false" | ||
/> | ||
|
||
<swal | ||
#confirmationSwal | ||
title="The transaction has been sent, yay! 🎉" | ||
text="Your transaction has been sent successfully. You can check the status of your transaction in the transaction history." | ||
icon="success" | ||
[showCancelButton]="false" | ||
[focusCancel]="false" | ||
confirmButtonText="Go to transaction history" | ||
(confirm)="onGoToTransactionHistory()" | ||
/> |
85 changes: 85 additions & 0 deletions
85
apps/webapp/src/app/components/account/send/send.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Component, effect, inject, OnInit, ViewChild } from '@angular/core'; | ||
import { Router } from '@angular/router'; | ||
import { CommonModule } from '@angular/common'; | ||
import { SwalComponent, SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | ||
import Swal from 'sweetalert2'; | ||
|
||
import { WalletStore } from '../../../store'; | ||
import { | ||
TransferForm, | ||
TransferFormComponent, | ||
} from '../../forms/transfer/transfer.component'; | ||
import { environment } from '../../../../environments/environment'; | ||
|
||
@Component({ | ||
selector: 'webapp-account-send', | ||
standalone: true, | ||
imports: [ | ||
CommonModule, | ||
TransferFormComponent, | ||
SweetAlert2Module, | ||
MatProgressSpinnerModule, | ||
], | ||
templateUrl: './send.component.html', | ||
providers: [WalletStore], | ||
}) | ||
export class SendComponent implements OnInit { | ||
readonly walletStore = inject(WalletStore); | ||
readonly isSendTransactionLoading = this.walletStore.isLoading; | ||
readonly publicKey$ = this.walletStore.wallet().publicKey$; | ||
readonly connected$ = this.walletStore.wallet().connected$; | ||
errorMessage = ''; | ||
@ViewChild('errorSwal') | ||
public readonly errorSwal!: SwalComponent; | ||
@ViewChild('confirmationSwal') | ||
public readonly confirmationSwal!: SwalComponent; | ||
|
||
constructor(private router: Router) { | ||
effect(() => { | ||
if (this.walletStore.error && this.walletStore.error()) { | ||
const error = this.walletStore.error(); | ||
console.error('error', error); | ||
this.errorMessage = | ||
error || 'An error occurred while sending transaction'; | ||
this.errorSwal.fire(); | ||
} | ||
}); | ||
effect(() => { | ||
if (this.walletStore.signature && this.walletStore.signature()) { | ||
console.warn('signature', this.walletStore.signature()); | ||
this.confirmationSwal.fire(); | ||
} | ||
}); | ||
} | ||
|
||
ngOnInit() { | ||
this.walletStore.clearSignature(); | ||
} | ||
|
||
async onSubmitForm(payload: TransferForm, publicKey: PublicKey) { | ||
console.log('onSubmitForm', payload); | ||
this.walletStore.sendTransaction({ | ||
amount: payload.amount, | ||
memo: payload.memo, | ||
tokenAddress: environment.mintUSDC, | ||
senderAddress: publicKey.toBase58(), | ||
receiverAddress: payload.receiver, | ||
}); | ||
|
||
Swal.fire({ | ||
title: 'Loading...', | ||
html: 'Please wait...', | ||
allowEscapeKey: false, | ||
allowOutsideClick: false, | ||
didOpen: () => { | ||
Swal.showLoading(); | ||
}, | ||
}); | ||
} | ||
|
||
onGoToTransactionHistory() { | ||
this.router.navigate(['/account']); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
apps/webapp/src/app/components/account/wallet/wallet.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
|
||
<mat-tab-group preserveContent backgroundColor="primary"> | ||
<mat-tab label="Wallet Balance" class="text-black"> | ||
<div | ||
*ngIf="(account$ | async) as account; else elseAccount" | ||
class="rounded-lg bg-gray-50 px-4 py-6 sm:flex sm:items-center sm:justify-between sm:space-x-6 sm:px-6 lg:space-x-8"> | ||
<dl | ||
class="flex-auto space-y-6 divide-y divide-gray-200 text-sm text-gray-600 sm:grid sm:grid-cols-3 sm:gap-x-6 sm:space-y-0 sm:divide-y-0 lg:w-1/2 lg:flex-none lg:gap-x-8"> | ||
<div class="flex justify-between sm:block"> | ||
<dd class="sm:mt-1"> | ||
<img [src]="account.info.image" alt="account.info.name" | ||
class="h-16 w-16 rounded object-cover object-center" /> | ||
</dd> | ||
</div> | ||
<div class="flex justify-between pt-6 sm:block sm:pt-0"> | ||
<dt class="font-medium text-gray-900">Balance</dt> | ||
<dd class="sm:mt-1">{{ account.balance }}</dd> | ||
</div> | ||
<div class="flex justify-between pt-6 font-medium text-gray-900 sm:block sm:pt-0"> | ||
<dt>Is Frozen</dt> | ||
<dd class="sm:mt-1"> | ||
{{ account.isFrozen ? 'Yes' : 'No' }} | ||
</dd> | ||
</div> | ||
</dl> | ||
</div> | ||
<ng-template #elseAccount> | ||
<div *ngIf="(connected$ | async) as connected; else disconnected" class="mt-4 flex justify-center"> | ||
<mat-spinner diameter="32"></mat-spinner> | ||
</div> | ||
<ng-template #disconnected> | ||
<p class="text-gray-500 p-5"> | ||
Please connect your wallet to see your balance. | ||
</p> | ||
</ng-template> | ||
</ng-template> | ||
</mat-tab> | ||
<mat-tab label="Transaction History" class="text-black"> | ||
<p *ngIf="(connected$ | async) === false" class="text-gray-500 p-5"> | ||
Please connect your wallet to see your history. | ||
</p> | ||
<div *ngIf="!!isTransactionsLoading?.()" class="mt-4 flex justify-center"> | ||
<mat-spinner diameter="32"></mat-spinner> | ||
</div> | ||
<table *ngIf="!!transactions().length && (connected$ | async)" class="mt-4 w-full text-gray-500 sm:mt-6 table-auto"> | ||
<thead class="text-left text-sm text-gray-500"> | ||
<tr> | ||
<th scope="col" class="py-3 pr-8 font-normal"> | ||
Type | ||
</th> | ||
<th scope="col" class="hidden sm:w-2/5 lg:w-1/3 py-3 pr-8 font-normal sm:table-cell"> | ||
Memo | ||
</th> | ||
<th scope="col" class="py-3 pr-8 font-normal sm:table-cell"> | ||
Amount | ||
</th> | ||
<th scope="col" class="w-0 py-3 font-normal"> | ||
Date | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody class="divide-y divide-gray-200 border-b border-gray-200 text-sm sm:border-t"> | ||
<tr class="text-medium items-center" *ngFor="let transaction of transactions()"> | ||
<td class="py-6 pr-8"> | ||
<div class="flex items-center"> | ||
<div> | ||
<div class="font-medium text-gray-900"> | ||
{{ transaction.type }} | ||
</div> | ||
</div> | ||
</div> | ||
</td> | ||
<td class="hidden py-6 pr-8 sm:table-cell text-gray-900"> | ||
{{ transaction.memo }} | ||
</td> | ||
<td class="py-6 pr-8 sm:table-cell text-gray-900 font-bold text-lg flex flex-row items-center gap-3 h-full align-middle"> | ||
{{ transaction.amount }} | ||
<ng-icon | ||
*ngIf="transaction.sign !== undefined" | ||
[name]="transaction.sign > 1 ? 'heroArrowUp' : 'heroArrowDown'" | ||
class="h-4 w-4" | ||
[ngClass]="transaction.sign > 1 ? 'text-green-500' : 'text-red-500'" | ||
aria-hidden="true" | ||
/> | ||
</td> | ||
<td class="py-6 text-gray-900"> | ||
{{ transaction.timestamp | date: 'short' }} | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<div *ngIf="error && error()?.length" class="mt-4"> | ||
<div class="text-red-600">{{ error() }}</div> | ||
<button (click)="loadTransactions()" class="text-sm font-medium text-indigo-600 hover:text-indigo-500"> | ||
Try again | ||
</button> | ||
</div> | ||
</mat-tab> | ||
</mat-tab-group> |
32 changes: 32 additions & 0 deletions
32
apps/webapp/src/app/components/account/wallet/wallet.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Component, inject, OnInit } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { MatTabsModule } from '@angular/material/tabs'; | ||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | ||
import { heroArrowUp, heroArrowDown } from '@ng-icons/heroicons/outline'; | ||
import { NgIconComponent, provideIcons } from '@ng-icons/core'; | ||
|
||
import { WalletStore } from '../../../store'; | ||
|
||
@Component({ | ||
selector: 'webapp-account-wallet', | ||
standalone: true, | ||
imports: [CommonModule, MatTabsModule, MatProgressSpinnerModule, NgIconComponent], | ||
templateUrl: './wallet.component.html', | ||
providers: [WalletStore, provideIcons({ heroArrowUp, heroArrowDown })], | ||
}) | ||
export class WalletComponent implements OnInit { | ||
readonly walletStore = inject(WalletStore); | ||
readonly account$ = this.walletStore.account(); | ||
readonly connected$ = this.walletStore.wallet().connected$; | ||
readonly transactions = this.walletStore.transactions; | ||
readonly isTransactionsLoading = this.walletStore.isLoading; | ||
readonly error = this.walletStore.error; | ||
|
||
ngOnInit() { | ||
this.loadTransactions(); | ||
} | ||
|
||
loadTransactions() { | ||
this.walletStore.loadTransactions(10); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
apps/webapp/src/app/components/forms/transfer/transfer.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="flex flex-col items-center gap-5"> | ||
<mat-form-field appearance="fill" subscriptSizing="dynamic" class="w-full"> | ||
<mat-label>Memo</mat-label> | ||
<input matInput placeholder="Memo" formControlName="memo" required /> | ||
<mat-icon matSuffix>description</mat-icon> | ||
<mat-error *ngIf="form.get('memo')?.hasError('required')"> | ||
Memo is requiredf | ||
</mat-error> | ||
</mat-form-field> | ||
|
||
<mat-form-field appearance="fill" subscriptSizing="dynamic" class="w-full"> | ||
<mat-label>Amount</mat-label> | ||
<input type="number" matInput placeholder="Memo" formControlName="amount" required /> | ||
<mat-icon matSuffix>attach_money</mat-icon> | ||
<mat-error *ngIf="form.get('amount')?.hasError('required')"> | ||
Amount is required | ||
</mat-error> | ||
</mat-form-field> | ||
|
||
<mat-form-field appearance="fill" subscriptSizing="dynamic" class="w-full"> | ||
<mat-label>Receiver</mat-label> | ||
<input matInput placeholder="Receiver" formControlName="receiver" required /> | ||
<mat-icon matSuffix>key</mat-icon> | ||
<mat-error *ngIf="form.get('receiver')?.hasError('required')"> | ||
Receiver is required | ||
</mat-error> | ||
</mat-form-field> | ||
|
||
<footer> | ||
<button | ||
type="submit" | ||
mat-raised-button | ||
color="primary" | ||
class="self-end w-auto rounded-md bg-indigo-600 px-10 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" | ||
> | ||
Transfer | ||
</button> | ||
</footer> | ||
</form> |
60 changes: 60 additions & 0 deletions
60
apps/webapp/src/app/components/forms/transfer/transfer.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { | ||
FormControl, | ||
FormGroup, | ||
FormsModule, | ||
ReactiveFormsModule, | ||
Validators, | ||
} from '@angular/forms'; | ||
import { MatError, MatFormFieldModule } from '@angular/material/form-field'; | ||
import { MatInputModule } from '@angular/material/input'; | ||
import { MatIconModule } from '@angular/material/icon'; | ||
|
||
export type TransferForm = { | ||
memo: string; | ||
amount: number; | ||
receiver: string; | ||
}; | ||
|
||
@Component({ | ||
selector: 'webapp-forms-transfer', | ||
standalone: true, | ||
imports: [ | ||
CommonModule, | ||
ReactiveFormsModule, | ||
FormsModule, | ||
MatFormFieldModule, | ||
MatInputModule, | ||
MatIconModule, | ||
MatError, | ||
], | ||
templateUrl: './transfer.component.html', | ||
}) | ||
export class TransferFormComponent implements OnInit { | ||
@Input() isLoading = false; | ||
@Output() readonly submitForm = new EventEmitter<TransferForm>(); | ||
|
||
readonly form = new FormGroup({ | ||
memo: new FormControl('', Validators.required), | ||
amount: new FormControl<number | undefined>(undefined, Validators.required), | ||
receiver: new FormControl('', Validators.required), | ||
}); | ||
|
||
ngOnInit() { | ||
this.form.setValue({ | ||
memo: '', | ||
amount: null, | ||
receiver: '', | ||
}); | ||
} | ||
|
||
onSubmit() { | ||
if (this.form.invalid) return; | ||
this.submitForm.emit({ | ||
memo: this.form.value.memo || '', | ||
amount: this.form.value.amount || 0, | ||
receiver: this.form.value.receiver || '', | ||
}); | ||
} | ||
} |
Oops, something went wrong.