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

Implement shopping cart #128

Merged
merged 27 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d654ffa
Initial shopping cart and sweep to cart work
amenconi Jan 18, 2024
2a7ac4e
Finished minimum viable functionality for cart checkout review, netwo…
amenconi Jan 22, 2024
3a01530
Initial bug testing and fixing finished
amenconi Jan 24, 2024
0eda115
Prep for PR
amenconi Jan 24, 2024
9b50b49
Implemented single nft bulk buy
amenconi Jan 26, 2024
cc0aa93
Improved cart quantities and fixed Sweep to Cart function
amenconi Jan 30, 2024
0ee051d
Major revisions based on initial feedback
amenconi Feb 6, 2024
25f7fff
backed out minor config file change
amenconi Feb 6, 2024
30fd69d
minor config styling issue fix
amenconi Feb 6, 2024
cdefd66
Shopping cart revision: reactive data, real-time cross tab updates, m…
amenconi Feb 11, 2024
8d51969
Minor linting changes made
amenconi Feb 11, 2024
d94395e
Merge branch 'develop' into amenconi-shopping-cart
adamunchained Feb 12, 2024
5c136c9
Merge branch 'develop' into amenconi-bulk-buy-single-nft
adamunchained Feb 12, 2024
433eb7e
Fixed qty input of fractions as well as allowing pasting of values ou…
amenconi Feb 16, 2024
619b91d
linting/prettier fixes
amenconi Feb 16, 2024
140c036
Various fixed for cart items counts, fractional qtys, second hand ava…
amenconi Feb 17, 2024
a4b79ab
Merge branch 'amenconi-shopping-cart' of https://github.com/soonavers…
amenconi Feb 17, 2024
6f1a25b
prettier/lint commit
amenconi Feb 17, 2024
2f0738a
Merge branch 'amenconi-bulk-buy-single-nft' into amenconi-shopping-cart
amenconi Feb 21, 2024
a905a88
NFT checkout styling fixes, USD conversions added in more places, ext…
amenconi Feb 22, 2024
387e755
Persisted selected network and current step with local storage to all…
amenconi Feb 25, 2024
0d33e81
lint/prettier commit
amenconi Feb 25, 2024
49bba17
Cart item remove button disable logic now includes expiry of pending …
amenconi Feb 28, 2024
5d984e4
refactored cross tab checkout state management
amenconi Mar 3, 2024
3ab2ab3
lint/prettier
amenconi Mar 3, 2024
66a6781
Remove tester string from cart modal
amenconi Mar 8, 2024
fba8fdd
added contributions
Mar 14, 2024
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: 4 additions & 0 deletions src/app/@api/collection.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class CollectionApi extends BaseApi<Collection> {
super(Dataset.COLLECTION, httpClient);
}

public getCollectionById(collectionId: string): Observable<Collection | undefined> {
return this.listen(collectionId);
}

