mobile: pricing fixes

This commit is contained in:
Ammar Ahmed
2025-09-03 10:54:01 +05:00
committed by Abdullah Atta
parent dc1498d672
commit 7dddebef2e
12 changed files with 118 additions and 68 deletions

View File

@@ -132,7 +132,8 @@ function ReorderableList<T extends { id: string }>({
); );
function getOrderedItems() { function getOrderedItems() {
const items: T[] = customizableSidebarFeature?.isAllowed ? data : []; if (!customizableSidebarFeature?.isAllowed) return data;
const items: T[] = [];
itemOrderState.forEach((id) => { itemOrderState.forEach((id) => {
const item = data.find((i) => i.id === id); const item = data.find((i) => i.id === id);
if (!item) return; if (!item) return;
@@ -183,7 +184,6 @@ function ReorderableList<T extends { id: string }>({
dragging: true dragging: true
}); });
}} }}
disableVirtualization
itemsDraggable={disableDefaultDrag ? dragging : true} itemsDraggable={disableDefaultDrag ? dragging : true}
lockItemDragsToMainAxis lockItemDragsToMainAxis
onItemReorder={async ({ fromIndex, fromItem, toIndex, toItem }) => { onItemReorder={async ({ fromIndex, fromItem, toIndex, toItem }) => {

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { getFeaturesTable } from "@notesnook/common"; import { getFeaturesTable } from "@notesnook/common";
import { EV, EVENTS, Plan, SubscriptionPlan, User } from "@notesnook/core";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@@ -37,6 +38,15 @@ import { SafeAreaView } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { WebView } from "react-native-webview"; import { WebView } from "react-native-webview";
import ToggleSwitch from "toggle-switch-react-native"; import ToggleSwitch from "toggle-switch-react-native";
import {
ANDROID_POLICE_SVG,
APPLE_INSIDER_PNG,
ITS_FOSS_NEWS_PNG,
NESS_LABS_PNG,
PRIVACY_GUIDES_SVG,
TECHLORE_SVG,
XDA_SVG
} from "../../assets/images/assets";
import { useNavigationFocus } from "../../hooks/use-navigation-focus"; import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import usePricingPlans, { PricingPlan } from "../../hooks/use-pricing-plans"; import usePricingPlans, { PricingPlan } from "../../hooks/use-pricing-plans";
import Navigation, { NavigationProps } from "../../services/navigation"; import Navigation, { NavigationProps } from "../../services/navigation";
@@ -44,28 +54,16 @@ import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions"; import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize } from "../../utils/size"; import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles"; import { DefaultAppStyles } from "../../utils/styles";
import { AuthMode } from "../auth/common";
import { Header } from "../header"; import { Header } from "../header";
import { BuyPlan } from "../sheets/buy-plan"; import { BuyPlan } from "../sheets/buy-plan";
import { Toast } from "../toast"; import { Toast } from "../toast";
import AppIcon from "../ui/AppIcon"; import AppIcon from "../ui/AppIcon";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button"; import { IconButton } from "../ui/icon-button";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading"; import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
import { db } from "../../common/database";
import { SvgView } from "../ui/svg";
import {
ANDROID_POLICE_SVG,
APPLE_INSIDER_PNG,
FREEDOM_PRESS_SVG,
ITS_FOSS_NEWS_PNG,
NESS_LABS_PNG,
PRIVACY_GUIDES_SVG,
TECHLORE_SVG,
XDA_SVG
} from "../../assets/images/assets";
import { EV, EVENTS, Plan, SubscriptionPlan, User } from "@notesnook/core";
import { AuthMode } from "../auth/common";
const Steps = { const Steps = {
select: 1, select: 1,
@@ -944,7 +942,7 @@ const PricingPlanCard = ({
const price = pricingPlans?.getPrice( const price = pricingPlans?.getPrice(
product as RNIap.Subscription, product as RNIap.Subscription,
1, pricingPlans.hasTrialOffer(plan.id, product?.productId) ? 1 : 0,
annualBilling annualBilling
); );

View File

@@ -16,6 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme"; import { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
@@ -27,7 +28,6 @@ import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store"; import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store"; import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants"; import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { MenuItemsList } from "../../utils/menu-items";
import { DefaultAppStyles } from "../../utils/styles"; import { DefaultAppStyles } from "../../utils/styles";
import ReorderableList from "../list/reorderable-list"; import ReorderableList from "../list/reorderable-list";
import { MenuItemProperties } from "../sheets/menu-item-properties"; import { MenuItemProperties } from "../sheets/menu-item-properties";
@@ -36,6 +36,7 @@ import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item"; import { MenuItem } from "./menu-item";
import { PinnedSection } from "./pinned-section"; import { PinnedSection } from "./pinned-section";
import { SideMenuHeader } from "./side-menu-header"; import { SideMenuHeader } from "./side-menu-header";
import { MenuItemsList } from "../../utils/menu-items";
const pro = { const pro = {
title: strings.upgradePlan(), title: strings.upgradePlan(),
@@ -59,7 +60,7 @@ export function SideMenuHome() {
state.hiddenItems["routes"] state.hiddenItems["routes"]
]); ]);
const subscriptionType = useUserStore( const subscriptionType = useUserStore(
(state) => state.user?.subscription?.type (state) => state.user?.subscription?.plan
); );
const user = useUserStore.getState().user; const user = useUserStore.getState().user;
@@ -139,8 +140,9 @@ export function SideMenuHome() {
paddingVertical: DefaultAppStyles.GAP_VERTICAL paddingVertical: DefaultAppStyles.GAP_VERTICAL
}} }}
> >
{subscriptionType === SUBSCRIPTION_STATUS.TRIAL || {((subscriptionType === SUBSCRIPTION_STATUS.TRIAL ||
subscriptionType === SUBSCRIPTION_STATUS.BASIC || subscriptionType === SUBSCRIPTION_STATUS.BASIC) &&
subscriptionType === SubscriptionPlan.FREE) ||
!user ? ( !user ? (
<Button <Button
title={pro.title} title={pro.title}

View File

@@ -53,12 +53,21 @@ export const Walkthrough = ({
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
padding: DefaultAppStyles.GAP, padding: DefaultAppStyles.GAP,
paddingBottom: 0 paddingBottom: DefaultAppStyles.GAP * 2,
gap: DefaultAppStyles.GAP_VERTICAL
}} }}
> >
{step.walkthroughItem(colors)} {step.walkthroughItem(colors)}
{step.title ? <Heading>{step.title}</Heading> : null} {step.title ? (
<Heading
style={{
textAlign: "center"
}}
>
Notesnook Free plan activated
</Heading>
) : null}
{step.text ? ( {step.text ? (
<Paragraph <Paragraph
style={{ style={{
@@ -74,8 +83,7 @@ export const Walkthrough = ({
{step.actionButton && ( {step.actionButton && (
<Button <Button
style={{ style={{
height: 30, height: 30
marginTop: DefaultAppStyles.GAP_VERTICAL
}} }}
textStyle={{ textStyle={{
textDecorationLine: "underline" textDecorationLine: "underline"
@@ -89,11 +97,6 @@ export const Walkthrough = ({
)} )}
<Button <Button
style={{
borderRadius: 100,
height: 40,
marginTop: DefaultAppStyles.GAP
}}
onPress={async () => { onPress={async () => {
switch (step.button?.type) { switch (step.button?.type) {
case "next": case "next":

View File

@@ -40,6 +40,10 @@ import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading"; import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
import { DefaultAppStyles } from "../../utils/styles"; import { DefaultAppStyles } from "../../utils/styles";
import { useUserStore } from "../../stores/use-user-store";
import { planToId, SubscriptionPlan } from "@notesnook/core";
import { planToDisplayName } from "../../utils/constants";
import AppIcon from "../ui/AppIcon";
export type TStep = { export type TStep = {
text?: string; text?: string;
@@ -356,7 +360,6 @@ const Support = () => {
alignItems: "center" alignItems: "center"
}} }}
> >
<SvgView src={SUPPORT_SVG()} />
<Heading>{strings.prioritySupport()}</Heading> <Heading>{strings.prioritySupport()}</Heading>
<Paragraph <Paragraph
style={{ style={{
@@ -384,21 +387,6 @@ const Support = () => {
title={strings.joinDiscord()} title={strings.joinDiscord()}
/> />
<Button
style={{
justifyContent: "flex-start",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
width: "90%"
}}
onPress={() => {
Linking.openURL("https://t.me/notesnook").catch(() => {
/* empty */
});
}}
icon="telegram"
type="secondary"
title={strings.joinTelegram()}
/>
<Button <Button
style={{ style={{
justifyContent: "flex-start", justifyContent: "flex-start",
@@ -415,7 +403,7 @@ const Support = () => {
marginBottom: DefaultAppStyles.GAP_VERTICAL, marginBottom: DefaultAppStyles.GAP_VERTICAL,
width: "90%" width: "90%"
}} }}
icon="mail" icon="email"
type="secondary" type="secondary"
title={strings.emailSupport()} title={strings.emailSupport()}
/> />
@@ -427,18 +415,15 @@ const prouser: { id: string; steps: TStep[] } = {
id: "prouser", id: "prouser",
steps: [ steps: [
{ {
title: strings.welcomeToNotesnookPro(), title: strings.welcomeToPlan(
planToDisplayName(
useUserStore.getState().user?.subscription?.plan as SubscriptionPlan
)
),
text: strings.thankYouPrivacy(), text: strings.thankYouPrivacy(),
walkthroughItem: (colors) => ( walkthroughItem: (colors) => (
<SvgView src={LAUNCH_ROCKET(colors.primary.paragraph)} /> <AppIcon name="check" color={colors.primary.accent} size={50} />
), ),
button: {
type: "next",
title: strings.next()
}
},
{
walkthroughItem: () => <Support />,
button: { button: {
type: "done", type: "done",
title: strings.continue() title: strings.continue()

View File

@@ -22,6 +22,8 @@ import {
EVENTS, EVENTS,
EventManagerSubscription, EventManagerSubscription,
SYNC_CHECK_IDS, SYNC_CHECK_IDS,
SubscriptionPlan,
SubscriptionType,
SyncStatusEvent, SyncStatusEvent,
User User
} from "@notesnook/core"; } from "@notesnook/core";
@@ -224,7 +226,11 @@ const onUserEmailVerified = async () => {
const onUserSubscriptionStatusChanged = async ( const onUserSubscriptionStatusChanged = async (
subscription: User["subscription"] subscription: User["subscription"]
) => { ) => {
if (!PremiumService.get() && subscription.type === 5) { if (
!PremiumService.get() &&
(subscription.type === SubscriptionType.PREMIUM ||
subscription.plan != SubscriptionPlan.FREE)
) {
PremiumService.subscriptions.clear(); PremiumService.subscriptions.clear();
Walkthrough.present("prouser", false, true); Walkthrough.present("prouser", false, true);
} }

View File

@@ -86,9 +86,7 @@ const pricingPlans: PricingPlan[] = [
subscriptionSkuList: [ subscriptionSkuList: [
"notesnook.pro.monthly", "notesnook.pro.monthly",
"notesnook.pro.yearly", "notesnook.pro.yearly",
"notesnook.pro.monthly.tier2",
"notesnook.pro.yearly.tier2", "notesnook.pro.yearly.tier2",
"notesnook.pro.monthly.tier3",
"notesnook.pro.yearly.tier3", "notesnook.pro.yearly.tier3",
// no trial // no trial
"notesnook.pro.monthly.nt", "notesnook.pro.monthly.nt",
@@ -281,8 +279,11 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
: (product as Plan).price.gross : (product as Plan).price.gross
}`; }`;
const pricingPhaseListItem = (product as RNIap.SubscriptionAndroid) const pricingPhaseListItem =
?.subscriptionOfferDetails?.[0]?.pricingPhases?.pricingPhaseList?.[1]; (product as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
?.pricingPhases?.pricingPhaseList?.[1] ||
(product as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
?.pricingPhases?.pricingPhaseList?.[0];
return ( return (
pricingPhaseListItem?.formattedPrice || pricingPhaseListItem?.formattedPrice ||
@@ -534,10 +535,18 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
if (!p1 || !p2) return 0; if (!p1 || !p2) return 0;
if (Platform.OS === "android") { if (Platform.OS === "android") {
const androidPricingPhase1 = (p1 as RNIap.SubscriptionAndroid) const androidPricingPhase1 =
?.subscriptionOfferDetails?.[0].pricingPhases?.pricingPhaseList?.[1]; (p1 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
const androidPricingPhase2 = (p2 as RNIap.SubscriptionAndroid) .pricingPhases?.pricingPhaseList?.[1] ||
?.subscriptionOfferDetails?.[0].pricingPhases?.pricingPhaseList?.[1]; (p1 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[0];
const androidPricingPhase2 =
(p2 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[1] ||
(p2 as RNIap.SubscriptionAndroid)?.subscriptionOfferDetails?.[0]
.pricingPhases?.pricingPhaseList?.[0];
if (!androidPricingPhase1 || !androidPricingPhase2) return 0;
return getDiscountValue( return getDiscountValue(
androidPricingPhase1.priceAmountMicros, androidPricingPhase1.priceAmountMicros,

View File

@@ -40,6 +40,8 @@ import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { AppFontSize } from "../../utils/size"; import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles"; import { DefaultAppStyles } from "../../utils/styles";
import { SectionItem } from "./section-item"; import { SectionItem } from "./section-item";
import { planToDisplayName } from "../../utils/constants";
import { SubscriptionType } from "@notesnook/core";
export const getTimeLeft = (t2) => { export const getTimeLeft = (t2) => {
let daysRemaining = dayjs(t2).diff(dayjs(), "days"); let daysRemaining = dayjs(t2).diff(dayjs(), "days");
@@ -118,6 +120,8 @@ const onChangePicture = () => {
}); });
}; };
const LONG_MAX = 9223372036854776000;
const SettingsUserSection = ({ item }) => { const SettingsUserSection = ({ item }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
const [user] = useUserStore((state) => [state.user]); const [user] = useUserStore((state) => [state.user]);
@@ -276,7 +280,10 @@ const SettingsUserSection = ({ item }) => {
{strings.storage()} {strings.storage()}
</Paragraph> </Paragraph>
<Paragraph size={AppFontSize.xxs}> <Paragraph size={AppFontSize.xxs}>
{formatBytes(used)}/{formatBytes(total)} {strings.used()} {formatBytes(used)}/
{total === LONG_MAX
? "Unlimited"
: formatBytes(total) + " " + strings.used()}
</Paragraph> </Paragraph>
</View> </View>
<View <View
@@ -317,7 +324,10 @@ const SettingsUserSection = ({ item }) => {
}} }}
> >
<Paragraph size={AppFontSize.sm}> <Paragraph size={AppFontSize.sm}>
{strings.freePlan()} {user.subscription.plan === 0 &&
user.subscription.type === SubscriptionType.PREMIUM
? strings.proPlan()
: planToDisplayName(user.subscription.plan)}
</Paragraph> </Paragraph>
<Paragraph <Paragraph
color={colors.secondary.paragraph} color={colors.secondary.paragraph}

View File

@@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { Platform } from "react-native"; import { Platform } from "react-native";
import { getVersion } from "react-native-device-info"; import { getVersion } from "react-native-device-info";
@@ -63,6 +65,23 @@ export const itemSkus = [
"notesnook.believer.5year" "notesnook.believer.5year"
]; ];
export function planToDisplayName(plan: SubscriptionPlan): string {
switch (plan) {
case SubscriptionPlan.FREE:
return strings.freePlan();
case SubscriptionPlan.ESSENTIAL:
return strings.essentialPlan();
case SubscriptionPlan.PRO:
return strings.proPlan();
case SubscriptionPlan.BELIEVER:
return strings.believerPlan();
case SubscriptionPlan.EDUCATION:
return strings.educationPlan();
default:
return strings.freePlan();
}
}
export const SUBSCRIPTION_STATUS = { export const SUBSCRIPTION_STATUS = {
BASIC: 0, BASIC: 0,
TRIAL: 1, TRIAL: 1,

View File

@@ -2372,6 +2372,10 @@ msgstr "Edit your full name"
msgid "Editor" msgid "Editor"
msgstr "Editor" msgstr "Editor"
#: src/strings.ts:2573
msgid "Education plan"
msgstr "Education plan"
#: src/strings.ts:1480 #: src/strings.ts:1480
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -7055,6 +7059,10 @@ msgstr "Welcome back, {email}"
msgid "Welcome back!" msgid "Welcome back!"
msgstr "Welcome back!" msgstr "Welcome back!"
#: src/strings.ts:2574
msgid "Welcome to Notesnook {plan} plan"
msgstr "Welcome to Notesnook {plan} plan"
#: src/strings.ts:2072 #: src/strings.ts:2072
msgid "Welcome to Notesnook Pro" msgid "Welcome to Notesnook Pro"
msgstr "Welcome to Notesnook Pro" msgstr "Welcome to Notesnook Pro"

View File

@@ -2361,6 +2361,10 @@ msgstr ""
msgid "Editor" msgid "Editor"
msgstr "" msgstr ""
#: src/strings.ts:2573
msgid "Education plan"
msgstr ""
#: src/strings.ts:1480 #: src/strings.ts:1480
msgid "Email" msgid "Email"
msgstr "" msgstr ""
@@ -7006,6 +7010,10 @@ msgstr ""
msgid "Welcome back!" msgid "Welcome back!"
msgstr "" msgstr ""
#: src/strings.ts:2574
msgid "Welcome to Notesnook {plan} plan"
msgstr ""
#: src/strings.ts:2072 #: src/strings.ts:2072
msgid "Welcome to Notesnook Pro" msgid "Welcome to Notesnook Pro"
msgstr "" msgstr ""

View File

@@ -2577,5 +2577,7 @@ Use this if changes from other devices are not appearing on this device. This wi
bestValue: () => t`Best value`, bestValue: () => t`Best value`,
planLimits: () => t`Plan limits`, planLimits: () => t`Plan limits`,
unlimited: () => t`Unlimited`, unlimited: () => t`Unlimited`,
fiveYearPlan: () => t`5 year plan (One time purchase)` fiveYearPlan: () => t`5 year plan (One time purchase)`,
educationPlan: () => t`Education plan`,
welcomeToPlan: (plan: string) => t`Welcome to Notesnook ${plan}`
}; };