diff --git a/apps/mobile/app/common/database/index.ts b/apps/mobile/app/common/database/index.ts index b5a59ec11..cae6785ab 100644 --- a/apps/mobile/app/common/database/index.ts +++ b/apps/mobile/app/common/database/index.ts @@ -39,7 +39,7 @@ export async function setupDatabase(password?: string) { const key = await getDatabaseKey(password); if (!key) throw new Error(strings.databaseSetupFailed()); - // const base = `http://192.168.100.88`; + // const base = `http://192.168.100.92`; // database.host({ // API_HOST: `${base}:5264`, diff --git a/apps/mobile/app/package.json b/apps/mobile/app/package.json index ade73b446..72ed3879a 100644 --- a/apps/mobile/app/package.json +++ b/apps/mobile/app/package.json @@ -44,7 +44,8 @@ "@trpc/react-query": "^10.45.2", "@trpc/server": "^10.45.2", "@tanstack/react-query": "^4.36.1", - "async-mutex": "0.5.0" + "async-mutex": "0.5.0", + "react-async-hook": "^4.0.0" }, "sideEffects": false } diff --git a/apps/mobile/app/screens/settings/components.tsx b/apps/mobile/app/screens/settings/components.tsx index 1b6996670..20ac19215 100644 --- a/apps/mobile/app/screens/settings/components.tsx +++ b/apps/mobile/app/screens/settings/components.tsx @@ -43,6 +43,7 @@ import { ServersConfiguration } from "./server-config"; import SoundPicker from "./sound-picker"; import ThemeSelector from "./theme-selector"; import { TitleFormat } from "./title-format"; +import { NotesnookCircle } from "./notesnook-circle"; export const components: { [name: string]: ReactElement } = { homeselector: , @@ -69,5 +70,6 @@ export const components: { [name: string]: ReactElement } = { ), "sidebar-tab-selector": , "change-password": , - "change-email": + "change-email": , + "notesnook-circle": }; diff --git a/apps/mobile/app/screens/settings/notesnook-circle.tsx b/apps/mobile/app/screens/settings/notesnook-circle.tsx new file mode 100644 index 000000000..af0170e91 --- /dev/null +++ b/apps/mobile/app/screens/settings/notesnook-circle.tsx @@ -0,0 +1,190 @@ +import { CirclePartner, SubscriptionStatus } from "@notesnook/core"; +import { strings } from "@notesnook/intl"; +import { useThemeColors } from "@notesnook/theme"; +import Clipboard from "@react-native-clipboard/clipboard"; +import { useEffect, useState } from "react"; +import { + ActivityIndicator, + Image, + ScrollView, + TouchableOpacity, + View +} from "react-native"; +import { db } from "../../common/database"; +import AppIcon from "../../components/ui/AppIcon"; +import { Button } from "../../components/ui/button"; +import Heading from "../../components/ui/typography/heading"; +import Paragraph from "../../components/ui/typography/paragraph"; +import { ToastManager } from "../../services/event-manager"; +import Navigation from "../../services/navigation"; +import PremiumService from "../../services/premium"; +import { useUserStore } from "../../stores/use-user-store"; +import { AppFontSize, defaultBorderRadius } from "../../utils/size"; +import { DefaultAppStyles } from "../../utils/styles"; +import { useAsync } from "react-async-hook"; +import { Notice } from "../../components/ui/notice"; + +export const NotesnookCircle = () => { + const user = useUserStore((state) => state.user); + const isOnTrial = + PremiumService.get() && + user?.subscription?.status === SubscriptionStatus.TRIAL; + const isFree = !PremiumService.get(); + const partners = useAsync(db.circle.partners, []); + + return ( + + {!isFree && !isOnTrial ? null : ( + + + {isFree + ? strings.freeUserCircleNotice() + : strings.trialUserCircleNotice()} + + + {!isOnTrial ? null : ( + + + {partner.codeRedeemUrl ? ( + + + Click here + {" "} + to directly claim the promotion. + + ) : null} + + ) : ( + + )} + + ))} + + )} + + ); +} diff --git a/apps/web/src/dialogs/settings/index.tsx b/apps/web/src/dialogs/settings/index.tsx index 5b1638f47..420bb10e5 100644 --- a/apps/web/src/dialogs/settings/index.tsx +++ b/apps/web/src/dialogs/settings/index.tsx @@ -39,7 +39,8 @@ import { Servers, ShieldLock, Sync, - Inbox + Inbox, + CircleEmpty } from "../../components/icons"; import NavigationItem from "../../components/navigation-menu/navigation-item"; import { FlexScrollContainer } from "../../components/scroll-container"; @@ -80,6 +81,7 @@ import { strings } from "@notesnook/intl"; import { mdToHtml } from "../../utils/md"; import { InboxSettings } from "./inbox-settings"; import { withFeatureCheck } from "../../common"; +import { NotesnookCircleSettings } from "./notesnook-circle-settings"; type SettingsDialogProps = BaseDialogProps & { activeSection?: SectionKeys; @@ -109,6 +111,12 @@ const sectionGroups: SectionGroup[] = [ icon: Sync, isHidden: () => !useUserStore.getState().isLoggedIn }, + { + key: "circle", + title: "Notesnook Circle", + icon: CircleEmpty, + isHidden: () => !useUserStore.getState().isLoggedIn + }, { key: "inbox", title: "Inbox", @@ -185,7 +193,8 @@ const SettingsGroups = [ ...AboutSettings, ...SubscriptionSettings, ...ServersSettings, - ...InboxSettings + ...InboxSettings, + ...NotesnookCircleSettings ]; // Thoughts: diff --git a/apps/web/src/dialogs/settings/notesnook-circle-settings.ts b/apps/web/src/dialogs/settings/notesnook-circle-settings.ts new file mode 100644 index 000000000..d507eb642 --- /dev/null +++ b/apps/web/src/dialogs/settings/notesnook-circle-settings.ts @@ -0,0 +1,43 @@ +/* +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 . +*/ + +import { strings } from "@notesnook/intl"; +import { CirclePartners } from "./components/circle-partners"; +import { SettingsGroup } from "./types"; + +export const NotesnookCircleSettings: SettingsGroup[] = [ + { + header: strings.notesnookCircle(), + key: "notesnook-circle", + section: "circle", + settings: [ + { + key: "partners", + title: "", + description: strings.notesnookCircleDesc(), + components: [ + { + type: "custom", + component: CirclePartners + } + ] + } + ] + } +]; diff --git a/apps/web/src/dialogs/settings/types.ts b/apps/web/src/dialogs/settings/types.ts index 26a1a572b..074879bda 100644 --- a/apps/web/src/dialogs/settings/types.ts +++ b/apps/web/src/dialogs/settings/types.ts @@ -41,7 +41,8 @@ export type SectionKeys = | "legal" | "developer" | "about" - | "inbox"; + | "inbox" + | "circle"; export type SectionGroupKeys = | "account" diff --git a/packages/common/src/utils/is-feature-available.ts b/packages/common/src/utils/is-feature-available.ts index 010cf3718..fcf70c953 100644 --- a/packages/common/src/utils/is-feature-available.ts +++ b/packages/common/src/utils/is-feature-available.ts @@ -447,6 +447,17 @@ const features = { believer: createLimit(true), legacyPro: createLimit(true) } + }), + notesnookCircle: createFeature({ + id: "notesnookCircle", + title: "Notesnook Circle", + availability: { + free: createLimit(false), + essential: createLimit(true), + pro: createLimit(true), + believer: createLimit(true), + legacyPro: createLimit(true) + } }) }; diff --git a/packages/core/src/api/circle.ts b/packages/core/src/api/circle.ts new file mode 100644 index 000000000..e6d00dd19 --- /dev/null +++ b/packages/core/src/api/circle.ts @@ -0,0 +1,48 @@ +/* +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 . +*/ + +import hosts from "../utils/constants.js"; +import http from "../utils/http.js"; +import Database from "./index.js"; + +export type CirclePartner = { + id: string; + name: string; + url: string; + logoBase64: string; + shortDescription: string; + longDescription: string; + offerDescription: string; + codeRedeemUrl?: string; +}; +export class Circle { + constructor(private readonly db: Database) {} + + partners(): Promise { + return http.get(`${hosts.SUBSCRIPTIONS_HOST}/circle/partners`); + } + + async redeem(partnerId: string): Promise<{ code?: string } | undefined> { + const token = await this.db.tokenManager.getAccessToken(); + return http.get( + `${hosts.SUBSCRIPTIONS_HOST}/circle/redeem?partnerId=${partnerId}`, + token + ); + } +} diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 1261bd66f..8ef577836 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -82,6 +82,7 @@ import { NNMigrationProvider } from "../database/migrations.js"; import { ConfigStorage } from "../database/config.js"; import { LazyPromise } from "../utils/lazy-promise.js"; import { InboxApiKeys } from "./inbox-api-keys.js"; +import { Circle } from "./circle.js"; type EventSourceConstructor = new ( uri: string, @@ -192,6 +193,7 @@ class Database { tokenManager = new TokenManager(this.kv); mfa = new MFAManager(this.tokenManager); subscriptions = new Subscriptions(this); + circle = new Circle(this); offers = Offers; debug = new Debug(); pricing = Pricing; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f624f0ff1..5ff1e1dba 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -39,6 +39,7 @@ export * from "./api/debug.js"; export * from "./api/monographs.js"; export * from "./api/subscriptions.js"; export * from "./api/pricing.js"; +export * from "./api/circle.js"; export { VAULT_ERRORS } from "./api/vault.js"; export type { SyncOptions } from "./api/sync/index.js"; export { sanitizeTag } from "./collections/tags.js"; diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po index f0f0e9b0f..1fea29cb3 100644 --- a/packages/intl/locale/en.po +++ b/packages/intl/locale/en.po @@ -4217,6 +4217,14 @@ msgstr "notes imported" msgid "Notesnook" msgstr "Notesnook" +#: src/strings.ts:2596 +msgid "Notesnook Circle" +msgstr "Notesnook Circle" + +#: src/strings.ts:2598 +msgid "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom." +msgstr "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom." + #: src/strings.ts:2066 msgid "Notesnook encrypts everything offline before syncing to your other devices. This means that no one can read your notes except you. Not even us." msgstr "Notesnook encrypts everything offline before syncing to your other devices. This means that no one can read your notes except you. Not even us." @@ -5006,6 +5014,10 @@ msgstr "Recovery successful!" msgid "Redeem" msgstr "Redeem" +#: src/strings.ts:2595 +msgid "Redeem code" +msgstr "Redeem code" + #: src/strings.ts:2431 msgid "Redeem gift code" msgstr "Redeem gift code" @@ -6367,6 +6379,10 @@ msgstr "The {title} at {url} is not compatible with this client." msgid "The information above will be publically available at" msgstr "The information above will be publically available at" +#: src/strings.ts:2602 +msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits." +msgstr "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits." + #: src/strings.ts:2081 msgid "The password/pin for unlocking the app." msgstr "The password/pin for unlocking the app." @@ -6764,6 +6780,10 @@ msgstr "Upgrade to Notesnook Pro to create more tags." msgid "Upgrade to Pro" msgstr "Upgrade to Pro" +#: src/strings.ts:2594 +msgid "Upgrade to redeem" +msgstr "Upgrade to redeem" + #: src/strings.ts:509 msgid "Upload" msgstr "Upload" diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po index 2263d5d40..36475b457 100644 --- a/packages/intl/locale/pseudo-LOCALE.po +++ b/packages/intl/locale/pseudo-LOCALE.po @@ -4197,6 +4197,14 @@ msgstr "" msgid "Notesnook" msgstr "" +#: src/strings.ts:2596 +msgid "Notesnook Circle" +msgstr "" + +#: src/strings.ts:2598 +msgid "Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom." +msgstr "" + #: src/strings.ts:2066 msgid "Notesnook encrypts everything offline before syncing to your other devices. This means that no one can read your notes except you. Not even us." msgstr "" @@ -4980,6 +4988,10 @@ msgstr "" msgid "Redeem" msgstr "" +#: src/strings.ts:2595 +msgid "Redeem code" +msgstr "" + #: src/strings.ts:2431 msgid "Redeem gift code" msgstr "" @@ -6326,6 +6338,10 @@ msgstr "" msgid "The information above will be publically available at" msgstr "" +#: src/strings.ts:2602 +msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits." +msgstr "" + #: src/strings.ts:2081 msgid "The password/pin for unlocking the app." msgstr "" @@ -6723,6 +6739,10 @@ msgstr "" msgid "Upgrade to Pro" msgstr "" +#: src/strings.ts:2594 +msgid "Upgrade to redeem" +msgstr "" + #: src/strings.ts:509 msgid "Upload" msgstr "" diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts index 01a4c1aa9..e33e72ce1 100644 --- a/packages/intl/src/strings.ts +++ b/packages/intl/src/strings.ts @@ -2590,5 +2590,14 @@ Use this if changes from other devices are not appearing on this device. This wi t`You can change your subscription plan from the web app`, announcement: () => t`ANNOUNCEMENT`, cannotChangePlan: () => - t`Your current subscription does not allow changing plans` + t`Your current subscription does not allow changing plans`, + upgradeToRedeem: () => t`Upgrade to redeem`, + redeemCode: () => t`Redeem code`, + notesnookCircle: () => t`Notesnook Circle`, + notesnookCircleDesc: () => + t`Notesnook Circle brings together trusted partners who share our commitment to privacy, transparency, and user freedom.`, + trialUserCircleNotice: () => + `Notesnook Circle is reserved for members with an active subscription. You'll get full access after your trial period is over and your subscription is confirmed.`, + freeUserCircleNotice: () => + t`The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits.` }; diff --git a/scripts/build.mjs b/scripts/build.mjs index 608ee2260..aaf29cb47 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -67,7 +67,7 @@ await Promise.all([ IS_WATCH ? "--watch" : "" ), cmd( - TSCGO, + TSC, "--emitDeclarationOnly", "--outDir", "dist/types",