diff --git a/apps/webapp/src/app/app.config.ts b/apps/webapp/src/app/app.config.ts index 8d6355a..5b73995 100644 --- a/apps/webapp/src/app/app.config.ts +++ b/apps/webapp/src/app/app.config.ts @@ -1,9 +1,10 @@ import { provideHttpClient } from '@angular/common/http'; -import { ApplicationConfig } from '@angular/core'; +import { ApplicationConfig, importProvidersFrom } from '@angular/core'; import { provideRouter } from '@angular/router'; 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'; @@ -12,7 +13,12 @@ export const appConfig: ApplicationConfig = { provideClientHydration(), provideRouter(appRoutes), provideAnimationsAsync(), - provideWalletAdapter(), + provideWalletAdapter({ + autoConnect: !!localStorage.getItem('autoConnect'), + }), provideHttpClient(), + importProvidersFrom([ + SweetAlert2Module.forRoot(), + ]) ], }; diff --git a/apps/webapp/src/app/app.routes.ts b/apps/webapp/src/app/app.routes.ts index 24d6c46..4105a2d 100644 --- a/apps/webapp/src/app/app.routes.ts +++ b/apps/webapp/src/app/app.routes.ts @@ -11,8 +11,8 @@ export const appRoutes: Route[] = [ }, { path: 'account', - loadComponent: () => - import('./pages/account/account.page').then((m) => m.AccountPage), + loadChildren: () => + import('./pages/account/account.module').then((m) => m.AccountPageRoutingModule), }, { path: '', diff --git a/apps/webapp/src/app/components/account/send/send.component.html b/apps/webapp/src/app/components/account/send/send.component.html new file mode 100644 index 0000000..1e539fc --- /dev/null +++ b/apps/webapp/src/app/components/account/send/send.component.html @@ -0,0 +1,36 @@ + + + +
+ +
+ +

+ Please connect your wallet to be able to send transactions. +

+
+
+ + + + diff --git a/apps/webapp/src/app/components/account/send/send.component.ts b/apps/webapp/src/app/components/account/send/send.component.ts new file mode 100644 index 0000000..55b47b2 --- /dev/null +++ b/apps/webapp/src/app/components/account/send/send.component.ts @@ -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']); + } +} diff --git a/apps/webapp/src/app/components/account/wallet/wallet.component.html b/apps/webapp/src/app/components/account/wallet/wallet.component.html new file mode 100644 index 0000000..c099382 --- /dev/null +++ b/apps/webapp/src/app/components/account/wallet/wallet.component.html @@ -0,0 +1,99 @@ + + + +
+
+
+
+ account.info.name +
+
+
+
Balance
+
{{ account.balance }}
+
+
+
Is Frozen
+
+ {{ account.isFrozen ? 'Yes' : 'No' }} +
+
+
+
+ +
+ +
+ +

+ Please connect your wallet to see your balance. +

+
+
+
+ +

+ Please connect your wallet to see your history. +

