Skip to content

Commit

Permalink
Mints Feature (#5037)
Browse files Browse the repository at this point in the history
* generic card

* static card

* stuff

* more things

* stuff

* ui

* just committing stuff

* fun

* stuff

* for christian

* dnoe ish

* overlay

* img stuff

* img stuff

* analytics

* stuff

* media component

* Improve FeaturedMintCard accent color contrast

* lots of things

* refactor

* tweak

* english

* turn on feature flag

* network badges

* rename index + show chain badge for mainnet

* fixes

* api updates

* fix account switching crash

* remote config

* turn off local flag

* fix

* flashlist -> flatlist

* lang

* perf

* remove no data state

* discover layout

* fix e2e

* lint

* fix

* fix e2e

* fix sizing

* recent mint gif

* png

* rm

* disable wallet switcher android

* wc: use bottom sheets on android

* tab bar color

* avatar fix

---------

Co-authored-by: Christian Baroni <[email protected]>
Co-authored-by: skylarbarrera <[email protected]>
  • Loading branch information
3 people authored and Ibrahim Taveras committed Sep 27, 2023
1 parent a15ccb2 commit f04ddc0
Show file tree
Hide file tree
Showing 28 changed files with 2,013 additions and 80 deletions.
25 changes: 25 additions & 0 deletions src/analytics/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ export const event = {
poapsViewedOnPoap: 'Viewed POAP on poap.gallery',
positionsOpenedSheet: 'Opened position Sheet',
positionsOpenedExternalDapp: 'Viewed external dapp',

mintsPressedFeaturedMintCard: 'Pressed featured mint card',
mintsPressedCollectionCell: 'Pressed collection cell in mints card',
mintsPressedMintButton: 'Pressed mint button in mints sheet',
mintsPressedViewAllMintsButton: 'Pressed view all mints button in mints card',
mintsChangedFilter: 'Changed mints filter',
} as const;

/**
Expand Down Expand Up @@ -304,4 +310,23 @@ export type EventProperties = {
[event.positionsOpenedSheet]: {
dapp: string;
};
[event.mintsPressedFeaturedMintCard]: {
contractAddress: string;
chainId: number;
totalMints: number;
mintsLastHour: number;
priceInEth: number;
};
[event.mintsPressedCollectionCell]: {
contractAddress: string;
chainId: number;
priceInEth: number;
};
[event.mintsPressedMintButton]: {
contractAddress: string;
chainId: number;
priceInEth: number;
};
[event.mintsPressedViewAllMintsButton]: undefined;
[event.mintsChangedFilter]: { filter: string };
};
5 changes: 5 additions & 0 deletions src/analytics/userProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export interface UserProperties {
nftOffersMeanOfferVariance?: number;
nftOffersMedianOfferVariance?: number;

// mint.fun
numberOfMints?: number;
numberOfFreeMints?: number;
numberOfPaidMints?: number;

// ens
// TODO: remove ensProfile tracking the entire object
ensProfile?: Record<any, any>;
Expand Down
88 changes: 88 additions & 0 deletions src/components/Media.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState } from 'react';
import { maybeSignUri } from '@/handlers/imgix';
import { Image, ImageStyle, View } from 'react-native';
// @ts-ignore
import Video from 'react-native-video';
import SvgImage from './svg/SvgImage';

export enum MimeType {
MP4 = 'video/mp4',
GIF = 'image/gif',
PNG = 'image/png',
SVG = 'image/svg+xml',
JPG = 'image/jpeg',
}

export function Media({
url,
mimeType,
fallbackUrl,
style,
size,
onLayout,
onError,
}: {
url: string;
mimeType?: string;
fallbackUrl?: string;
style?: ImageStyle;
size?: number;
onLayout?: () => void;
onError?: () => void;
}) {
const [loading, setLoading] = useState(false);

const signedUrl = maybeSignUri(url, {
// resizing breaks svg
w: mimeType === MimeType.SVG ? undefined : size,
fm: !mimeType ? 'png' : undefined,
});
const signedFallbackUrl = maybeSignUri(fallbackUrl ?? url, {
w: size,
fm: 'png',
});

switch (mimeType) {
case MimeType.MP4:
return (
<Video
onLayout={onLayout}
loading={loading}
onError={onError}
muted
resizeMode="cover"
poster={signedFallbackUrl}
posterResizeMode="cover"
setLoading={setLoading}
repeat
style={style}
source={{ uri: signedUrl }}
uri={signedUrl}
/>
);
case MimeType.SVG:
return (
<View style={{ ...style, overflow: 'hidden' }}>
<SvgImage
fallbackUri={signedFallbackUrl}
onLayout={onLayout}
onError={onError}
style={style}
source={{ uri: signedUrl }}
/>
</View>
);
case MimeType.GIF:
case MimeType.PNG:
case MimeType.JPG:
default:
return (
<Image
onLayout={onLayout}
onError={onError}
source={{ uri: signedUrl }}
style={style}
/>
);
}
}
1 change: 0 additions & 1 deletion src/components/PromoSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import LinearGradient from 'react-native-linear-gradient';
import MaskedView from '@react-native-masked-view/masked-view';
import { SheetActionButton, SheetHandle, SlackSheet } from '@/components/sheet';
import { CampaignKey } from '@/campaigns/campaignChecks';
import { ImgixImage } from '@/components/images';
import { analytics } from '@/analytics';
import {
AccentColorProvider,
Expand Down
170 changes: 170 additions & 0 deletions src/components/cards/CarouselCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {
Bleed,
Box,
Column,
Columns,
Inline,
Stack,
Text,
useColorMode,
useForegroundColor,
} from '@/design-system';
import React from 'react';
import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { ButtonPressAnimation } from '@/components/animations';
import { useDimensions } from '@/hooks';
import ActivityIndicator from '@/components/ActivityIndicator';
import { IS_ANDROID } from '@/env';
import Spinner from '@/components/Spinner';
import { ScrollView, View } from 'react-native';

const HORIZONTAL_PADDING = 20;

const LoadingSpinner = IS_ANDROID ? Spinner : ActivityIndicator;

type CarouselItem<T> = {
renderItem: ListRenderItem<T>;
keyExtractor: (item: T, index: number) => string;
placeholder: React.ReactNode;
width: number;
height: number; // make sure to include extra 20px to accommodate for vertical bleed
padding: number;
verticalOverflow?: number;
};

export function CarouselCard<T>({
title,
data,
carouselItem,
button,
menu,
refresh,
canRefresh,
isRefreshing,
}: {
title: string | React.ReactNode;
data: T[] | null | undefined;
carouselItem: CarouselItem<T>;
button: React.ReactNode;
menu?: React.ReactNode;
refresh?: () => void;
canRefresh?: boolean;
isRefreshing?: boolean;
}) {
const borderColor = useForegroundColor('separator');
const { width: deviceWidth } = useDimensions();
const { colorMode } = useColorMode();

const actualItemHeight =
carouselItem.height + (carouselItem.verticalOverflow ?? 0) * 2;

return (
<Stack space="20px">
<Box
flexDirection="row"
alignItems="center"
justifyContent="space-between"
>
{typeof title === 'string' ? (
<Text color="label" weight="heavy" size="20pt">
{title}
</Text>
) : (
<Box width="full">{title}</Box>
)}
{menu}
</Box>
{/* FlashList vertical visible overflow does not work due to a bug,
so we need to manually add vertical padding to the recycled component.
The vertical bleed here is to accommodate the vertical padding w/o affecting the layout.
See https://github.com/Shopify/flash-list/issues/723*/}
<Bleed
horizontal="20px"
vertical={{ custom: carouselItem.verticalOverflow ?? 0 }}
>
<Box height={{ custom: actualItemHeight }}>
{data?.length ? (
<FlashList
data={data}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingHorizontal: HORIZONTAL_PADDING,
}}
estimatedItemSize={carouselItem.width}
horizontal
estimatedListSize={{
height: actualItemHeight,
width: deviceWidth * 2,
}}
style={{ flex: 1 }}
renderItem={info => (
<View
style={{
paddingVertical: carouselItem.verticalOverflow,
alignItems: 'center',
}}
>
{carouselItem.renderItem(info)}
</View>
)}
ItemSeparatorComponent={() => (
<View style={{ width: carouselItem.padding }} />
)}
keyExtractor={carouselItem.keyExtractor}
/>
) : (
// need this due to FlashList bug https://github.com/Shopify/flash-list/issues/757
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingHorizontal: HORIZONTAL_PADDING,
}}
>
<Inline space={{ custom: carouselItem.padding }}>
{carouselItem.placeholder}
{carouselItem.placeholder}
{carouselItem.placeholder}
{carouselItem.placeholder}
{carouselItem.placeholder}
</Inline>
</ScrollView>
)}
</Box>
</Bleed>
<Columns space="10px">
<Column>{button}</Column>
{!!refresh && (
<Column width="content">
<Box
as={ButtonPressAnimation}
// @ts-ignore
disabled={!canRefresh}
onPress={refresh}
justifyContent="center"
alignItems="center"
borderRadius={18}
style={{
borderWidth: isRefreshing ? 0 : 1,
borderColor,
width: 36,
height: 36,
}}
>
{isRefreshing ? (
<LoadingSpinner
color={colorMode === 'light' ? 'black' : 'white'}
size={20}
/>
) : (
<Text align="center" color="label" size="17pt" weight="bold">
􀅈
</Text>
)}
</Box>
</Column>
)}
</Columns>
</Stack>
);
}
Loading

0 comments on commit f04ddc0

Please sign in to comment.