fix ux for announcements for logged out users

This commit is contained in:
ammarahm-ed
2021-11-27 11:20:04 +05:00
parent 5586ec5129
commit e9a90a40c1
10 changed files with 205 additions and 152 deletions

View File

@@ -1,22 +1,21 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import {View} from 'react-native';
import { useTracked } from '../../provider'; import {useTracked} from '../../provider';
import { eSendEvent, presentSheet } from '../../services/EventManager'; import {eSendEvent, presentSheet} from '../../services/EventManager';
import { eCloseAnnouncementDialog, eOpenPremiumDialog } from '../../utils/Events'; import {eCloseAnnouncementDialog} from '../../utils/Events';
import { openLinkInBrowser } from '../../utils/functions'; import {openLinkInBrowser} from '../../utils/functions';
import { SIZE } from '../../utils/SizeUtils'; import {SIZE} from '../../utils/SizeUtils';
import { sleep } from '../../utils/TimeUtils'; import {sleep} from '../../utils/TimeUtils';
import SettingsBackupAndRestore from '../../views/Settings/backup-restore'; import SettingsBackupAndRestore from '../../views/Settings/backup-restore';
import { Button } from '../Button'; import {Button} from '../Button';
import { allowedOnPlatform, getStyle } from './functions'; import GeneralSheet from '../GeneralSheet';
import {PricingPlans} from '../Premium/pricing-plans';
import {allowedOnPlatform, getStyle} from './functions';
export const Cta = ({actions, style = {}, color,inline}) => { export const Cta = ({actions, style = {}, color, inline}) => {
const [state] = useTracked(); const [state] = useTracked();
const colors = state.colors; const colors = state.colors;
let buttons = let buttons = actions.filter(item => allowedOnPlatform(item.platforms)) || [];
actions.filter(item =>
allowedOnPlatform(item.platforms)
) || [];
const onPress = async item => { const onPress = async item => {
if (!inline) { if (!inline) {
@@ -28,9 +27,18 @@ export const Cta = ({actions, style = {}, color,inline}) => {
await openLinkInBrowser(item.data, colors); await openLinkInBrowser(item.data, colors);
} catch (e) {} } catch (e) {}
} else if (item.type === 'promo') { } else if (item.type === 'promo') {
eSendEvent(eOpenPremiumDialog, { presentSheet({
component: (
<PricingPlans
marginTop={1}
promo={{
promoCode: item.data, promoCode: item.data,
text: item.title text: item.title
}}
/>
),
noIcon: true,
noProgress: true
}); });
} else if (item.type === 'backup') { } else if (item.type === 'backup') {
presentSheet({ presentSheet({
@@ -48,6 +56,7 @@ export const Cta = ({actions, style = {}, color,inline}) => {
paddingHorizontal: 12, paddingHorizontal: 12,
...getStyle(style) ...getStyle(style)
}}> }}>
<GeneralSheet context="premium_cta" />
{buttons.length > 0 && {buttons.length > 0 &&
buttons.slice(0, 1).map(item => ( buttons.slice(0, 1).map(item => (
<Button <Button

View File

@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react';
import {FlatList, View} from 'react-native'; import {FlatList, View} from 'react-native';
import {useTracked} from '../../provider'; import {useTracked} from '../../provider';
import {useMessageStore} from '../../provider/stores'; import {useMessageStore} from '../../provider/stores';
import {DDS} from '../../services/DeviceDetection';
import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager'; import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager';
import { import {
eCloseAnnouncementDialog, eCloseAnnouncementDialog,
@@ -48,17 +49,18 @@ export const AnnouncementDialog = () => {
visible={visible}> visible={visible}>
<View <View
style={{ style={{
width: '100%', width: DDS.isTab ? 600 : '100%',
backgroundColor: colors.bg, backgroundColor: colors.bg,
maxHeight: '100%' maxHeight: DDS.isTab ? '90%' : '100%',
borderRadius: DDS.isTab ? 10 : 0,
overflow: 'hidden',
marginBottom: DDS.isTab ? 20 : 0
}}> }}>
<FlatList <FlatList
style={{ style={{
width: '100%' width: '100%'
}} }}
data={info?.body.filter(item => data={info?.body.filter(item => allowedOnPlatform(item.platforms))}
allowedOnPlatform(item.platforms)
)}
renderItem={renderItem} renderItem={renderItem}
/> />

View File

@@ -5,6 +5,7 @@ import AnimatedProgress from 'react-native-reanimated-progress-bar';
import {useTracked} from '../../provider'; import {useTracked} from '../../provider';
import { import {
useFavoriteStore, useFavoriteStore,
useMessageStore,
useNoteStore, useNoteStore,
useSettingStore, useSettingStore,
useUserStore useUserStore
@@ -23,7 +24,11 @@ import PremiumService from '../../services/PremiumService';
import {editing} from '../../utils'; import {editing} from '../../utils';
import {COLOR_SCHEME_DARK} from '../../utils/Colors'; import {COLOR_SCHEME_DARK} from '../../utils/Colors';
import {db} from '../../utils/database'; import {db} from '../../utils/database';
import {eOpenLoginDialog, eOpenRateDialog} from '../../utils/Events'; import {
eOpenAnnouncementDialog,
eOpenLoginDialog,
eOpenRateDialog
} from '../../utils/Events';
import {MMKV} from '../../utils/mmkv'; import {MMKV} from '../../utils/mmkv';
import {tabBarRef} from '../../utils/Refs'; import {tabBarRef} from '../../utils/Refs';
import {SIZE} from '../../utils/SizeUtils'; import {SIZE} from '../../utils/SizeUtils';
@@ -51,6 +56,7 @@ const AppLoader = ({onLoad}) => {
const verifyUser = useUserStore(state => state.verifyUser); const verifyUser = useUserStore(state => state.verifyUser);
const setVerifyUser = useUserStore(state => state.setVerifyUser); const setVerifyUser = useUserStore(state => state.setVerifyUser);
const deviceMode = useSettingStore(state => state.deviceMode); const deviceMode = useSettingStore(state => state.deviceMode);
const isIntroCompleted = useSettingStore(state => state.isIntroCompleted);
const pwdInput = useRef(); const pwdInput = useRef();
const load = async value => { const load = async value => {
@@ -86,6 +92,7 @@ const AppLoader = ({onLoad}) => {
eSendEvent(eOpenLoginDialog, 4); eSendEvent(eOpenLoginDialog, 4);
return; return;
} }
let settingsStore = useSettingStore.getState(); let settingsStore = useSettingStore.getState();
if (await Backup.checkBackupRequired(settingsStore.settings.reminder)) { if (await Backup.checkBackupRequired(settingsStore.settings.reminder)) {
await Backup.checkAndRun(); await Backup.checkAndRun();
@@ -93,7 +100,15 @@ const AppLoader = ({onLoad}) => {
} }
if (await checkForRateAppRequest()) return; if (await checkForRateAppRequest()) return;
if (await checkNeedsBackup()) return; if (await checkNeedsBackup()) return;
PremiumService.getRemainingTrialDaysStatus(); if (await PremiumService.getRemainingTrialDaysStatus()) return;
await useMessageStore.getState().setAnnouncement();
if (isIntroCompleted) {
let dialogs = useMessageStore.getState().dialogs;
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
}
}
})(); })();
} }
}, [_loading]); }, [_loading]);

View File

@@ -7,9 +7,8 @@ import {
StyleSheet, StyleSheet,
TouchableOpacity TouchableOpacity
} from 'react-native'; } from 'react-native';
import {useTracked} from '../../provider';
import useIsFloatingKeyboard from '../../utils/use-is-floating-keyboard'; import useIsFloatingKeyboard from '../../utils/use-is-floating-keyboard';
import {BouncingView} from '../ActionSheetComponent/BouncingView'; import { BouncingView } from '../ActionSheetComponent/BouncingView';
const BaseDialog = ({ const BaseDialog = ({
visible, visible,

View File

@@ -1,15 +1,14 @@
import React, {useEffect, useRef, useState} from 'react'; import React, { useEffect, useRef, useState } from 'react';
import {View} from 'react-native'; import { View } from 'react-native';
import {useTracked} from '../../provider'; import { useTracked } from '../../provider';
import {DDS} from '../../services/DeviceDetection'; import { DDS } from '../../services/DeviceDetection';
import {eSubscribeEvent, eUnSubscribeEvent} from '../../services/EventManager'; import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/EventManager';
import {getElevation} from '../../utils'; import { getElevation } from '../../utils';
import {eCloseSimpleDialog, eOpenSimpleDialog} from '../../utils/Events'; import { eCloseSimpleDialog, eOpenSimpleDialog } from '../../utils/Events';
import {ph, pv} from '../../utils/SizeUtils'; import { sleep } from '../../utils/TimeUtils';
import {sleep} from '../../utils/TimeUtils';
import Input from '../Input'; import Input from '../Input';
import Seperator from '../Seperator'; import Seperator from '../Seperator';
import {Toast} from '../Toast'; import { Toast } from '../Toast';
import BaseDialog from './base-dialog'; import BaseDialog from './base-dialog';
import DialogButtons from './dialog-buttons'; import DialogButtons from './dialog-buttons';
import DialogHeader from './dialog-header'; import DialogHeader from './dialog-header';

View File

@@ -1,24 +1,24 @@
import React, {useState} from 'react'; import React, { useState } from 'react';
import {Image, ScrollView, View} from 'react-native'; import { ScrollView, View } from 'react-native';
import {LAUNCH_ROCKET} from '../../assets/images/assets'; import { LAUNCH_ROCKET } from '../../assets/images/assets';
import {useTracked} from '../../provider'; import { useTracked } from '../../provider';
import {useUserStore} from '../../provider/stores'; import { useUserStore } from '../../provider/stores';
import {DDS} from '../../services/DeviceDetection'; import { DDS } from '../../services/DeviceDetection';
import {eSendEvent, presentSheet} from '../../services/EventManager'; import { eSendEvent, presentSheet } from '../../services/EventManager';
import {getElevation} from '../../utils'; import { getElevation } from '../../utils';
import {eOpenLoginDialog} from '../../utils/Events'; import { eOpenLoginDialog } from '../../utils/Events';
import {SIZE} from '../../utils/SizeUtils'; import { SIZE } from '../../utils/SizeUtils';
import {ActionIcon} from '../ActionIcon'; import { ActionIcon } from '../ActionIcon';
import {Button} from '../Button'; import { Button } from '../Button';
import GeneralSheet from '../GeneralSheet'; import GeneralSheet from '../GeneralSheet';
import {SvgToPngView} from '../ListPlaceholders'; import { SvgToPngView } from '../ListPlaceholders';
import Seperator from '../Seperator'; import Seperator from '../Seperator';
import {Toast} from '../Toast'; import { Toast } from '../Toast';
import Heading from '../Typography/Heading'; import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph'; import Paragraph from '../Typography/Paragraph';
import {features} from './features'; import { features } from './features';
import {Group} from './group'; import { Group } from './group';
import {PricingPlans} from './pricing-plans'; import { PricingPlans } from './pricing-plans';
export const Component = ({close, promo, getRef}) => { export const Component = ({close, promo, getRef}) => {
const [state, dispatch] = useTracked(); const [state, dispatch] = useTracked();
@@ -84,6 +84,8 @@ export const Component = ({close, promo, getRef}) => {
style={{ style={{
paddingHorizontal: DDS.isTab ? DDS.width / 5 : 0 paddingHorizontal: DDS.isTab ? DDS.width / 5 : 0
}} }}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onScroll={onScroll}> onScroll={onScroll}>
<View <View
style={{ style={{

View File

@@ -1,5 +1,5 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import {ActivityIndicator, Platform, View} from 'react-native'; import {ActivityIndicator, Platform, Text, View} from 'react-native';
import * as RNIap from 'react-native-iap'; import * as RNIap from 'react-native-iap';
import {useTracked} from '../../provider'; import {useTracked} from '../../provider';
import {useUserStore} from '../../provider/stores'; import {useUserStore} from '../../provider/stores';
@@ -13,6 +13,7 @@ import {db} from '../../utils/database';
import { import {
eClosePremiumDialog, eClosePremiumDialog,
eCloseProgressDialog, eCloseProgressDialog,
eCloseSimpleDialog,
eOpenLoginDialog eOpenLoginDialog
} from '../../utils/Events'; } from '../../utils/Events';
import {openLinkInBrowser} from '../../utils/functions'; import {openLinkInBrowser} from '../../utils/functions';
@@ -79,10 +80,18 @@ export const PricingPlans = ({
} }
}; };
const getPromo = async productId => { const getPromo = async code => {
let products = PremiumService.getProducts(); try {
let productId;
if (code.startsWith('com.streetwriters.notesnook')) {
productId = code;
} else {
productId = await db.offers.getCode(code.split(':')[0], Platform.OS);
}
let products = await PremiumService.getProducts();
let product = products.find(p => p.productId === productId); let product = products.find(p => p.productId === productId);
if (!product) return; if (!product) return false;
let isMonthly = product.productId.indexOf('.mo') > -1; let isMonthly = product.productId.indexOf('.mo') > -1;
let cycleText = isMonthly let cycleText = isMonthly
? promoCyclesMonthly[ ? promoCyclesMonthly[
@@ -101,6 +110,11 @@ export const PricingPlans = ({
cycleText: cycleText, cycleText: cycleText,
info: 'Pay monthly, cancel anytime' info: 'Pay monthly, cancel anytime'
}); });
return true;
} catch (e) {
console.log('PROMOCODE ERROR:', code, e);
return false;
}
}; };
useEffect(() => { useEffect(() => {
@@ -247,23 +261,19 @@ export const PricingPlans = ({
positivePress: async value => { positivePress: async value => {
if (!value) return; if (!value) return;
console.log(value); console.log(value);
eSendEvent(eCloseSimpleDialog);
setBuying(true);
try { try {
let productId = await db.offers.getCode(value, Platform.OS); if (!(await getPromo(value)))
if (productId) { throw new Error('Error applying promo code');
getPromo(productId);
ToastEvent.show({ ToastEvent.show({
heading: 'Discount applied!', heading: 'Discount applied!',
type: 'success', type: 'success',
context: 'local' context: 'local'
}); });
} else { setBuying(false);
ToastEvent.show({
heading: 'Promo code invalid or expired',
type: 'error',
context: 'local'
});
}
} catch (e) { } catch (e) {
setBuying(false);
ToastEvent.show({ ToastEvent.show({
heading: 'Promo code invalid or expired', heading: 'Promo code invalid or expired',
message: e.message, message: e.message,
@@ -282,9 +292,11 @@ export const PricingPlans = ({
) : ( ) : (
<View> <View>
{!user ? ( {!user ? (
<>
<Button <Button
onPress={() => { onPress={() => {
eSendEvent(eClosePremiumDialog); eSendEvent(eClosePremiumDialog);
eSendEvent(eCloseProgressDialog);
setTimeout(() => { setTimeout(() => {
eSendEvent(eOpenLoginDialog, 1); eSendEvent(eOpenLoginDialog, 1);
}, 400); }, 400);
@@ -297,17 +309,46 @@ export const PricingPlans = ({
marginBottom: 10 marginBottom: 10
}} }}
/> />
{promo &&
!promo.promoCode.startsWith('com.streetwriters.notesnook') ? (
<Paragraph
size={SIZE.md}
textBreakStrategy="balanced"
style={{
alignSelf: 'center',
justifyContent: 'center',
textAlign: 'center'
}}>
Use promo code{' '}
<Text
style={{
fontFamily: 'OpenSans-SemiBold'
}}>
{promo.promoCode}
</Text>{' '}
at checkout
</Paragraph>
) : null}
</>
) : ( ) : (
<> <>
<PricingItem <Button
product={product}
onPress={() => buySubscription(product.data)} onPress={() => buySubscription(product.data)}
height={40}
width="50%"
type="accent"
title="Subscribe now"
/> />
<Button <Button
onPress={() => { onPress={() => {
setProduct(null); setProduct(null);
}} }}
style={{
marginTop: 5
}}
height={30} height={30}
fontSize={13}
type="errorShade" type="errorShade"
title="Cancel promo code" title="Cancel promo code"
/> />

View File

@@ -1,17 +1,15 @@
import React, { useEffect } from 'react'; import React from 'react';
import {View} from 'react-native'; import { View } from 'react-native';
import {useTracked} from '../../provider'; import { useTracked } from '../../provider';
import {useMessageStore} from '../../provider/stores'; import { useMessageStore } from '../../provider/stores';
import {COLORS_NOTE} from '../../utils/Colors'; import { COLORS_NOTE } from '../../utils/Colors';
import {hexToRGBA} from '../../utils/ColorUtils'; import { hexToRGBA } from '../../utils/ColorUtils';
import {normalize, SIZE} from '../../utils/SizeUtils'; import { normalize, SIZE } from '../../utils/SizeUtils';
import {Button} from '../Button'; import { Announcement } from '../Announcements/announcement';
import {Placeholder} from '../ListPlaceholders'; import { Button } from '../Button';
import { Placeholder } from '../ListPlaceholders';
import Heading from '../Typography/Heading'; import Heading from '../Typography/Heading';
import {Announcement} from '../Announcements/announcement'; import { Card } from './card';
import {Card} from './card';
import { eSendEvent } from '../../services/EventManager';
import { eOpenAnnouncementDialog } from '../../utils/Events';
export const Header = React.memo( export const Header = React.memo(
({ ({
@@ -30,13 +28,7 @@ export const Header = React.memo(
const [state] = useTracked(); const [state] = useTracked();
const {colors} = state; const {colors} = state;
const announcements = useMessageStore(state => state.announcements); const announcements = useMessageStore(state => state.announcements);
const dialogs = useMessageStore(state => state.dialogs);
useEffect(() => {
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog,dialogs[0]);
}
},[dialogs])
return announcements.length !== 0 && !noAnnouncement ? ( return announcements.length !== 0 && !noAnnouncement ? (
<Announcement color={color || colors.accent} /> <Announcement color={color || colors.accent} />
) : type === 'search' ? null : !shouldShow ? ( ) : type === 'search' ? null : !shouldShow ? (

View File

@@ -364,9 +364,11 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
announcements = []; announcements = [];
} }
} catch (e) { } catch (e) {
console.log("ERROR",e);
set({announcements: []}); set({announcements: []});
} finally { } finally {
let all = await getFiltered(announcements); let all = await getFiltered(announcements);
console.log("all", all)
set({ set({
announcements: all.filter(a => a.type === 'inline'), announcements: all.filter(a => a.type === 'inline'),
dialogs: all.filter(a => a.type === 'dialog') dialogs: all.filter(a => a.type === 'dialog')
@@ -415,9 +417,7 @@ async function shouldShowAnnouncement(announcement) {
let show = announcement.platforms.some( let show = announcement.platforms.some(
platform => allowedPlatforms.indexOf(platform) > -1 platform => allowedPlatforms.indexOf(platform) > -1
); );
if (!show) return false; if (!show) return false;
const subStatus = PremiumService.getUser()?.subscription?.type; const subStatus = PremiumService.getUser()?.subscription?.type;
show = announcement.userTypes.some(userType => { show = announcement.userTypes.some(userType => {
switch (userType) { switch (userType) {
@@ -428,22 +428,15 @@ async function shouldShowAnnouncement(announcement) {
case 'trialExpired': case 'trialExpired':
return subStatus === SUBSCRIPTION_STATUS.BASIC; return subStatus === SUBSCRIPTION_STATUS.BASIC;
case 'loggedOut': case 'loggedOut':
show = !PremiumService.getUser(); return !PremiumService.getUser();
break;
case 'verified': case 'verified':
show = PremiumService.getUser()?.isEmailVerified; return PremiumService.getUser()?.isEmailVerified;
break;
case 'loggedIn': case 'loggedIn':
show = !!PremiumService.getUser(); return !!PremiumService.getUser();
break;
case 'unverified': case 'unverified':
show = !PremiumService.getUser()?.isEmailVerified; return !PremiumService.getUser()?.isEmailVerified;
break;
case 'proExpired': case 'proExpired':
show = return subStatus === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED || subStatus === SUBSCRIPTION_STATUS.PREMIUM_CANCELED;
subStatus === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED ||
subStatus === SUBSCRIPTION_STATUS.PREMIUM_CANCELED;
break;
case 'any': case 'any':
default: default:
return true; return true;

View File

@@ -1,20 +1,20 @@
import {CHECK_IDS} from 'notes-core/common'; import { CHECK_IDS } from 'notes-core/common';
import React from 'react'; import React from 'react';
import * as RNIap from 'react-native-iap'; import * as RNIap from 'react-native-iap';
import DialogHeader from '../components/Dialog/dialog-header'; import DialogHeader from '../components/Dialog/dialog-header';
import {CompactFeatures} from '../components/Premium/compact-features'; import { CompactFeatures } from '../components/Premium/compact-features';
import {PricingPlans} from '../components/Premium/pricing-plans'; import { PricingPlans } from '../components/Premium/pricing-plans';
import Seperator from '../components/Seperator'; import Seperator from '../components/Seperator';
import {useMessageStore, useUserStore} from '../provider/stores'; import { useUserStore } from '../provider/stores';
import {itemSkus, SUBSCRIPTION_STATUS} from '../utils'; import { itemSkus, SUBSCRIPTION_STATUS } from '../utils';
import {db} from '../utils/database'; import { db } from '../utils/database';
import { import {
eOpenPremiumDialog, eOpenPremiumDialog,
eOpenTrialEndingDialog, eOpenTrialEndingDialog,
eShowGetPremium eShowGetPremium
} from '../utils/Events'; } from '../utils/Events';
import {MMKV} from '../utils/mmkv'; import { MMKV } from '../utils/mmkv';
import {eSendEvent, presentSheet, ToastEvent} from './EventManager'; import { eSendEvent, presentSheet, ToastEvent } from './EventManager';
let premiumStatus = 0; let premiumStatus = 0;
let products = []; let products = [];
@@ -39,7 +39,6 @@ async function setPremiumStatus() {
} catch (e) { } catch (e) {
premiumStatus = 0; premiumStatus = 0;
} finally { } finally {
useMessageStore.getState().setAnnouncement();
if (get()) { if (get()) {
await subscriptions.clear(); await subscriptions.clear();
} }
@@ -283,7 +282,7 @@ const subscriptions = {
async function getRemainingTrialDaysStatus() { async function getRemainingTrialDaysStatus() {
let user = await db.user.getUser(); let user = await db.user.getUser();
if (!user) return; if (!user) return false;
let premium = user.subscription.type !== SUBSCRIPTION_STATUS.BASIC; let premium = user.subscription.type !== SUBSCRIPTION_STATUS.BASIC;
let isTrial = user.subscription.type === SUBSCRIPTION_STATUS.TRIAL; let isTrial = user.subscription.type === SUBSCRIPTION_STATUS.TRIAL;
let total = user.subscription.expiry - user.subscription.start; let total = user.subscription.expiry - user.subscription.start;
@@ -299,7 +298,7 @@ async function getRemainingTrialDaysStatus() {
extend: false extend: false
}); });
MMKV.setItem('lastTrialDialogShownAt', 'ending'); MMKV.setItem('lastTrialDialogShownAt', 'ending');
return; return true;
} }
if (!premium && lastTrialDialogShownAt !== 'expired') { if (!premium && lastTrialDialogShownAt !== 'expired') {
@@ -309,7 +308,9 @@ async function getRemainingTrialDaysStatus() {
extend: false extend: false
}); });
MMKV.setItem('lastTrialDialogShownAt', 'expired'); MMKV.setItem('lastTrialDialogShownAt', 'expired');
return true;
} }
return false;
} }
const features_list = [ const features_list = [