From 0438413e139ca740da75401cd01b88395c2484f5 Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Wed, 1 Feb 2023 13:10:17 +0000 Subject: [PATCH 01/35] removed obsolete feature flags Changelog: deprecated --- AppInitManager.ts | 17 ++----- ExperimentsProvider.tsx | 7 +-- __tests__/auth/RegisterForm.js | 2 +- src/auth/register/RegisterForm.tsx | 19 ++----- src/channel/v2/ChannelButtons.tsx | 11 ++-- src/channel/v2/ChannelHeader.tsx | 13 ++--- src/channel/v2/ChannelMoreMenu.tsx | 7 ++- src/channel/v2/ChannelScreen.tsx | 7 +-- .../notices/EmailVerifyNotice.tsx | 10 +--- src/common/services/api.service.ts | 3 +- .../services/friendly-captcha.interceptor.ts | 38 -------------- src/compose/ComposeBottomBar.tsx | 21 +++----- src/discovery/v2/DiscoveryV2Screen.tsx | 10 +--- src/navigation/Drawer.tsx | 25 ++++----- src/navigation/MoreStack.tsx | 22 +++----- src/navigation/NavigationStack.tsx | 51 +------------------ src/newsfeed/NewsfeedScreen.tsx | 17 +++---- src/newsfeed/activity/Actions.tsx | 11 +--- src/newsfeed/activity/ActivityActionSheet.tsx | 11 +--- src/newsfeed/activity/actions/ShareAction.tsx | 4 +- src/onboarding/v2/OnboardingScreen.tsx | 11 ++-- .../v2/steps/SelectHashtagsScreen.tsx | 8 +-- .../screens/SupermindSettingsScreen.tsx | 9 +--- .../SupermindSettingsScreen.spec.tsx.snap | 6 +++ src/supermind/SupermindConsoleScreen.tsx | 9 +--- src/tabs/TabChatPreModal.tsx | 4 +- src/topbar/Topbar.tsx | 8 +-- src/wallet/v2/address/UsdSettings.tsx | 31 ++--------- 28 files changed, 89 insertions(+), 303 deletions(-) delete mode 100644 src/common/services/friendly-captcha.interceptor.ts diff --git a/AppInitManager.ts b/AppInitManager.ts index dcd03644a..ad9216acb 100644 --- a/AppInitManager.ts +++ b/AppInitManager.ts @@ -19,7 +19,7 @@ import badgeService from './src/common/services/badge.service'; import Clipboard from '@react-native-clipboard/clipboard'; import mindsConfigService from './src/common/services/minds-config.service'; import openUrlService from '~/common/services/open-url.service'; -import { hasVariation, updateGrowthBookAttributes } from 'ExperimentsProvider'; +import { updateGrowthBookAttributes } from 'ExperimentsProvider'; import checkTOS from '~/tos/checkTOS'; import { storeRatingService } from 'modules/store-rating'; @@ -70,7 +70,9 @@ export class AppInitManager { { text: 'Yes', onPress: () => { - if (err instanceof Error) Clipboard.setString(err.stack || ''); + if (err instanceof Error) { + Clipboard.setString(err.stack || ''); + } }, }, { text: 'No' }, @@ -143,11 +145,7 @@ export class AppInitManager { // if the navigator is ready, handle initial navigation (this is needed when the user lands on the welcome screen) if (this.navReady) { // when the experiment is enabled, we don't want to navigate to the initial screen because the navigation is done after the email verification. - this.initialNavigationHandling( - hasVariation('minds-3055-email-codes') - ? Boolean(user.email_confirmed) - : true, - ); + this.initialNavigationHandling(true); } }; @@ -181,15 +179,10 @@ export class AppInitManager { // handle deep link (if the app is opened by one) if (deepLinkUrl) { setTimeout(() => { - //TODO: remove after we check the push notification issue - console.log('[App] Handling deeplink'); deeplinkService.navigate(deepLinkUrl); }, 300); } - //TODO: remove after we check the push notification issue - console.log('[App] Handling initial notifications'); - // handle initial notifications (if the app is opened by tap on one) pushService.handleInitialNotification(); diff --git a/ExperimentsProvider.tsx b/ExperimentsProvider.tsx index 864472b2b..13ecd5d5a 100644 --- a/ExperimentsProvider.tsx +++ b/ExperimentsProvider.tsx @@ -79,11 +79,8 @@ export const useIsAndroidFeatureOn = (feature: FeatureID) => useGrowthbookFeature(feature).on && !IS_IOS; export type FeatureID = - | 'mobile-supermind' - | 'mob-4630-hide-chat-icon' | 'mob-4637-ios-hide-minds-superminds' - | 'mob-stripe-connect-4587' | 'mob-4638-boost-v3' - | 'mob-minds-3119-captcha-for-engagement' | 'mob-4424-sockets' - | 'minds-3055-email-codes'; + | 'mob-discovery-redirect' + | 'mob-4472-in-app-verification'; diff --git a/__tests__/auth/RegisterForm.js b/__tests__/auth/RegisterForm.js index e84bcb747..8104d20cd 100644 --- a/__tests__/auth/RegisterForm.js +++ b/__tests__/auth/RegisterForm.js @@ -13,7 +13,6 @@ jest.mock('../../src/auth/UserStore'); jest.mock('../../src/common/components/Captcha'); jest.mock('react-native-safe-area-context'); jest.mock('../../AppMessages', () => ({ showNotification: jest.fn() })); -jest.mock('@gorhom/bottom-sheet'); describe('RegisterScreen component', () => { let navigation; @@ -89,6 +88,7 @@ describe('RegisterScreen component', () => { captcha: '{"jwtToken":"FAFA","clientText":"some45"}', email: 'myuser@minds.com', exclusive_promotions: true, + friendly_captcha_enabled: true, password: 'Temp!1234', username: 'myuser', }); diff --git a/src/auth/register/RegisterForm.tsx b/src/auth/register/RegisterForm.tsx index 96ac7e6d5..1441949c3 100644 --- a/src/auth/register/RegisterForm.tsx +++ b/src/auth/register/RegisterForm.tsx @@ -26,7 +26,6 @@ import KeyboardSpacingView from '~/common/components/keyboard/KeyboardSpacingVie import FitScrollView from '~/common/components/FitScrollView'; import DismissKeyboard from '~/common/components/DismissKeyboard'; import FriendlyCaptcha from '~/common/components/friendly-captcha/FriendlyCaptcha'; -import { useFeature } from '@growthbook/growthbook-react'; type PropsType = { // called after registration is finished @@ -42,7 +41,6 @@ const RegisterForm = observer(({ onRegister }: PropsType) => { const scrollViewRef = useRef(); const emailRef = useRef(null); const passwordRef = useRef(null); - const friendlyCaptchaEnabled = useFeature('mob-4231-captcha').on; const store = useLocalStore(() => ({ focused: false, @@ -63,10 +61,6 @@ const RegisterForm = observer(({ onRegister }: PropsType) => { }); store.usernameTaken = !response.valid; }, 300), - friendlyCaptchaEnabled, - setFriendlyCaptchaEnabled(enabled: boolean) { - store.friendlyCaptchaEnabled = enabled; - }, setCaptcha(value: string) { this.captcha = value; }, @@ -90,9 +84,7 @@ const RegisterForm = observer(({ onRegister }: PropsType) => { exclusive_promotions: store.exclusivePromotions, captcha: store.captcha, } as registerParams; - if (store.friendlyCaptchaEnabled) { - params.friendly_captcha_enabled = true; - } + params.friendly_captcha_enabled = true; await authService.register(params); await apiService.clearCookies(); await delay(100); @@ -146,12 +138,11 @@ const RegisterForm = observer(({ onRegister }: PropsType) => { return; } - // use friendly captcha if it was enabled and if the puzzle was solved, + // use friendly captchaif the puzzle was solved, // otherwise fall back to the legacy captcha - if (store.friendlyCaptchaEnabled && this.captcha) { + if (this.captcha) { return this.register(); } else { - store.setFriendlyCaptchaEnabled(false); captchaRef.current?.show(); } }, @@ -271,9 +262,7 @@ const RegisterForm = observer(({ onRegister }: PropsType) => { } /> - {store.friendlyCaptchaEnabled && ( - - )} + ); diff --git a/src/channel/v2/ChannelButtons.tsx b/src/channel/v2/ChannelButtons.tsx index fb024c03e..99f06c2dc 100644 --- a/src/channel/v2/ChannelButtons.tsx +++ b/src/channel/v2/ChannelButtons.tsx @@ -20,7 +20,6 @@ import { withErrorBoundary } from '../../common/components/ErrorBoundary'; import Edit from './buttons/Edit'; import { Row } from '~ui'; import SupermindButton from '../../common/components/supermind/SupermindButton'; -import { IfFeatureEnabled } from '@growthbook/growthbook-react'; import ThemedStyles from '../../styles/ThemedStyles'; type ButtonsType = @@ -128,12 +127,10 @@ const ChannelButtons = withErrorBoundary( )} {showSubscribe && } {shouldShow('supermind') && ( - - - + )} {shouldShow('more') && ( - - + )} ); diff --git a/src/channel/v2/ChannelMoreMenu.tsx b/src/channel/v2/ChannelMoreMenu.tsx index ef6f95492..40f0a0ea8 100644 --- a/src/channel/v2/ChannelMoreMenu.tsx +++ b/src/channel/v2/ChannelMoreMenu.tsx @@ -15,7 +15,7 @@ import { } from '../../common/components/bottom-sheet'; import { Platform } from 'react-native'; import { useStores } from '../../common/hooks/use-stores'; -import { hasVariation, useIsFeatureOn } from 'ExperimentsProvider'; +import { hasVariation } from 'ExperimentsProvider'; function dismiss(ref) { setTimeout(() => { @@ -195,7 +195,6 @@ type NavigationType = NativeStackNavigationProp; const ChannelMoreMenu = forwardRef((props: PropsType, ref: any) => { const navigation = useNavigation(); const { chat } = useStores(); - const isChatHidden = useIsFeatureOn('mob-4630-hide-chat-icon'); /** * Opens chat @@ -207,7 +206,7 @@ const ChannelMoreMenu = forwardRef((props: PropsType, ref: any) => { if (Platform.OS === 'android') { try { - chat.checkAppInstalled(!isChatHidden).then(installed => { + chat.checkAppInstalled().then(installed => { if (!installed) { return; } @@ -221,7 +220,7 @@ const ChannelMoreMenu = forwardRef((props: PropsType, ref: any) => { } else { chat.directMessage(props.channel.guid); } - }, [chat, props.channel, isChatHidden]); + }, [chat, props.channel]); const options = getOptions( props.channel, diff --git a/src/channel/v2/ChannelScreen.tsx b/src/channel/v2/ChannelScreen.tsx index 36f74c55b..ede43da62 100644 --- a/src/channel/v2/ChannelScreen.tsx +++ b/src/channel/v2/ChannelScreen.tsx @@ -42,7 +42,6 @@ import InteractionsBottomSheet from '../../common/components/interactions/Intera import Empty from '~/common/components/Empty'; import { B1, Column } from '~/common/ui'; import ChannelRecommendation from '~/common/components/ChannelRecommendation/ChannelRecommendation'; -import { IfFeatureEnabled } from '@growthbook/growthbook-react'; import withModalProvider from '~/navigation/withModalProvide'; import { hasVariation } from '../../../ExperimentsProvider'; @@ -434,10 +433,8 @@ const ChannelScreen = observer((props: PropsType) => { username: store.channel?.username, })} - - - - + + ); diff --git a/src/common/components/in-feed-notices/notices/EmailVerifyNotice.tsx b/src/common/components/in-feed-notices/notices/EmailVerifyNotice.tsx index 807f2ec95..66db23378 100644 --- a/src/common/components/in-feed-notices/notices/EmailVerifyNotice.tsx +++ b/src/common/components/in-feed-notices/notices/EmailVerifyNotice.tsx @@ -4,8 +4,6 @@ import { observer } from 'mobx-react-lite'; import i18nService from '~/common/services/i18n.service'; import inFeedNoticesService from '~/common/services/in-feed.notices.service'; -import { hasVariation } from 'ExperimentsProvider'; -import sessionService from '~/common/services/session.service'; import InFeedNotice from './BaseNotice'; /** @@ -22,13 +20,7 @@ function EmailVerifyNotice() { description={i18nService.t('inFeedNotices.verifyEmailDescription')} btnText={i18nService.t('inFeedNotices.verifyEmail')} iconName="warning" - onPress={() => { - if (hasVariation('minds-3055-email-codes')) { - sessionService.getUser().confirmEmailCode(); - } else { - navigation.navigate('VerifyEmail'); - } - }} + onPress={() => navigation.navigate('VerifyEmail')} /> ); } diff --git a/src/common/services/api.service.ts b/src/common/services/api.service.ts index 7a792846c..e6a947017 100644 --- a/src/common/services/api.service.ts +++ b/src/common/services/api.service.ts @@ -31,7 +31,6 @@ import NavigationService from '../../navigation/NavigationService'; import CookieManager from '@react-native-cookies/cookies'; import analyticsService from './analytics.service'; import AuthService from '~/auth/AuthService'; -import friendlyCaptchaInterceptor from './friendly-captcha.interceptor'; type FieldError = { field: string; message: string }; @@ -146,7 +145,7 @@ export class ApiService { config.headers = this.buildHeaders(config.headers); config.timeout = NETWORK_TIMEOUT; - return friendlyCaptchaInterceptor(config); + return config; }); this.axios.interceptors.response.use( diff --git a/src/common/services/friendly-captcha.interceptor.ts b/src/common/services/friendly-captcha.interceptor.ts deleted file mode 100644 index 091cf16ab..000000000 --- a/src/common/services/friendly-captcha.interceptor.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { hasVariation } from 'ExperimentsProvider'; -import { CAPTCHA_ENABLED_ENDPOINTS } from '~/config/Config'; -import { friendlyCaptchaReference } from '../components/friendly-captcha/FriendlyCaptchaProvider'; - -const friendlyCaptchaInterceptor = async config => { - if (!config.url) { - return config; - } - - if (!hasVariation('mob-minds-3119-captcha-for-engagement')) { - return config; - } - - const endpointConfig = CAPTCHA_ENABLED_ENDPOINTS.find( - ({ url, method }) => url.test(config.url!) && method === config.method, - ); - - if (endpointConfig) { - try { - console.log('[ApiService] Checking captcha'); - const solution = await friendlyCaptchaReference?.solveAPuzzle( - endpointConfig.origin, - ); - console.log('[ApiService] Captcha solved with solution ', solution); - // TODO: move to request header once backend supports it - config.data.puzzle_solution = solution; - } catch (e) { - console.error( - '[ApiService] Captcha failed for friendly captcha interceptor', - e, - ); - } - } - - return config; -}; - -export default friendlyCaptchaInterceptor; diff --git a/src/compose/ComposeBottomBar.tsx b/src/compose/ComposeBottomBar.tsx index 3d745a902..8c03584c8 100644 --- a/src/compose/ComposeBottomBar.tsx +++ b/src/compose/ComposeBottomBar.tsx @@ -1,4 +1,3 @@ -import { IfFeatureEnabled, useFeature } from '@growthbook/growthbook-react'; import { useNavigation } from '@react-navigation/native'; import { useIsIOSFeatureOn } from 'ExperimentsProvider'; import { observer } from 'mobx-react'; @@ -10,13 +9,11 @@ import ThemedStyles from '../styles/ThemedStyles'; function ComposeBottomBar(props) { const theme = ThemedStyles.style; const navigation = useNavigation(); - const mediaQuoteFF = useFeature('mobile-5645-media-quotes'); const isIosMindsHidden = useIsIOSFeatureOn( 'mob-4637-ios-hide-minds-superminds', ); - const allowMedia = - !props.store.isEdit && (mediaQuoteFF.on || !props.store.isRemind); + const allowMedia = !props.store.isEdit && !props.store.isRemind; const iconStyle = useMemo( () => [ @@ -78,15 +75,13 @@ function ComposeBottomBar(props) { { // don't allow superminding in the context of a supermind reply !props.store.isSupermindReply && ( - - - + ) } diff --git a/src/discovery/v2/DiscoveryV2Screen.tsx b/src/discovery/v2/DiscoveryV2Screen.tsx index a41ca21ee..81340506d 100644 --- a/src/discovery/v2/DiscoveryV2Screen.tsx +++ b/src/discovery/v2/DiscoveryV2Screen.tsx @@ -23,7 +23,6 @@ import Topbar from '~/topbar/Topbar'; import ChannelRecommendation from '~/common/components/ChannelRecommendation/ChannelRecommendation'; import FeedListSticky from '~/common/components/FeedListSticky'; import { Screen } from '~/common/ui'; -import { useFeature } from '@growthbook/growthbook-react'; import { IS_IOS } from '~/config/Config'; interface Props { @@ -39,9 +38,6 @@ export const DiscoveryV2Screen = withErrorBoundary( false, ); const store = useDiscoveryV2Store(); - const isSupermindsGlobalFeedOn = useFeature( - 'mob-4482-global-supermind-feed', - ).on; const listRef = React.useRef>(null); // inject items in the store the first time @@ -74,12 +70,10 @@ export const DiscoveryV2Screen = withErrorBoundary( { id: 'your-tags', title: i18n.t('discovery.yourTags') }, { id: 'trending-tags', title: i18n.t('discovery.trending') }, { id: 'boosts', title: i18n.t('boosted') }, - isSupermindsGlobalFeedOn - ? { id: 'superminds', title: i18n.t('supermind.supermind') } - : null, + { id: 'superminds', title: i18n.t('supermind.supermind') }, ].filter(Boolean) as { id: string; title: string }[], // eslint-disable-next-line react-hooks/exhaustive-deps - [i18n.locale, isSupermindsGlobalFeedOn], + [i18n.locale], ); const emptyBoosts = React.useMemo( diff --git a/src/navigation/Drawer.tsx b/src/navigation/Drawer.tsx index 72328679a..2b207119a 100644 --- a/src/navigation/Drawer.tsx +++ b/src/navigation/Drawer.tsx @@ -21,7 +21,7 @@ import { import apiService, { isNetworkError } from '~/common/services/api.service'; import { showNotification } from 'AppMessages'; import { IS_IOS } from '~/config/Config'; -import { useIsFeatureOn, useIsIOSFeatureOn } from 'ExperimentsProvider'; +import { useIsIOSFeatureOn } from 'ExperimentsProvider'; import { MoreStackParamList } from './NavigationTypes'; import { NavigationProp } from '@react-navigation/native'; @@ -73,12 +73,9 @@ const getOptionsSmallList = navigation => { ]; }; -type Flags = Record<'isSupermindFeatureOn' | 'isIosMindsHidden', boolean>; +type Flags = Record<'isIosMindsHidden', boolean>; -const getOptionsList = ( - navigation, - { isIosMindsHidden, isSupermindFeatureOn }: Flags, -) => { +const getOptionsList = (navigation, { isIosMindsHidden }: Flags) => { const channel = sessionService.getUser(); let list = [ { @@ -98,14 +95,12 @@ const getOptionsList = ( }, } : null, - isSupermindFeatureOn - ? { - name: 'Supermind', - onPress: () => { - navigation.navigate('SupermindConsole'); - }, - } - : null, + { + name: 'Supermind', + onPress: () => { + navigation.navigate('SupermindConsole'); + }, + }, { name: i18n.t('moreScreen.wallet'), icon: 'bank', @@ -168,7 +163,6 @@ const getOptionsList = ( */ export default function Drawer(props) { const channel = sessionService.getUser(); - const isSupermindFeatureOn = useIsFeatureOn('mobile-supermind'); const isIosMindsHidden = useIsIOSFeatureOn( 'mob-4637-ios-hide-minds-superminds', ); @@ -183,7 +177,6 @@ export default function Drawer(props) { channel && channel.getAvatarSource ? channel.getAvatarSource('medium') : {}; const optionsList = getOptionsList(props.navigation, { - isSupermindFeatureOn, isIosMindsHidden, }); const optionsSmallList = getOptionsSmallList(props.navigation); diff --git a/src/navigation/MoreStack.tsx b/src/navigation/MoreStack.tsx index 332c830e5..b6a4634c4 100644 --- a/src/navigation/MoreStack.tsx +++ b/src/navigation/MoreStack.tsx @@ -9,7 +9,6 @@ import ThemedStyles from '~/styles/ThemedStyles'; import Drawer from './Drawer'; import i18n from '~/common/services/i18n.service'; import { IS_IOS } from '~/config/Config'; -import { useFeature } from '@growthbook/growthbook-react'; const MoreStack = createNativeStackNavigator(); const hideHeader: NativeStackNavigationOptions = { headerShown: false }; @@ -20,7 +19,6 @@ const WalletOptions = () => ({ }); export default function () { - const supermindFeatureFlag = useFeature('mobile-supermind'); const AccountScreenOptions = navigation => [ { title: i18n.t('settings.accountOptions.1'), @@ -86,20 +84,16 @@ export default function () { title: i18n.t('settings.billingOptions.2'), onPress: () => navigation.push('RecurringPayments'), }, - supermindFeatureFlag.on - ? { - title: 'Supermind', - onPress: () => navigation.push('SupermindSettingsScreen'), - } - : null, + { + title: 'Supermind', + onPress: () => navigation.push('SupermindSettingsScreen'), + }, ] : navigation => [ - supermindFeatureFlag.on - ? { - title: 'Supermind', - onPress: () => navigation.push('SupermindSettingsScreen'), - } - : null, + { + title: 'Supermind', + onPress: () => navigation.push('SupermindSettingsScreen'), + }, ]; return ( diff --git a/src/navigation/NavigationStack.tsx b/src/navigation/NavigationStack.tsx index b2d48a289..3f69ddbd8 100644 --- a/src/navigation/NavigationStack.tsx +++ b/src/navigation/NavigationStack.tsx @@ -29,8 +29,6 @@ import TransparentLayer from '../common/components/TransparentLayer'; import withModalProvider from './withModalProvide'; import { observer } from 'mobx-react'; import sessionService from '~/common/services/session.service'; -import { useFeature } from '@growthbook/growthbook-react'; -import AuthService from '~/auth/AuthService'; import { isStoryBookOn } from '~/config/Config'; const hideHeader: NativeStackNavigationOptions = { headerShown: false }; @@ -39,8 +37,6 @@ const AppStackNav = createNativeStackNavigator(); const AuthStackNav = createStackNavigator(); const RootStackNav = createStackNavigator(); const InternalStackNav = createNativeStackNavigator(); -// const MainSwiper = createMaterialTopTabNavigator(); -// const DrawerNav = createDrawerNavigator(); const modalOptions = { gestureResponseDistance: 240, @@ -158,19 +154,6 @@ const AppStack = observer(() => { getComponent={() => require('~/wire/v2/FabScreen').default} options={hideHeader} /> - {/* - - */} require('~/report/ReportScreen').default} @@ -281,15 +264,6 @@ const defaultScreenOptions: StackNavigationOptions = { }; const RootStack = observer(function () { - const codeEmailFF = useFeature('minds-3055-email-codes'); - const is_email_confirmed = sessionService.getUser()?.email_confirmed; - - const shouldShowEmailVerification = - !is_email_confirmed && - !sessionService.switchingAccount && - codeEmailFF.on && - AuthService.justRegistered; - return ( {!sessionService.showAuthNav ? ( @@ -302,26 +276,6 @@ const RootStack = observer(function () { ...TransitionPresets.RevealFromBottomAndroid, }} /> - ) : shouldShowEmailVerification ? ( - <> - - require('~/auth/InitialEmailVerificationScreen').default - } - options={TransitionPresets.RevealFromBottomAndroid} - /> - - require('~/auth/multi-user/MultiUserScreen').default - } - options={{ - title: i18n.t('multiUser.switchChannel'), - }} - /> - ) : ( <> require('~/settings/screens/ChooseBrowserModalScreen').default @@ -378,10 +333,6 @@ const RootStack = observer(function () { name="ImageGallery" getComponent={() => require('~/media/ImageGalleryScreen').default} /> - {/* */} require('~/upgrade/UpgradeScreen').default} diff --git a/src/newsfeed/NewsfeedScreen.tsx b/src/newsfeed/NewsfeedScreen.tsx index 35c002c1b..a953b100a 100644 --- a/src/newsfeed/NewsfeedScreen.tsx +++ b/src/newsfeed/NewsfeedScreen.tsx @@ -1,4 +1,3 @@ -import { IfFeatureEnabled } from '@growthbook/growthbook-react'; import { RouteProp } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; import { observer } from 'mobx-react'; @@ -34,6 +33,7 @@ import PrefetchNotifications from '~/notifications/v3/PrefetchNotifications'; import { IS_IOS } from '~/config/Config'; import { NotificationsTabOptions } from '~/notifications/v3/NotificationsTopBar'; import { CodePushUpdatePrompt } from 'modules/codepush'; +import { useIsFeatureOn } from 'ExperimentsProvider'; type NewsfeedScreenRouteProp = RouteProp; type NewsfeedScreenNavigationProp = StackNavigationProp< @@ -71,6 +71,7 @@ const overrideItemLayout = (layout, item, index) => { const NewsfeedScreen = observer(({ navigation }: NewsfeedScreenProps) => { const { newsfeed } = useLegacyStores(); const portrait = useStores().portrait; + const inAppVerification = useIsFeatureOn('mob-4472-in-app-verification'); const refreshNewsfeed = useCallback(() => { newsfeed.scrollToTop(); @@ -124,9 +125,7 @@ const NewsfeedScreen = observer(({ navigation }: NewsfeedScreenProps) => { - - - + {inAppVerification ? : null} { overrideItemLayout={overrideItemLayout} bottomComponent={ isLatest ? ( - - - + ) : undefined } header={} diff --git a/src/newsfeed/activity/Actions.tsx b/src/newsfeed/activity/Actions.tsx index 905c4aa90..a47f16517 100644 --- a/src/newsfeed/activity/Actions.tsx +++ b/src/newsfeed/activity/Actions.tsx @@ -15,7 +15,6 @@ import type ActivityModel from '../ActivityModel'; import { useNavigation } from '@react-navigation/native'; import ThemedStyles from '../../styles/ThemedStyles'; import SupermindAction from './actions/SupermindAction'; -import { useFeature } from '@growthbook/growthbook-react'; import ShareAction from './actions/ShareAction'; import { IS_IOS } from '~/config/Config'; @@ -29,8 +28,6 @@ type PropsType = { export const Actions = observer((props: PropsType) => { const navigation = useNavigation(); - const supermindFeatureFlag = useFeature('mobile-supermind'); - if (props.hideTabs) { return null; } @@ -59,9 +56,7 @@ export const Actions = observer((props: PropsType) => { /> - {(supermindFeatureFlag.off || IS_IOS) && ( - - )} + {IS_IOS && } {!isOwner && hasWire && ( @@ -71,9 +66,7 @@ export const Actions = observer((props: PropsType) => { )} - {supermindFeatureFlag.on && !isOwner && !IS_IOS && ( - - )} + {!isOwner && !IS_IOS && } )} diff --git a/src/newsfeed/activity/ActivityActionSheet.tsx b/src/newsfeed/activity/ActivityActionSheet.tsx index 8dd8899d9..5af87c9c8 100644 --- a/src/newsfeed/activity/ActivityActionSheet.tsx +++ b/src/newsfeed/activity/ActivityActionSheet.tsx @@ -23,7 +23,7 @@ import { withChannelContext } from '~/channel/v2/ChannelContext'; import type UserModel from '~/channel/UserModel'; import SendIntentAndroid from 'react-native-send-intent'; import logService from '~/common/services/log.service'; -import { hasVariation, useIsFeatureOn } from 'ExperimentsProvider'; +import { hasVariation } from 'ExperimentsProvider'; type PropsType = { entity: ActivityModel; @@ -502,11 +502,4 @@ class ActivityActionSheet extends PureComponent { } } -const withHiddenChat = WrappedComponent => props => { - const isChatHidden = useIsFeatureOn('mob-4630-hide-chat-icon'); - return ; -}; - -export default withSafeAreaInsets( - withChannelContext(withHiddenChat(ActivityActionSheet)), -); +export default withSafeAreaInsets(withChannelContext(ActivityActionSheet)); diff --git a/src/newsfeed/activity/actions/ShareAction.tsx b/src/newsfeed/activity/actions/ShareAction.tsx index f289f5cbd..3b0e451d4 100644 --- a/src/newsfeed/activity/actions/ShareAction.tsx +++ b/src/newsfeed/activity/actions/ShareAction.tsx @@ -17,7 +17,6 @@ import { BottomSheetModal, BottomSheetMenuItem, } from '~/common/components/bottom-sheet'; -import { useIsFeatureOn } from 'ExperimentsProvider'; type PropsType = { entity: ActivityModel; @@ -26,7 +25,6 @@ type PropsType = { export default observer(function ShareAction({ entity }: PropsType) { // Do not render BottomSheet unless it is necessary const ref = React.useRef(null); - const isChatHidden = useIsFeatureOn('mob-4630-hide-chat-icon'); // store const localStore = useLocalStore(() => ({ menuShown: false, @@ -62,7 +60,7 @@ export default observer(function ShareAction({ entity }: PropsType) { type: SendIntentAndroid.TEXT_PLAIN, package: ANDROID_CHAT_APP, }); - } else if (!isChatHidden) { + } else { Linking.openURL('market://details?id=com.minds.chat'); } } catch (error) { diff --git a/src/onboarding/v2/OnboardingScreen.tsx b/src/onboarding/v2/OnboardingScreen.tsx index efa539436..60baaa035 100644 --- a/src/onboarding/v2/OnboardingScreen.tsx +++ b/src/onboarding/v2/OnboardingScreen.tsx @@ -1,6 +1,5 @@ import { useDimensions } from '@react-native-community/hooks'; import { useFocusEffect, useNavigation } from '@react-navigation/native'; -import { hasVariation } from 'ExperimentsProvider'; import { observer, useLocalStore } from 'mobx-react'; import moment from 'moment-timezone'; import React, { useRef } from 'react'; @@ -131,13 +130,9 @@ export default observer(function OnboardingScreen() { s => s.id === 'VerifyEmailStep' && s.is_completed, ); if (!done) { - if (hasVariation('minds-3055-email-codes')) { - sessionService.getUser().confirmEmailCode(); - } else { - navigation.navigate('VerifyEmail', { - store: progressStore, - }); - } + navigation.navigate('VerifyEmail', { + store: progressStore, + }); } else { showNotification(i18n.t('emailConfirm.alreadyConfirmed')); } diff --git a/src/onboarding/v2/steps/SelectHashtagsScreen.tsx b/src/onboarding/v2/steps/SelectHashtagsScreen.tsx index cda112309..e589af437 100644 --- a/src/onboarding/v2/steps/SelectHashtagsScreen.tsx +++ b/src/onboarding/v2/steps/SelectHashtagsScreen.tsx @@ -1,4 +1,4 @@ -import { useFeature } from '@growthbook/growthbook-react'; +import { useIsFeatureOn } from 'ExperimentsProvider'; import { LinearGradient } from 'expo-linear-gradient'; import { observer } from 'mobx-react'; import React from 'react'; @@ -21,7 +21,7 @@ import ModalContainer from './ModalContainer'; export default observer(function SelectHashtagsScreen({ navigation, route }) { const theme = ThemedStyles.style; - const redirectExperiment = useFeature('mob-discovery-redirect'); + const redirectExperiment = useIsFeatureOn('mob-discovery-redirect'); const { hashtag } = useLegacyStores(); @@ -49,13 +49,13 @@ export default observer(function SelectHashtagsScreen({ navigation, route }) { // refresh in-feed notices when leaving the screen inFeedNoticesService.load(); - if (route.params?.initial && redirectExperiment.on) { + if (route.params?.initial && redirectExperiment) { navigation.navigate('Tabs', { screen: 'Discovery', }); } }; - }, [hashtag, navigation, redirectExperiment.on, route]); + }, [hashtag, navigation, redirectExperiment, route]); const onPress = () => { if (hashtag.suggested.filter(s => s.selected).length >= 3) { diff --git a/src/settings/screens/SupermindSettingsScreen.tsx b/src/settings/screens/SupermindSettingsScreen.tsx index 0fe38cc7c..8e046af5b 100644 --- a/src/settings/screens/SupermindSettingsScreen.tsx +++ b/src/settings/screens/SupermindSettingsScreen.tsx @@ -18,8 +18,6 @@ import { ScreenHeader, ScreenSection, } from '~/common/ui'; -import AddBankInformation from '~/supermind/AddBankInformation'; -import { useIsFeatureOn } from '../../../ExperimentsProvider'; import StripeConnectButton from '../../wallet/v2/stripe-connect/StripeConnectButton'; type Settings = { @@ -30,7 +28,6 @@ type Settings = { export default observer(function SupermindSettingsScreen({ navigation }) { const fetchStore = useApiFetch('api/v3/supermind/settings'); const localStore = useLocalStore(createStore, { navigation, fetchStore }); - const isStripeConnectFeatureOn = useIsFeatureOn('mob-stripe-connect-4587'); /** * Sync values with local store @@ -74,11 +71,7 @@ export default observer(function SupermindSettingsScreen({ navigation }) { - {isStripeConnectFeatureOn ? ( - - ) : ( - - )} + ); diff --git a/src/settings/screens/__snapshots__/SupermindSettingsScreen.spec.tsx.snap b/src/settings/screens/__snapshots__/SupermindSettingsScreen.spec.tsx.snap index cdc4bf5f1..3e68fed17 100644 --- a/src/settings/screens/__snapshots__/SupermindSettingsScreen.spec.tsx.snap +++ b/src/settings/screens/__snapshots__/SupermindSettingsScreen.spec.tsx.snap @@ -384,6 +384,9 @@ exports[`Supermind settings should load data and render correctly 1`] = ` size="large" /> + @@ -1457,6 +1460,9 @@ exports[`Supermind settings should update the store and submit 1`] = ` size="large" /> + diff --git a/src/supermind/SupermindConsoleScreen.tsx b/src/supermind/SupermindConsoleScreen.tsx index 8edde586a..bbda4d9f3 100644 --- a/src/supermind/SupermindConsoleScreen.tsx +++ b/src/supermind/SupermindConsoleScreen.tsx @@ -11,7 +11,6 @@ import i18n from '~/common/services/i18n.service'; import { IconButton, Screen, ScreenHeader } from '~/common/ui'; import { IS_IOS } from '~/config/Config'; import ThemedStyles from '~/styles/ThemedStyles'; -import { useIsFeatureOn } from '../../ExperimentsProvider'; import { SupermindOnboardingOverlay, useSupermindOnboarding, @@ -19,7 +18,6 @@ import { import { MoreStackParamList } from '../navigation/NavigationTypes'; import SeeLatestButton from '../newsfeed/SeeLatestButton'; import StripeConnectButton from '../wallet/v2/stripe-connect/StripeConnectButton'; -import AddBankInformation from './AddBankInformation'; import SupermindConsoleFeedFilter, { SupermindFilterType, } from './SupermindConsoleFeedFilter'; @@ -75,7 +73,6 @@ function SupermindConsoleScreen({ ); const listRef = React.useRef(null); const [onboarding, dismissOnboarding] = useSupermindOnboarding('producer'); - const isStripeConnectFeatureOn = useIsFeatureOn('mob-stripe-connect-4587'); const scrollDirection = useSharedValue(0); const scrollY = useSharedValue(0); @@ -161,11 +158,7 @@ function SupermindConsoleScreen({ /> } /> - {isStripeConnectFeatureOn ? ( - - ) : ( - - )} + } contentContainerStyle={ThemedStyles.style.paddingTop2x} diff --git a/src/tabs/TabChatPreModal.tsx b/src/tabs/TabChatPreModal.tsx index 724e985b8..e8823b9d0 100644 --- a/src/tabs/TabChatPreModal.tsx +++ b/src/tabs/TabChatPreModal.tsx @@ -11,7 +11,6 @@ import { BottomSheetButton, } from '~/common/components/bottom-sheet'; import { useStores } from '~/common/hooks/use-stores'; -import { useIsAndroidFeatureOn } from 'ExperimentsProvider'; export type ChatModalHandle = { showModal: () => void; @@ -24,7 +23,6 @@ export const TabChatPreModal = forwardRef( const { chat } = useStores(); const [isShown, setShown] = useState(false); const modalRef: any = useRef(); - const isChatHidden = useIsAndroidFeatureOn('mob-4630-hide-chat-icon'); useImperativeHandle( ref, @@ -53,7 +51,7 @@ export const TabChatPreModal = forwardRef( const handleChatOpen = async () => { // Check if it is installed; Open the store if it isn't - const isInstalled = await chat.checkAppInstalled(!isChatHidden); + const isInstalled = await chat.checkAppInstalled(); if (isInstalled) { // if it is installed opens the chat diff --git a/src/topbar/Topbar.tsx b/src/topbar/Topbar.tsx index c8e7edcd7..ae79ba433 100644 --- a/src/topbar/Topbar.tsx +++ b/src/topbar/Topbar.tsx @@ -11,7 +11,6 @@ import TabChatPreModal, { ChatModalHandle } from '~/tabs/TabChatPreModal'; import ChatIcon from '~/chat/ChatIcon'; import Animated, { useAnimatedStyle } from 'react-native-reanimated'; import sessionService from '~/common/services/session.service'; -import { useIsFeatureOn } from 'ExperimentsProvider'; import SendIntentAndroid from 'react-native-send-intent'; import { ANDROID_CHAT_APP } from '~/config/Config'; import { useScrollContext } from '../common/contexts/scroll.context'; @@ -195,17 +194,14 @@ export const styles = StyleSheet.create({ }); const useChatIconState = () => { - const isChatHidden = useIsFeatureOn('mob-4630-hide-chat-icon'); const [isChatIconHidden, setChatIconHidden] = useState(false); useEffect(() => { if (Platform.OS === 'android') { SendIntentAndroid.isAppInstalled(ANDROID_CHAT_APP).then(installed => { - if (!installed) { - setChatIconHidden(isChatHidden); - } + setChatIconHidden(installed); }); } - }, [isChatHidden]); + }, []); return isChatIconHidden; }; diff --git a/src/wallet/v2/address/UsdSettings.tsx b/src/wallet/v2/address/UsdSettings.tsx index 181519265..5edf64948 100644 --- a/src/wallet/v2/address/UsdSettings.tsx +++ b/src/wallet/v2/address/UsdSettings.tsx @@ -1,8 +1,6 @@ import React, { useCallback } from 'react'; import { Alert, View } from 'react-native'; import { B1, B2, H3, Spacer } from '~ui'; -import { useIsFeatureOn } from '../../../../ExperimentsProvider'; -import Button from '../../../common/components/Button'; import MenuItem from '../../../common/components/menus/MenuItem'; import i18n from '../../../common/services/i18n.service'; import ThemedStyles from '../../../styles/ThemedStyles'; @@ -19,16 +17,11 @@ type PropsType = { route: WalletScreenRouteProp; }; -const UsdSettings = ({ walletStore, navigation }: PropsType) => { +const UsdSettings = ({ walletStore }: PropsType) => { const theme = ThemedStyles.style; const hasBankInfo = walletStore.wallet.cash.address !== null && walletStore.wallet.cash.address !== ''; - const isStripeConnectFeatureOn = useIsFeatureOn('mob-stripe-connect-4587'); - const hasBankAccount = walletStore.stripeDetails.hasBank; - - const navToBankScreen = () => - navigation.push('BankInfoScreen', { walletStore }); const confirm = useCallback(() => { Alert.alert( @@ -54,32 +47,14 @@ const UsdSettings = ({ walletStore, navigation }: PropsType) => { {i18n.t('wallet.usd.bankInfoDescription')} - {isStripeConnectFeatureOn ? ( - - ) : ( - + } + border + /> + + + +

+ {texts.title} +

+ + {texts.description} + + + {params.requiresTwitter && ( + <> + + + + {texts.example} + + + + )} +
+
+ + ); +} + +export function confirmSupermindReply( + navigation, + requiresTwitter: boolean, +): Promise { + return new Promise(resolve => { + navigation.push('SupermindConfirmation', { + requiresTwitter, + onConfirm: () => { + navigation.goBack?.(); + InteractionManager.runAfterInteractions(() => resolve(true)); + }, + onDismiss: () => resolve(false), + }); + }); +} diff --git a/src/compose/createComposeStore.spec.ts b/src/compose/createComposeStore.spec.ts index 898bdf4b3..60ecccb9a 100644 --- a/src/compose/createComposeStore.spec.ts +++ b/src/compose/createComposeStore.spec.ts @@ -5,13 +5,31 @@ import createComposeStore from './createComposeStore'; import { SupermindRequestParam } from './SupermindComposeScreen'; import { confirm } from '../common/components/Confirm'; import api from '../common/services/api.service'; +import { confirmSupermindReply } from './SupermindConfirmation'; jest.mock('../navigation/NavigationService'); jest.mock('../common/components/Confirm'); -jest.mock('../common/services/api.service'); +jest.mock('./SupermindConfirmation'); +jest.mock('../common/services/api.service', () => ({ + post: jest.fn(), + rawPost: jest.fn(), + get: jest.fn(), + put: jest.fn(), + upload: jest.fn(), + delete: jest.fn(), + clearCookies: jest.fn(), + buildAuthorizationHeader: jest.fn(), + + isApiError: jest.fn(), + isNetworkError: jest.fn(), + isAbort: jest.fn(), +})); const mockedApi = api as jest.Mocked; const mockedConfirm = confirm as jest.Mock; +const mockedConfirmSupermindReply = confirmSupermindReply as jest.Mock< + typeof confirmSupermindReply +>; jest.mock('../common/services/minds-config.service', () => ({ settings: { @@ -162,6 +180,7 @@ describe('createComposeStore', () => { it('should reply to a supermind correctly', async () => { const supermindGuid = 'supermindFakeGuid'; mockedConfirm.mockReturnValue(true); + mockedConfirmSupermindReply.mockReturnValue(true); store = createComposeStore({ navigation: mockedNavigation, route: { @@ -185,6 +204,7 @@ describe('createComposeStore', () => { it('should not reply to a supermind if user didnt confirm', async () => { const supermindGuid = 'supermindFakeGuid'; mockedConfirm.mockReturnValue(false); + mockedConfirmSupermindReply.mockReturnValue(false); store = createComposeStore({ navigation: mockedNavigation, route: { diff --git a/src/compose/createComposeStore.tsx b/src/compose/createComposeStore.tsx index 53679956d..280baf52f 100644 --- a/src/compose/createComposeStore.tsx +++ b/src/compose/createComposeStore.tsx @@ -22,6 +22,7 @@ import MultiAttachmentStore from '~/common/stores/MultiAttachmentStore'; import SupermindRequestModel from '../supermind/SupermindRequestModel'; import { confirm } from '../common/components/Confirm'; import { storeRatingService } from 'modules/store-rating'; +import { confirmSupermindReply } from './SupermindConfirmation'; /** * Display an error message to the user. @@ -545,14 +546,13 @@ export default function (props) { if (this.supermindObject) { if ( - !(await confirm({ - title: i18n.t('supermind.confirm.title'), - description: i18n.t('supermind.confirm.description'), - })) + !(await confirmSupermindReply( + props.navigation, + !!this.supermindObject?.twitter_required, + )) ) { return; } - newPost.supermind_reply_guid = this.supermindObject.guid; } diff --git a/src/navigation/MoreStack.tsx b/src/navigation/MoreStack.tsx index b6a4634c4..1bf8b9c4f 100644 --- a/src/navigation/MoreStack.tsx +++ b/src/navigation/MoreStack.tsx @@ -380,6 +380,13 @@ export default function () { getComponent={() => require('modules/boost').BoostComposerStack} options={{ headerShown: false }} /> + + require('~/supermind/SupermindTwitterConnectScreen').default + } + options={{ headerShown: false }} + /> ); } diff --git a/src/navigation/NavigationStack.tsx b/src/navigation/NavigationStack.tsx index 5f4bbfccb..fad5c2870 100644 --- a/src/navigation/NavigationStack.tsx +++ b/src/navigation/NavigationStack.tsx @@ -329,6 +329,16 @@ const RootStack = observer(function () { getComponent={() => require('~/compose/ComposeScreen').default} options={TransitionPresets.ModalPresentationIOS} /> + + require('~/compose/SupermindConfirmation').default + } + options={{ + ...TransitionPresets.ModalPresentationIOS, + gestureEnabled: true, + }} + /> diff --git a/src/navigation/NavigationTypes.ts b/src/navigation/NavigationTypes.ts index 262164db2..116fb2ccc 100644 --- a/src/navigation/NavigationTypes.ts +++ b/src/navigation/NavigationTypes.ts @@ -15,6 +15,9 @@ import { SupermindRequestParam } from '../compose/SupermindComposeScreen'; import SupermindRequestModel from '../supermind/SupermindRequestModel'; import { BottomSheetScreenParams } from '../common/components/bottom-sheet/BottomSheetScreen'; import type { BoostType } from '../boost/legacy/createBoostStore'; +import type { WebViewNavigation } from 'react-native-webview'; +import type { SupermindTwitterConnectRouteParams } from '../supermind/SupermindTwitterConnectScreen'; +import type { SupermindConfirmationRouteParams } from '../compose/SupermindConfirmation'; type AnyType = any; @@ -36,7 +39,7 @@ type WebViewParams = { url: string; headers?: { [key: string]: string }; redirectUrl?: string; - onRedirect?: () => void; + onRedirect?: (event: WebViewNavigation) => void; }; export type DiscoveryStackParamList = { @@ -54,6 +57,7 @@ export type DiscoveryStackParamList = { }; export type MoreStackParamList = { + SupermindTwitterConnect: SupermindTwitterConnectRouteParams; WebView: WebViewParams; SupermindConsole?: { tab: 'inbound' | 'outbound'; @@ -142,6 +146,7 @@ export type RootStackParamList = { group?: GroupModel; parentKey?: string; }; + SupermindConfirmation: SupermindConfirmationRouteParams; SupermindCompose: { data: SupermindRequestParam; closeComposerOnClear?: boolean; diff --git a/src/newsfeed/activity/ActivityActionSheet.tsx b/src/newsfeed/activity/ActivityActionSheet.tsx index 5af87c9c8..bf00ac20f 100644 --- a/src/newsfeed/activity/ActivityActionSheet.tsx +++ b/src/newsfeed/activity/ActivityActionSheet.tsx @@ -23,7 +23,8 @@ import { withChannelContext } from '~/channel/v2/ChannelContext'; import type UserModel from '~/channel/UserModel'; import SendIntentAndroid from 'react-native-send-intent'; import logService from '~/common/services/log.service'; -import { hasVariation } from 'ExperimentsProvider'; +import { hasVariation, useIsFeatureOn } from 'ExperimentsProvider'; +import { isApiError } from '../../common/services/api.service'; type PropsType = { entity: ActivityModel; @@ -393,6 +394,9 @@ class ActivityActionSheet extends PureComponent { this.props.navigation.goBack(); } } catch (err) { + if (isApiError(err)) { + return this.showError(err?.message); + } this.showError(); } } @@ -432,10 +436,10 @@ class ActivityActionSheet extends PureComponent { /** * Show an error message */ - showError() { + showError(message?: string) { showNotification( - i18n.t('errorMessage') + '\n' + i18n.t('activity.tryAgain'), - 'warning', + message || i18n.t('errorMessage') + '\n' + i18n.t('activity.tryAgain'), + 'danger', 2000, ); } diff --git a/src/supermind/SupermindRequest.tsx b/src/supermind/SupermindRequest.tsx index d9f9d80c3..65a8ffca9 100644 --- a/src/supermind/SupermindRequest.tsx +++ b/src/supermind/SupermindRequest.tsx @@ -1,14 +1,15 @@ -import React from 'react'; -import { observer } from 'mobx-react'; import { useNavigation } from '@react-navigation/native'; +import { observer } from 'mobx-react'; +import React from 'react'; import { View } from 'react-native'; - +import SupermindLabel from '~/common/components/supermind/SupermindLabel'; import i18n from '~/common/services/i18n.service'; +import { B2, Button, Column, Row, Spacer } from '~/common/ui'; import Activity from '~/newsfeed/activity/Activity'; +import { hasVariation } from '../../ExperimentsProvider'; import { borderBottomStyle } from './AddBankInformation'; import SupermindRequestModel from './SupermindRequestModel'; -import { B2, Button, Column, Row, Spacer } from '~/common/ui'; -import SupermindLabel from '~/common/components/supermind/SupermindLabel'; +import { ensureTwitterConnected } from './SupermindTwitterConnectScreen'; import { SupermindRequestStatus } from './types'; type Props = { @@ -116,7 +117,15 @@ const InboundButtons = observer( ({ request }: { request: SupermindRequestModel }) => { const navigation = useNavigation(); - const answer = React.useCallback(() => { + const answer = React.useCallback(async () => { + if (request.twitter_required && hasVariation('mob-twitter-oauth-4715')) { + const connected = await ensureTwitterConnected(navigation); + + if (!connected) { + return; + } + } + navigation.navigate('Compose', { isRemind: true, supermindObject: request, @@ -140,6 +149,7 @@ const InboundButtons = observer( type="action" bottom="L" onPress={answer} + spinner disabled={request.isLoading > 0}> {i18n.t('supermind.acceptOffer')} diff --git a/src/supermind/SupermindTwitterConnectScreen.tsx b/src/supermind/SupermindTwitterConnectScreen.tsx new file mode 100644 index 000000000..08006979b --- /dev/null +++ b/src/supermind/SupermindTwitterConnectScreen.tsx @@ -0,0 +1,135 @@ +import { useRoute } from '@react-navigation/core'; +import { useNavigation } from '@react-navigation/native'; +import React from 'react'; +import { InteractionManager } from 'react-native'; +import { WebViewNavigation } from 'react-native-webview'; +import { showNotification } from '../../AppMessages'; +import useApiFetch from '../common/hooks/useApiFetch'; +import apiService from '../common/services/api.service'; +import i18n from '../common/services/i18n.service'; +import { B2, Button, Column, H3, Screen, ScreenHeader } from '../common/ui'; +import { MINDS_URI } from '../config/Config'; + +export interface SupermindTwitterConnectRouteParams { + onConnect: (success: boolean) => void; +} + +export default function SupermindTwitterConnectScreen() { + const navigation = useNavigation(); + const { params } = useRoute(); + const { onConnect } = (params || { + onConnect: () => false, + }) as SupermindTwitterConnectRouteParams; + const { fetch: getRedirectUrl } = useApiFetch( + 'api/v3/twitter/request-oauth-token', + { + skip: true, + }, + ); + + const openTwitter = async () => { + try { + const { authorization_url } = await getRedirectUrl(); + + return new Promise(resolve => + navigation.navigate('WebView', { + url: authorization_url, + redirectUrl: MINDS_URI + 'api/v3/twitter/oauth-callback', + onRedirect: async (event: WebViewNavigation) => { + resolve(true); + await apiService.get(event.url); + showNotification( + i18n.t('supermind.twitterConnect.connectSuccess'), + 'success', + ); + navigation.goBack(); + InteractionManager.runAfterInteractions(() => { + onConnect(true); + }); + }, + }), + ); + } catch (e) { + console.error(e); + onConnect(false); + } + }; + + return ( + + + +

+ {i18n.t('supermind.twitterConnect.twitterPermission.title')} +

+ + {i18n.t('supermind.twitterConnect.twitterPermission.description')} + + + + +
+ ); +} + +interface TwitterConfigResponse { + discoverable: boolean; + last_imported_tweet_id: string; + last_sync_ts: number; + twitter_followers_count: number; + twitter_oauth2_connected: boolean; + twitter_user_id: string; + twitter_username: string; + user_guid: string; +} + +/** + * Checks whether twitter is connected or not, and tries to connect to twitter if it wasn't + * @param navigation + * @returns Promise - whether twitter is connected or not + */ +export const ensureTwitterConnected = async navigation => { + try { + /** + * Get twitter config + */ + const { + twitter_oauth2_connected, + } = await apiService.get('api/v3/twitter/config'); + + /** + * Open twitter connect screen if twitter wasn't connected + */ + if (!twitter_oauth2_connected) { + return new Promise((resolve, reject) => { + navigation.navigate('SupermindTwitterConnect', { + onConnect: (success: boolean) => { + if (success) { + resolve(true); + } else { + reject(false); + } + }, + }); + }); + } + + return true; + } catch (e) { + if (e === false) { + showNotification( + i18n.t('supermind.twitterConnect.connectFailed'), + 'danger', + ); + return false; + } + + showNotification( + i18n.t('supermind.twitterConnect.connectConfigFailed'), + 'danger', + ); + return false; + } +}; From 68cdf11d4eaf2cdd661b5a1efe2b3d8255745fa5 Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Wed, 8 Feb 2023 15:28:58 +0100 Subject: [PATCH 21/35] (fix) add supermind confirmation screen to notification stack --- src/navigation/NavigationTypes.ts | 1 + src/navigation/NotificationsStack.tsx | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/navigation/NavigationTypes.ts b/src/navigation/NavigationTypes.ts index 116fb2ccc..d079fafad 100644 --- a/src/navigation/NavigationTypes.ts +++ b/src/navigation/NavigationTypes.ts @@ -284,6 +284,7 @@ export type AppStackParamList = { Boost: {}; Analytics: {}; Notifications: {}; + SupermindTwitterConnect: SupermindTwitterConnectRouteParams; Channel: {}; ChannelEdit: {}; Bio: { diff --git a/src/navigation/NotificationsStack.tsx b/src/navigation/NotificationsStack.tsx index e05c4806a..aeb28a446 100644 --- a/src/navigation/NotificationsStack.tsx +++ b/src/navigation/NotificationsStack.tsx @@ -21,6 +21,13 @@ export default function () { name="Supermind" getComponent={() => require('~/supermind/SupermindScreen').default} /> + + require('~/supermind/SupermindTwitterConnectScreen').default + } + options={{ headerShown: false }} + /> ); } From 3deff9682fab52de51bdddc4ef75f19f0bbcfac9 Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Wed, 8 Feb 2023 15:41:44 +0100 Subject: [PATCH 22/35] (chore) fix import --- src/newsfeed/activity/ActivityActionSheet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newsfeed/activity/ActivityActionSheet.tsx b/src/newsfeed/activity/ActivityActionSheet.tsx index bf00ac20f..d6c6f7896 100644 --- a/src/newsfeed/activity/ActivityActionSheet.tsx +++ b/src/newsfeed/activity/ActivityActionSheet.tsx @@ -23,7 +23,7 @@ import { withChannelContext } from '~/channel/v2/ChannelContext'; import type UserModel from '~/channel/UserModel'; import SendIntentAndroid from 'react-native-send-intent'; import logService from '~/common/services/log.service'; -import { hasVariation, useIsFeatureOn } from 'ExperimentsProvider'; +import { hasVariation } from 'ExperimentsProvider'; import { isApiError } from '../../common/services/api.service'; type PropsType = { From 88546a606e96a2c85d615c9aa941003d96873e1a Mon Sep 17 00:00:00 2001 From: Martin Santangelo Date: Wed, 8 Feb 2023 15:28:16 -0300 Subject: [PATCH 23/35] Fix prod APK overwritten by dev builds & RC uploads should not run on master --- .gitlab-ci.yml | 6 ++++++ .gitlab/ci/upload.yml | 8 ++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be717a8b8..33f00f711 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,3 +73,9 @@ stages: when: on_success - if: $CI_COMMIT_BRANCH == "master" when: manual + +# Run automatic on develop only +.rule_automatic_develop: + rules: + - if: $CI_COMMIT_BRANCH == "develop" + when: on_success diff --git a/.gitlab/ci/upload.yml b/.gitlab/ci/upload.yml index 199db52e9..6b04e73fb 100644 --- a/.gitlab/ci/upload.yml +++ b/.gitlab/ci/upload.yml @@ -34,10 +34,6 @@ upload:s3:oss: # Upload to S3 - build:androidproduction rules: - !reference [.rule_nonpatch_release_success, rules] - - if: $CI_COMMIT_BRANCH == "develop" # Run on develop - when: on_success - - if: $CI_RC_NIGHTLY_TESTS == "true" && $CI_PROD_NIGHTLY_TESTS == "true" # Skip on QA pipes - when: never # Upload full APK to Browserstack upload:browserstack:oss: @@ -74,7 +70,7 @@ upload:browserstack:playstore: # Upload production RC (develop branch) upload:s3:oss:rc: # Upload to S3 <<: *upload_s3 - extends: .rule_automatic_develop_manual_master + extends: .rule_automatic_develop before_script: - !reference [.getversion, script] - export TARGET_NAME="Minds-$APPVERSION-RC.apk" @@ -84,7 +80,7 @@ upload:s3:oss:rc: # Upload to S3 # Upload to Browserstack upload:browserstack:oss:rc: <<: *upload_browserstack - extends: .rule_automatic_develop_manual_master + extends: .rule_automatic_develop before_script: - !reference [.getversion, script] - export TARGET_NAME="Minds-$APPVERSION-RC.apk" From 1752174fce49551f3c006bd0cc8f9fa70f8df9b9 Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Wed, 8 Feb 2023 20:28:37 +0000 Subject: [PATCH 24/35] add multiple FF for twitter --- ExperimentsProvider.tsx | 10 +++++++--- src/compose/SupermindComposeScreen.tsx | 11 ++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ExperimentsProvider.tsx b/ExperimentsProvider.tsx index b5a6a5572..d6f76a8e6 100644 --- a/ExperimentsProvider.tsx +++ b/ExperimentsProvider.tsx @@ -33,9 +33,13 @@ export const growthbook = new GrowthBook({ * @param { string|number|boolean } variation - variation to check, e.g. 'on' or 'off'. * @returns { boolean } - true if params reflect current variation. */ -export function hasVariation(featureKey: FeatureID, variation: string = 'on') { - const featureResult = growthbook.feature(featureKey); - return featureResult[variation]; +export function hasVariation( + featureKey: FeatureID | FeatureID[], + variation: string = 'on', +) { + return Array.isArray(featureKey) + ? featureKey.every(key => growthbook.feature(key)[variation]) + : growthbook.feature(featureKey)[variation]; } export function IfHasVariation({ diff --git a/src/compose/SupermindComposeScreen.tsx b/src/compose/SupermindComposeScreen.tsx index 3efe3a297..febe464e9 100644 --- a/src/compose/SupermindComposeScreen.tsx +++ b/src/compose/SupermindComposeScreen.tsx @@ -26,7 +26,7 @@ import { SupermindOnboardingOverlay, useSupermindOnboarding, } from './SupermindOnboarding'; -import { IfHasVariation } from 'ExperimentsProvider'; +import { hasVariation } from 'ExperimentsProvider'; const showError = (error: string) => showNotification(error, 'danger', undefined); @@ -91,6 +91,11 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { data?.payment_options?.payment_method_id, ); + const isTwitterEnabled = hasVariation([ + 'engine-2503-twitter-feats', + 'mob-twitter-oauth-4715', + ]); + const { min_cash = 0, min_offchain_tokens = 0 } = channel?.supermind_settings ?? {}; @@ -309,7 +314,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { valueExtractor={v => v.label} keyExtractor={v => v.value} /> - + {isTwitterEnabled && ( setRequireTwitter(val => !val)} @@ -318,7 +323,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { mode="checkbox" multiLine /> - + )} setTermsAgreed(val => !val)} title={ From 8f2926a46052324612b263866455647022fcad81 Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Wed, 8 Feb 2023 21:52:32 +0000 Subject: [PATCH 25/35] refactor SupermindComposeScreen state --- src/compose/SupermindComposeScreen.tsx | 94 +++++++++++++++----------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/src/compose/SupermindComposeScreen.tsx b/src/compose/SupermindComposeScreen.tsx index febe464e9..75f5ccd69 100644 --- a/src/compose/SupermindComposeScreen.tsx +++ b/src/compose/SupermindComposeScreen.tsx @@ -1,9 +1,10 @@ +/* eslint-disable no-shadow */ import { RouteProp } from '@react-navigation/core'; import { StackNavigationProp } from '@react-navigation/stack'; import _ from 'lodash'; import { observer } from 'mobx-react'; import { AnimatePresence } from 'moti'; -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useReducer, useRef, useState } from 'react'; import { showNotification } from '../../AppMessages'; import UserModel from '../channel/UserModel'; import FitScrollView from '../common/components/FitScrollView'; @@ -34,14 +35,12 @@ const showError = (error: string) => type PasswordConfirmation = RouteProp; type Navigation = StackNavigationProp; -// eslint-disable-next-line no-shadow export enum ReplyType { text = 0, image = 1, video = 2, } -// eslint-disable-next-line no-shadow enum PaymentType { cash = 0, token = 1, @@ -59,6 +58,19 @@ export interface SupermindRequestParam { terms_agreed: boolean; } +type SupermindState = { + channel?: UserModel; + replyType?: ReplyType; + requireTwitter?: boolean; + termsAgreed?: boolean; + paymentMethod?: PaymentType; + cardId?: string; +}; +type SupermindStateFn = ( + prev: SupermindState, + next: SupermindState, +) => SupermindState; + interface SupermindComposeScreen { route?: PasswordConfirmation; navigation: Navigation; @@ -70,25 +82,26 @@ interface SupermindComposeScreen { */ function SupermindComposeScreen(props: SupermindComposeScreen) { const theme = ThemedStyles.style; - const data: SupermindRequestParam | undefined = props.route?.params?.data; + const { params } = props.route ?? {}; + const { data, closeComposerOnClear, onClear, onSave } = params ?? {}; const offerRef = useRef(null); - const [channel, setChannel] = useState(data?.channel); - const [replyType, setReplyType] = useState( - data?.reply_type ?? ReplyType.text, - ); - const [requireTwitter, setRequireTwitter] = useState( - data?.twitter_required ?? false, - ); - const [termsAgreed, setTermsAgreed] = useState( - data?.terms_agreed || false, - ); - const [paymentMethod, setPaymentMethod] = useState( - data?.payment_options?.payment_type || IS_IOS - ? PaymentType.token - : PaymentType.cash, - ); - const [cardId, setCardId] = useState( - data?.payment_options?.payment_method_id, + + const [ + { channel, replyType, requireTwitter, termsAgreed, paymentMethod, cardId }, + setState, + ] = useReducer( + (prevState, nextState) => ({ ...prevState, ...nextState }), + { + channel: data?.channel, + replyType: data?.reply_type ?? ReplyType.text, + requireTwitter: data?.twitter_required ?? false, + termsAgreed: data?.terms_agreed ?? false, + paymentMethod: + data?.payment_options?.payment_type ?? IS_IOS + ? PaymentType.token + : PaymentType.cash, + cardId: data?.payment_options?.payment_method_id, + }, ); const isTwitterEnabled = hasVariation([ @@ -142,40 +155,40 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { }, [cardId, channel, offer, paymentMethod, termsAgreed, minValue]); const onBack = useCallback(() => { - props.route?.params?.onClear(); + onClear?.(); - if (props.route?.params?.closeComposerOnClear) { + if (closeComposerOnClear) { props.navigation.pop(2); } else { props.navigation.goBack(); } - }, [props.navigation, props.route]); + }, [closeComposerOnClear, onClear, props.navigation]); - const onSave = useCallback(() => { + const onValidate = useCallback(() => { if (!validate()) { return; } - const supermindRequest = { + const supermindRequest: SupermindRequestParam = { channel: channel!, payment_options: { amount: Number(offer), payment_method_id: cardId!, - payment_type: paymentMethod, + payment_type: paymentMethod ?? PaymentType.cash, }, - reply_type: replyType, - twitter_required: requireTwitter, - terms_agreed: termsAgreed, + reply_type: replyType ?? ReplyType.text, + twitter_required: requireTwitter ?? false, + terms_agreed: termsAgreed ?? false, }; // if object wasn't dirty, just go back without saving - if (_.isEqual(supermindRequest, props.route?.params?.data)) { + if (_.isEqual(supermindRequest, data)) { NavigationService.goBack(); return; } NavigationService.goBack(); - props.route?.params?.onSave(supermindRequest); + onSave?.(supermindRequest); }, [ validate, channel, @@ -185,7 +198,8 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { replyType, termsAgreed, requireTwitter, - props.route, + data, + onSave, ]); /** @@ -213,7 +227,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { } extra={ !onboarding && ( - ) @@ -224,7 +238,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { {!tabsDisabled && ( setState({ paymentMethod })} containerStyle={theme.paddingTop} tabs={[ { id: PaymentType.cash, title: i18nService.t('wallet.cash') }, @@ -240,7 +254,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { label={'Target Channel'} onPress={() => { NavigationService.push('ChannelSelectScreen', { - onSelect: selectedChannel => setChannel(selectedChannel), + onSelect: (channel?: UserModel) => setState({ channel }), }); setErrors(err => ({ ...err, @@ -284,7 +298,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { { - setCardId(card.id); + setState({ cardId: card.id }); setErrors(err => ({ ...err, card: '', @@ -294,7 +308,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { /> )} setState({ replyType })} selected={replyType} label="Response Type" data={[ @@ -317,7 +331,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { {isTwitterEnabled && ( setRequireTwitter(val => !val)} + onPress={() => setState({ requireTwitter: !requireTwitter })} selected={requireTwitter} title={i18nService.t('supermind.requireTwitter')} mode="checkbox" @@ -325,7 +339,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { /> )} setTermsAgreed(val => !val)} + onPress={() => setState({ termsAgreed: !termsAgreed })} title={ I agree to the{' '} From decee051b9017164e5330cccf25e695439305143 Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Wed, 8 Feb 2023 22:16:58 +0000 Subject: [PATCH 26/35] missed states during refactor --- src/compose/SupermindComposeScreen.tsx | 89 +++++++++++++++----------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/src/compose/SupermindComposeScreen.tsx b/src/compose/SupermindComposeScreen.tsx index 75f5ccd69..754f838b5 100644 --- a/src/compose/SupermindComposeScreen.tsx +++ b/src/compose/SupermindComposeScreen.tsx @@ -4,7 +4,7 @@ import { StackNavigationProp } from '@react-navigation/stack'; import _ from 'lodash'; import { observer } from 'mobx-react'; import { AnimatePresence } from 'moti'; -import React, { useCallback, useReducer, useRef, useState } from 'react'; +import React, { useCallback, useReducer, useRef } from 'react'; import { showNotification } from '../../AppMessages'; import UserModel from '../channel/UserModel'; import FitScrollView from '../common/components/FitScrollView'; @@ -65,6 +65,8 @@ type SupermindState = { termsAgreed?: boolean; paymentMethod?: PaymentType; cardId?: string; + offer?: string; + errors?: any; }; type SupermindStateFn = ( prev: SupermindState, @@ -86,8 +88,28 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { const { data, closeComposerOnClear, onClear, onSave } = params ?? {}; const offerRef = useRef(null); + const { min_cash = 0, min_offchain_tokens = 0 } = + data?.channel?.supermind_settings ?? {}; + + const defaultPaymentMethod = + data?.payment_options?.payment_type ?? IS_IOS + ? PaymentType.token + : PaymentType.cash; + + const minValue = + defaultPaymentMethod === PaymentType.cash ? min_cash : min_offchain_tokens; + const [ - { channel, replyType, requireTwitter, termsAgreed, paymentMethod, cardId }, + { + channel, + replyType, + requireTwitter, + termsAgreed, + paymentMethod, + cardId, + offer, + errors = {}, + }, setState, ] = useReducer( (prevState, nextState) => ({ ...prevState, ...nextState }), @@ -96,11 +118,11 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { replyType: data?.reply_type ?? ReplyType.text, requireTwitter: data?.twitter_required ?? false, termsAgreed: data?.terms_agreed ?? false, - paymentMethod: - data?.payment_options?.payment_type ?? IS_IOS - ? PaymentType.token - : PaymentType.cash, + paymentMethod: defaultPaymentMethod, cardId: data?.payment_options?.payment_method_id, + offer: data?.payment_options?.amount + ? String(data?.payment_options?.amount) + : `${minValue}`, }, ); @@ -109,18 +131,6 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { 'mob-twitter-oauth-4715', ]); - const { min_cash = 0, min_offchain_tokens = 0 } = - channel?.supermind_settings ?? {}; - - const minValue = - paymentMethod === PaymentType.cash ? min_cash : min_offchain_tokens; - - const [offer, setOffer] = useState( - data?.payment_options?.amount - ? String(data?.payment_options?.amount) - : `${minValue}`, - ); - const [errors, setErrors] = useState({}); const [onboarding, dismissOnboarding] = useSupermindOnboarding('consumer'); // hide payment method tabs @@ -139,7 +149,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { err.offer = 'Offer is not valid'; } else if (oferValue < minValue) { err.offer = `Offer must be greater than ${minValue}`; - } else if (offer.includes('.') && offer.split('.')[1].length > 2) { + } else if (offer?.includes('.') && offer.split('.')[1].length > 2) { err.offer = i18nService.t('supermind.maxTwoDecimals'); } if (!termsAgreed) { @@ -149,7 +159,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { if (hasErrors) { showError(err[Object.keys(err)[0]]); - setErrors(err); + setState({ errors: err }); } return !hasErrors; }, [cardId, channel, offer, paymentMethod, termsAgreed, minValue]); @@ -256,10 +266,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { NavigationService.push('ChannelSelectScreen', { onSelect: (channel?: UserModel) => setState({ channel }), }); - setErrors(err => ({ - ...err, - username: '', - })); + setState({ errors: { ...errors, username: '' } }); }} value={channel ? `@${channel.username}` : '@'} error={errors.username} @@ -273,11 +280,13 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { onChangeText={value => { if (/\d+\.?\d*$/.test(value) || value === '') { // remove leading 0 - setOffer(value.length > 1 ? value.replace(/^0+/, '') : value); - setErrors(err => ({ - ...err, - offer: '', - })); + setState({ + offer: value.length > 1 ? value.replace(/^0+/, '') : value, + errors: { + ...errors, + offer: '', + }, + }); } }} hint={`Min: ${minValue}`} @@ -287,10 +296,12 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { containerStyle={theme.paddingTop4x} returnKeyType="next" onFocus={() => - setErrors(err => ({ - ...err, - offer: '', - })) + setState({ + errors: { + ...errors, + offer: '', + }, + }) } keyboardType="numeric" /> @@ -298,11 +309,13 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { { - setState({ cardId: card.id }); - setErrors(err => ({ - ...err, - card: '', - })); + setState({ + cardId: card.id, + errors: { + ...errors, + card: '', + }, + }); }} error={errors.card} /> From 299a33670410fecc166f3ab2fc18a6fa9638992b Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Thu, 9 Feb 2023 11:19:31 +0100 Subject: [PATCH 27/35] (fix) put channel boosts and recommendations under boost partners FF --- ExperimentsProvider.tsx | 3 ++- src/channel/v2/ChannelScreen.tsx | 2 +- src/channel/v2/createChannelStore.ts | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ExperimentsProvider.tsx b/ExperimentsProvider.tsx index 1184199d3..7ca9b5fd9 100644 --- a/ExperimentsProvider.tsx +++ b/ExperimentsProvider.tsx @@ -100,4 +100,5 @@ export type FeatureID = | 'mob-4472-in-app-verification' | 'mob-4637-ios-hide-minds-superminds' | 'mob-twitter-oauth-4715' - | 'mob-4638-boost-v3'; + | 'mob-4638-boost-v3' + | 'epic-303-boost-partners'; diff --git a/src/channel/v2/ChannelScreen.tsx b/src/channel/v2/ChannelScreen.tsx index 6e6c14f41..d7a78439a 100644 --- a/src/channel/v2/ChannelScreen.tsx +++ b/src/channel/v2/ChannelScreen.tsx @@ -446,7 +446,7 @@ const ChannelScreen = observer((props: PropsType) => { if ( !store.feedStore.injectItems && !store.channel.isOwner() && - hasVariation('mob-4638-boost-v3') + hasVariation('epic-303-boost-partners') ) { store.feedStore.setInjectedItems([ new InjectItem(RECOMMENDATION_POSITION, 'channel', () => ( diff --git a/src/channel/v2/createChannelStore.ts b/src/channel/v2/createChannelStore.ts index 32b93e35f..3264a111b 100644 --- a/src/channel/v2/createChannelStore.ts +++ b/src/channel/v2/createChannelStore.ts @@ -200,7 +200,10 @@ const createChannelStore = () => { if (!this.loaded) { this.loaded = true; this.feedStore.getScheduledCount(this.channel.guid); - if (!this.channel.isOwner() && hasVariation('mob-4638-boost-v3')) { + if ( + !this.channel.isOwner() && + hasVariation('epic-303-boost-partners') + ) { this.feedStore.setInjectBoost(true); } } From 42b744892072eb1b5760358702719d8204d3466d Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Wed, 8 Feb 2023 20:02:21 +0000 Subject: [PATCH 28/35] Link to boost content policy with rejection message Changelog: added --- .../boost-console/components/v3/Boost.tsx | 139 ++++++++++-------- .../screens/BoostConsoleScreen.tsx | 2 +- .../boost-console/types/BoostConsoleBoost.ts | 7 + src/modules/boost/models/BoostModelV3.tsx | 2 + 4 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/modules/boost/boost-console/components/v3/Boost.tsx b/src/modules/boost/boost-console/components/v3/Boost.tsx index 3dadac8ee..4e30d9f5b 100644 --- a/src/modules/boost/boost-console/components/v3/Boost.tsx +++ b/src/modules/boost/boost-console/components/v3/Boost.tsx @@ -1,9 +1,11 @@ -import { NavigationProp } from '@react-navigation/core'; -import { observer } from 'mobx-react'; import React from 'react'; import { View } from 'react-native'; +import { observer } from 'mobx-react'; +import { useNavigation } from '@react-navigation/core'; + import ChannelBadges from '~/channel/badges/ChannelBadges'; import UserModel from '~/channel/UserModel'; +import Link from '~/common/components/Link'; import MPressable from '~/common/components/MPressable'; import { Avatar, B1, Column, Row } from '~/common/ui'; import Activity from '~/newsfeed/activity/Activity'; @@ -11,25 +13,28 @@ import ActivityModel from '~/newsfeed/ActivityModel'; import ThemedStyles from '~/styles/ThemedStyles'; import { useTranslation } from '../../../locales'; import BoostModel from '../../../models/BoostModelV3'; -import { BoostStatus } from '../../types/BoostConsoleBoost'; +import { + BoostRejectionReason, + BoostStatus, + BoostTargetLocation, +} from '../../types/BoostConsoleBoost'; import BoostActionBar from './BoostActionBar'; import BoostHeader from './BoostHeader'; interface BoostProps { boost: BoostModel; - navigation: NavigationProp; } /** * Boost console item */ -function Boost({ boost, navigation }: BoostProps) { +function Boost({ boost }: BoostProps) { return ( - + {boost.boost_status === BoostStatus.REJECTED ? ( - + ) : ( )} @@ -37,7 +42,8 @@ function Boost({ boost, navigation }: BoostProps) { ); } -const BoostEntity = ({ boost, navigation }: BoostProps) => { +const BoostEntity = ({ boost }: BoostProps) => { + const navigation = useNavigation(); const { t } = useTranslation(); const entity = boost.entity; @@ -45,68 +51,83 @@ const BoostEntity = ({ boost, navigation }: BoostProps) => { return null; } - const renderActivity = () => ( - - ); - - const renderUser = () => { - const user = UserModel.create(entity); - return ( - - - navigation.navigate('Channel', { - guid: user.guid, - entity: user, - }) - }> - - - - {user.name} - @{user.username} - - + const renderEntityByType = { + activity: () => ( + + ), + user: () => { + const user = UserModel.create(entity); + return ( + + + navigation.navigate('Channel', { + guid: user.guid, + entity: user, + }) + }> + + + + {user.name} + @{user.username} + + + + + + {user.briefdescription} - - - - {user.briefdescription} - - - ); + + ); + }, + default: () => ( + + {t('Entity {{type}} {{subtype}} not supported', { + type: entity.type, + subtype: entity.subtype, + })} + + ), }; - switch (entity.type) { - case 'activity': - return renderActivity(); - case 'user': - return renderUser(); - default: - return ( - - {t('Entity {{type}} {{subtype}} not supported', { - type: entity.type, - subtype: entity.subtype, - })} - - ); - } + return renderEntityByType[entity.type ?? 'default'](); }; -const Rejection = () => { +const Rejection = ({ boost }: BoostProps) => { const { t } = useTranslation(); + const navigation = useNavigation(); + const wasWrongAudience = + boost.rejection_reason === BoostRejectionReason.WRONG_AUDIENCE; return ( {t('Reason for rejection')} - {t('Did not meet the acceptance criteria for the selected audience')} + {t('Did not meet the acceptance criteria for the selected audience. ')} + {wasWrongAudience ? ( + + navigation?.navigate('BoostScreenV2', { + entity: boost.entity, + boostType: + boost.target_location === BoostTargetLocation.newsfeed + ? 'post' + : 'channel', + }) + }> + {t('Boost again.')} + + ) : ( + + {t('Learn more')} + + )} ); diff --git a/src/modules/boost/boost-console/screens/BoostConsoleScreen.tsx b/src/modules/boost/boost-console/screens/BoostConsoleScreen.tsx index 6f8651770..7aac91d33 100644 --- a/src/modules/boost/boost-console/screens/BoostConsoleScreen.tsx +++ b/src/modules/boost/boost-console/screens/BoostConsoleScreen.tsx @@ -62,7 +62,7 @@ function BoostConsoleScreen({ const renderBoost = row => { const boost = row.item; if (hasVariation('mob-4638-boost-v3')) { - return ; + return ; } return ; diff --git a/src/modules/boost/boost-console/types/BoostConsoleBoost.ts b/src/modules/boost/boost-console/types/BoostConsoleBoost.ts index d570c1bc6..7c1c3c9ad 100644 --- a/src/modules/boost/boost-console/types/BoostConsoleBoost.ts +++ b/src/modules/boost/boost-console/types/BoostConsoleBoost.ts @@ -28,6 +28,13 @@ export enum BoostTargetSuitability { mature, } +export enum BoostRejectionReason { + WRONG_AUDIENCE = 1, + AGAINST_MINDS_BOOST_POLICY = 2, + AGAINST_STRIPE_TERMS_OF_SERVICE = 3, + ONCHAIN_PAYMENT_FAILED = 4, +} + export type BoostConsoleBoost = { approved_timestamp: number | null; boost_status: BoostStatus; diff --git a/src/modules/boost/models/BoostModelV3.tsx b/src/modules/boost/models/BoostModelV3.tsx index f2dcf025c..005bcc960 100644 --- a/src/modules/boost/models/BoostModelV3.tsx +++ b/src/modules/boost/models/BoostModelV3.tsx @@ -8,6 +8,7 @@ import ActivityModel from '../../../newsfeed/ActivityModel'; import { revokeBoost } from '../boost-console/boost-console.api'; import { BoostPaymentMethod, + BoostRejectionReason, BoostStatus, BoostTargetLocation, BoostTargetSuitability, @@ -29,6 +30,7 @@ export default class BoostModel extends BaseModel { target_location!: BoostTargetLocation; target_suitability!: BoostTargetSuitability; updated_timestamp?: number | null; + rejection_reason?: BoostRejectionReason; summary?: { views_delivered: number; }; From 9374c7e32919b09778b2b0375b8c1fab1f5f6bf0 Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Thu, 9 Feb 2023 14:57:15 +0000 Subject: [PATCH 29/35] update SupermindRequest --- src/supermind/SupermindRequest.tsx | 139 +++++++++++------------------ 1 file changed, 51 insertions(+), 88 deletions(-) diff --git a/src/supermind/SupermindRequest.tsx b/src/supermind/SupermindRequest.tsx index 65a8ffca9..67a1bcb42 100644 --- a/src/supermind/SupermindRequest.tsx +++ b/src/supermind/SupermindRequest.tsx @@ -1,5 +1,4 @@ import { useNavigation } from '@react-navigation/native'; -import { observer } from 'mobx-react'; import React from 'react'; import { View } from 'react-native'; import SupermindLabel from '~/common/components/supermind/SupermindLabel'; @@ -19,6 +18,29 @@ type Props = { export default function SupermindRequest({ request, outbound }: Props) { const navigation = useNavigation(); + const isTwitterEnabled = + request.twitter_required && hasVariation('engine-2503-twitter-feats'); + + const answer = React.useCallback(async () => { + if (isTwitterEnabled && hasVariation('mob-twitter-oauth-4715')) { + const connected = await ensureTwitterConnected(navigation); + + if (!connected) { + return; + } + } + + navigation.navigate('Compose', { + isRemind: true, + supermindObject: request, + allowedMode: composerModes[request.reply_type], + entity: request.entity, + onSave: entity => { + request.setStatus(SupermindRequestStatus.ACCEPTED); + request.setReplyGuid(entity.guid); + }, + }); + }, [isTwitterEnabled, navigation, request]); return ( @@ -48,18 +70,40 @@ export default function SupermindRequest({ request, outbound }: Props) { ' · ' : i18n.t('requirements') + ': '} {i18n.t(`supermind.replyType.${request.reply_type}`)} - {request.twitter_required && ( + {isTwitterEnabled && ( <> {' · '} Twitter )} - {outbound ? ( - - ) : ( - - )} + + {!outbound && + request.status === SupermindRequestStatus.CREATED && + !request.isExpired() && ( + <> + + + + )} + + ); } @@ -110,87 +154,6 @@ const Status = ({ request }: { request: SupermindRequestModel }) => { ); }; -/** - * Inbound buttons - */ -const InboundButtons = observer( - ({ request }: { request: SupermindRequestModel }) => { - const navigation = useNavigation(); - - const answer = React.useCallback(async () => { - if (request.twitter_required && hasVariation('mob-twitter-oauth-4715')) { - const connected = await ensureTwitterConnected(navigation); - - if (!connected) { - return; - } - } - - navigation.navigate('Compose', { - isRemind: true, - supermindObject: request, - allowedMode: composerModes[request.reply_type], - entity: request.entity, - onSave: entity => { - request.setStatus(SupermindRequestStatus.ACCEPTED); - request.setReplyGuid(entity.guid); - }, - }); - }, [navigation, request]); - - return ( - - {request.status === SupermindRequestStatus.CREATED && - !request.isExpired() && ( - <> - - - - )} - - - ); - }, -); - -/** - * Outbound buttons - */ -const OutboundButtons = observer( - ({ request }: { request: SupermindRequestModel }) => { - return ( - - {/* {request.status === SupermindRequestStatus.CREATED && ( - - )} */} - - - ); - }, -); - /** * Supermind view reply button */ From 1944f2ec08f2c902986501e58843c73f4477678d Mon Sep 17 00:00:00 2001 From: Martin Santangelo Date: Thu, 9 Feb 2023 11:57:25 -0300 Subject: [PATCH 30/35] Added violated premium content & security report reasons Changelog: added --- locales/en.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/locales/en.json b/locales/en.json index e4da8fb85..8de34b3ac 100644 --- a/locales/en.json +++ b/locales/en.json @@ -807,6 +807,17 @@ }, "16": { "label": "Inauthentic engagement" + }, + "17": { + "label": "Security", + "reasons": { + "1": { + "label": "Hacked account" + } + } + }, + "18": { + "label": "Violates Premium Content policy" } }, "DMCA": "Please submit a DMCA notice to copyright@minds.com.", From 9fd649e82cf80e2e9d7a097421f740c0d070c2de Mon Sep 17 00:00:00 2001 From: Vali Nagacevschi Date: Thu, 9 Feb 2023 15:04:03 +0000 Subject: [PATCH 31/35] update SupermindComposeScreen --- src/compose/SupermindComposeScreen.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compose/SupermindComposeScreen.tsx b/src/compose/SupermindComposeScreen.tsx index 754f838b5..b5d4aa463 100644 --- a/src/compose/SupermindComposeScreen.tsx +++ b/src/compose/SupermindComposeScreen.tsx @@ -85,7 +85,7 @@ interface SupermindComposeScreen { function SupermindComposeScreen(props: SupermindComposeScreen) { const theme = ThemedStyles.style; const { params } = props.route ?? {}; - const { data, closeComposerOnClear, onClear, onSave } = params ?? {}; + const { data, closeComposerOnClear, onClear } = params ?? {}; const offerRef = useRef(null); const { min_cash = 0, min_offchain_tokens = 0 } = @@ -174,7 +174,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { } }, [closeComposerOnClear, onClear, props.navigation]); - const onValidate = useCallback(() => { + const onSave = useCallback(() => { if (!validate()) { return; } @@ -198,7 +198,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { } NavigationService.goBack(); - onSave?.(supermindRequest); + params?.onSave?.(supermindRequest); }, [ validate, channel, @@ -209,7 +209,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { termsAgreed, requireTwitter, data, - onSave, + params, ]); /** @@ -237,7 +237,7 @@ function SupermindComposeScreen(props: SupermindComposeScreen) { } extra={ !onboarding && ( - ) From 1e69ae7655dc3b6a489e5817809ddf3b8d4513e0 Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Thu, 9 Feb 2023 16:28:03 +0100 Subject: [PATCH 32/35] Added PlusUpgrade top in-feed notice Changelog: added --- ExperimentsProvider.tsx | 5 ++ locales/en.json | 14 +++- .../notices/PlusUpgradeNotice.tsx | 64 +++++++++++++++++++ .../in-feed-notices/notices/index.tsx | 2 + .../services/in-feed.notices.service.ts | 2 +- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx diff --git a/ExperimentsProvider.tsx b/ExperimentsProvider.tsx index 7ca9b5fd9..0745d6656 100644 --- a/ExperimentsProvider.tsx +++ b/ExperimentsProvider.tsx @@ -81,6 +81,10 @@ export function useIsFeatureOn(feature: FeatureID) { return useGrowthbookFeature(feature).on; } +export function useFeature(feature: FeatureID) { + return useGrowthbookFeature(feature); +} + export default function ExperimentsProvider({ children }) { return ( {children} @@ -101,4 +105,5 @@ export type FeatureID = | 'mob-4637-ios-hide-minds-superminds' | 'mob-twitter-oauth-4715' | 'mob-4638-boost-v3' + | 'minds-3639-plus-notice' | 'epic-303-boost-partners'; diff --git a/locales/en.json b/locales/en.json index e4da8fb85..24f5642e7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -240,7 +240,19 @@ "uniquenessDescription": "Verify your uniqueness to get started earning token rewards for engagement on your content.", "inviteFriendsTitle": "Invite Friends", "inviteFriendsDescription": "Minds gets better with friends. Share your referral link to invite them to join. If they earn money on Minds, you'll earn a 5% referral fee.", - "inviteFriendsAction": "Invite Friend" + "inviteFriendsAction": "Invite Friend", + "plusUpgrade": { + "title": "Upgrade to Minds+", + "description": "Support Minds and unlock features such as earning revenue for your content, hiding ads, accessing exclusive content, receiving a badge and verifying your channel.", + "descriptionVariations": { + "1": "Free-thinking content creators like you earn more and grow their reach with Minds+.", + "2": "Access exclusive content produced by the Minds+ community.", + "3": "Get verified, hide ads, and get the most of your Minds experience", + "4": "Support Minds and creators like you." + }, + "action": "Upgrade", + "secondaryAction": "Learn more" + } }, "blockchain": { "exportLegacyWallet": "Export legacy wallets", diff --git a/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx b/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx new file mode 100644 index 000000000..dd19ecb8a --- /dev/null +++ b/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx @@ -0,0 +1,64 @@ +import { useNavigation } from '@react-navigation/native'; +import { useFeature } from 'ExperimentsProvider'; +import { observer } from 'mobx-react-lite'; +import React, { useCallback } from 'react'; +import { useLegacyStores } from '~/common/hooks/use-stores'; +import i18n from '~/common/services/i18n.service'; +import inFeedNoticesService from '~/common/services/in-feed.notices.service'; +import { PRO_PLUS_SUBSCRIPTION_ENABLED } from '~/config/Config'; +import InFeedNotice from './BaseNotice'; +import openUrlService from '../../../services/open-url.service'; + +/** + * Upgrade to Minds plus Notice + */ +function PlusUpgradeNotice() { + const navigation = useNavigation(); + const { user } = useLegacyStores(); + const { value: activeExperiment } = useFeature('minds-3639-plus-notice'); + let description = i18n.t('inFeedNotices.plusUpgrade.description'); + + if ([1, 2, 3, 4].includes(activeExperiment)) { + description = i18n.t( + `inFeedNotices.plusUpgrade.descriptionVariations.${ + activeExperiment as 1 | 2 | 3 | 4 + }`, + ); + } + + const onPress = useCallback(() => { + navigation.navigate('UpgradeScreen', { + onComplete: (success: any) => { + if (success) { + user.me.togglePlus(); + } + }, + pro: false, + }); + }, [navigation, user]); + + if ( + !inFeedNoticesService.visible('plus-upgrade') || + user.me.plus || + !PRO_PLUS_SUBSCRIPTION_ENABLED + ) { + return null; + } + + return ( + + openUrlService.openLinkInInAppBrowser('https://minds.com/plus') + } + onClose={() => inFeedNoticesService.dismiss('plus-upgrade')} + /> + ); +} + +export default observer(PlusUpgradeNotice); diff --git a/src/common/components/in-feed-notices/notices/index.tsx b/src/common/components/in-feed-notices/notices/index.tsx index 28fa173aa..05ff471dc 100644 --- a/src/common/components/in-feed-notices/notices/index.tsx +++ b/src/common/components/in-feed-notices/notices/index.tsx @@ -6,6 +6,7 @@ import PendingSupermindNotice from './PendingSupermindNotice'; import SetupChannelNotice from './SetupChannelNotice'; import TagsNotice from './TagsNotice'; import VerifyUniquenessNotice from './VerifyUniquenessNotice'; +import PlusUpgradeNotice from './PlusUpgradeNotice'; export const noticeMapper = { 'supermind-pending': , @@ -15,6 +16,7 @@ export const noticeMapper = { 'setup-channel': , 'verify-uniqueness': , 'invite-friends': , + 'plus-upgrade': , }; export type NoticeName = keyof typeof noticeMapper; diff --git a/src/common/services/in-feed.notices.service.ts b/src/common/services/in-feed.notices.service.ts index cbbaa419d..6cd50f212 100644 --- a/src/common/services/in-feed.notices.service.ts +++ b/src/common/services/in-feed.notices.service.ts @@ -207,7 +207,7 @@ export class InFeedNoticesService { * returns true if the notice should be shown * @returns boolean */ - visible(noticeName: string) { + visible(noticeName: keyof typeof noticeMapper) { return ( this.data && this.data.some( From 1a0b473a167f467c236ba053603845833fdee567 Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Thu, 9 Feb 2023 17:10:10 +0100 Subject: [PATCH 33/35] (fix) broken pipes --- .../in-feed-notices/notices/PlusUpgradeNotice.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx b/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx index dd19ecb8a..1e71525df 100644 --- a/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx +++ b/src/common/components/in-feed-notices/notices/PlusUpgradeNotice.tsx @@ -2,19 +2,19 @@ import { useNavigation } from '@react-navigation/native'; import { useFeature } from 'ExperimentsProvider'; import { observer } from 'mobx-react-lite'; import React, { useCallback } from 'react'; -import { useLegacyStores } from '~/common/hooks/use-stores'; +import useCurrentUser from '~/common/hooks/useCurrentUser'; import i18n from '~/common/services/i18n.service'; import inFeedNoticesService from '~/common/services/in-feed.notices.service'; +import openUrlService from '~/common/services/open-url.service'; import { PRO_PLUS_SUBSCRIPTION_ENABLED } from '~/config/Config'; import InFeedNotice from './BaseNotice'; -import openUrlService from '../../../services/open-url.service'; /** * Upgrade to Minds plus Notice */ function PlusUpgradeNotice() { const navigation = useNavigation(); - const { user } = useLegacyStores(); + const user = useCurrentUser()!; const { value: activeExperiment } = useFeature('minds-3639-plus-notice'); let description = i18n.t('inFeedNotices.plusUpgrade.description'); @@ -30,7 +30,7 @@ function PlusUpgradeNotice() { navigation.navigate('UpgradeScreen', { onComplete: (success: any) => { if (success) { - user.me.togglePlus(); + user.togglePlus(); } }, pro: false, @@ -39,7 +39,7 @@ function PlusUpgradeNotice() { if ( !inFeedNoticesService.visible('plus-upgrade') || - user.me.plus || + user.plus || !PRO_PLUS_SUBSCRIPTION_ENABLED ) { return null; From 5a9da57121cfded0a9be35888adc8a37780a5df7 Mon Sep 17 00:00:00 2001 From: Mani Shooshtari Date: Fri, 10 Feb 2023 11:08:58 +0100 Subject: [PATCH 34/35] Changed timestamp format and stats visibility on Boost Console v2 Changelog: changed --- .../components/v3/BoostActionBar.tsx | 39 +++++++++++-------- .../components/v3/BoostHeader.tsx | 7 +++- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/modules/boost/boost-console/components/v3/BoostActionBar.tsx b/src/modules/boost/boost-console/components/v3/BoostActionBar.tsx index 16d5fa603..99612f855 100644 --- a/src/modules/boost/boost-console/components/v3/BoostActionBar.tsx +++ b/src/modules/boost/boost-console/components/v3/BoostActionBar.tsx @@ -18,8 +18,9 @@ type BoostActionBarProps = { function BoostActionBar({ boost }: BoostActionBarProps) { const boostConsoleStore = useBoostConsoleStore(); const { t } = useTranslation(); + const date = i18n.date(boost.created_timestamp * 1000); const revokable = boost.boost_status === BoostStatus.PENDING; - const date = i18n.date(boost.created_timestamp * 1000, 'friendly'); + const showStats = boost.boost_status === BoostStatus.APPROVED; const revoke = async () => { if ( @@ -39,22 +40,26 @@ function BoostActionBar({ boost }: BoostActionBarProps) { return ( - - - {t('Results')} - - - {t('Start date')} - - - - - {boost.summary?.views_delivered ?? ''} - - - {date} - - + {showStats && ( + <> + + + {t('Results')} + + + {t('Start date')} + + + + + {boost.summary?.views_delivered ?? ''} + + + {date} + + + + )} {!!revokable && (