Skip to content

Commit

Permalink
Mobile: Add pinned announcement notification powered by Sanity + Make…
Browse files Browse the repository at this point in the history
… Mint Flow configurable via Sanity (#2445)

* add pinned announcement notification powered by Sanity

* make mint flow bottom sheet generic, powered by sanity

* sanity powered mint config

* address comments

* update dark mode colors
  • Loading branch information
kaitoo1 authored May 1, 2024
1 parent 3c49cf0 commit 80c3381
Show file tree
Hide file tree
Showing 15 changed files with 521 additions and 86 deletions.
46 changes: 26 additions & 20 deletions apps/mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { CheckMaintenanceOnAppForeground, MaintenanceScreen } from './components
import SearchProvider from './components/Search/SearchContext';
import BottomSheetModalProvider from './contexts/BottomSheetModalContext';
import ManageWalletProvider from './contexts/ManageWalletContext';
import { SanityAnnouncementProvider } from './contexts/SanityAnnouncementContext';
import SanityDataProvider from './contexts/SanityDataContext';
import SyncTokensProvider from './contexts/SyncTokensContext';
import ToastProvider from './contexts/ToastContext';
import { TokenStateManagerProvider } from './contexts/TokenStateManagerContext';
Expand Down Expand Up @@ -156,26 +158,30 @@ export default function App() {
<magic.Relayer />
<SearchProvider>
<NavigationContainer ref={navigationRef}>
<ToastProvider>
<TokenStateManagerProvider>
<PortalProvider>
<BottomSheetModalProvider>
<SyncTokensProvider>
<ManageWalletProvider>
{/* Register the user's push token if one exists (does not prompt the user) */}
<NotificationRegistrar />
<DevMenuItems />
<DeepLinkRegistrar />
<RootStackNavigator
navigationContainerRef={navigationRef}
/>
</ManageWalletProvider>
</SyncTokensProvider>
<PortalHost name="app-context" />
</BottomSheetModalProvider>
</PortalProvider>
</TokenStateManagerProvider>
</ToastProvider>
<SanityDataProvider>
<ToastProvider>
<TokenStateManagerProvider>
<PortalProvider>
<BottomSheetModalProvider>
<SyncTokensProvider>
<ManageWalletProvider>
<SanityAnnouncementProvider>
{/* Register the user's push token if one exists (does not prompt the user) */}
<NotificationRegistrar />
<DevMenuItems />
<DeepLinkRegistrar />
<RootStackNavigator
navigationContainerRef={navigationRef}
/>
</SanityAnnouncementProvider>
</ManageWalletProvider>
</SyncTokensProvider>
<PortalHost name="app-context" />
</BottomSheetModalProvider>
</PortalProvider>
</TokenStateManagerProvider>
</ToastProvider>
</SanityDataProvider>
</NavigationContainer>
</SearchProvider>
</SafeAreaProvider>
Expand Down
26 changes: 18 additions & 8 deletions apps/mobile/src/components/ClaimMintUpsellBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useCallback } from 'react';
import { View } from 'react-native';
import { graphql, useFragment } from 'react-relay';
import { MCHX_CLAIM_CODE_KEY } from 'src/constants/storageKeys';
import usePersistedState from 'src/hooks/usePersistedState';
import { XMarkIcon } from 'src/icons/XMarkIcon';

import { useBottomSheetModalActions } from '~/contexts/BottomSheetModalContext';
import { useSanityAnnouncementContext } from '~/contexts/SanityAnnouncementContext';
import { ClaimMintUpsellBannerFragment$key } from '~/generated/ClaimMintUpsellBannerFragment.graphql';
import { contexts } from '~/shared/analytics/constants';
import colors from '~/shared/theme/colors';
Expand All @@ -17,9 +17,10 @@ import { Typography } from './Typography';

type Props = {
queryRef: ClaimMintUpsellBannerFragment$key;
projectInternalId: string;
};

export function ClaimMintUpsellBanner({ queryRef }: Props) {
export function ClaimMintUpsellBanner({ queryRef, projectInternalId }: Props) {
const query = useFragment(
graphql`
fragment ClaimMintUpsellBannerFragment on Query {
Expand All @@ -37,10 +38,10 @@ export function ClaimMintUpsellBanner({ queryRef }: Props) {

const { showBottomSheetModal, hideBottomSheetModal } = useBottomSheetModalActions();

const [claimCode] = usePersistedState(MCHX_CLAIM_CODE_KEY, '');
const [claimCode] = usePersistedState(`${projectInternalId}-claim-code`, '');

const [isUpsellMintBannerDismissed, setIsUpsellMintBannerDismissed] = usePersistedState(
'isUpsellMintBannerDismissed',
`${projectInternalId}-isUpsellMintBannerDismissed`,
false
);

Expand All @@ -49,12 +50,21 @@ export function ClaimMintUpsellBanner({ queryRef }: Props) {
}, [setIsUpsellMintBannerDismissed]);

const handleClaimPress = useCallback(() => {
showBottomSheetModal({ content: <MintCampaignBottomSheet onClose={hideBottomSheetModal} /> });
}, [hideBottomSheetModal, showBottomSheetModal]);
showBottomSheetModal({
content: (
<MintCampaignBottomSheet
onClose={hideBottomSheetModal}
projectInternalId={projectInternalId}
/>
),
});
}, [hideBottomSheetModal, projectInternalId, showBottomSheetModal]);

const { announcement } = useSanityAnnouncementContext();

const user = query.viewer?.user;

if (!user || claimCode || isUpsellMintBannerDismissed) {
if (!user || !announcement || claimCode || isUpsellMintBannerDismissed) {
return <View className="bg-white dark:bg-black-900" />;
}

Expand All @@ -71,7 +81,7 @@ export function ClaimMintUpsellBanner({ queryRef }: Props) {
font={{ family: 'ABCDiatype', weight: 'Regular' }}
className="text-offWhite text-xs"
>
Claim your free generative work by MCHX
{announcement.description}
</Typography>
</View>
<View className="flex-row items-center space-x-2">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
import { MCHX_CLAIM_CODE_KEY } from 'src/constants/storageKeys';
import { useMemo } from 'react';
import usePersistedState from 'src/hooks/usePersistedState';

import { useSanityDataContext } from '~/contexts/SanityDataContext';

import MintCampaignPostTransaction from './MintCampaignPostTransaction';
import MintCampaignPreTransaction from './MintCampaignPreTransaction';

export default function MintCampaignBottomSheet({ onClose }: { onClose: () => void }) {
type Props = {
onClose?: () => void;
projectInternalId: string;
};

export default function MintCampaignBottomSheet({ onClose, projectInternalId }: Props) {
// claimCode is the identifer used to poll for the status of the mint
// Once we kick off the mint, the backend returns a claim code from Highlight that we can use to check the status of the mint

const [claimCode, setClaimCode] = usePersistedState(MCHX_CLAIM_CODE_KEY, '');
const [claimCode, setClaimCode] = usePersistedState(`${projectInternalId}_claim_code`, '');

const { data } = useSanityDataContext();
const projectData = useMemo(() => {
return data?.mintProjects.find((document) => document.internalId === projectInternalId);
}, [data, projectInternalId]);

if (!projectData) {
// TODO decide best way to handle missing data
return null;
}

if (claimCode) {
return <MintCampaignPostTransaction claimCode={claimCode} onClose={onClose} />;
return (
<MintCampaignPostTransaction
claimCode={claimCode}
onClose={onClose}
projectData={projectData}
/>
);
}

return <MintCampaignPreTransaction setClaimCode={setClaimCode} />;
return (
<MintCampaignPreTransaction
setClaimCode={setClaimCode}
projectInternalId={projectInternalId}
projectData={projectData}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SpinnerIcon } from 'src/icons/SpinnerIcon';

import { Button } from '~/components/Button';
import { BaseM, TitleS } from '~/components/Text';
import { MintProject } from '~/contexts/SanityDataContext';
import {
HighlightTxStatus,
MintCampaignPostTransactionMintStatusQuery,
Expand All @@ -26,9 +27,11 @@ import { NftDetailAsset } from '../../../screens/NftDetailScreen/NftDetailAsset/
export default function MintCampaignPostTransaction({
claimCode,
onClose,
projectData,
}: {
claimCode: string;
onClose: () => void;
onClose?: () => void;
projectData: MintProject;
}) {
const [state, setState] = useState<HighlightTxStatus>('TX_PENDING');
// TODO: Fix any - prioritizing merging atm
Expand Down Expand Up @@ -119,7 +122,9 @@ export default function MintCampaignPostTransaction({
});
}

onClose();
if (onClose) {
onClose();
}

// close bottom sheet
}, [navigation, onClose, token?.dbid]);
Expand All @@ -129,15 +134,22 @@ export default function MintCampaignPostTransaction({
if (token?.definition?.community) {
navigateToCommunity(token?.definition?.community);
}
onClose();
if (onClose) {
onClose();
}
}, [navigateToCommunity, onClose, token?.definition?.community]);

if (state === 'TOKEN_SYNCED' && token) {
return (
<View>
<View className="mb-1">
<TitleS>Congratulations!</TitleS>
<TitleS>You collected {`${token?.definition?.name ?? 'Radiance'} by MCHX`}</TitleS>
<TitleS>
You collected{' '}
{`${token?.definition?.name ?? projectData.collectionName} by ${
projectData.artistName
}`}
</TitleS>
</View>
<BaseM>
Thank you for using the Gallery mobile app. Share your unique piece with others below!
Expand Down Expand Up @@ -173,7 +185,7 @@ export default function MintCampaignPostTransaction({
<BaseM>
{state === 'TX_PENDING'
? 'Your new artwork is being minted onchain. This should take less than a minute.'
: 'Revealing your unique artwork. It will be ready to view in a moment!'}
: 'Revealing your artwork. It will be ready to view in a moment!'}
</BaseM>

<View className="my-4 flex justify-center items-center bg-faint dark:bg-black-700 w-full aspect-square ">
Expand All @@ -183,30 +195,18 @@ export default function MintCampaignPostTransaction({
size="s"
colorOverride={{ lightMode: colors.shadow, darkMode: colors.shadow }}
/>
<LoadingStateMessage />
<LoadingStateMessage funFacts={projectData.funFacts} />
</View>
</View>
{error && <BaseM classNameOverride="text-red">{error}</BaseM>}
</View>
);
}

const COPY = [
'Anton Dubrovin, aka MCHX, was born in Kazakhstan and is currently based in Georgia.',
'Anton is a digital artist known for experimenting with colors and form.',
'Anton uses color as a universal channel of emotional connection and self-exploration.',
'For this project, MCHX created over 60 unique color modes and used a circle as the central object due to its universal symbolism of unity and integrity.',
'This work leverages Javascript, GLSL, and Display P3 wide-gamut to explore emotional connection through color.',
"Anton's diverse inspiration comes from 20th-century abstraction, Abstract Expressionism, Color Field artists, nature, music, and the internet.",
'In his free time, Anton enjoys taking walks, reading, and watching Japanese anime and dramas.',
'Anton has been creating art since 2016, but entered the NFT and Web3 space in 2020.',
'Anton believes in the exchange of energy inherent in blockchain interactions and his work carries imprints of his emotional states or needs at the time of creation.',
];

const FADE_DURATION = 250;
const TEXT_DURATION = 8000;

function LoadingStateMessage() {
function LoadingStateMessage({ funFacts }: { funFacts: string[] }) {
const [index, setIndex] = useState(0);

const fadeInOpacity = useSharedValue(1);
Expand Down Expand Up @@ -235,18 +235,18 @@ function LoadingStateMessage() {
const updateDisplayedMessage = async () => {
fadeOut();
await new Promise((resolve) => setTimeout(resolve, FADE_DURATION));
setIndex((index) => (index + 1) % COPY.length);
setIndex((index) => (index + 1) % funFacts.length);
fadeIn();
};

const interval = setInterval(updateDisplayedMessage, TEXT_DURATION);

return () => clearInterval(interval);
}, [fadeIn, fadeOut]);
}, [fadeIn, fadeOut, funFacts.length]);
return (
<View className="text-center h-32">
<Animated.View style={[animatedStyle]}>
<BaseM classNameOverride="text-shadow text-center ">{COPY[index]}</BaseM>
<BaseM classNameOverride="text-shadow text-center ">{funFacts[index]}</BaseM>
</Animated.View>
</View>
);
Expand Down
Loading

0 comments on commit 80c3381

Please sign in to comment.