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

Feature: Add remote promo sheet #5140

Merged
merged 49 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
77f66bd
feat: initial work on generalizing promo sheet checks and remote prom…
walmat Oct 19, 2023
79b4f33
Merge branch 'develop' into @matthew/APP-865
walmat Oct 20, 2023
4c6d3c9
more work on remote promo sheets
walmat Oct 20, 2023
082b12d
update queries
walmat Oct 24, 2023
552ab5d
Update arc.graphql to expose promoSheet and promoSheetCollection
walmat Oct 25, 2023
9e1c0eb
update campaign checks and consume getPromoSheet query
walmat Oct 25, 2023
d9c8ee8
write promoSheet and promoSheetCollection queries
walmat Oct 25, 2023
75830f3
add remote promo sheet route and name
walmat Oct 25, 2023
aa68885
add a couple mmkv STORAGE_IDS to control whether or not we show promos
walmat Oct 25, 2023
2da3c89
add remote promo sheets feature flag
walmat Oct 25, 2023
a8eef90
tweak remote promo sheet logic
walmat Oct 25, 2023
f586e4f
fix signing remote images
walmat Oct 25, 2023
f3a0420
more sanity checks and refetch interval
walmat Oct 25, 2023
90ba2ac
add RemotePromoSheetProvider and remove unnecessary campaignChecks
walmat Oct 25, 2023
042422b
tweak checks and add Context/Provider for controlling remote promo sh…
walmat Oct 25, 2023
9b7105c
re-enable firstLaunch and hasViewed checks
walmat Oct 25, 2023
e19ac94
another sanity check
walmat Oct 25, 2023
70e4652
fix hasNonZeroAssetBalance
walmat Oct 25, 2023
2f5b8a6
update check fns
walmat Oct 26, 2023
72e063b
add campaign storage to @storage model
walmat Oct 26, 2023
99146fc
update fns and remove some unused ones
walmat Oct 26, 2023
fc6c525
update arc.graphql query to include priority
walmat Oct 26, 2023
6e41777
update provider and sheet to use @storage
walmat Oct 26, 2023
ceb1325
add priority tag to collection query
walmat Oct 26, 2023
e9853e0
update check for campaign to use @/storage and abstraction of check-f…
walmat Oct 26, 2023
36b574f
adjust asset check fns
walmat Oct 26, 2023
595f7f3
syncronize feature unlocks and campaign checks
walmat Oct 27, 2023
da99524
add notifications promo and cleanup analytic events
walmat Oct 27, 2023
07fb766
add nft offers promo sheet and cleanup priority logic
walmat Oct 27, 2023
fdd6e8a
fix conflicting nft offers asset type with contentful
walmat Oct 27, 2023
afd1d55
replace PromoSheet analytics with v2
walmat Oct 27, 2023
3c838a4
revert graphql arc config change and cleanup local promo sheets
walmat Oct 27, 2023
cff32aa
enable i18n in contentful and pass locale through
walmat Oct 27, 2023
29b6865
enable i18n clientside
walmat Oct 28, 2023
3123fab
update language
walmat Oct 30, 2023
cdbab1b
Merge branch 'develop' into @matthew/APP-865
walmat Nov 8, 2023
3263588
remove unused campaigns folder and uncomment check
walmat Nov 9, 2023
cb77217
remove unused campaigns folder
walmat Nov 9, 2023
f065a2c
fix lint and func name
walmat Nov 9, 2023
374c2e8
pass all locales through localized fields
walmat Nov 9, 2023
001327f
change default colors to hex
walmat Nov 13, 2023
9e32a42
Merge branch 'develop' into @matthew/APP-865
walmat Nov 13, 2023
0189eb9
add specific address for testing preview purposes
walmat Nov 13, 2023
803d0f1
Merge branch 'develop' into @matthew/APP-865
walmat Nov 13, 2023
800dbc5
final touches
walmat Nov 14, 2023
15e25b5
re-add hasShown check
walmat Nov 14, 2023
9140043
add isPreviewing actionFn to bypass hasShown check
walmat Nov 14, 2023
4b49b84
get color from theme if primary/secondary button has that prop
walmat Nov 14, 2023
d666bdc
add network to asset check
walmat Nov 14, 2023
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
16 changes: 13 additions & 3 deletions src/components/PromoSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, { useCallback, useEffect, useReducer } from 'react';
import { ImageSourcePropType, StatusBar, ImageBackground } from 'react-native';
import {
ImageSourcePropType,
StatusBar,
ImageBackground,
ImageURISource,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import MaskedView from '@react-native-masked-view/masked-view';
import { SheetActionButton, SheetHandle, SlackSheet } from '@/components/sheet';
Expand Down Expand Up @@ -38,7 +43,7 @@ type PromoSheetProps = {
backgroundColor: string;
accentColor: string;
sheetHandleColor?: string;
campaignKey: CampaignKey;
campaignKey: CampaignKey | string;
header: string;
subHeader: string;
primaryButtonProps: SheetActionButtonProps;
Expand Down Expand Up @@ -117,7 +122,12 @@ export function PromoSheet({
testID={campaignKey}
>
{/* @ts-ignore */}
<Box as={ImageBackground} height="full" source={backgroundImage}>
<Box
as={ImageBackground}
height="full"
src={(backgroundImage as ImageURISource).uri}
source={backgroundImage}
>
<Rows>
<Row>
<Stack space={{ custom: isSmallPhone ? 46 : 54 }}>
Expand Down
131 changes: 131 additions & 0 deletions src/components/remote-promo-sheet/RemotePromoSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React, { useEffect } from 'react';
import { useRoute, RouteProp } from '@react-navigation/native';
import { noop, get } from 'lodash';
import { MMKV } from 'react-native-mmkv';

import { useNavigation } from '@/navigation/Navigation';
import { PromoSheet } from '@/components/PromoSheet';
import { useTheme } from '@/theme';
import { CampaignCheckResult } from './useRunCampaignChecks';
import { usePromoSheetQuery } from '@/resources/promoSheet/promoSheetQuery';
import { STORAGE_IDS } from '@/model/mmkv';
import { maybeSignUri } from '@/handlers/imgix';

const HEADER_HEIGHT = 285;
const HEADER_WIDTH = 390;

type RootStackParamList = {
RemotePromoSheet: CampaignCheckResult;
};

const mmkv = new MMKV();

export function RemotePromoSheet() {
const { colors } = useTheme();
const { goBack } = useNavigation();
const { params } = useRoute<
RouteProp<RootStackParamList, 'RemotePromoSheet'>
>();
const { campaignId, campaignKey } = params;

useEffect(() => {
mmkv.set(STORAGE_IDS.PROMO_CURRENTLY_SHOWN, true);
mmkv.set(STORAGE_IDS.LAST_PROMO_SHEET_TIMESTAMP, Date.now());

return () => {
mmkv.set(STORAGE_IDS.PROMO_CURRENTLY_SHOWN, false);
};
}, []);

const { data, error } = usePromoSheetQuery(
{
id: campaignId,
},
{
enabled: !!campaignId,
}
);

if (!data?.promoSheet || error) {
return null;
}

const {
accentColor: accentColorString,
backgroundColor: backgroundColorString,
sheetHandleColor: sheetHandleColorString,
backgroundImage,
headerImage,
headerImageAspectRatio,
header,
items,
primaryButtonProps,
secondaryButtonProps,
subHeader,
} = data.promoSheet;

const accentColor =
(colors as { [key: string]: any })[accentColorString as string] ??
colors.whiteLabel;

const backgroundColor =
(colors as { [key: string]: any })[backgroundColorString as string] ??
colors.trueBlack;

const sheetHandleColor =
(colors as { [key: string]: any })[sheetHandleColorString as string] ??
colors.trueBlack;

const backgroundSignedImageUrl = backgroundImage?.url
? maybeSignUri(backgroundImage.url)
: undefined;

const headerSignedImageUrl = headerImage?.url
? maybeSignUri(headerImage.url)
: undefined;

return (
<PromoSheet
accentColor={accentColor}
backgroundColor={backgroundColor}
backgroundImage={{ uri: backgroundSignedImageUrl }}
campaignKey={campaignKey}
headerImage={{ uri: headerSignedImageUrl }}
headerImageAspectRatio={
headerImageAspectRatio ?? HEADER_WIDTH / HEADER_HEIGHT
}
sheetHandleColor={sheetHandleColor}
header={header ?? ''} // TODO: Probably should have a default header here
subHeader={subHeader ?? ''} // TODO: Probably should have a default subHeader here
primaryButtonProps={{
...primaryButtonProps,
onPress: noop, // TODO: Primary action should be passed
}}
secondaryButtonProps={{
...secondaryButtonProps,
onPress: goBack,
}}
items={items.map((item: any) => {
if (item.gradient) {
if (item.gradient.includes('.')) {
const gradient = get(colors, item.gradient);

return {
...item,
gradient,
};
}

const gradient = colors.gradients[item.gradient] ?? undefined;

return {
...item,
gradient,
};
}

return item;
})}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import store from '@/redux/store';
import { RainbowNetworks } from '@/networks';
import { ethereumUtils } from '@/utils';
import { EthereumAddress } from '@/entities';

export const hasNonZeroAssetBalance = async (
assetAddress: EthereumAddress
): Promise<boolean> => {
const { selected } = store.getState().wallets;
if (!selected) return false;

const { accountAddress } = store.getState().settings;

const networks = RainbowNetworks.map(network => network.value);

// check native asset balances on networks
const balancePromises = networks.map(network =>
ethereumUtils
.getNativeAssetForNetwork(network, accountAddress)
walmat marked this conversation as resolved.
Show resolved Hide resolved
.then(nativeAsset => Number(nativeAsset?.balance?.amount) > 0)
.catch(() => {
return false;
})
);

for (const balancePromise of balancePromises) {
// eslint-disable-next-line no-await-in-loop
if (await balancePromise) {
return true;
}
}

return false;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import store from '@/redux/store';
import { EthereumAddress } from '@/entities';
import { Network } from '@/helpers/networkTypes';
import { RainbowNetworks } from '@/networks';
import { ethereumUtils } from '@/utils';

export const hasNonZeroTotalBalance = async (): Promise<boolean> => {
walmat marked this conversation as resolved.
Show resolved Hide resolved
const {
accountAddress,
}: {
accountAddress: EthereumAddress;
network: Network;
} = store.getState().settings;

const networks: Network[] = RainbowNetworks.map(network => network.value);
const balances = await Promise.all(
networks.map(async network => {
const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(
walmat marked this conversation as resolved.
Show resolved Hide resolved
network,
accountAddress
);
return Number(nativeAsset?.balance?.amount);
})
);

return balances.some(balance => balance > 0);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import store from '@/redux/store';

export const hasSelectedWallet = (): boolean => {
walmat marked this conversation as resolved.
Show resolved Hide resolved
const { selected } = store.getState().wallets;
return !!selected;
};
6 changes: 6 additions & 0 deletions src/components/remote-promo-sheet/check-fns/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './hasNonZeroAssetBalance';
export * from './hasNonZeroTotalBalance';
export * from './hasSelectedWallet';
export * from './isAfterCampaignLaunch';
export * from './isFirstLaunch';
export * from './isSelectedWalletReadOnly';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PromoSheet } from '@/graphql/__generated__/arc';

export const isAfterCampaignLaunch = ({ launchDate }: PromoSheet): boolean => {
return new Date() > launchDate;
};
6 changes: 6 additions & 0 deletions src/components/remote-promo-sheet/check-fns/isFirstLaunch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { STORAGE_IDS } from '@/model/mmkv';
import { MMKV } from 'react-native-mmkv';

export const isFirstLaunch = (mmkv: MMKV): boolean => {
return mmkv.getBoolean(STORAGE_IDS.FIRST_APP_LAUNCH) ?? false;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import store from '@/redux/store';
import WalletTypes from '@/helpers/walletTypes';

export const isSelectedWalletReadyOnly = (): boolean => {
const { selected } = store.getState().wallets;

// if no selected wallet, we will treat it as a read-only wallet
if (!selected || selected.type === WalletTypes.readOnly) {
walmat marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

return false;
};
Loading
Loading