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

Release 1.0 #11

Merged
merged 11 commits into from
Mar 1, 2024
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ Thumbs.db

.nx/cache
.angular

# Environment Variables
.env
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ProjectX

<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
<p align="center">
<img width="1289" alt="ProjectX-angular" src="https://github.com/proyecto26/ProjectX-angular/assets/2154886/4487731e-322e-4419-a6c5-65e26ffdb9ae">
</p>

✨ **This workspace has been generated by [Nx, Smart Monorepos · Fast CI.](https://nx.dev)** ✨

Expand Down
1 change: 1 addition & 0 deletions apps/webapp/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NG_APP_SHYFT_API_KEY=fill-this-in
36 changes: 17 additions & 19 deletions apps/webapp/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,34 @@
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"outputs": [
"{options.outputPath}"
],
"executor": "@ngx-env/builder:application",
"outputs": ["{options.outputPath}"],
"options": {
"allowedCommonJsDependencies": ["crypto", "stream"],
"outputPath": "dist/apps/webapp",
"index": "apps/webapp/src/index.html",
"browser": "apps/webapp/src/main.ts",
"polyfills": [
"zone.js",
"apps/webapp/src/polyfills.ts"
],
"polyfills": ["apps/webapp/src/polyfills.ts"],
"tsConfig": "apps/webapp/tsconfig.app.json",
"assets": [
"apps/webapp/src/favicon.ico",
"apps/webapp/src/assets"
],
"assets": ["apps/webapp/src/favicon.ico", "apps/webapp/src/assets"],
"styles": [
"@angular/material/prebuilt-themes/purple-green.css",
"apps/webapp/src/styles.css"
],
"scripts": [],
"server": "apps/webapp/src/main.server.ts",
"prerender": true,
"ssr": {
"entry": "apps/webapp/server.ts"
}
"prerender": false,
"ssr": false
},
"configurations": {
"production": {
"polyfills": ["apps/webapp/src/polyfills.ts"],
"fileReplacements": [
{
"replace": "apps/webapp/src/environments/environment.ts",
"with": "apps/webapp/src/environments/environment.prod.ts"
}
],
"budgets": [
{
"type": "initial",
Expand All @@ -60,7 +58,7 @@
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"executor": "@ngx-env/builder:dev-server",
"configurations": {
"production": {
"buildTarget": "webapp:build:production"
Expand All @@ -72,7 +70,7 @@
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"executor": "@ngx-env/builder:extract-i18n",
"options": {
"buildTarget": "webapp:build"
}
Expand All @@ -85,4 +83,4 @@
}
}
}
}
}
21 changes: 18 additions & 3 deletions apps/webapp/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideWalletAdapter } from '@heavy-duty/wallet-adapter';
import { SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2';

import { appRoutes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(), provideRouter(appRoutes), provideAnimationsAsync()],
providers: [
provideClientHydration(),
provideRouter(appRoutes),
provideAnimationsAsync(),
provideWalletAdapter({
autoConnect: !!localStorage.getItem('autoConnect'),
}),
provideHttpClient(),
importProvidersFrom([
SweetAlert2Module.forRoot(),
])
],
};
7 changes: 6 additions & 1 deletion apps/webapp/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ export const appRoutes: Route[] = [
{
path: 'home',
loadComponent: () =>
import('./pages/home.page').then((m) => m.HomePage),
import('./pages/home/home.page').then((m) => m.HomePage),
},
{
path: 'account',
loadChildren: () =>
import('./pages/account/account.module').then((m) => m.AccountPageRoutingModule),
},
{
path: '',
Expand Down
36 changes: 36 additions & 0 deletions apps/webapp/src/app/components/account/send/send.component.html
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()"
/>
84 changes: 84 additions & 0 deletions apps/webapp/src/app/components/account/send/send.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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) {
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']);
}
}
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 apps/webapp/src/app/components/account/wallet/wallet.component.ts
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);
}
}
Loading