diff --git a/assets/icons/clock_stroke2_corner0_rounded.svg b/assets/icons/clock_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..6699b35f63
--- /dev/null
+++ b/assets/icons/clock_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 59b2ad9563..113caa7068 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -2,9 +2,9 @@ import 'react-native-url-polyfill/auto'
import '#/lib/sentry' // must be near top
import '#/view/icons'
-import Purchases from 'react-native-purchases'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
+import Purchases from 'react-native-purchases'
import {RootSiblingParent} from 'react-native-root-siblings'
import {
initialWindowMetrics,
@@ -45,6 +45,7 @@ import {init as initPersistedState} from '#/state/persisted'
import {Provider as PrefsStateProvider} from '#/state/preferences'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts'
+import {Provider as PurchasesProvider} from '#/state/purchases'
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import {
Provider as SessionProvider,
@@ -133,37 +134,39 @@ function InnerApp() {
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
-
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -178,15 +181,17 @@ function App() {
const [isReady, setReady] = useState(false)
React.useEffect(() => {
- Promise.all([initPersistedState(), ensureGeolocationResolved(), Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG)]).then(() => {
- if (isIOS) {
- Purchases.configure({apiKey: RC_APPLE_PUBLIC_KEY})
- } else if (isAndroid) {
- Purchases.configure({apiKey: RC_GOOGLE_PUBLIC_KEY})
- }
+ Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(
+ () => {
+ setReady(true)
+ },
+ )
- setReady(true)
- })
+ if (isIOS) {
+ Purchases.configure({apiKey: RC_APPLE_PUBLIC_KEY})
+ } else if (isAndroid) {
+ Purchases.configure({apiKey: RC_GOOGLE_PUBLIC_KEY})
+ }
}, [])
if (!isReady) {
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 808b0fc278..b55c199613 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -30,6 +30,7 @@ import {init as initPersistedState} from '#/state/persisted'
import {Provider as PrefsStateProvider} from '#/state/preferences'
import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts'
+import {Provider as PurchasesProvider} from '#/state/purchases'
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import {
Provider as SessionProvider,
@@ -110,6 +111,8 @@ function InnerApp() {
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
+
+
@@ -139,6 +142,7 @@ function InnerApp() {
+
diff --git a/src/components/dialogs/BlueskyPlus/index.tsx b/src/components/dialogs/BlueskyPlus/index.tsx
index 7b8809c3e4..3686733676 100644
--- a/src/components/dialogs/BlueskyPlus/index.tsx
+++ b/src/components/dialogs/BlueskyPlus/index.tsx
@@ -1,17 +1,18 @@
import React from 'react'
import {View} from 'react-native'
+import {PURCHASES_ERROR_CODE} from 'react-native-purchases'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {
- OfferingId,
- SubscriptionGroupId,
-} from '#/state/purchases/subscriptions/types'
import {
usePurchaseOffering,
useSubscriptionGroup,
-} from '#/state/purchases/subscriptions/useSubscriptionGroup'
-import {parseOfferingId} from '#/state/purchases/subscriptions/util'
+} from '#/state/purchases/hooks/useSubscriptionGroup'
+import {
+ SubscriptionGroupId,
+ SubscriptionOfferingId,
+} from '#/state/purchases/types'
+import {parseOfferingId} from '#/state/purchases/types'
import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
@@ -22,7 +23,7 @@ import {GradientFill} from '#/components/GradientFill'
import {Full as BlueskyPlusLogo} from '#/components/icons/BlueskyPlus'
import {CheckThick_Stroke2_Corner0_Rounded as CheckThink} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-import {InlineLinkText} from '#/components/Link'
+import {InlineLinkText, Link} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
@@ -30,20 +31,20 @@ export function BlueskyPlus({control}: {control: Dialog.DialogControlProps}) {
return (
-
+
)
}
-function DialogInner() {
+function DialogInner({control}: {control: Dialog.DialogControlProps}) {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {currentAccount} = useSession()
const copy = useCoreOfferingCopy()
- const [offeringId, setOfferingId] = React.useState(
- OfferingId.CoreAnnual,
+ const [offeringId, setOfferingId] = React.useState(
+ SubscriptionOfferingId.CoreAnnual,
)
const {data: coreOffering} = useSubscriptionGroup(SubscriptionGroupId.Core)
const {mutateAsync: purchaseOffering, isPending} = usePurchaseOffering()
@@ -57,13 +58,29 @@ function DialogInner() {
throw new Error('No offering')
}
- await purchaseOffering({
- did: currentAccount.did,
- email: currentAccount.email!,
- offering,
+ control.close(async () => {
+ try {
+ await purchaseOffering({
+ did: currentAccount.did,
+ email: currentAccount.email!,
+ offering,
+ })
+ } catch (e: any) {
+ /**
+ * @see https://www.revenuecat.com/docs/test-and-launch/errors
+ */
+ if (e.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
+ control.open()
+ } else {
+ Toast.show(
+ _(msg`Hmmmm, something went wrong. Please try again.`),
+ 'xmark',
+ )
+ }
+ }
})
} catch (e: any) {
- Toast.show(_(msg`Could not take you to checkout`), 'xmark')
+ Toast.show(_(msg`Hmmmm. We couldn't locate that subscription.`), 'xmark')
}
}
@@ -76,7 +93,7 @@ function DialogInner() {
]}>
-
+
Let's build the social web.
@@ -99,7 +116,10 @@ function DialogInner() {
values={[offeringId]}
onChange={values => setOfferingId(parseOfferingId(values[0]))}
style={[a.w_full, a.gap_sm, a.pt_lg]}>
- {[OfferingId.CoreMonthly, OfferingId.CoreAnnual].map(id => {
+ {[
+ SubscriptionOfferingId.CoreMonthly,
+ SubscriptionOfferingId.CoreAnnual,
+ ].map(id => {
return (
{({selected, hovered}) => (
@@ -209,7 +229,18 @@ function DialogInner() {
})}
-
+
+
+
+ Learn more
+
+