From e9c6a8461ca6afc95a614f02beca18de8e1cf029 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 28 Oct 2024 18:14:36 -0500 Subject: [PATCH] Bunch of contract updates --- src/screens/Subscriptions/index.tsx | 139 +++++++++++++----- .../purchases/subscriptions/api/index.ts | 26 +++- .../purchases/subscriptions/api/types.ts | 10 +- src/state/purchases/subscriptions/index.ts | 23 ++- .../purchases/subscriptions/index.web.ts | 7 +- src/state/purchases/subscriptions/types.ts | 18 ++- .../subscriptions/useEntitlements.tsx | 14 ++ src/state/purchases/subscriptions/util.ts | 4 +- src/view/shell/Drawer.tsx | 11 +- 9 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 src/state/purchases/subscriptions/useEntitlements.tsx diff --git a/src/screens/Subscriptions/index.tsx b/src/screens/Subscriptions/index.tsx index e71e6726df..5230ff052e 100644 --- a/src/screens/Subscriptions/index.tsx +++ b/src/screens/Subscriptions/index.tsx @@ -9,11 +9,14 @@ import { useMainSubscriptions, usePurchaseSubscription, } from '#/state/purchases/subscriptions' +// import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements' +import {Subscriptions as SubscriptionsResponse} from '#/state/purchases/subscriptions/types' import {ViewHeader} from '#/view/com/util/ViewHeader' import {CenteredView} from '#/view/com/util/Views' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Layout from '#/components/Layout' +import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' export type ScreenProps = NativeStackScreenProps< @@ -24,54 +27,110 @@ export type ScreenProps = NativeStackScreenProps< export function Subscriptions(_props: ScreenProps) { const {_} = useLingui() - const {data: subscriptions} = useMainSubscriptions() - const {mutateAsync: purchaseSubscription} = usePurchaseSubscription() + // const {data: entitlements} = useEntitlements() + const {data: subscriptions, isError} = useMainSubscriptions() return ( - + Subs - - {subscriptions?.monthly.map(s => ( - - - - ))} - - - {subscriptions?.annual.map(s => ( - - - - ))} - + {isError ? ( + Error + ) : !subscriptions ? ( + + ) : ( + + )} ) } + +export function MainSubscription({ + subscriptions, +}: { + subscriptions: SubscriptionsResponse +}) { + const t = useTheme() + + const active = subscriptions.active.at(0) + const {mutateAsync: purchaseSubscription} = usePurchaseSubscription() + + if (active) { + const contents = ( + + + Bluesky Plus + + Supporter + + + + Managed via: {active?.platform} + + ) + + return ( + + {contents} + + ) + } else { + return ( + + + {subscriptions.available.monthly.map(s => ( + + + + ))} + + + {subscriptions.available.annual.map(s => ( + + + + ))} + + + ) + } +} diff --git a/src/state/purchases/subscriptions/api/index.ts b/src/state/purchases/subscriptions/api/index.ts index c65329b394..9be0595454 100644 --- a/src/state/purchases/subscriptions/api/index.ts +++ b/src/state/purchases/subscriptions/api/index.ts @@ -1,4 +1,5 @@ import {RawSubscriptionObject} from '#/state/purchases/subscriptions/api/types' +import {EntitlementId} from '#/state/purchases/subscriptions/types' export async function getMainSubscriptions({ did, @@ -13,10 +14,31 @@ export async function getMainSubscriptions({ if (!res.ok) { console.error('error fetching subscriptions', res.status, await res.text()) + return { + active: [], + available: [], + } + } + + const json = await res.json() + + return json as { + active: RawSubscriptionObject[] + available: RawSubscriptionObject[] + } +} + +export async function getEntitlements({did}: {did: string}) { + const res = await fetch( + `https://bsky-purchases.ngrok.io/entitlements?user=${did}`, + ) + + if (!res.ok) { + console.error('error fetching entitlements', res.status, await res.text()) return [] } - const {subscriptions} = await res.json() + const {entitlements} = await res.json() - return subscriptions as RawSubscriptionObject[] + return entitlements as {id: EntitlementId}[] } diff --git a/src/state/purchases/subscriptions/api/types.ts b/src/state/purchases/subscriptions/api/types.ts index 306555d7a7..e4e5f19012 100644 --- a/src/state/purchases/subscriptions/api/types.ts +++ b/src/state/purchases/subscriptions/api/types.ts @@ -6,7 +6,15 @@ export type RawSubscriptionObjectBase = T & { lookupKey: string checkoutId: string interval: 'monthly' | 'annual' - autoRenew: boolean + active: boolean + autoRenewStatus: + | 'will_renew' + | 'will_not_renew' + | 'will_change_product' + | 'will_pause' + | 'requires_price_increase_consent' + | 'has_already_renewed' + | null status: | 'trialing' | 'active' diff --git a/src/state/purchases/subscriptions/index.ts b/src/state/purchases/subscriptions/index.ts index 598ca509ae..ed3d308c1f 100644 --- a/src/state/purchases/subscriptions/index.ts +++ b/src/state/purchases/subscriptions/index.ts @@ -1,5 +1,5 @@ import Purchases from 'react-native-purchases' -import {useMutation, useQuery} from '@tanstack/react-query' +import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {isAndroid} from '#/platform/detection' import {getMainSubscriptions} from '#/state/purchases/subscriptions/api' @@ -17,15 +17,12 @@ export function useMainSubscriptions() { return useQuery({ queryKey: ['availableSubscriptions', did], async queryFn() { - Purchases.logIn(did) - Purchases.setEmail(currentAccount!.email!) - - const platform = isAndroid ? 'android' : ('ios' as const) + const platform = isAndroid ? 'android' : 'ios' const rawSubscriptions = await getMainSubscriptions({ did: currentAccount!.did, platform, }) - const platformSubscriptions = rawSubscriptions + const platformSubscriptions = rawSubscriptions.available .filter(s => s.platform !== 'web') .filter(s => s.platform === platform) const lookupKeys = Array.from( @@ -51,17 +48,29 @@ export function useMainSubscriptions() { }) .filter(Boolean) as Subscription[] - return organizeMainSubscriptionsByTier(subscriptions) + return { + active: rawSubscriptions.active, + available: organizeMainSubscriptionsByTier(subscriptions), + } }, }) } export function usePurchaseSubscription() { + const {currentAccount} = useSession() + const queryClient = useQueryClient() + return useMutation({ async mutationFn(product: Subscription['product']) { if (product.platform === 'web') { throw new Error('Cannot purchase web subscription on native') } + // TODO don't do this here + Purchases.addCustomerInfoUpdateListener(_info => { + queryClient.invalidateQueries({ + queryKey: ['availableSubscriptions', currentAccount!.did], + }) + }) return Purchases.purchaseStoreProduct(product.data) }, }) diff --git a/src/state/purchases/subscriptions/index.web.ts b/src/state/purchases/subscriptions/index.web.ts index 90bcf1712f..233ebedb45 100644 --- a/src/state/purchases/subscriptions/index.web.ts +++ b/src/state/purchases/subscriptions/index.web.ts @@ -22,7 +22,7 @@ export function useMainSubscriptions() { did: currentAccount!.did, platform: 'web', }) - const platformSubscriptions = rawSubscriptions.filter( + const platformSubscriptions = rawSubscriptions.available.filter( s => s.platform === 'web', ) const subscriptions = platformSubscriptions.map(sub => { @@ -40,7 +40,10 @@ export function useMainSubscriptions() { return subscription }) - return organizeMainSubscriptionsByTier(subscriptions) + return { + active: rawSubscriptions.active, + available: organizeMainSubscriptionsByTier(subscriptions), + } }, }) } diff --git a/src/state/purchases/subscriptions/types.ts b/src/state/purchases/subscriptions/types.ts index 6c8e33ce7a..31859490dd 100644 --- a/src/state/purchases/subscriptions/types.ts +++ b/src/state/purchases/subscriptions/types.ts @@ -1,6 +1,9 @@ import {PurchasesStoreProduct} from 'react-native-purchases' -import {RawSubscriptionObjectBase} from '#/state/purchases/subscriptions/api/types' +import { + RawSubscriptionObject, + RawSubscriptionObjectBase, +} from '#/state/purchases/subscriptions/api/types' export enum SubscriptionId { Main0MonthlyAuto = 'main:0:monthly:auto', @@ -11,6 +14,12 @@ export enum SubscriptionId { Main2AnnualAuto = 'main:2:annual:auto', } +export enum EntitlementId { + Main0 = 'main:0', + Main1 = 'main:1', + Main2 = 'main:2', +} + export type Subscription = | RawSubscriptionObjectBase<{ platform: 'android' @@ -50,6 +59,9 @@ export type Subscription = }> export type Subscriptions = { - monthly: Subscription[] - annual: Subscription[] + active: RawSubscriptionObject[] + available: { + monthly: Subscription[] + annual: Subscription[] + } } diff --git a/src/state/purchases/subscriptions/useEntitlements.tsx b/src/state/purchases/subscriptions/useEntitlements.tsx new file mode 100644 index 0000000000..6d9dc821a9 --- /dev/null +++ b/src/state/purchases/subscriptions/useEntitlements.tsx @@ -0,0 +1,14 @@ +import {useQuery} from '@tanstack/react-query' + +import {getEntitlements} from '#/state/purchases/subscriptions/api' +import {useSession} from '#/state/session' + +export function useEntitlements() { + const {currentAccount} = useSession() + return useQuery({ + queryKey: ['entitlements', currentAccount!.did], + async queryFn() { + return getEntitlements({did: currentAccount!.did}) + }, + }) +} diff --git a/src/state/purchases/subscriptions/util.ts b/src/state/purchases/subscriptions/util.ts index e86c81aad9..b01e5e7890 100644 --- a/src/state/purchases/subscriptions/util.ts +++ b/src/state/purchases/subscriptions/util.ts @@ -2,8 +2,8 @@ import {Subscription, Subscriptions} from './types' export function organizeMainSubscriptionsByTier( subscriptions: Subscription[], -): Subscriptions { - const result: Subscriptions = { +): Subscriptions['subscriptions'] { + const result: Subscriptions['subscriptions'] = { monthly: [], annual: [], } diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 30b5139e64..5197bb835d 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -5,7 +5,6 @@ import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {StackActions, useNavigation} from '@react-navigation/native' -import {IS_INTERNAL} from '#/lib/app-info' import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' @@ -276,12 +275,10 @@ let DrawerContent = ({}: {}): React.ReactNode => { onPress={onPressProfile} /> - {IS_INTERNAL && ( - - )} + ) : ( <>