public mintCollection = (
req: Build5Request<CollectionMintRequest>,
): Observable<Transaction | undefined> => this.request(WEN_FUNC.mintCollection, req);
Expand Down
4 changes: 4 additions & 0 deletions src/app/@api/nft.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export class NftApi extends BaseApi<Nft> {
public stakeNft = (req: Build5Request<NftStakeRequest>): Observable<Transaction | undefined> =>
this.request(WEN_FUNC.stakeNft, req);

public getNftById(nftId: string): Observable<Nft | undefined> {
return this.listen(nftId);
}

public successfullOrders(
nftId: string,
network?: Network,
Expand Down
5 changes: 5 additions & 0 deletions src/app/@api/order.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
WEN_FUNC,
Build5Request,
NftPurchaseRequest,
NftPurchaseBulkRequest,
OrderTokenRequest,
AddressValidationRequest,
NftBidRequest,
Expand All @@ -27,6 +28,10 @@ export class OrderApi extends BaseApi<Transaction> {
public orderNft = (req: Build5Request<NftPurchaseRequest>): Observable<Transaction | undefined> =>
this.request(WEN_FUNC.orderNft, req);

public orderNfts = (
req: Build5Request<NftPurchaseBulkRequest>,
): Observable<Transaction | undefined> => this.request(WEN_FUNC.orderNftBulk, req);

public orderToken = (
req: Build5Request<OrderTokenRequest>,
): Observable<Transaction | undefined> => this.request(WEN_FUNC.orderToken, req);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface MarketCollectionsFilters {
access?: string[];
space?: string[];
category?: string[];
status?: string[];
};
range?: {
price: string;
Expand Down Expand Up @@ -194,7 +195,7 @@ export class FilterStorageService {
public marketNftsResetVisible$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public marketNftsFilters$: BehaviorSubject<MarketNftsFilters> =
new BehaviorSubject<MarketNftsFilters>({
sortBy: this.marketNftsFiltersOptions.sortItems[0].value,
sortBy: this.marketNftsFiltersOptions.sortItems[2].value,
amenconi marked this conversation as resolved.
Show resolved Hide resolved
});

public marketCollectionsFiltersOptions = {
Expand Down
9 changes: 8 additions & 1 deletion src/app/@core/services/router/router.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { NavigationEnd, Router, Event } from '@angular/router';
import { ROUTER_UTILS } from '@core/utils/router.utils';
import { BehaviorSubject } from 'rxjs';
import { DeviceService } from '../device';
import { filter } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
Expand All @@ -21,6 +22,12 @@ export class RouterService {
public urlToNewToken = '/' + ROUTER_UTILS.config.token.root + '/new';

constructor(private router: Router, private deviceService: DeviceService) {
// this.router.events.pipe(
// filter((event: Event): event is NavigationEnd => event instanceof NavigationEnd)
// ).subscribe((event: NavigationEnd) => {
// console.log('Navigation Event:', event);
// });

this.updateVariables();

this.router.events.subscribe((obj) => {
Expand Down
1 change: 1 addition & 0 deletions src/app/@core/utils/local-storage.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum StorageItem {
SelectedTradePriceOption = 'App/selectedTradePriceOption',
DepositNftTransaction = 'App/depositNftTransaction-',
StakeNftTransaction = 'App/stakeNftTransaction-',
CartItems = 'App/cartItems',
}

export const getBitItemItem = (nftId: string): unknown | null => {
Expand Down
38 changes: 38 additions & 0 deletions src/app/@shell/ui/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@
</button>
</nz-badge>

<button
nz-button
nzType="default"
nzShape="circle"
class="relative inline-flex items-center justify-center border-0 wen-header-button ml-0 mr-2"
(click)="openShoppingCart()"
>
<nz-badge *ngIf="cartItemCount > 0" [nzCount]="cartItemCount">
<wen-icon-cart
class="text-foregrounds-primary dark:text-foregrounds-primary-dark"
></wen-icon-cart>
</nz-badge>
<wen-icon-cart
*ngIf="cartItemCount === 0"
class="text-foregrounds-primary dark:text-foregrounds-primary-dark"
></wen-icon-cart>
</button>

<button
nz-button
nzShape="circle"
Expand All @@ -53,6 +71,7 @@
<wen-icon-menu></wen-icon-menu>
</button>
</div>

<wen-mobile-menu
[enableCreateAwardProposal]="enableCreateAwardProposal"
[isVisible]="isMobileMenuVisible"
Expand Down Expand Up @@ -163,6 +182,24 @@
</button>
</nz-badge>

<button
nz-button
nzType="default"
nzShape="circle"
class="relative inline-flex items-center justify-center border-0 wen-header-button ml-0 mr-1"
(click)="openShoppingCart()"
>
<nz-badge *ngIf="cartItemCount > 0" [nzCount]="cartItemCount">
<wen-icon-cart
class="text-foregrounds-primary dark:text-foregrounds-primary-dark"
></wen-icon-cart>
</nz-badge>
<wen-icon-cart
*ngIf="cartItemCount === 0"
class="text-foregrounds-primary dark:text-foregrounds-primary-dark"
></wen-icon-cart>
</button>

<button
nz-button
nz-dropdown
Expand Down Expand Up @@ -245,6 +282,7 @@
</header>

<wen-sign-in-modal></wen-sign-in-modal>
<wen-app-cart-modal (openCartModal)="handleOpenCartModal()"></wen-app-cart-modal>

<wen-nft-checkout
*ngIf="isCheckoutOpen"
Expand Down
68 changes: 68 additions & 0 deletions src/app/@shell/ui/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
OnInit,
TemplateRef,
ViewChild,
Output,
EventEmitter,
} from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { CollectionApi } from '@api/collection.api';
Expand Down Expand Up @@ -35,6 +37,7 @@ import {
NotificationType,
TRANSACTION_AUTO_EXPIRY_MS,
Transaction,
TransactionPayloadType,
} from '@build-5/interfaces';
import dayjs from 'dayjs';
import { NzNotificationRef, NzNotificationService } from 'ng-zorro-antd/notification';
Expand All @@ -48,6 +51,9 @@ import {
skip,
} from 'rxjs';
import { MemberApi } from './../../../@api/member.api';
import { CartService } from './../../../components/cart/services/cart.service';
import { NzModalService } from 'ng-zorro-antd/modal';
import { CheckoutOverlayComponent } from '@components/cart/components/checkout/checkout-overlay.component';

const IS_SCROLLED_HEIGHT = 20;

Expand Down Expand Up @@ -76,6 +82,7 @@ export class HeaderComponent implements OnInit, OnDestroy {
public isMobileMenuVisible = false;
public isScrolled = false;
public isCheckoutOpen = false;
public isCartCheckoutOpen = false;
public currentCheckoutNft?: Nft;
public currentCheckoutCollection?: Collection;
public notifications$: BehaviorSubject<Notification[]> = new BehaviorSubject<Notification[]>([]);
Expand All @@ -87,6 +94,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
>(undefined);
private subscriptionTransaction$?: Subscription;
private subscriptionNotification$?: Subscription;
public cartItemCount = 0;
private cartItemsSubscription!: Subscription;
amenconi marked this conversation as resolved.
Show resolved Hide resolved

@Output() openCartModal = new EventEmitter<void>();

constructor(
public auth: AuthService,
Expand All @@ -102,6 +113,8 @@ export class HeaderComponent implements OnInit, OnDestroy {
private cd: ChangeDetectorRef,
private nzNotification: NzNotificationService,
private checkoutService: CheckoutService,
public cartService: CartService,
private modalService: NzModalService,
) {}

public ngOnInit(): void {
Expand All @@ -122,6 +135,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
});

this.cartItemsSubscription = this.cartService.getCartItems().subscribe((items) => {
this.cartItemCount = items.length;
});

const memberRoute = `/${ROUTER_UTILS.config.member.root}/`;
const landingPageRoute = `/${ROUTER_UTILS.config.base.home}`;

Expand Down Expand Up @@ -218,10 +235,24 @@ export class HeaderComponent implements OnInit, OnDestroy {
lastMember = undefined;
}
});

this.cartService.showCart$.subscribe((value) => {
// console.log('Current value of showCart$: ', value);
});
}

public async onOpenCheckout(): Promise<void> {
const t = this.transaction$.getValue();
console.log('[header-onOpenCheckout] transaction: ', t);
amenconi marked this conversation as resolved.
Show resolved Hide resolved
console.log('[header-onOpenCheckout] t?.payload.type: ', t?.payload.type);
if (t?.payload.type == TransactionPayloadType.NFT_PURCHASE_BULK) {
console.log(
amenconi marked this conversation as resolved.
Show resolved Hide resolved
'[header-onOpenCheckout] !t?.payload.type && t?.payload.type == TransactionPayloadType.NFT_PURCHASE_BULK equals true and isCartCheckoutOpen set to true',
);
this.openCartModal.emit();
this.openCheckoutOverlay();
}

if (!t?.payload.nft || !t.payload.collection) {
return;
}
Expand All @@ -245,6 +276,22 @@ export class HeaderComponent implements OnInit, OnDestroy {
}
}

private openCheckoutOverlay(): void {
const cartItems = this.cartService.getCartItems().getValue();

this.modalService.create({
nzTitle: 'Checkout',
nzContent: CheckoutOverlayComponent,
nzComponentParams: { items: cartItems },
nzFooter: null,
nzWidth: '80%',
});
}

public handleOpenCartModal(): void {
this.openCartModal.emit();
}

public get filesizes(): typeof FILE_SIZES {
return FILE_SIZES;
}
Expand All @@ -266,6 +313,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
this.isCheckoutOpen = false;
}

public closeCartCheckout() {
this.isCartCheckoutOpen = false;
}

public goToMyProfile(): void {
if (this.member$.value?.uid) {
this.router.navigate([
Expand Down Expand Up @@ -315,6 +366,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
}, 2500);
}

public getCartItemCount(): number {
return this.cartItemCount;
}

public unreadNotificationCount(): number {
if (!this.notifications$.value.length || !this.auth.member$.value?.uid) {
return 0;
Expand Down Expand Up @@ -372,11 +427,24 @@ export class HeaderComponent implements OnInit, OnDestroy {
});
}

public openShoppingCart(): void {
// console.log('Opening shopping cart...');
this.cartService.showCart();
}

public handleCartCheckout(): void {
this.isCartCheckoutOpen = true;
this.cd.markForCheck();
}

public ngOnDestroy(): void {
this.cancelAccessSubscriptions();
this.subscriptionNotification$?.unsubscribe();
this.subscriptionTransaction$?.unsubscribe();
this.currentCheckoutNft = undefined;
this.currentCheckoutCollection = undefined;
if (this.cartItemsSubscription) {
this.cartItemsSubscription.unsubscribe();
}
}
}
2 changes: 2 additions & 0 deletions src/app/@shell/ui/header/header.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { MobileHeaderModule } from '../mobile-header/mobile-header.module';
import { MobileMenuModule } from '../mobile-menu/mobile-menu.module';
import { TruncateModule } from './../../../@core/pipes/truncate/truncate.module';
import { HeaderComponent } from './header.component';
import { CartModule } from '@components/cart/cart.module';

@NgModule({
declarations: [HeaderComponent],
Expand Down Expand Up @@ -50,6 +51,7 @@ import { HeaderComponent } from './header.component';
NftCheckoutModule,
MobileMenuModule,
MobileHeaderModule,
CartModule,
],
exports: [HeaderComponent],
})
Expand Down
14 changes: 13 additions & 1 deletion src/app/components/algolia/services/algolia.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RefinementMappings } from '@components/algolia/refinement/refinement.co
import { enumToArray } from '@core/utils/manipulations.utils';
import { environment } from '@env/environment';
import { UntilDestroy } from '@ngneat/until-destroy';
import { Access, Categories, NftAvailable } from '@build-5/interfaces';
import { Access, Categories, NftAvailable, CollectionStatus } from '@build-5/interfaces';
import algoliasearch from 'algoliasearch/lite';

const accessMapping: RefinementMappings = {};
Expand Down Expand Up @@ -78,4 +78,16 @@ export class AlgoliaService {
};
});
}

public convertCollectionStatus(algoliaItems: any[]) {
const statuses = enumToArray(CollectionStatus);
return algoliaItems.map((algolia) => {
const label = statuses.find((status) => status.key === algolia.value)?.value;
return {
...algolia,
label: label,
highlighted: label,
};
});
}
}
Loading
Loading