Files
explore/ui/marketplace/MarketplaceAppCard.tsx

177 lines
5.2 KiB
TypeScript

import type { MouseEvent } from 'react';
import React, { useCallback } from 'react';
import type { MarketplaceApp } from 'types/client/marketplace';
import { cn } from 'lib/utils/cn';
import { useColorModeValue } from 'toolkit/next/color-mode';
import { IconButton } from '@luxfi/ui/icon-button';
import { Image } from '@luxfi/ui/image';
import { Link, LinkBox } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import { isBrowser } from 'toolkit/utils/isBrowser';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import Rating from './Rating/Rating';
interface Props extends MarketplaceApp {
onInfoClick: (id: string) => void;
isFavorite: boolean;
onFavoriteClick: (id: string, isFavorite: boolean) => void;
isLoading: boolean;
onAppClick: (event: MouseEvent, id: string) => void;
className?: string;
graphLinks?: Array<{ title: string; url: string }>;
}
const MarketplaceAppCard = ({
id,
url,
external,
title,
logo,
logoDarkMode,
shortDescription,
categories,
onInfoClick,
isFavorite,
onFavoriteClick,
isLoading,
internalWallet,
onAppClick,
className,
rating,
ratingsTotalCount,
userRating,
graphLinks,
}: Props) => {
const categoriesLabel = categories.join(', ');
const handleInfoClick = useCallback((event: MouseEvent) => {
event.preventDefault();
onInfoClick(id);
}, [ onInfoClick, id ]);
const handleFavoriteClick = useCallback(() => {
onFavoriteClick(id, isFavorite);
}, [ onFavoriteClick, id, isFavorite ]);
const logoUrl = useColorModeValue(logo, logoDarkMode || logo);
return (
<LinkBox
className={ cn(
'rounded-[var(--radius-base,8px)] p-3 border border-solid border-black/30 dark:border-white/30',
!isLoading && 'hover:shadow-md focus-within:shadow-md',
className,
) }
>
<div className="flex flex-col h-full content-start gap-2">
<div className="flex gap-4">
<Skeleton
loading={ isLoading }
w="64px"
h="64px"
display="flex"
alignItems="center"
justifyContent="center"
flexShrink={ 0 }
>
<Image
src={ isLoading ? undefined : logoUrl }
alt={ `${ title } app icon` }
borderRadius="8px"
/>
</Skeleton>
<div className="flex flex-col gap-2 pt-1">
<Skeleton
loading={ isLoading }
display="inline-flex"
alignItems="center"
>
<MarketplaceAppCardLink
id={ id }
url={ url }
external={ external }
title={ title }
onClick={ onAppClick }
className="text-sm font-semibold"
/>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks
links={ graphLinks }
className="ml-2 align-middle"
/>
</Skeleton>
<Skeleton
loading={ isLoading }
color="text.secondary"
className="text-xs leading-4"
>
<span>{ categoriesLabel }</span>
</Skeleton>
</div>
</div>
<Skeleton
loading={ isLoading }
asChild
>
<span className="line-clamp-2 text-sm">
{ shortDescription }
</span>
</Skeleton>
{ !isLoading && (
<div className="flex items-center justify-between mt-auto h-[30px]">
<Link
className="text-sm font-medium pr-3 h-full"
href="#"
onClick={ handleInfoClick }
>
Info
</Link>
<div className="flex items-center gap-3">
<Rating
appId={ id }
rating={ rating }
ratingsTotalCount={ ratingsTotalCount }
userRating={ userRating }
isLoading={ isLoading }
source="Discovery"
/>
<div className="flex gap-2">
<IconButton
aria-label="Mark as favorite"
title="Mark as favorite"
variant="icon_background"
size="md"
onClick={ handleFavoriteClick }
selected={ isFavorite }
>
<FavoriteIcon isFavorite={ isFavorite }/>
</IconButton>
<CopyToClipboard
text={ isBrowser() ? window.location.origin + `/apps/${ id }` : '' }
type="share"
variant="icon_background"
size="md"
className="rounded-[var(--radius-base,8px)] ml-0"
/>
</div>
</div>
</div>
) }
</div>
</LinkBox>
);
};
export default MarketplaceAppCard;