This commit is contained in:
Eric Bailey
2024-11-20 18:18:22 -06:00
parent 262651f966
commit 8857758789
5 changed files with 107 additions and 54 deletions
+8 -10
View File
@@ -178,17 +178,15 @@ function App() {
const [isReady, setReady] = useState(false) const [isReady, setReady] = useState(false)
React.useEffect(() => { React.useEffect(() => {
Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() => Promise.all([initPersistedState(), ensureGeolocationResolved(), Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG)]).then(() => {
setReady(true), if (isIOS) {
) Purchases.configure({apiKey: RC_APPLE_PUBLIC_KEY})
} else if (isAndroid) {
Purchases.configure({apiKey: RC_GOOGLE_PUBLIC_KEY})
}
Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG) setReady(true)
})
if (isIOS) {
Purchases.configure({apiKey: RC_APPLE_PUBLIC_KEY})
} else if (isAndroid) {
Purchases.configure({apiKey: RC_GOOGLE_PUBLIC_KEY})
}
}, []) }, [])
if (!isReady) { if (!isReady) {
+58 -36
View File
@@ -6,6 +6,7 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {CommonNavigatorParams} from '#/lib/routes/types' import {CommonNavigatorParams} from '#/lib/routes/types'
import {useSubscriptionsState} from '#/state/purchases/subscriptions/useSubscriptionsState' import {useSubscriptionsState} from '#/state/purchases/subscriptions/useSubscriptionsState'
import {useSyncPurchases} from '#/state/purchases/subscriptions/useSyncPurchases'
import {useManageSubscription} from '#/state/purchases/subscriptions/useManageSubscription' import {useManageSubscription} from '#/state/purchases/subscriptions/useManageSubscription'
import {Subscription} from '#/state/purchases/subscriptions/types' import {Subscription} from '#/state/purchases/subscriptions/types'
import {CenteredView} from '#/view/com/util/Views' import {CenteredView} from '#/view/com/util/Views'
@@ -17,7 +18,6 @@ import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useCurrencyFormatter} from '#/lib/currency'
import {InlineLinkText, createStaticClick} from '#/components/Link' import {InlineLinkText, createStaticClick} from '#/components/Link'
export type ScreenProps = NativeStackScreenProps< export type ScreenProps = NativeStackScreenProps<
@@ -32,36 +32,64 @@ export function Subscriptions(_props: ScreenProps) {
const isSubscribed = state?.entitlements?.some(e => e.id === 'core') const isSubscribed = state?.entitlements?.some(e => e.id === 'core')
const control = useDialogControl() const control = useDialogControl()
const {mutateAsync: syncPurchases} = useSyncPurchases()
const [data, setData] = React.useState<any>(null)
const sync = async () => {
try {
const data = await syncPurchases()
setData(data)
} catch (e: any) {
setData({ message: e.message })
}
}
return ( return (
<Layout.Screen> <Layout.Screen>
<Layout.Header title={_(msg`Subscriptions`)} /> <Layout.Header title={_(msg`Subscriptions`)} />
<CenteredView sideBorders={true} style={[a.util_screen_outer]}> <Layout.Content>
<View style={[a.px_xl, a.py_xl, a.gap_lg]}> <CenteredView sideBorders={true} style={[a.util_screen_outer]}>
{isStateLoading ? ( <View style={[a.px_xl, a.py_xl, a.gap_lg]}>
<Loader /> {isStateLoading ? (
) : ( <Loader />
<> ) : (
{isSubscribed ? ( <>
<CoreSubscriptions subscriptions={state!.subscriptions} /> {isSubscribed ? (
) : ( <CoreSubscriptions subscriptions={state!.subscriptions} />
<> ) : (
<Button <>
label={_('Subscribe')} <Button
onPress={() => control.open()} label={_('Subscribe')}
size="large" onPress={() => control.open()}
variant="solid" size="large"
color="primary"> variant="solid"
<ButtonText>Subscribe</ButtonText> color="primary">
<ButtonIcon icon={Plus} /> <ButtonText>Subscribe</ButtonText>
</Button> <ButtonIcon icon={Plus} />
<BlueskyPlus control={control} /> </Button>
</> <BlueskyPlus control={control} />
)} </>
</> )}
)} </>
</View> )}
</CenteredView>
<Button
label={_('Subscribe')}
onPress={() => sync()}
size="large"
variant="solid"
color="secondary">
<ButtonText>Restore Purchase</ButtonText>
<ButtonIcon icon={Plus} />
</Button>
{data && (
<Text>{JSON.stringify(data, null, 2)}</Text>
)}
</View>
</CenteredView>
</Layout.Content>
</Layout.Screen> </Layout.Screen>
) )
} }
@@ -69,27 +97,21 @@ export function Subscriptions(_props: ScreenProps) {
function CoreSubscriptions(props: { subscriptions: Subscription[] }) { function CoreSubscriptions(props: { subscriptions: Subscription[] }) {
const t = useTheme() const t = useTheme()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const { format } = useCurrencyFormatter()
const {mutateAsync: manageSubscription} = useManageSubscription() const {mutateAsync: manageSubscription} = useManageSubscription()
return props.subscriptions.map(sub => ( return props.subscriptions.map(sub => (
<View style={[a.p_lg, a.rounded_md, a.border, a.gap_xs, t.atoms.border_contrast_low]}> <View style={[a.p_lg, a.rounded_md, a.border, a.gap_xs, t.atoms.border_contrast_low]}>
<Text style={[a.text_lg, a.font_heavy]}>Bluesky+</Text> <Text style={[a.text_lg, a.font_heavy]}>Bluesky+</Text>
<Text style={[a.text_sm]}> {sub.renews ? (
<Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}><Trans>Status:</Trans></Text>{' '}
<Text style={[a.text_sm]}>{sub.status}</Text>
</Text>
{sub.renewalStatus === 'will_renew' ? (
<Text style={[a.text_sm]}> <Text style={[a.text_sm]}>
<Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}><Trans>Renews:</Trans></Text>{' '} <Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}><Trans>Renews:</Trans></Text>{' '}
<Text style={[a.text_sm]}>{i18n.date(new Date(sub.endsAt), { dateStyle: "medium", timeStyle: "medium" })}</Text> <Text style={[a.text_sm]}>{i18n.date(new Date(sub.periodEndsAt), { dateStyle: "medium", timeStyle: "medium" })}</Text>
</Text> </Text>
) : ( ) : (
<Text style={[a.text_sm]}> <Text style={[a.text_sm]}>
<Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}><Trans>Ends:</Trans></Text>{' '} <Text style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}><Trans>Ends:</Trans></Text>{' '}
<Text style={[a.text_sm]}>{i18n.date(new Date(sub.endsAt), { dateStyle: "medium" })}</Text> <Text style={[a.text_sm]}>{i18n.date(new Date(sub.periodEndsAt), { dateStyle: "medium" })}</Text>
</Text> </Text>
)} )}
+4 -4
View File
@@ -38,8 +38,8 @@ export type Offering =
export type Subscription = { export type Subscription = {
group: SubscriptionGroupId group: SubscriptionGroupId
platform: PlatformId platform: PlatformId
status: string renews: boolean
startsAt: number periodDtartsAt: string
endsAt: number periodEndsAt: string
renewalStatus: string purchasedAt: string
} }
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {useQuery, useMutation} from '@tanstack/react-query' import {useQuery, useMutation} from '@tanstack/react-query'
import Purchases from 'react-native-purchases' import Purchases, { PURCHASES_ERROR_CODE } from 'react-native-purchases'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
import {api} from '#/state/purchases/subscriptions/api' import {api} from '#/state/purchases/subscriptions/api'
@@ -52,9 +52,17 @@ export function usePurchaseOffering() {
throw new Error('Unsupported platform') throw new Error('Unsupported platform')
} }
Purchases.logIn(did) try {
Purchases.setEmail(email) await Purchases.logIn(did)
Purchases.purchaseStoreProduct(offering.package) await Purchases.setEmail(email)
await Purchases.purchaseStoreProduct(offering.package)
} catch (e: any) {
if (e.code === PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR) {
console.log('recipt error', e)
}
throw e
}
} }
}) })
} }
@@ -0,0 +1,25 @@
import {useMutation} from '@tanstack/react-query'
import Purchases, { PURCHASES_ERROR_CODE } from 'react-native-purchases'
import {useSession} from '#/state/session'
export function useSyncPurchases() {
const {currentAccount} = useSession()
return useMutation({
async mutationFn() {
if (!currentAccount) {
throw new Error('Not logged in')
}
try {
await Purchases.logIn(currentAccount.did)
return await Purchases.restorePurchases()
} catch (e: any) {
if (e.code === PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR) {
console.log('recipt error', e)
}
throw e
}
},
})
}