+
+ +
+ + + + + + + + + + + + + + + + + +
+ Type + + Amount + + Date +
+
+
+
+ {{ transaction.type }} +
+
+
+
+ {{ transaction.amount }} + + {{ transaction.timestamp | date: 'short' }} +
+
+
{{ error() }}
+ +
+
+
diff --git a/apps/webapp/src/app/components/account/wallet/wallet.component.ts b/apps/webapp/src/app/components/account/wallet/wallet.component.ts new file mode 100644 index 0000000..318c263 --- /dev/null +++ b/apps/webapp/src/app/components/account/wallet/wallet.component.ts @@ -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); + } +} diff --git a/apps/webapp/src/app/components/forms/transfer/transfer.component.html b/apps/webapp/src/app/components/forms/transfer/transfer.component.html new file mode 100644 index 0000000..256f781 --- /dev/null +++ b/apps/webapp/src/app/components/forms/transfer/transfer.component.html @@ -0,0 +1,39 @@ +
+ + Memo + + description + + Memo is requiredf + + + + + Amount + + attach_money + + Amount is required + + + + + Receiver + + key + + Receiver is required + + + +
+ +
+
\ No newline at end of file diff --git a/apps/webapp/src/app/components/forms/transfer/transfer.component.ts b/apps/webapp/src/app/components/forms/transfer/transfer.component.ts new file mode 100644 index 0000000..dba200c --- /dev/null +++ b/apps/webapp/src/app/components/forms/transfer/transfer.component.ts @@ -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(); + + readonly form = new FormGroup({ + memo: new FormControl('', Validators.required), + amount: new FormControl(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 || '', + }); + } +} diff --git a/apps/webapp/src/app/containers/layout/layout-container.component.ts b/apps/webapp/src/app/containers/layout/layout-container.component.ts index 860415e..f49d8e1 100644 --- a/apps/webapp/src/app/containers/layout/layout-container.component.ts +++ b/apps/webapp/src/app/containers/layout/layout-container.component.ts @@ -1,18 +1,14 @@ import { Component, Input } from '@angular/core'; import { HeaderComponent, FooterComponent } from '@projectx/ui'; -import { - HdObscureAddressPipe, - HdWalletAdapterDirective, -} from '@heavy-duty/wallet-adapter-cdk'; -import { HdWalletMultiButtonComponent } from '@heavy-duty/wallet-adapter-material'; +import { HdWalletAdapterCdkModule } from '@heavy-duty/wallet-adapter-cdk'; +import { HdWalletAdapterMaterialModule } from '@heavy-duty/wallet-adapter-material'; @Component({ imports: [ HeaderComponent, FooterComponent, - HdWalletAdapterDirective, - HdObscureAddressPipe, - HdWalletMultiButtonComponent, + HdWalletAdapterCdkModule, + HdWalletAdapterMaterialModule, ], selector: 'webapp-layout-container', templateUrl: './layout-container.component.html', @@ -22,9 +18,7 @@ import { HdWalletMultiButtonComponent } from '@heavy-duty/wallet-adapter-materia export class LayoutContainerComponent { @Input() title?: string = 'Jam Sessions'; - headerLinks = [ - { label: 'Account', href: '/account' }, - ]; + headerLinks = [{ label: 'Account', href: '/account' }]; footerLinks = [ { diff --git a/apps/webapp/src/app/pages/account/account.module.ts b/apps/webapp/src/app/pages/account/account.module.ts new file mode 100644 index 0000000..225744b --- /dev/null +++ b/apps/webapp/src/app/pages/account/account.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AccountPage } from './account.page'; + +const routes: Routes = [ + { + path: '', + component: AccountPage, + children: [ + { path: '', redirectTo: 'wallet', pathMatch: 'full' }, + { + path: 'wallet', + loadComponent: () => import('../../components/account/wallet/wallet.component').then(m => m.WalletComponent) + }, + { + path: 'send', + loadComponent: () => import('../../components/account/send/send.component').then(m => m.SendComponent) + }, + ] + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountPageRoutingModule { } \ No newline at end of file diff --git a/apps/webapp/src/app/pages/account/account.page.html b/apps/webapp/src/app/pages/account/account.page.html index 824b654..271866d 100644 --- a/apps/webapp/src/app/pages/account/account.page.html +++ b/apps/webapp/src/app/pages/account/account.page.html @@ -1,103 +1,27 @@ -
-
-

Account

-

- Check the status of your account +

+
+

+ Account +

+

+ Manage your account; view your wallet balance, see your transaction history and more, all in one place.

-
-

- Wallet Status -

- -
-
-
-
-
-
- account.info.name -
-
-
-
Balance
-
- {{ account.balance }} -
-
-
-
Is Frozen
-
- {{ account.isFrozen ? 'Yes' : 'No' }} -
-
-
-
- - - - - - - - - - - - - - - - - - - -
Transactions
- Type - - Date -
-
-
-
- {{ transaction.type }} -
-
-
-
- {{ transaction.timestamp | date: 'short' }} -
- -
- - -

- Please connect your wallet to view your account details. -

-
- -
-
- {{ error() }} -
- -
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/apps/webapp/src/app/pages/account/account.page.ts b/apps/webapp/src/app/pages/account/account.page.ts index fa634e2..8c6179c 100644 --- a/apps/webapp/src/app/pages/account/account.page.ts +++ b/apps/webapp/src/app/pages/account/account.page.ts @@ -1,28 +1,20 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; import { LayoutContainerComponent } from '../../containers/layout/layout-container.component'; -import { WalletStore } from '../../store'; @Component({ selector: 'webapp-account', standalone: true, - imports: [CommonModule, LayoutContainerComponent], + imports: [RouterModule, CommonModule, LayoutContainerComponent], templateUrl: './account.page.html', styleUrl: './account.page.css', - providers: [WalletStore], + providers: [], }) -export class AccountPage implements OnInit { - readonly walletStore = inject(WalletStore); - readonly account$ = this.walletStore.account(); - readonly transactions = this.walletStore.transactions; - readonly error = this.walletStore.error; - - ngOnInit() { - this.loadTransactions(); - } - - loadTransactions() { - this.walletStore.loadTransactions(10); - } +export class AccountPage { + links = [ + { path: 'wallet', label: 'Wallet' }, + { path: 'send', label: 'Send Tokens' }, + ]; } diff --git a/apps/webapp/src/app/store/wallet/models.ts b/apps/webapp/src/app/store/wallet/models.ts index 7f0db58..54cd2c9 100644 --- a/apps/webapp/src/app/store/wallet/models.ts +++ b/apps/webapp/src/app/store/wallet/models.ts @@ -93,7 +93,7 @@ export type Transaction = { }; version: string; }; -} +}; export type TransactionHistoryResponse = { success: boolean; @@ -104,7 +104,39 @@ export type TransactionHistoryResponse = { export type Transactions = Array<{ timestamp: Date; type: 'transfer' | 'unknown'; - memo?: string - amount?: number, - sign?: -1 | 1, -}>; \ No newline at end of file + memo?: string; + amount?: number; + sign?: -1 | 1; +}>; + +export type TokenInfoResponse = { + success: boolean; + message: string; + result: { + name: string; + symbol: string; + image: string; + decimals: number; + address: string; + freeze_authority: string; + current_supply: number; + extensions: Array<{ + extension: string; + state: { + newerTransferFee: { + epoch: number; + maximumFee: number; + transferFeeBasisPoints: number; + }; + olderTransferFee: { + epoch: number; + maximumFee: number; + transferFeeBasisPoints: number; + }; + transferFeeConfigAuthority: null; + withdrawWithheldAuthority: string; + withheldAmount: number; + }; + }>; + }; +}; diff --git a/apps/webapp/src/app/store/wallet/service.ts b/apps/webapp/src/app/store/wallet/service.ts index f27aba4..b8935f8 100644 --- a/apps/webapp/src/app/store/wallet/service.ts +++ b/apps/webapp/src/app/store/wallet/service.ts @@ -1,12 +1,13 @@ import { HttpClient } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { getAssociatedTokenAddressSync } from '@solana/spl-token'; -import { map, of, timeout } from 'rxjs'; import { PublicKey } from '@solana/web3.js'; +import { map, of, timeout } from 'rxjs'; import { environment } from '../../../environments/environment'; import { TokenBalanceResponse, + TokenInfoResponse, Transaction, TransactionHistoryResponse, Transactions, @@ -22,6 +23,12 @@ const isInvalidTransaction = (transaction: Transaction) => { export class ShyftApiService { private readonly http = inject(HttpClient); + getRpcUrl() { + const url = new URL(environment.rpcUrl); + url.searchParams.set('api_key', environment.shyftApiKey); + return url.toString(); + } + getAccount(publicKey?: string) { if (!publicKey) { return of(null); @@ -76,7 +83,7 @@ export class ShyftApiService { : { timestamp: new Date(transaction.timestamp), memo: transaction.actions[1].info.message, - amount: transaction.actions[1].info.amount, + amount: transaction.actions[0].info.amount, sign: transaction.actions[0].info.sender === wallet ? -1 : 1, type: 'transfer', }), @@ -84,4 +91,23 @@ export class ShyftApiService { ) ); } + + getTokenInfo(tokenAddress = environment.mintUSDC) { + const url = new URL( + '/sol/v1/token/get_info', + environment.shyftApiUrl + ); + url.searchParams.append('network', environment.walletNetwork); + url.searchParams.append('token_address', tokenAddress); + + return this.http + .get(url.toString(), { + headers: { + 'x-api-key': environment.shyftApiKey, + }, + }) + .pipe( + timeout(5000), + map((res) => res.result)); + } } diff --git a/apps/webapp/src/app/store/wallet/store.ts b/apps/webapp/src/app/store/wallet/store.ts index bcff010..44e0057 100644 --- a/apps/webapp/src/app/store/wallet/store.ts +++ b/apps/webapp/src/app/store/wallet/store.ts @@ -1,14 +1,21 @@ -import { computed, inject } from '@angular/core'; +import { InjectionToken, computed, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { tapResponse } from '@ngrx/operators'; import { patchState, signalStore, withComputed, + withHooks, withMethods, withState, } from '@ngrx/signals'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; -import { WalletStore as WalletAdapterStore } from '@heavy-duty/wallet-adapter'; +import { + ConnectionStore, + WalletStore as WalletAdapterStore, + injectTransactionSender, +} from '@heavy-duty/wallet-adapter'; +import { createTransferInstructions } from '@heavy-duty/spl-utils'; import { combineLatest, of, pipe, switchMap, tap } from 'rxjs'; import { ShyftApiService } from './service'; @@ -16,53 +23,127 @@ import { Transactions } from './models'; type WalletState = { wallet: WalletAdapterStore; + connection: ConnectionStore; transactions: Transactions; isLoading?: boolean; error?: string; + signature?: string; }; +const WALLET_STATE = new InjectionToken('WalletState', { + factory: () => ({ + wallet: inject(WalletAdapterStore), + connection: inject(ConnectionStore), + transactions: [], + error: '', + signature: '', + }), +}); + export const WalletStore = signalStore( - withState( - () => - { - wallet: inject(WalletAdapterStore), - transactions: [], - error: '', - } - ), + withState(() => inject(WALLET_STATE)), + withHooks({ + onInit(store) { + const wallet = store.wallet(); + // Connect the wallet to the RPC endpoint + const walletService = inject(ShyftApiService); + store.connection().setEndpoint(walletService.getRpcUrl()); + // Auto connect the wallet if it was connected before + wallet.connected$.pipe(takeUntilDestroyed()).subscribe((connected) => { + connected && localStorage.setItem('autoConnect', 'true'); + }); + wallet.disconnecting$ + .pipe(takeUntilDestroyed()) + .subscribe((disconnected) => { + disconnected && localStorage.removeItem('autoConnect'); + }); + }, + }), withComputed((store, walletService = inject(ShyftApiService)) => ({ account: computed(() => { return store .wallet() .publicKey$.pipe( switchMap((publicKey) => - walletService.getAccount(publicKey?.toBase58()) + publicKey + ? walletService.getAccount(publicKey.toBase58()) + : of(null) ) ); }), })), - withMethods((store, walletService = inject(ShyftApiService)) => ({ - loadTransactions: rxMethod( - pipe( - tap(() => patchState(store, { isLoading: true })), - switchMap((limit) => - combineLatest([store.wallet().publicKey$, of(limit)]) - ), - switchMap(([publicKey, limit]) => { - if (!publicKey) return []; - return walletService.getTransactions(publicKey, limit).pipe( - tapResponse({ - next: (transactions) => patchState(store, { transactions }), - error: (error: Error) => { - console.error('error', error); - patchState(store, { error: error.message }); - }, - complete: () => - patchState(store, { isLoading: false, error: '' }), - }) - ); - }) - ) - ), - })) + withMethods( + ( + store, + walletService = inject(ShyftApiService), + transactionSender = injectTransactionSender() + ) => ({ + clearSignature(): void { + patchState(store, { signature: undefined }); + }, + loadTransactions: rxMethod( + pipe( + tap(() => patchState(store, { isLoading: true })), + switchMap((limit) => + combineLatest([store.wallet().publicKey$, of(limit)]) + ), + switchMap(([publicKey, limit]) => { + if (!publicKey) return of([]); + return walletService.getTransactions(publicKey, limit) + }), + tapResponse({ + next: (transactions) => patchState(store, { transactions }), + error: (error: Error) => { + console.error('error', error); + patchState(store, { error: error.message }); + }, + complete: () => + patchState(store, { isLoading: false, error: undefined }), + }) + ) + ), + sendTransaction: rxMethod<{ + amount: number; + memo: string; + tokenAddress: string; + senderAddress: string; + receiverAddress: string; + }>( + pipe( + tap(() => patchState(store, { isLoading: true })), + switchMap((payload) => + combineLatest([ + walletService.getTokenInfo(payload.tokenAddress), + of(payload), + ]) + ), + switchMap(([tokenInfo, payload]) => { + return transactionSender.send( + createTransferInstructions({ + amount: payload.amount * 10 ** tokenInfo.decimals, + memo: payload.memo, + mintAddress: payload.tokenAddress, + senderAddress: payload.senderAddress, + receiverAddress: payload.receiverAddress, + fundReceiver: true, + }) + ); + }), + tapResponse({ + next: (signature) => { + return patchState(store, { signature }); + }, + error: (error: Error) => { + console.error('error', error); + patchState(store, { error: error.message }); + }, + complete: () => { + console.log('completed'); + patchState(store, { isLoading: false, error: undefined }); + }, + }) + ) + ), + }) + ) ); diff --git a/apps/webapp/src/environments/environment.prod.ts b/apps/webapp/src/environments/environment.prod.ts index e744a38..909647c 100644 --- a/apps/webapp/src/environments/environment.prod.ts +++ b/apps/webapp/src/environments/environment.prod.ts @@ -2,6 +2,7 @@ export const environment = { production: import.meta.env.NODE_ENV === 'production', shyftApiKey: import.meta.env.NG_APP_SHYFT_API_KEY, shyftApiUrl: 'https://api.shyft.to', + rpcUrl: 'https://rpc.shyft.to', walletNetwork: 'mainnet-beta', mintUSDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' }; \ No newline at end of file diff --git a/apps/webapp/src/environments/environment.ts b/apps/webapp/src/environments/environment.ts index 6e7ced9..efa58d1 100644 --- a/apps/webapp/src/environments/environment.ts +++ b/apps/webapp/src/environments/environment.ts @@ -2,6 +2,7 @@ export const environment = { production: false, shyftApiKey: import.meta.env.NG_APP_SHYFT_API_KEY, shyftApiUrl: 'https://api.shyft.to', + rpcUrl: 'https://devnet-rpc.shyft.to', walletNetwork: 'devnet', - mintUSDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU' + mintUSDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // https://faucet.circle.com/ }; \ No newline at end of file diff --git a/libs/ui/src/lib/header/header.component.html b/libs/ui/src/lib/header/header.component.html index 1595ac4..7010e47 100644 --- a/libs/ui/src/lib/header/header.component.html +++ b/libs/ui/src/lib/header/header.component.html @@ -30,7 +30,7 @@
-