Bunch of contract updates

This commit is contained in:
Eric Bailey
2024-10-28 18:14:36 -05:00
parent 6d0c5078e9
commit e9c6a8461c
9 changed files with 188 additions and 64 deletions
+99 -40
View File
@@ -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 (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ViewHeader title={_(msg`Liked By`)} />
<ViewHeader title={_(msg`Subscriptions`)} />
<View style={[a.px_xl, a.py_xl, a.gap_lg]}>
<Text style={[a.text_2xl]}>Subs</Text>
<View style={[a.gap_sm]}>
{subscriptions?.monthly.map(s => (
<View key={s.id}>
<Button
label={s.id}
size="small"
variant="solid"
color={s.status === 'active' ? 'primary' : 'secondary'}
onPress={() => {
purchaseSubscription(s.product)
}}>
<ButtonText>
{s.id} ({s.price.formatted})
</ButtonText>
</Button>
</View>
))}
</View>
<View style={[a.gap_sm]}>
{subscriptions?.annual.map(s => (
<View key={s.id}>
<Button
label={s.id}
size="small"
variant="solid"
color={s.status === 'active' ? 'primary' : 'secondary'}
onPress={() => {
purchaseSubscription(s.product)
}}>
<ButtonText>
{s.id} ({s.price.formatted})
</ButtonText>
</Button>
</View>
))}
</View>
{isError ? (
<Text>Error</Text>
) : !subscriptions ? (
<Loader />
) : (
<MainSubscription subscriptions={subscriptions} />
)}
</View>
</CenteredView>
</Layout.Screen>
)
}
export function MainSubscription({
subscriptions,
}: {
subscriptions: SubscriptionsResponse
}) {
const t = useTheme()
const active = subscriptions.active.at(0)
const {mutateAsync: purchaseSubscription} = usePurchaseSubscription()
if (active) {
const contents = (
<View style={[a.gap_sm]}>
<View style={[a.flex_row, a.align_center, a.justify_between]}>
<Text style={[a.text_xl, a.font_heavy]}>Bluesky Plus</Text>
<Text
style={[
a.text_md,
a.font_bold,
a.p_xs,
a.rounded_xs,
{
color: t.palette.white,
backgroundColor: t.palette.primary_500,
},
]}>
Supporter
</Text>
</View>
<Text style={[a.text_md]}>Managed via: {active?.platform}</Text>
</View>
)
return (
<View
style={[a.p_lg, a.rounded_md, a.border, t.atoms.border_contrast_low]}>
{contents}
</View>
)
} else {
return (
<View
style={[a.p_lg, a.rounded_md, a.border, t.atoms.border_contrast_low]}>
<View style={[a.gap_sm, a.pb_xl]}>
{subscriptions.available.monthly.map(s => (
<View key={s.id}>
<Button
label={s.id}
size="small"
variant="solid"
color={s.status === 'active' ? 'primary' : 'secondary'}
onPress={() => {
purchaseSubscription(s.product)
}}>
<ButtonText>
{s.id} ({s.price.formatted})
</ButtonText>
</Button>
</View>
))}
</View>
<View style={[a.gap_sm]}>
{subscriptions.available.annual.map(s => (
<View key={s.id}>
<Button
label={s.id}
size="small"
variant="solid"
color={s.status === 'active' ? 'primary' : 'secondary'}
onPress={() => {
purchaseSubscription(s.product)
}}>
<ButtonText>
{s.id} ({s.price.formatted})
</ButtonText>
</Button>
</View>
))}
</View>
</View>
)
}
}
+24 -2
View File
@@ -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}[]
}
@@ -6,7 +6,15 @@ export type RawSubscriptionObjectBase<T> = 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'
+16 -7
View File
@@ -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<Subscriptions>({
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)
},
})
@@ -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),
}
},
})
}
+15 -3
View File
@@ -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[]
}
}
@@ -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})
},
})
}
+2 -2
View File
@@ -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: [],
}
+4 -7
View File
@@ -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}
/>
<SettingsMenuItem onPress={onPressSettings} />
{IS_INTERNAL && (
<SubscriptionsMenuItem
isActive={false}
onPress={onPressSubscriptions}
/>
)}
<SubscriptionsMenuItem
isActive={false}
onPress={onPressSubscriptions}
/>
</>
) : (
<>