Skip to content

Commit

Permalink
feat: add popular collections table (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahin-hq authored Dec 12, 2023
1 parent 528235e commit c1da7b3
Show file tree
Hide file tree
Showing 21 changed files with 978 additions and 7 deletions.
86 changes: 86 additions & 0 deletions app/Http/Controllers/PopularCollectionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Data\Collections\CollectionData;
use App\Data\Collections\CollectionStatsData;
use App\Enums\Chain;
use App\Enums\CurrencyCode;
use App\Models\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class PopularCollectionController extends Controller
{
public function index(Request $request): Response|JsonResponse|RedirectResponse
{
$user = $request->user();

$chainId = match ($request->query('chain')) {
'polygon' => Chain::Polygon->value,
'ethereum' => Chain::ETH->value,
default => null,
};

$currency = $user ? $user->currency() : CurrencyCode::USD;

$collections = Collection::query()
->when($request->query('sort') !== 'floor-price', fn ($q) => $q->orderBy('volume', 'desc')) // TODO: order by top...
->filterByChainId($chainId)
->orderByFloorPrice('desc', $currency)
->with([
'network',
'floorPriceToken',
'nfts' => fn ($q) => $q->limit(4),
])
->withCount('nfts')
->selectVolumeFiat($currency)
->addSelect('collections.*')
->groupBy('collections.id')
->paginate(25);

return Inertia::render('Collections/PopularCollections/Index', [
'title' => trans('metatags.popular-collections.title'),
'allowsGuests' => true,
'collections' => CollectionData::collection(
$collections->through(fn ($collection) => CollectionData::fromModel($collection, $currency))
),
'stats' => new CollectionStatsData(
nfts: 145,
collections: 25,
value: 256000,
),
'filters' => $this->getFilters($request),
]);
}

/**
* @return object{chain?: string, sort?: string}
*/
private function getFilters(Request $request): object
{
$filter = [
'chain' => $this->getValidValue($request->get('chain'), ['polygon', 'ethereum']),
'sort' => $this->getValidValue($request->get('sort'), ['floor-price']),
];

// If value is not defined (or invalid), remove it from the array since
// the frontend expect `undefined` values (not `null`)

// Must be cast to an object due to some Inertia front-end stuff...
return (object) array_filter($filter);
}

/**
* @param array<string> $validValues
*/
private function getValidValue(?string $value, array $validValues): ?string
{
return in_array($value, $validValues) ? $value : null;
}
}
1 change: 1 addition & 0 deletions lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,5 @@
'votes' => 'Votes',
'vote' => 'Vote',
'vol' => 'Vol',
'collection_preview' => 'Collection Preview',
];
4 changes: 4 additions & 0 deletions lang/en/metatags.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
'title' => 'My Collections | Dashbrd',
],

'popular-collections' => [
'title' => 'Popular NFT Collections | Dashbrd',
],

'nfts' => [
'view' => [
'title' => ':nft NFT | Dashbrd',
Expand Down
4 changes: 4 additions & 0 deletions lang/en/pages.php
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,8 @@
'terms_of_service' => [
'title' => 'Terms of Service',
],
'popular_collections' => [
'title' => 'Popular NFT Collections',
'header_title' => '<0>:nftsCount</0> :nfts from <0>:collectionsCount</0> :collections, worth about <0><1>:worth</1></0>',
],
];
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const CollectionImages = ({
<div
className="h-20 w-20"
key={nft.id}
data-testid="CollectionImages__Image"
>
<Img
wrapperClassName="aspect-square"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import { CollectionName } from "@/Components/Collections/CollectionsFullTable/CollectionName";
import CollectionFactory from "@/Tests/Factories/Collections/CollectionFactory";
import { render, screen } from "@/Tests/testing-library";

describe("CollectionName", () => {
it("should render", () => {
const collection = new CollectionFactory().create();

render(<CollectionName collection={collection} />);

expect(screen.getByTestId("CollectionName")).toBeInTheDocument();
expect(screen.getAllByTestId("Img")).toHaveLength(1);
expect(screen.getByTestId("NetworkIcon")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useRef } from "react";
import { useTranslation } from "react-i18next";
import { Img } from "@/Components/Image";
import { NetworkIcon } from "@/Components/Networks/NetworkIcon";
import { Tooltip } from "@/Components/Tooltip";
import { useIsTruncated } from "@/Hooks/useIsTruncated";

export const CollectionName = ({ collection }: { collection: App.Data.Collections.CollectionData }): JSX.Element => {
const { t } = useTranslation();
const collectionNameReference = useRef<HTMLParagraphElement>(null);
const isTruncated = useIsTruncated({ reference: collectionNameReference });

return (
<div
data-testid="CollectionName"
className="group relative h-11 w-full cursor-pointer md:h-20"
>
<div className="absolute flex w-full items-center space-x-4">
<div className="relative h-8 w-8 shrink-0 md:h-20 md:w-20">
<Img
wrapperClassName="aspect-square"
className="h-full w-full rounded-full object-cover"
src={collection.image}
isCircle
/>

<div className="absolute left-5 top-5 block h-4 w-4 rounded-full ring-4 ring-white dark:ring-theme-dark-900 md:hidden">
<NetworkIcon networkId={collection.chainId} />
</div>
</div>

<div className="break-word-legacy min-w-0 space-y-0.5">
<Tooltip
disabled={!isTruncated}
content={collection.name}
>
<p
ref={collectionNameReference}
data-testid="CollectionName__name"
className="group-hover md:leading-auto truncate text-sm font-medium leading-6 text-theme-secondary-900 group-hover:text-theme-primary-700 dark:text-theme-dark-50 dark:group-hover:text-theme-primary-400 md:text-lg"
>
{collection.name}
</p>
</Tooltip>

<p className="block truncate text-xs font-medium leading-4.5 text-theme-secondary-500 md:hidden">
{collection.nftsCount} {t("common.items").toLowerCase()}
</p>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface CollectionTableItemProperties {
collection: App.Data.Collections.CollectionData;
uniqueKey: string;
user: App.Data.UserData | null;
}

export interface CollectionTableProperties {
collections: App.Data.Collections.CollectionData[];
user: App.Data.UserData | null;
activeSort?: string;
sortDirection?: "asc" | "desc";
onSort?: ({
sortBy,
direction,
selectedChainIds,
}: {
sortBy: string;
direction?: string;
selectedChainIds?: number[];
}) => void;
selectedChainIds?: number[];
}
Loading

0 comments on commit c1da7b3

Please sign in to comment.