mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 22:49:45 +01:00
mobile: initial pricing changes
This commit is contained in:
@@ -25,7 +25,6 @@ import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { eCloseAnnouncementDialog } from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { PricingPlans } from "../premium/pricing-plans";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Button } from "../ui/button";
|
||||
import { allowedOnPlatform, getStyle } from "./functions";
|
||||
@@ -45,18 +44,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
Linking.openURL(item.data).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
} else if (item.type === "promo") {
|
||||
presentSheet({
|
||||
component: (
|
||||
<PricingPlans
|
||||
marginTop={1}
|
||||
promo={{
|
||||
promoCode: item.data,
|
||||
text: item.title
|
||||
}}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -31,7 +31,6 @@ export const AuthHeader = (props: { welcome?: boolean }) => {
|
||||
<View
|
||||
style={{
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -17,101 +17,27 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { Toast } from "../toast";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { AuthMode, initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
const Auth = ({ navigation, route }) => {
|
||||
const [currentAuthMode, setCurrentAuthMode] = useState(
|
||||
route?.params?.mode || AuthMode.login
|
||||
);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
initialAuthMode.current = route?.params.mode || AuthMode.login;
|
||||
useNavigationFocus(navigation, {});
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
paddingTop: insets.top,
|
||||
top: 0,
|
||||
zIndex: 999,
|
||||
backgroundColor:
|
||||
deviceMode === "mobile" ? colors.secondary.background : null,
|
||||
width: "100%"
|
||||
}}
|
||||
<SafeAreaView
|
||||
style={{ flex: 1, backgroundColor: colors.primary.background }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
width: "100%",
|
||||
height: 50,
|
||||
justifyContent:
|
||||
initialAuthMode.current !== AuthMode.welcomeSignup
|
||||
? "space-between"
|
||||
: "flex-end"
|
||||
}}
|
||||
>
|
||||
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
|
||||
<IconButton
|
||||
name="arrow-left"
|
||||
onPress={() => {
|
||||
if (initialAuthMode.current === 2) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
}}
|
||||
color={colors.primary.paragraph}
|
||||
/>
|
||||
)}
|
||||
|
||||
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
|
||||
<Button
|
||||
title={strings.skip()}
|
||||
onPress={() => {
|
||||
if (initialAuthMode.current === 2) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState();
|
||||
}, 1000);
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
}}
|
||||
iconSize={16}
|
||||
type="plain"
|
||||
iconPosition="right"
|
||||
icon="chevron-right"
|
||||
height={25}
|
||||
iconStyle={{
|
||||
marginTop: 2
|
||||
}}
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
<Signup
|
||||
changeMode={(mode) => setCurrentAuthMode(mode)}
|
||||
@@ -126,7 +52,7 @@ const Auth = ({ navigation, route }) => {
|
||||
)}
|
||||
|
||||
<Toast context="local" />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,12 +24,15 @@ import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import PremiumService from "../../services/premium";
|
||||
import Sync from "../../services/sync";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eUserLoggedIn } from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { sleep } from "../../utils/time";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Progress } from "../sheets/progress";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
@@ -37,9 +40,8 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { hideAuth } from "./common";
|
||||
import { ForgotPassword } from "./forgot-password";
|
||||
import { AuthHeader } from "./header";
|
||||
import { useLogin } from "./use-login";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Dialog } from "../dialog";
|
||||
|
||||
const LoginSteps = {
|
||||
emailAuth: 1,
|
||||
@@ -62,15 +64,21 @@ export const Login = ({ changeMode }) => {
|
||||
setError,
|
||||
login
|
||||
} = useLogin(async () => {
|
||||
hideAuth();
|
||||
eSendEvent(eUserLoggedIn, true);
|
||||
await sleep(500);
|
||||
Progress.present();
|
||||
hideAuth();
|
||||
setTimeout(() => {
|
||||
if (!useUserStore.getState().syncing) {
|
||||
Sync.run("global", false, "full");
|
||||
}
|
||||
}, 5000);
|
||||
if (!PremiumService.get()) {
|
||||
Navigation.navigate("PayWall", {
|
||||
context: "logged-in"
|
||||
});
|
||||
} else {
|
||||
Progress.present();
|
||||
}
|
||||
});
|
||||
const { width, height } = useWindowDimensions();
|
||||
const isTablet = width > 600;
|
||||
@@ -89,6 +97,7 @@ export const Login = ({ changeMode }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthHeader />
|
||||
<ForgotPassword />
|
||||
<Dialog context="two_factor_verify" />
|
||||
<View
|
||||
@@ -105,7 +114,6 @@ export const Login = ({ changeMode }) => {
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderBottomWidth: 0.8,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
borderBottomColor: colors.primary.border,
|
||||
@@ -165,7 +173,8 @@ export const Login = ({ changeMode }) => {
|
||||
: "99.9%",
|
||||
backgroundColor: colors.primary.background,
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -179,6 +188,7 @@ export const Login = ({ changeMode }) => {
|
||||
returnKeyType="next"
|
||||
autoComplete="email"
|
||||
validationType="email"
|
||||
marginBottom={0}
|
||||
autoCorrect={false}
|
||||
autoCapitalize="none"
|
||||
errorMessage={strings.emailInvalid()}
|
||||
@@ -234,11 +244,7 @@ export const Login = ({ changeMode }) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginTop: 25
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<Button
|
||||
loading={loading}
|
||||
onPress={() => {
|
||||
@@ -246,10 +252,11 @@ export const Login = ({ changeMode }) => {
|
||||
login();
|
||||
}}
|
||||
style={{
|
||||
width: 250
|
||||
width: "100%"
|
||||
}}
|
||||
type="accent"
|
||||
title={!loading ? strings.continue() : null}
|
||||
fontSize={AppFontSize.md}
|
||||
/>
|
||||
|
||||
{step === LoginSteps.passwordAuth && (
|
||||
|
||||
@@ -28,9 +28,10 @@ import { ToastManager } from "../../services/event-manager";
|
||||
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { AppFontSize, SIZE } from "../../utils/size";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Loading } from "../loading";
|
||||
import { PaywallComponent } from "../premium/component";
|
||||
import { PaywallComponent } from "../premium/paywall";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import Heading from "../ui/typography/heading";
|
||||
@@ -38,6 +39,7 @@ import Paragraph from "../ui/typography/paragraph";
|
||||
import { hideAuth } from "./common";
|
||||
import { AuthHeader } from "./header";
|
||||
import { SignupContext } from "./signup-context";
|
||||
import Navigation from "../../services/navigation";
|
||||
|
||||
const SignupSteps = {
|
||||
signup: 0,
|
||||
@@ -60,7 +62,6 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||
const { width, height } = useWindowDimensions();
|
||||
const isTablet = width > 600;
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current || !confirmPassword.current) {
|
||||
ToastManager.show({
|
||||
@@ -89,7 +90,9 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
setLastSynced(await db.lastSynced());
|
||||
clearMessage();
|
||||
setEmailVerifyMessage();
|
||||
setCurrentStep(SignupSteps.selectPlan);
|
||||
Navigation.navigate("PayWall", {
|
||||
canGoBack: false
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
setCurrentStep(SignupSteps.signup);
|
||||
@@ -138,17 +141,15 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: colors.secondary.background,
|
||||
marginBottom: 20,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
borderBottomWidth: 0.8,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor:
|
||||
deviceMode !== "mobile" ? colors.primary.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%",
|
||||
alignSelf: isTablet ? "center" : undefined,
|
||||
borderWidth: isTablet ? 1 : null,
|
||||
borderColor: isTablet ? colors.primary.border : null,
|
||||
borderRadius: isTablet ? 20 : null,
|
||||
marginTop: isTablet ? 50 : null,
|
||||
width: !isTablet ? null : "50%",
|
||||
minHeight: height * 0.25
|
||||
}}
|
||||
>
|
||||
@@ -182,7 +183,7 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
marginBottom: 25,
|
||||
marginTop: 10
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
size={AppFontSize.xxl}
|
||||
>
|
||||
{strings.createAccount()}
|
||||
</Heading>
|
||||
@@ -268,7 +269,7 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
onPress={() => {
|
||||
signup();
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
fontSize={AppFontSize.md}
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
@@ -285,12 +286,12 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
size={AppFontSize.xs + 1}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{strings.alreadyHaveAccount()}{" "}
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
size={AppFontSize.xs + 1}
|
||||
style={{ color: colors.primary.accent }}
|
||||
>
|
||||
{strings.login()}
|
||||
@@ -299,9 +300,15 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginBottom: 25
|
||||
marginBottom: 25,
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={AppFontSize.xxs}
|
||||
color={colors.secondary.paragraph}
|
||||
@@ -324,7 +331,10 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
<Paragraph
|
||||
size={AppFontSize.xxs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/privacy", colors);
|
||||
openLinkInBrowser(
|
||||
"https://notesnook.com/privacy",
|
||||
colors
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
textDecorationLine: "underline"
|
||||
@@ -337,27 +347,16 @@ export const Signup = ({ changeMode, welcome }) => {
|
||||
{strings.signupAgreement[4]()}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
</>
|
||||
) : currentStep === SignupSteps.createAccount ? (
|
||||
) : (
|
||||
<>
|
||||
<Loading
|
||||
title={"Setting up your account..."}
|
||||
description="Your account is almost ready, please wait..."
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PaywallComponent
|
||||
close={() => {
|
||||
hideAuth();
|
||||
}}
|
||||
setupAccount={() => {
|
||||
setCurrentStep(SignupSteps.createAccount);
|
||||
}}
|
||||
isModal={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SignupContext.Provider>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useRef, useState } from "react";
|
||||
import { TextInput } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
@@ -27,7 +28,6 @@ import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSimpleDialog } from "../../utils/events";
|
||||
import TwoFactorVerification from "./two-factor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const LoginSteps = {
|
||||
emailAuth: 1,
|
||||
|
||||
@@ -31,8 +31,6 @@ import ResultDialog from "../dialogs/result";
|
||||
import { VaultDialog } from "../dialogs/vault";
|
||||
import ImagePreview from "../image-preview";
|
||||
import MergeConflicts from "../merge-conflicts";
|
||||
import PremiumDialog from "../premium";
|
||||
import { Expiring } from "../premium/expiring";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import RateAppSheet from "../sheets/rate-app";
|
||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||
@@ -46,7 +44,6 @@ const DialogProvider = () => {
|
||||
<AppLockPassword />
|
||||
<LoadingDialog />
|
||||
<Dialog context="global" />
|
||||
<PremiumDialog colors={colors} />
|
||||
<AuthModal colors={colors} />
|
||||
<MergeConflicts />
|
||||
<RecoveryKeySheet colors={colors} />
|
||||
@@ -56,7 +53,6 @@ const DialogProvider = () => {
|
||||
<VaultDialog colors={colors} />
|
||||
<RateAppSheet />
|
||||
<ImagePreview />
|
||||
<Expiring />
|
||||
<AnnouncementDialog />
|
||||
<SessionExpired />
|
||||
<PDFPreview />
|
||||
|
||||
@@ -115,23 +115,28 @@ const Intro = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<View
|
||||
testID="notesnook.splashscreen"
|
||||
style={{
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.background
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width: "100%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.primary.border,
|
||||
paddingTop: insets.top + 10,
|
||||
paddingBottom: insets.top + 10,
|
||||
minHeight: height * 0.7 - (insets.top + insets.bottom)
|
||||
flexGrow: 1
|
||||
},
|
||||
isTablet && {
|
||||
width: width / 2,
|
||||
@@ -162,18 +167,23 @@ const Intro = () => {
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
minHeight: height * 0.3
|
||||
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
width={250}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={async () => {
|
||||
SettingsService.set({ introCompleted: true });
|
||||
// SettingsService.set({ introCompleted: true });
|
||||
Navigation.push("Auth", {
|
||||
mode: AuthMode.welcomeSignup
|
||||
});
|
||||
@@ -184,17 +194,23 @@ const Intro = () => {
|
||||
/>
|
||||
|
||||
<Button
|
||||
width="100%"
|
||||
title={"I already have an account"}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
title={strings.iAlreadyHaveAnAccount()}
|
||||
type="secondary"
|
||||
fontSize={AppFontSize.md}
|
||||
onPress={() => {
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
// SettingsService.set({
|
||||
// introCompleted: true
|
||||
// });
|
||||
Navigation.push("Auth", {
|
||||
mode: AuthMode.login
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { FeatureBlock } from "./feature";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const CompactFeatures = ({
|
||||
vertical,
|
||||
features = [],
|
||||
maxHeight = 600,
|
||||
scrollRef
|
||||
}) => {
|
||||
let data = vertical
|
||||
? features
|
||||
: [
|
||||
{
|
||||
highlight: "Everything",
|
||||
content: "in basic",
|
||||
icon: "emoticon-wink"
|
||||
},
|
||||
{
|
||||
highlight: "Unlimited",
|
||||
content: "notebooks",
|
||||
icon: "notebook"
|
||||
},
|
||||
{
|
||||
highlight: "File & image",
|
||||
content: "attachments",
|
||||
icon: "attachment"
|
||||
},
|
||||
{
|
||||
highlight: "Instant",
|
||||
content: "syncing",
|
||||
icon: "sync"
|
||||
},
|
||||
{
|
||||
highlight: "Private",
|
||||
content: "vault",
|
||||
icon: "shield"
|
||||
},
|
||||
{
|
||||
highlight: "Daily, weekly & monthly",
|
||||
content: "recurring reminders",
|
||||
icon: "bell"
|
||||
},
|
||||
{
|
||||
highlight: "Rich text",
|
||||
content: "editing",
|
||||
icon: "square-edit-outline"
|
||||
},
|
||||
{
|
||||
highlight: "PDF & markdown",
|
||||
content: "exports",
|
||||
icon: "file"
|
||||
},
|
||||
{
|
||||
highlight: "Encrypted",
|
||||
content: "backups",
|
||||
icon: "backup-restore"
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal={!vertical}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: maxHeight,
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
{data.map((item) => (
|
||||
<FeatureBlock key={item.highlight} vertical={vertical} {...item} />
|
||||
))}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { usePricing } from "../../hooks/use-pricing";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import {
|
||||
eOpenPremiumDialog,
|
||||
eOpenResultDialog,
|
||||
eOpenTrialEndingDialog
|
||||
} from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
import { Button } from "../ui/button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { CompactFeatures } from "./compact-features";
|
||||
import { Offer } from "./offer";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const Expiring = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [status, setStatus] = useState({
|
||||
title: "Your trial is ending soon",
|
||||
offer: "Get 30% off",
|
||||
extend: true
|
||||
});
|
||||
const pricing = usePricing("yearly");
|
||||
const promo =
|
||||
status.offer && pricing?.info
|
||||
? {
|
||||
promoCode:
|
||||
pricing?.info?.discount > 30
|
||||
? pricing.info.sku
|
||||
: "com.streetwriters.notesnook.sub.yr.trialoffer",
|
||||
text: `GET ${
|
||||
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}% OFF on yearly`,
|
||||
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
|
||||
}
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOpenTrialEndingDialog, open);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOpenTrialEndingDialog, open);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = (status) => {
|
||||
setStatus(status);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog
|
||||
onRequestClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<DialogContainer>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
textBreakStrategy="balanced"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
paddingTop: 18
|
||||
}}
|
||||
>
|
||||
{status.title}
|
||||
</Heading>
|
||||
<Seperator />
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{status.offer ? (
|
||||
<>
|
||||
<Offer padding={20} off={promo?.discount || 30} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
textBreakStrategy="balanced"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
paddingTop: 0,
|
||||
paddingBottom: 20
|
||||
}}
|
||||
size={AppFontSize.md + 2}
|
||||
>
|
||||
Upgrade now to continue using all the pro features after
|
||||
your trial ends
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
|
||||
<CompactFeatures />
|
||||
|
||||
<Paragraph
|
||||
onPress={async () => {
|
||||
setVisible(false);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenPremiumDialog, promo);
|
||||
}}
|
||||
size={AppFontSize.xs}
|
||||
style={{
|
||||
textDecorationLine: "underline",
|
||||
color: colors.secondary.paragraph,
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
{"See what's included in Basic & Pro plans"}
|
||||
</Paragraph>
|
||||
|
||||
<Seperator />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: "100%",
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="transparent"
|
||||
title="Subscribe now"
|
||||
onPress={async () => {
|
||||
setVisible(false);
|
||||
await sleep(300);
|
||||
PremiumService.sheet(
|
||||
null,
|
||||
promo?.discount > 30 ? null : promo
|
||||
);
|
||||
}}
|
||||
fontSize={AppFontSize.md + 2}
|
||||
style={{
|
||||
marginBottom: status.extend ? 0 : 10,
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP * 2
|
||||
}}
|
||||
/>
|
||||
|
||||
{status.extend && (
|
||||
<Button
|
||||
type="plain"
|
||||
title="Not sure yet? Extend trial for 7 days"
|
||||
textStyle={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={async () => {
|
||||
setVisible(false);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenResultDialog, {
|
||||
title: "Your trial has been extended",
|
||||
paragraph:
|
||||
"Try out all features of Notesnook free for 7 more days. No limitations. No commitments.",
|
||||
button: "Continue"
|
||||
});
|
||||
}}
|
||||
fontSize={AppFontSize.xs}
|
||||
height={30}
|
||||
style={{
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</DialogContainer>
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { ProTag } from "./pro-tag";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const FeatureBlock = ({
|
||||
vertical,
|
||||
highlight,
|
||||
content,
|
||||
icon,
|
||||
pro,
|
||||
proTagBg
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return vertical ? (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 10,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
marginLeft: 5,
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={AppFontSize.sm}
|
||||
>
|
||||
{content}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
height: 100,
|
||||
justifyContent: "center",
|
||||
padding: DefaultAppStyles.GAP_SMALL,
|
||||
marginRight: 10,
|
||||
borderRadius: defaultBorderRadius,
|
||||
minWidth: 100
|
||||
}}
|
||||
>
|
||||
<Icon color={colors.primary.icon} name={icon} size={AppFontSize.xl} />
|
||||
<Paragraph size={AppFontSize.md}>
|
||||
<Text style={{ color: colors.primary.accent }}>{highlight}</Text>
|
||||
{content ? "\n" + content : null}
|
||||
</Paragraph>
|
||||
|
||||
{pro ? (
|
||||
<>
|
||||
<View style={{ height: 5 }} />
|
||||
<ProTag width={50} size={AppFontSize.xs} background={proTagBg} />
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
width: 30,
|
||||
height: 3,
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
borderRadius: 100,
|
||||
backgroundColor: colors.primary.accent
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,318 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const features = [
|
||||
{
|
||||
title: "Focused on privacy",
|
||||
detail:
|
||||
"Everything you do in Notesnook stays private. We use XChaCha20-Poly1305-IETF and Argon2 to encrypt your notes.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Zero ads",
|
||||
content: "& zero trackers",
|
||||
icon: "billboard"
|
||||
},
|
||||
{
|
||||
highlight: "On device",
|
||||
content: "encryption",
|
||||
icon: "cellphone"
|
||||
},
|
||||
{
|
||||
highlight: "Secure app",
|
||||
content: "lock for all",
|
||||
icon: "cellphone-lock"
|
||||
},
|
||||
{
|
||||
highlight: "100% end-to-end ",
|
||||
content: "encrypted",
|
||||
icon: "lock"
|
||||
},
|
||||
{
|
||||
highlight: "Password protected",
|
||||
content: "notes sharing",
|
||||
icon: "file-lock"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "No limit on notes or devices",
|
||||
detail:
|
||||
"Basic or Pro, you can create unlimited number of notes and access them on all your devices. You won't be running out of space or blocks ever."
|
||||
},
|
||||
{
|
||||
title: "Attach files & images",
|
||||
detail:
|
||||
"Add your documents, PDFs, images and videos, and keep them safe and organized.",
|
||||
pro: true,
|
||||
features: [
|
||||
{
|
||||
highlight: "Bullet proof",
|
||||
content: "encryption",
|
||||
icon: "lock"
|
||||
},
|
||||
{
|
||||
highlight: "High quality",
|
||||
content: "4k images",
|
||||
icon: "image-multiple"
|
||||
},
|
||||
{
|
||||
highlight: "No monthly",
|
||||
content: "storage limit",
|
||||
icon: "harddisk"
|
||||
},
|
||||
{
|
||||
highlight: "Generous 500 MB",
|
||||
content: "max file size",
|
||||
icon: "file-cabinet"
|
||||
},
|
||||
{
|
||||
highlight: "No restriction",
|
||||
content: "on file type",
|
||||
icon: "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Cross platform Reminders",
|
||||
detail: "Stay updated on all your upcoming tasks with reminders.",
|
||||
features: [
|
||||
{
|
||||
highlight: "One-time",
|
||||
content: "reminders",
|
||||
icon: "bell"
|
||||
},
|
||||
{
|
||||
highlight: "Daily, weekly & monthly",
|
||||
content: "reminders",
|
||||
icon: "refresh",
|
||||
pro: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Two-factor authentication",
|
||||
detail:
|
||||
"Improve account security & prevent intruders from accessing your notes",
|
||||
info: "* 2FA via email is enabled by default for all users.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Email *",
|
||||
icon: "bell"
|
||||
},
|
||||
{
|
||||
highlight: "Authentication",
|
||||
content: "app",
|
||||
icon: "refresh"
|
||||
},
|
||||
{
|
||||
highlight: "SMS",
|
||||
icon: "refresh",
|
||||
pro: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Keep secrets always locked with private vault",
|
||||
detail:
|
||||
"An extra layer of security for any important data. Notes in the vault always stay encrypted and require a password to be accessed or edited everytime.",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
title: "Organize yourself in the best way",
|
||||
detail:
|
||||
"We offer multiple ways to keep you organized. The only limit is your imagination.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Unlimited",
|
||||
content: "notebooks & tags*",
|
||||
icon: "emoticon",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Organize",
|
||||
content: "with colors",
|
||||
icon: "palette",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Side menu",
|
||||
content: "shortcuts",
|
||||
icon: "link-variant"
|
||||
},
|
||||
{
|
||||
highlight: "Pin note in",
|
||||
content: "notifications",
|
||||
icon: "pin",
|
||||
platform: "android"
|
||||
}
|
||||
],
|
||||
info: "* Free users are limited to keeping 3 notebooks and 5 tags."
|
||||
},
|
||||
|
||||
{
|
||||
title: "Instant sync",
|
||||
detail:
|
||||
"Seamlessly work from anywhere on any device. Every change is synced instantly to all your devices.",
|
||||
info: "* Disable sync completely, turn off auto sync or disable editor realtime sync.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Sync to unlimited",
|
||||
content: "devices",
|
||||
icon: "cellphone"
|
||||
},
|
||||
{
|
||||
highlight: "Realtime",
|
||||
content: "editor sync",
|
||||
icon: "sync"
|
||||
},
|
||||
{
|
||||
highlight: "Granular sync",
|
||||
content: "controls *",
|
||||
icon: "sync-off"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Rich tools for rich editing",
|
||||
detail:
|
||||
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Basic formatting",
|
||||
content: "and lists",
|
||||
icon: "format-bold"
|
||||
},
|
||||
{
|
||||
highlight: "Checklists",
|
||||
content: "& tables",
|
||||
icon: "table",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Markdown",
|
||||
content: "support",
|
||||
icon: "language-markdown",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Personalized",
|
||||
content: "editor toolbar",
|
||||
icon: "gesture-tap-button",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Write notes from",
|
||||
content: "notifications",
|
||||
icon: "bell",
|
||||
platform: "android"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Safe publishing to the Internet",
|
||||
detail:
|
||||
"Publishing is nothing new but we offer fully encrypted, anonymous publishing. Take any note & share it with the world.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Password protected",
|
||||
content: "sharing",
|
||||
icon: "send-lock"
|
||||
},
|
||||
{
|
||||
highlight: "Self destruct",
|
||||
content: "monographs",
|
||||
icon: "bomb"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Export and take your notes anywhere",
|
||||
pro: true,
|
||||
detail:
|
||||
"You own your notes, not us. No proprietary formats. No vendor lock in. No waiting for hours to download your notes.",
|
||||
info: "* Free users can export notes in well formatted plain text.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Export as ",
|
||||
content: "Markdown",
|
||||
icon: "language-markdown",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Export as",
|
||||
content: "PDF",
|
||||
icon: "file-pdf-box",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Export as",
|
||||
content: "HTML",
|
||||
icon: "language-html5",
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
highlight: "Export as",
|
||||
content: "text",
|
||||
icon: "clipboard-text-outline"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Backup & keep your notes safe",
|
||||
detail:
|
||||
"Do not worry about losing your data. Turn on automatic backups on weekly or daily basis.",
|
||||
features: [
|
||||
{
|
||||
highlight: "Backup",
|
||||
content: "encryption",
|
||||
icon: "backup-restore"
|
||||
}
|
||||
],
|
||||
pro: true
|
||||
},
|
||||
{
|
||||
title: "Personalize & make Notesnook your own",
|
||||
detail:
|
||||
"Change app themes to match your style. Custom themes are coming soon.",
|
||||
|
||||
features: [
|
||||
{
|
||||
highlight: "Automatic",
|
||||
content: "dark mode",
|
||||
icon: "theme-light-dark",
|
||||
pro: false
|
||||
},
|
||||
{
|
||||
highlight: "Change accent",
|
||||
content: "color",
|
||||
icon: "invert-colors",
|
||||
pro: true
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
{
|
||||
highlight: 'Private vault',
|
||||
content: 'for notes',
|
||||
icon: 'shield-lock'
|
||||
}
|
||||
*/
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { FeatureBlock } from "./feature";
|
||||
import { ProTag } from "./pro-tag";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const Group = ({ item, index }) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
backgroundColor:
|
||||
index % 2 !== 0
|
||||
? colors.primary.background
|
||||
: colors.secondary.background,
|
||||
paddingVertical: 40
|
||||
}}
|
||||
>
|
||||
{item?.pro ? (
|
||||
<ProTag
|
||||
size={AppFontSize.sm}
|
||||
background={
|
||||
index % 2 === 0
|
||||
? colors.primary.background
|
||||
: colors.secondary.background
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<Heading>{item.title}</Heading>
|
||||
<Paragraph size={AppFontSize.md}>{item.detail}</Paragraph>
|
||||
|
||||
{item.features && (
|
||||
<ScrollView
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP
|
||||
}}
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
{item.features?.map((item) => (
|
||||
<FeatureBlock
|
||||
key={item.detail}
|
||||
{...item}
|
||||
detail={item.detail}
|
||||
pro={item.pro}
|
||||
proTagBg={
|
||||
index % 2 === 0
|
||||
? colors.primary.background
|
||||
: colors.secondary.background
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
{item.info ? (
|
||||
<Paragraph
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
size={AppFontSize.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{item.info}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { eClosePremiumDialog, eOpenPremiumDialog } from "../../utils/events";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { PaywallComponent } from "./component";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
class PremiumDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
promo: null
|
||||
};
|
||||
this.actionSheetRef = createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
eSubscribeEvent(eOpenPremiumDialog, this.open);
|
||||
eSubscribeEvent(eClosePremiumDialog, this.close);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
eUnSubscribeEvent(eOpenPremiumDialog, this.open);
|
||||
eUnSubscribeEvent(eClosePremiumDialog, this.close);
|
||||
}
|
||||
|
||||
open = (promoInfo) => {
|
||||
this.setState({
|
||||
visible: true,
|
||||
promo: promoInfo
|
||||
});
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.setState({
|
||||
visible: false,
|
||||
promo: null
|
||||
});
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
visible: false
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return !this.state.visible ? null : (
|
||||
<BaseDialog
|
||||
animation="slide"
|
||||
bounce={false}
|
||||
background={this.props.colors.primary.background}
|
||||
onRequestClose={this.onClose}
|
||||
>
|
||||
<PaywallComponent
|
||||
getRef={() => this.actionSheetRef}
|
||||
promo={this.state.promo}
|
||||
close={this.close}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PremiumDialog;
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Text } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const Offer = ({
|
||||
off = "30",
|
||||
text = "on yearly plan, offer ends soon",
|
||||
padding = 0
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center",
|
||||
paddingVertical: padding
|
||||
}}
|
||||
size={AppFontSize.xxxl}
|
||||
>
|
||||
GET {off}
|
||||
<Text style={{ color: colors.primary.accent }}>%</Text> OFF!{"\n"}
|
||||
<Paragraph color={colors.secondary.paragraph}>{text}</Paragraph>
|
||||
</Paragraph>
|
||||
);
|
||||
};
|
||||
@@ -18,37 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
BackHandler,
|
||||
Image,
|
||||
NativeEventSubscription,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import usePricingPlans, { PricingPlan } from "../../hooks/use-pricing-plans";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Header } from "../header";
|
||||
import { BuyPlan } from "../sheets/buy-plan";
|
||||
import { Toast } from "../toast";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { FeaturesList } from "./features-list";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
type PaywallComponentProps = {
|
||||
isModal?: boolean;
|
||||
close?: () => void;
|
||||
promo?: {
|
||||
promoCode: string;
|
||||
};
|
||||
};
|
||||
|
||||
const Steps = {
|
||||
select: 1,
|
||||
@@ -56,20 +54,61 @@ const Steps = {
|
||||
finish: 3
|
||||
};
|
||||
|
||||
export const PaywallComponent = ({
|
||||
close,
|
||||
promo,
|
||||
isModal
|
||||
}: PaywallComponentProps) => {
|
||||
const PayWall = (props: NavigationProps<"PayWall">) => {
|
||||
const routeParams = props.route.params;
|
||||
const { colors } = useThemeColors();
|
||||
const pricingPlans = usePricingPlans({
|
||||
promoOffer: promo
|
||||
});
|
||||
const pricingPlans = usePricingPlans();
|
||||
const [annualBilling, setAnnualBilling] = useState(true);
|
||||
const [step, setStep] = useState(Steps.select);
|
||||
const isFocused = useNavigationFocus(props.navigation, {
|
||||
onBlur: () => true,
|
||||
onFocus: () => true
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let listener: NativeEventSubscription;
|
||||
if (isFocused) {
|
||||
listener = BackHandler.addEventListener("hardwareBackPress", () => {
|
||||
if (routeParams.context === "signup" && step === Steps.select)
|
||||
return true;
|
||||
if (step === Steps.buy) {
|
||||
setStep(Steps.select);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
listener.remove();
|
||||
};
|
||||
}, [isFocused, step]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SafeAreaView
|
||||
style={{
|
||||
backgroundColor: colors.primary.background,
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{routeParams.context === "signup" && step === Steps.select ? null : (
|
||||
<Header
|
||||
canGoBack={true}
|
||||
onLeftMenuButtonPress={() => {
|
||||
if (step === Steps.buy) {
|
||||
setStep(Steps.select);
|
||||
return;
|
||||
}
|
||||
props.navigation.goBack();
|
||||
}}
|
||||
title={
|
||||
step === Steps.buy
|
||||
? pricingPlans.userCanRequestTrial
|
||||
? `Try ${pricingPlans.currentPlan?.name} plan for free`
|
||||
: `${pricingPlans.currentPlan?.name} plan`
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{step === Steps.select ? (
|
||||
<>
|
||||
<ScrollView
|
||||
@@ -77,40 +116,20 @@ export const PaywallComponent = ({
|
||||
width: "100%"
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
gap: 12,
|
||||
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||
paddingBottom: 80
|
||||
}}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
stickyHeaderIndices={[0]}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: -12,
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
onPress={() => close?.()}
|
||||
name="chevron-left"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 100,
|
||||
borderBottomColor: colors.primary.border,
|
||||
borderBottomWidth: 1,
|
||||
paddingHorizontal: 16,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingBottom: 25,
|
||||
gap: 12
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -123,27 +142,27 @@ export const PaywallComponent = ({
|
||||
<Icon name="crown" size={30} color={colors.static.orange} />
|
||||
<Heading
|
||||
key="heading"
|
||||
size={SIZE.xl}
|
||||
size={AppFontSize.xl}
|
||||
style={{
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
Notesnook{" "}
|
||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
||||
Pro
|
||||
<Heading size={AppFontSize.xl} color={colors.primary.accent}>
|
||||
Plans
|
||||
</Heading>
|
||||
</Heading>
|
||||
</View>
|
||||
|
||||
<Paragraph key="description" size={SIZE.md}>
|
||||
<Paragraph key="description" size={AppFontSize.md}>
|
||||
Ready to take the next step on your private note taking journey?
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
gap: 16,
|
||||
paddingHorizontal: 16
|
||||
gap: DefaultAppStyles.GAP_VERTICAL,
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
@@ -231,7 +250,7 @@ export const PaywallComponent = ({
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
size={AppFontSize.lg}
|
||||
>
|
||||
Open Source
|
||||
</Paragraph>
|
||||
@@ -269,7 +288,7 @@ export const PaywallComponent = ({
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
size={AppFontSize.lg}
|
||||
>
|
||||
8.7K stars
|
||||
</Paragraph>
|
||||
@@ -307,7 +326,7 @@ export const PaywallComponent = ({
|
||||
maxWidth: 300,
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
size={AppFontSize.lg}
|
||||
>
|
||||
Recommended by Privacy Guides
|
||||
</Paragraph>
|
||||
@@ -436,6 +455,7 @@ After trying all the privacy security oriented note taking apps, for the price a
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.primary.border,
|
||||
position: "absolute",
|
||||
paddingBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
bottom: 0
|
||||
}}
|
||||
>
|
||||
@@ -451,7 +471,11 @@ After trying all the privacy security oriented note taking apps, for the price a
|
||||
}
|
||||
onPress={() => {
|
||||
if (pricingPlans.currentPlan?.id === "free") {
|
||||
close?.();
|
||||
if (routeParams.context === "signup") {
|
||||
Navigation.replace("FluidPanelsView", {});
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
}
|
||||
if (
|
||||
!pricingPlans.currentPlan?.id ||
|
||||
@@ -484,7 +508,7 @@ After trying all the privacy security oriented note taking apps, for the price a
|
||||
)}
|
||||
|
||||
<Toast context="local" />
|
||||
</>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -517,17 +541,19 @@ const FAQItem = (props: { question: string; answer: string }) => {
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
size={SIZE.md}
|
||||
size={AppFontSize.md}
|
||||
>
|
||||
{props.question}
|
||||
</Heading>
|
||||
<Icon
|
||||
name={expanded ? "chevron-up" : "chevron-down"}
|
||||
color={colors.secondary.icon}
|
||||
size={SIZE.xxl}
|
||||
size={AppFontSize.xxl}
|
||||
/>
|
||||
</View>
|
||||
{expanded ? <Paragraph size={SIZE.md}>{props.answer}</Paragraph> : null}
|
||||
{expanded ? (
|
||||
<Paragraph size={AppFontSize.md}>{props.answer}</Paragraph>
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
@@ -575,17 +601,17 @@ const ComparePlans = React.memo(
|
||||
{item === true ? (
|
||||
<Icon
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.sm}
|
||||
size={AppFontSize.sm}
|
||||
name="check"
|
||||
/>
|
||||
) : item === false ? (
|
||||
<Icon
|
||||
size={SIZE.sm}
|
||||
size={AppFontSize.sm}
|
||||
color={colors.static.red}
|
||||
name="close"
|
||||
/>
|
||||
) : keyIndex === 0 ? (
|
||||
<Heading size={SIZE.sm}>{item}</Heading>
|
||||
<Heading size={AppFontSize.sm}>{item}</Heading>
|
||||
) : (
|
||||
<Paragraph>{item}</Paragraph>
|
||||
)}
|
||||
@@ -626,7 +652,7 @@ const ReviewItem = (props: {
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
size={SIZE.md}
|
||||
size={AppFontSize.md}
|
||||
>
|
||||
{props.review}
|
||||
</Paragraph>
|
||||
@@ -655,7 +681,7 @@ const ReviewItem = (props: {
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Paragraph size={SIZE.sm}>{props.user}</Paragraph>
|
||||
<Paragraph size={AppFontSize.sm}>{props.user}</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -724,14 +750,14 @@ const PricingPlanCard = ({
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.static.white} size={SIZE.xs}>
|
||||
<Heading color={colors.static.white} size={AppFontSize.xs}>
|
||||
{discountPercentage}% Off
|
||||
</Heading>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<View>
|
||||
<Heading size={SIZE.md}>
|
||||
<Heading size={AppFontSize.md}>
|
||||
{plan.name}{" "}
|
||||
{plan.recommended ? (
|
||||
<Text
|
||||
@@ -751,12 +777,12 @@ const PricingPlanCard = ({
|
||||
<ActivityIndicator size="small" color={colors.primary.accent} />
|
||||
) : (
|
||||
<View>
|
||||
<Paragraph size={SIZE.lg}>
|
||||
<Paragraph size={AppFontSize.lg}>
|
||||
{isFreePlan ? "0.00" : price} <Paragraph>/month</Paragraph>
|
||||
</Paragraph>
|
||||
|
||||
{isFreePlan ? null : (
|
||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
|
||||
billed {annualBilling ? "annually" : "monthly"} at{" "}
|
||||
{pricingPlans?.getStandardPrice(product as RNIap.Subscription)}{" "}
|
||||
</Paragraph>
|
||||
@@ -766,3 +792,5 @@ const PricingPlanCard = ({
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default PayWall;
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Animated, { FadeInUp, FadeOutUp } from "react-native-reanimated";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import {
|
||||
eCloseActionSheet,
|
||||
eCloseSheet,
|
||||
eOpenPremiumDialog,
|
||||
eShowGetPremium
|
||||
} from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const PremiumToast = ({ context = "global", offset = 0 }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [msg, setMsg] = useState(null);
|
||||
const timer = useRef();
|
||||
const keyboard = useKeyboard();
|
||||
|
||||
const open = useCallback(
|
||||
(event) => {
|
||||
if (!event) {
|
||||
clearTimeout(timer);
|
||||
timer.current = null;
|
||||
setMsg(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.context === context && msg?.desc !== event.desc) {
|
||||
if (timer.current !== null) {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = null;
|
||||
}
|
||||
setMsg(event);
|
||||
timer.current = setTimeout(async () => {
|
||||
setMsg(null);
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
[context, msg?.desc]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eShowGetPremium, open);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eShowGetPremium, open);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const onPress = async () => {
|
||||
open(null);
|
||||
eSendEvent(eCloseActionSheet);
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
};
|
||||
|
||||
return (
|
||||
!!msg && (
|
||||
<Animated.View
|
||||
entering={FadeInUp}
|
||||
exiting={FadeOutUp}
|
||||
style={{
|
||||
position: "absolute",
|
||||
backgroundColor: colors.secondary.background,
|
||||
zIndex: 999,
|
||||
...getElevationStyle(20),
|
||||
padding: DefaultAppStyles.GAP,
|
||||
borderRadius: 10,
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
justifyContent: "space-between",
|
||||
top: offset + keyboard.keyboardHeight,
|
||||
maxWidth: DDS.isLargeTablet() ? 400 : "98%"
|
||||
}}
|
||||
onTouchEnd={() => {
|
||||
setMsg(null);
|
||||
clearTimeout(timer.current);
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
paddingRight: 6
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
color={colors.primary.accent}
|
||||
size={AppFontSize.md}
|
||||
>
|
||||
{msg.title}
|
||||
</Heading>
|
||||
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
size={AppFontSize.sm}
|
||||
color={colors.primary.paragraph}
|
||||
>
|
||||
{msg.desc}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<Button onPress={onPress} title="Get Now" type="accent" />
|
||||
</Animated.View>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import RNIap from "react-native-iap";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const PricingItem = ({
|
||||
product,
|
||||
onPress,
|
||||
compact,
|
||||
strikethrough
|
||||
}: {
|
||||
product: {
|
||||
type: "yearly" | "monthly";
|
||||
data?: RNIap.Subscription;
|
||||
info: string;
|
||||
offerType?: "yearly" | "monthly";
|
||||
};
|
||||
strikethrough?: boolean;
|
||||
onPress?: () => void;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
type="secondary"
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingVertical: compact ? 15 : 10,
|
||||
width: compact ? null : "100%",
|
||||
minWidth: 150,
|
||||
opacity: strikethrough ? 0.7 : 1
|
||||
}}
|
||||
disabled={strikethrough}
|
||||
>
|
||||
{!compact && (
|
||||
<View>
|
||||
<Heading size={AppFontSize.lg - 2}>
|
||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
||||
? "Yearly"
|
||||
: "Monthly"}
|
||||
</Heading>
|
||||
{product?.info && (
|
||||
<Paragraph size={AppFontSize.xs}>{product.info}</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View>
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
||||
}}
|
||||
size={AppFontSize.sm}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textDecorationLine: strikethrough ? "line-through" : undefined
|
||||
}}
|
||||
size={AppFontSize.lg - 2}
|
||||
>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid | undefined)
|
||||
?.subscriptionOfferDetails?.[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0]?.formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS | undefined)
|
||||
?.localizedPrice}
|
||||
</Heading>
|
||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
||||
? "/year"
|
||||
: "/month"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { ActivityIndicator, Platform, Text, View } from "react-native";
|
||||
import usePricingPlans from "../../hooks/use-pricing-plans";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import {
|
||||
eClosePremiumDialog,
|
||||
eCloseSheet,
|
||||
eCloseSimpleDialog,
|
||||
eOpenLoginDialog
|
||||
} from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Dialog } from "../dialog";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { PricingItem } from "./pricing-item";
|
||||
|
||||
const UUID_PREFIX = "0bdaea";
|
||||
const UUID_VERSION = "4";
|
||||
const UUID_VARIANT = "a";
|
||||
|
||||
function toUUID(str: string) {
|
||||
return [
|
||||
UUID_PREFIX + str.substring(0, 2), // 6 digit prefix + first 2 oid digits
|
||||
str.substring(2, 6), // # next 4 oid digits
|
||||
UUID_VERSION + str.substring(6, 9), // # 1 digit version(0x4) + next 3 oid digits
|
||||
UUID_VARIANT + str.substring(9, 12), // # 1 digit variant(0b101) + 1 zero bit + next 3 oid digits
|
||||
str.substring(12)
|
||||
].join("-");
|
||||
}
|
||||
|
||||
const promoCyclesMonthly = {
|
||||
1: "first month",
|
||||
2: "first 2 months",
|
||||
3: "first 3 months",
|
||||
4: "first 4 months",
|
||||
5: "first 5 months",
|
||||
6: "first 3 months"
|
||||
};
|
||||
|
||||
const promoCyclesYearly = {
|
||||
1: "first year",
|
||||
2: "first 2 years",
|
||||
3: "first 3 years"
|
||||
};
|
||||
|
||||
export const PricingPlans = ({
|
||||
promo,
|
||||
marginTop,
|
||||
heading = true,
|
||||
compact = false
|
||||
}: {
|
||||
promo?: {
|
||||
promoCode: string;
|
||||
};
|
||||
marginTop?: any;
|
||||
heading?: boolean;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const {
|
||||
buySubscription,
|
||||
buying,
|
||||
getStandardPrice,
|
||||
product,
|
||||
setBuying,
|
||||
loading,
|
||||
user,
|
||||
setProduct,
|
||||
monthlyPlan,
|
||||
yearlyPlan,
|
||||
getPromo
|
||||
} = usePricingPlans({
|
||||
promoOffer: promo
|
||||
});
|
||||
|
||||
return loading ? (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 100
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator color={colors.primary.accent} size={25} />
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
{buying ? (
|
||||
<BaseDialog visible statusBarTranslucent centered>
|
||||
<ActivityIndicator size={50} color="white" />
|
||||
</BaseDialog>
|
||||
) : null}
|
||||
|
||||
{!user && !product ? (
|
||||
<>
|
||||
{heading || (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
<>
|
||||
{monthlyPlan && (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: marginTop || 20,
|
||||
marginBottom: 20
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Get {monthlyPlan?.info?.discount}% off in{" "}
|
||||
{monthlyPlan?.info?.country}
|
||||
</Heading>
|
||||
</View>
|
||||
) : (
|
||||
<Heading
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: marginTop || 20,
|
||||
marginBottom: 20
|
||||
}}
|
||||
>
|
||||
Choose a plan
|
||||
</Heading>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: !compact ? "column" : "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "space-around"
|
||||
}}
|
||||
>
|
||||
<PricingItem
|
||||
onPress={() => {
|
||||
if (!monthlyPlan?.product) return;
|
||||
buySubscription(monthlyPlan?.product);
|
||||
}}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "monthly",
|
||||
data: monthlyPlan?.product,
|
||||
info: "Pay once a month, cancel anytime."
|
||||
}}
|
||||
/>
|
||||
|
||||
{!compact && (
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
marginVertical: 5
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PricingItem
|
||||
onPress={() => {
|
||||
if (!yearlyPlan?.product) return;
|
||||
buySubscription(yearlyPlan?.product);
|
||||
}}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "yearly",
|
||||
data: yearlyPlan?.product,
|
||||
info: "Pay once a year, cancel anytime."
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{Platform.OS !== "ios" ? (
|
||||
<Button
|
||||
height={35}
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
presentDialog({
|
||||
context: "local",
|
||||
input: true,
|
||||
inputPlaceholder: "Enter code",
|
||||
positiveText: "Apply",
|
||||
positivePress: async (value) => {
|
||||
if (!value) return;
|
||||
eSendEvent(eCloseSimpleDialog);
|
||||
setBuying(true);
|
||||
try {
|
||||
if (!(await getPromo(value as string)))
|
||||
throw new Error("Error applying promo code");
|
||||
ToastManager.show({
|
||||
heading: "Discount applied!",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
setBuying(false);
|
||||
} catch (e) {
|
||||
setBuying(false);
|
||||
ToastManager.show({
|
||||
heading: "Promo code invalid or expired",
|
||||
message: (e as Error).message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
},
|
||||
title: "Have a promo code?",
|
||||
paragraph: "Enter your promo code to get a special discount."
|
||||
});
|
||||
}}
|
||||
title="I have a promo code"
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
height: 15
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View>
|
||||
{!user ? (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
eSendEvent(eCloseSheet);
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOpenLoginDialog, 1);
|
||||
}, 400);
|
||||
}}
|
||||
title={"Sign up for free"}
|
||||
type="accent"
|
||||
width={250}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
marginTop: product?.type === "promo" ? 0 : 30,
|
||||
marginBottom: 10
|
||||
}}
|
||||
/>
|
||||
{Platform.OS !== "ios" &&
|
||||
promo &&
|
||||
!promo.promoCode.startsWith("com.streetwriters.notesnook") ? (
|
||||
<Paragraph
|
||||
size={AppFontSize.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}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (!product?.data) return;
|
||||
buySubscription(product.data);
|
||||
}}
|
||||
height={40}
|
||||
width="50%"
|
||||
type="accent"
|
||||
title="Subscribe now"
|
||||
/>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
setProduct(undefined);
|
||||
}}
|
||||
style={{
|
||||
marginTop: 5
|
||||
}}
|
||||
height={30}
|
||||
fontSize={13}
|
||||
type="errorShade"
|
||||
title="Cancel promo code"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!user ? (
|
||||
<Paragraph
|
||||
color={colors.secondary.paragraph}
|
||||
size={AppFontSize.xs}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
maxWidth: "80%"
|
||||
}}
|
||||
>
|
||||
{user
|
||||
? 'On clicking "Try free for 14 days", your free trial will be activated.'
|
||||
: "After sign up you will be asked to activate your free trial."}{" "}
|
||||
<Paragraph size={AppFontSize.xs} style={{ fontWeight: "bold" }}>
|
||||
No credit card is required.
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
) : null}
|
||||
|
||||
{user ? (
|
||||
<>
|
||||
{Platform.OS === "ios" ? (
|
||||
<Paragraph
|
||||
textBreakStrategy="balanced"
|
||||
size={AppFontSize.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
By subscribing, you will be charged to your iTunes Account for the
|
||||
selected plan. Subscriptions will automatically renew unless
|
||||
cancelled within 24-hours before the end of the current period.
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
By subscribing, you will be charged on your Google Account, and
|
||||
your subscription will automatically renew until you cancel prior
|
||||
to the end of the then current period.
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
By subscribing, you agree to our{" "}
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/tos")
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
style={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Terms of Service{" "}
|
||||
</Paragraph>
|
||||
and{" "}
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/privacy")
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
style={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Privacy Policy.
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Dialog context="local" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} param0
|
||||
* @returns
|
||||
*/
|
||||
export const ProTag = ({ width, size, background }) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: background || colors.primary.background,
|
||||
borderRadius: 100,
|
||||
width: width || 60,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2,
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
style={{
|
||||
marginRight: 3
|
||||
}}
|
||||
size={size}
|
||||
color={colors.static.orange}
|
||||
name="crown"
|
||||
/>
|
||||
<Paragraph size={size - 1.5} color={colors.primary.accent}>
|
||||
PRO
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,8 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import {
|
||||
Platform,
|
||||
@@ -24,19 +26,17 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import usePricingPlans from "../../../hooks/use-pricing-plans";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { Button } from "../../ui/button";
|
||||
import dayjs from "dayjs";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import usePricingPlans from "../../../hooks/use-pricing-plans";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
export const BuyPlan = (props: {
|
||||
planId: string;
|
||||
@@ -67,9 +67,8 @@ export const BuyPlan = (props: {
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
gap: 16,
|
||||
paddingBottom: 80,
|
||||
paddingTop: Platform.OS === "android" ? insets.top : 0
|
||||
marginTop: DefaultAppStyles.GAP
|
||||
}}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
@@ -77,40 +76,9 @@ export const BuyPlan = (props: {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.background
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
name="chevron-left"
|
||||
onPress={() => {
|
||||
props.goBack();
|
||||
}}
|
||||
style={{
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
gap: 16
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
{props.canActivateTrial
|
||||
? `Try ${pricingPlans.currentPlan?.name} plan for free`
|
||||
: `${pricingPlans.currentPlan?.name} plan`}{" "}
|
||||
</Heading>
|
||||
|
||||
{props.canActivateTrial ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -138,7 +106,7 @@ export const BuyPlan = (props: {
|
||||
>
|
||||
<Icon
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.lg}
|
||||
size={AppFontSize.lg}
|
||||
name="check"
|
||||
/>
|
||||
<Paragraph>{item}</Paragraph>
|
||||
@@ -164,11 +132,10 @@ export const BuyPlan = (props: {
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 20
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.primary.paragraph} size={SIZE.sm}>
|
||||
<Heading color={colors.primary.paragraph} size={AppFontSize.sm}>
|
||||
Due today{" "}
|
||||
{pricingPlans.userCanRequestTrial ? (
|
||||
<Text
|
||||
@@ -220,6 +187,10 @@ export const BuyPlan = (props: {
|
||||
? "Subscribe and start free trial"
|
||||
: "Subscribe"
|
||||
}
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
onPress={() => {
|
||||
const offerToken = pricingPlans.getOfferTokenAndroid(
|
||||
pricingPlans.selectedProduct as RNIap.SubscriptionAndroid,
|
||||
@@ -232,13 +203,12 @@ export const BuyPlan = (props: {
|
||||
}}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Heading
|
||||
style={{
|
||||
textAlign: "center"
|
||||
}}
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.xs}
|
||||
size={AppFontSize.xs}
|
||||
>
|
||||
{is5YearPlanSelected
|
||||
? `This is a one time purchase, no subscription.`
|
||||
@@ -249,7 +219,7 @@ export const BuyPlan = (props: {
|
||||
textAlign: "center"
|
||||
}}
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.xs}
|
||||
size={AppFontSize.xs}
|
||||
>
|
||||
By joining you agree to our{" "}
|
||||
<Text
|
||||
@@ -276,7 +246,6 @@ export const BuyPlan = (props: {
|
||||
.
|
||||
</Heading>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
@@ -307,20 +276,20 @@ const ProductItem = (props: {
|
||||
<Icon
|
||||
name={isSelected ? "radiobox-marked" : "radiobox-blank"}
|
||||
color={isSelected ? colors.primary.accent : colors.secondary.icon}
|
||||
size={SIZE.lg}
|
||||
size={AppFontSize.lg}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
gap: 10
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 10
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.md}>
|
||||
<Heading size={AppFontSize.md}>
|
||||
{isAnnual
|
||||
? "Yearly"
|
||||
: product?.productId.includes("5year")
|
||||
@@ -338,7 +307,7 @@ const ProductItem = (props: {
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.static.white} size={SIZE.xs}>
|
||||
<Heading color={colors.static.white} size={AppFontSize.xs}>
|
||||
Best value -{" "}
|
||||
{props.pricingPlans.compareProductPrice(
|
||||
props.pricingPlans.currentPlan?.id as string,
|
||||
|
||||
@@ -48,7 +48,6 @@ import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { ProTag } from "../../premium/pro-tag";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
@@ -227,7 +226,6 @@ const ExportNotesSheet = ({
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{!item.pro ? <ProTag size={12} /> : null}
|
||||
<Heading style={{ marginLeft: 10 }} size={AppFontSize.md}>
|
||||
{item.title}
|
||||
</Heading>
|
||||
|
||||
@@ -24,19 +24,17 @@ import {
|
||||
ColorValue,
|
||||
DimensionValue,
|
||||
TextStyle,
|
||||
View,
|
||||
ViewStyle,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import NativeTooltip from "../../../utils/tooltip";
|
||||
import { ProTag } from "../../premium/pro-tag";
|
||||
import { Pressable, PressableProps, useButton } from "../pressable";
|
||||
import Heading from "../typography/heading";
|
||||
import Paragraph from "../typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
export interface ButtonProps extends PressableProps {
|
||||
height?: number;
|
||||
icon?: string;
|
||||
@@ -167,15 +165,6 @@ export const Button = ({
|
||||
{title}
|
||||
</Component>
|
||||
)}
|
||||
{proTag && !premium ? (
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
>
|
||||
<ProTag size={10} width={40} background={colors.primary.shade} />
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{icon && !loading && iconPosition === "right" ? (
|
||||
<Icon
|
||||
|
||||
@@ -25,9 +25,9 @@ import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets"
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { getContainerBorder } from "../../../utils/colors";
|
||||
import { PremiumToast } from "../../premium/premium-toast";
|
||||
import { Toast } from "../../toast";
|
||||
import { NotesnookModule } from "../../../utils/notesnook-module";
|
||||
import { Toast } from "../../toast";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} param0
|
||||
@@ -155,11 +155,6 @@ const SheetWrapper = ({
|
||||
ExtraOverlayComponent={
|
||||
<>
|
||||
{overlay}
|
||||
<PremiumToast
|
||||
context="sheet"
|
||||
close={() => fwdRef?.current?.hide()}
|
||||
offset={50}
|
||||
/>
|
||||
<Toast context="local" />
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -329,7 +329,6 @@ const doAppLoadActions = async () => {
|
||||
if (NewFeature.present()) return;
|
||||
if (await checkAppUpdateAvailable()) return;
|
||||
if (await checkForRateAppRequest()) return;
|
||||
if (await PremiumService.getRemainingTrialDaysStatus()) return;
|
||||
if (SettingsService.get().introCompleted) {
|
||||
useMessageStore.subscribe((state) => {
|
||||
const dialogs = state.dialogs;
|
||||
|
||||
@@ -250,7 +250,7 @@ let MoveNotes: any = null;
|
||||
let Settings: any = null;
|
||||
let ManageTags: any = null;
|
||||
let AddReminder: any = null;
|
||||
|
||||
let PayWall: any = null;
|
||||
export const RootNavigation = () => {
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
@@ -353,6 +353,14 @@ export const RootNavigation = () => {
|
||||
return AddReminder;
|
||||
}}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name="PayWall"
|
||||
getComponent={() => {
|
||||
PayWall =
|
||||
PayWall || require("../components/premium/paywall").default;
|
||||
return PayWall;
|
||||
}}
|
||||
/>
|
||||
</RootStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
View
|
||||
} from "react-native";
|
||||
import Editor from ".";
|
||||
import { PremiumToast } from "../../components/premium/premium-toast";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
@@ -112,7 +111,6 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
|
||||
enabled={!floating}
|
||||
keyboardVerticalOffset={0}
|
||||
>
|
||||
<PremiumToast key="toast" context="editor" offset={50 + insets.top} />
|
||||
<TextInput
|
||||
key="input"
|
||||
ref={textInput}
|
||||
|
||||
@@ -70,7 +70,8 @@ const routeNames = {
|
||||
MoveNotes: "MoveNotes",
|
||||
Archive: "Archive",
|
||||
ManageTags: "ManageTags",
|
||||
AddReminder: "AddReminder"
|
||||
AddReminder: "AddReminder",
|
||||
PayWall: "PayWall"
|
||||
};
|
||||
|
||||
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<
|
||||
|
||||
@@ -18,27 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { CHECK_IDS } from "@notesnook/core";
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import Config from "react-native-config";
|
||||
import * as RNIap from "react-native-iap";
|
||||
import { db } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import DialogHeader from "../components/dialog/dialog-header";
|
||||
import { CompactFeatures } from "../components/premium/compact-features";
|
||||
import { PricingPlans } from "../components/premium/pricing-plans";
|
||||
import Seperator from "../components/ui/seperator";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { itemSkus, SUBSCRIPTION_STATUS } from "../utils/constants";
|
||||
import {
|
||||
eOpenPremiumDialog,
|
||||
eOpenTrialEndingDialog,
|
||||
eShowGetPremium
|
||||
} from "../utils/events";
|
||||
import { eOpenPremiumDialog, eShowGetPremium } from "../utils/events";
|
||||
import { eSendEvent, presentSheet, ToastManager } from "./event-manager";
|
||||
|
||||
import SettingsService from "./settings";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import SettingsService from "./settings";
|
||||
let premiumStatus = 0;
|
||||
|
||||
/**
|
||||
@@ -100,10 +91,12 @@ function getMontlySub() {
|
||||
}
|
||||
|
||||
async function loadProductsAndSubs() {
|
||||
try {
|
||||
if (!subs || subs.length === 0) {
|
||||
subs = await RNIap.getSubscriptions({
|
||||
skus: itemSkus
|
||||
});
|
||||
console.log("SUBS", subs);
|
||||
}
|
||||
|
||||
if (!products || products.length === 0) {
|
||||
@@ -116,6 +109,13 @@ async function loadProductsAndSubs() {
|
||||
subs,
|
||||
products
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to load products and subscriptions", e);
|
||||
return {
|
||||
subs: [],
|
||||
products: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function get() {
|
||||
@@ -355,87 +355,7 @@ const subscriptions = {
|
||||
}
|
||||
};
|
||||
|
||||
async function getRemainingTrialDaysStatus() {
|
||||
let user = await db.user.getUser();
|
||||
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
|
||||
|
||||
let premium = user.subscription.type !== SUBSCRIPTION_STATUS.BASIC;
|
||||
let isTrial = user.subscription.type === SUBSCRIPTION_STATUS.TRIAL;
|
||||
let total = user.subscription.expiry - user.subscription.start;
|
||||
let current = Date.now() - user.subscription.start;
|
||||
current = (current / total) * 100;
|
||||
|
||||
let lastTrialDialogShownAt = MMKV.getString("lastTrialDialogShownAt");
|
||||
|
||||
if (current > 75 && isTrial && lastTrialDialogShownAt !== "ending") {
|
||||
eSendEvent(eOpenTrialEndingDialog, {
|
||||
title: strings.trialEndingSoon(),
|
||||
offer: null,
|
||||
extend: false
|
||||
});
|
||||
MMKV.setItem("lastTrialDialogShownAt", "ending");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!premium && lastTrialDialogShownAt !== "expired") {
|
||||
eSendEvent(eOpenTrialEndingDialog, {
|
||||
title: strings.trialExpired(),
|
||||
offer: 30,
|
||||
extend: false
|
||||
});
|
||||
MMKV.setItem("lastTrialDialogShownAt", "expired");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const features_list = [
|
||||
{
|
||||
content: "Unlock unlimited notebooks, tags, colors. Organize like a pro"
|
||||
},
|
||||
{
|
||||
content: "Attach files upto 500MB, upload 4K images with unlimited storage"
|
||||
},
|
||||
{
|
||||
content: "Instantly sync to unlimited devices"
|
||||
},
|
||||
{
|
||||
content: "A private vault to keep everything important always locked"
|
||||
},
|
||||
{
|
||||
content:
|
||||
"Rich note editing experience with markdown, tables, checklists and more"
|
||||
},
|
||||
{
|
||||
content: "Export your notes in PDF, markdown and html formats"
|
||||
}
|
||||
];
|
||||
|
||||
const sheet = (context, promo, trial) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref) => (
|
||||
<>
|
||||
<DialogHeader
|
||||
centered
|
||||
title="Upgrade to Notesnook"
|
||||
titlePart="Pro"
|
||||
paragraph="Manage your work on another level, enjoy seamless sync and keep all notes in one place."
|
||||
padding={12}
|
||||
/>
|
||||
<Seperator />
|
||||
<CompactFeatures
|
||||
scrollRef={ref}
|
||||
maxHeight={400}
|
||||
features={features_list}
|
||||
vertical
|
||||
/>
|
||||
<Seperator half />
|
||||
<PricingPlans trial={trial} compact heading={false} promo={promo} />
|
||||
</>
|
||||
)
|
||||
});
|
||||
};
|
||||
const sheet = (context, promo, trial) => {};
|
||||
|
||||
const PremiumService = {
|
||||
verify,
|
||||
@@ -447,7 +367,6 @@ const PremiumService = {
|
||||
getUser,
|
||||
subscriptions,
|
||||
getMontlySub,
|
||||
getRemainingTrialDaysStatus,
|
||||
sheet
|
||||
};
|
||||
|
||||
|
||||
@@ -101,7 +101,11 @@ export interface RouteParams extends ParamListBase {
|
||||
reference?: Note;
|
||||
};
|
||||
Intro: GenericRouteParam;
|
||||
};
|
||||
PayWall: {
|
||||
canGoBack?: boolean;
|
||||
context: "signup" | "logged-in" | "logged-out";
|
||||
};
|
||||
}
|
||||
|
||||
export type RouteName = keyof RouteParams;
|
||||
|
||||
|
||||
Binary file not shown.
@@ -22,7 +22,7 @@ import {
|
||||
import Config from "react-native-config";
|
||||
|
||||
i18n.load({
|
||||
en: __DEV__ && Config.isTesting !== "true" ? $pseudo : $en
|
||||
en: !__DEV__ && Config.isTesting !== "true" ? $pseudo : $en
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
i18n.activate("en");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
const isGithubRelease = false;
|
||||
const config = {
|
||||
commands: require('@callstack/repack/commands/rspack'),
|
||||
// commands: require('@callstack/repack/commands/rspack'),
|
||||
project: {
|
||||
android: {
|
||||
sourceDir: './android'
|
||||
|
||||
9
apps/mobile/package-lock.json
generated
9
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.10",
|
||||
"version": "3.2.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.10",
|
||||
"version": "3.2.5",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -44449,11 +44449,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-format-currency/-/react-native-format-currency-0.0.5.tgz",
|
||||
"integrity": "sha512-iarFkCig987Zw8GFPv0+ltEdAsksf0gfZGWL3+tC+60VHtnfbtFBFWP4523ltuXwplDxKu4Veem1ThEr+pZlvQ=="
|
||||
},
|
||||
"node_modules/react-native-format-currency": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-format-currency/-/react-native-format-currency-0.0.5.tgz",
|
||||
"integrity": "sha512-iarFkCig987Zw8GFPv0+ltEdAsksf0gfZGWL3+tC+60VHtnfbtFBFWP4523ltuXwplDxKu4Veem1ThEr+pZlvQ=="
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz",
|
||||
|
||||
11
apps/monograph/package-lock.json
generated
11
apps/monograph/package-lock.json
generated
@@ -70,6 +70,7 @@
|
||||
"dependencies": {
|
||||
"@leeoniya/ufuzzy": "^1.0.10",
|
||||
"@microsoft/signalr": "^8.0.0",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@readme/data-urls": "^3.0.0",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
@@ -83,7 +84,7 @@
|
||||
"entities": "5.0.0",
|
||||
"fuzzyjs": "^5.0.1",
|
||||
"html-to-text": "^9.0.5",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"htmlparser2": "^10.0.0",
|
||||
"katex": "0.16.11",
|
||||
"linkedom": "^0.14.17",
|
||||
"liqe": "^1.13.0",
|
||||
@@ -91,8 +92,7 @@
|
||||
"prismjs": "^1.29.0",
|
||||
"qclone": "^1.2.0",
|
||||
"rfdc": "^1.3.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"sqlite-better-trigram": "0.0.2"
|
||||
"spark-md5": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
@@ -118,6 +118,9 @@
|
||||
"nanoid": "5.0.7",
|
||||
"otplib": "^12.0.1",
|
||||
"refractor": "^4.8.1",
|
||||
"sqlite-better-trigram": "^0.0.3",
|
||||
"sqlite-regex": "^0.2.4-alpha.1",
|
||||
"sqlite3-fts5-html": "^0.0.4",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"ws": "^8.13.0"
|
||||
@@ -138,7 +141,7 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook-importer/core": "^2.1.1",
|
||||
"@notesnook-importer/core": "^2.2.2",
|
||||
"@notesnook/common": "file:../common",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
|
||||
11
apps/theme-builder/package-lock.json
generated
11
apps/theme-builder/package-lock.json
generated
@@ -987,7 +987,7 @@
|
||||
},
|
||||
"../web": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.30",
|
||||
"version": "3.2.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -1002,7 +1002,7 @@
|
||||
"@lingui/react": "5.1.2",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/react": "1.6.1",
|
||||
"@notesnook-importer/core": "^2.1.1",
|
||||
"@notesnook-importer/core": "^2.2.2",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -1015,6 +1015,7 @@
|
||||
"@notesnook/themes-server": "file:../../servers/themes",
|
||||
"@notesnook/ui": "file:../../packages/ui",
|
||||
"@notesnook/web-clipper": "file:../../extensions/web-clipper",
|
||||
"@paddle/paddle-js": "^1.4.2",
|
||||
"@react-pdf-viewer/core": "^3.12.0",
|
||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||
"@rehookify/datepicker": "^6.6.7",
|
||||
@@ -1026,7 +1027,7 @@
|
||||
"@theme-ui/core": "0.16.1",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/react-query": "10.45.2",
|
||||
"@zip.js/zip.js": "^2.7.54",
|
||||
"@zip.js/zip.js": "^2.7.62",
|
||||
"async-mutex": "0.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"clipboard-polyfill": "4.1.0",
|
||||
@@ -1034,7 +1035,7 @@
|
||||
"cronosjs": "^1.7.1",
|
||||
"dayjs": "1.11.13",
|
||||
"diffblazer": "^1.0.1",
|
||||
"electron-trpc": "0.6.1",
|
||||
"electron-trpc": "0.7.1",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"fflate": "^0.8.0",
|
||||
"file-saver": "^2.0.5",
|
||||
@@ -1058,7 +1059,7 @@
|
||||
"react-modal": "3.16.3",
|
||||
"react-qrcode-logo": "^2.9.0",
|
||||
"react-scroll-sync": "^0.11.2",
|
||||
"react-virtuoso": "^4.6.2",
|
||||
"react-virtuoso": "^4.12.3",
|
||||
"snarkdown": "^2.0.0",
|
||||
"timeago.js": "4.0.2",
|
||||
"w3c-keyname": "^2.2.6",
|
||||
|
||||
1369
apps/web/package-lock.json
generated
1369
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3056,6 +3056,10 @@ msgstr "How to fix it?"
|
||||
msgid "hr"
|
||||
msgstr "hr"
|
||||
|
||||
#: src/strings.ts:2489
|
||||
msgid "I already have an account"
|
||||
msgstr "I already have an account"
|
||||
|
||||
#: src/strings.ts:125
|
||||
msgid "I don't have access to authenticator app"
|
||||
msgstr "I don't have access to authenticator app"
|
||||
|
||||
@@ -3038,6 +3038,10 @@ msgstr ""
|
||||
msgid "hr"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2489
|
||||
msgid "I already have an account"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:125
|
||||
msgid "I don't have access to authenticator app"
|
||||
msgstr ""
|
||||
|
||||
@@ -2493,5 +2493,6 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
scrollToBottom: () => t`Scroll to bottom`,
|
||||
emailConfirmedDesc: () =>
|
||||
t`Your email has been confirmed. You can now securely sync your encrypted notes across all devices.`,
|
||||
charactersCount: (count: number) => t`${count} characters`
|
||||
charactersCount: (count: number) => t`${count} characters`,
|
||||
iAlreadyHaveAnAccount: () => t`I already have an account`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user