subscription dialog
This commit is contained in:
committed by
Eric Bailey
parent
9be5761885
commit
0f15abc246
@@ -0,0 +1,269 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Linking, View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useCreateCheckout} from '#/state/purchases/subscriptions/useCreateCheckout'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {GradientFill} from '#/components/GradientFill'
|
||||||
|
import {Full as BlueskyPlusLogo} from '#/components/icons/BlueskyPlus'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron'
|
||||||
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function SubscriptionsDialog({
|
||||||
|
control,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<DialogInner />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogInner() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const t = useTheme()
|
||||||
|
const [plan, setPlan] = React.useState<'monthly' | 'annual'>('monthly')
|
||||||
|
|
||||||
|
const {mutateAsync: createCheckout, isPending} = useCreateCheckout()
|
||||||
|
|
||||||
|
const onPressSubscribe = async () => {
|
||||||
|
try {
|
||||||
|
if (!currentAccount) return
|
||||||
|
const data = await createCheckout({
|
||||||
|
did: currentAccount.did,
|
||||||
|
email: currentAccount.email!,
|
||||||
|
price:
|
||||||
|
plan === 'monthly'
|
||||||
|
? 'price_1QLsNLAwTlpRxHkAhuUwbnlL'
|
||||||
|
: 'price_1QLsOjAwTlpRxHkAcrV8DL2P',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!data?.checkoutUrl) {
|
||||||
|
throw new Error('No checkout URL')
|
||||||
|
}
|
||||||
|
|
||||||
|
Linking.openURL(data.checkoutUrl)
|
||||||
|
} catch (e: any) {
|
||||||
|
Toast.show(_(msg`Could not take you to checkout`), 'xmark')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Inner label={_(msg`Bluesky Plus`)} style={[web({maxWidth: 1000})]}>
|
||||||
|
<View style={[a.flex_1, a.gap_xl, gtMobile && a.flex_row]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.gap_md,
|
||||||
|
a.rounded_xs,
|
||||||
|
a.relative,
|
||||||
|
a.overflow_hidden,
|
||||||
|
]}>
|
||||||
|
<GradientFill gradient={tokens.gradients.nordic} rotate="270deg" />
|
||||||
|
<View style={[a.px_xl, a.py_4xl, a.gap_md]}>
|
||||||
|
<MarketingBlurb />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[a.flex_1, a.justify_end, a.gap_md]}>
|
||||||
|
<Text style={[a.font_bold, a.text_3xl]}>
|
||||||
|
<Trans>Early bird discount!</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_lg, a.leading_snug]}>
|
||||||
|
<Trans>Support Bluesky now and get a 25% discount for a year</Trans>
|
||||||
|
</Text>
|
||||||
|
<Toggle.Group
|
||||||
|
type="radio"
|
||||||
|
label={_(msg`Choose plan`)}
|
||||||
|
values={[plan]}
|
||||||
|
onChange={values => setPlan(values[0] as 'monthly' | 'annual')}
|
||||||
|
style={[a.w_full, a.gap_md, a.my_xl]}>
|
||||||
|
<Toggle.Item name="monthly" label={_(msg`Monthly plan`)}>
|
||||||
|
{({selected}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.p_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.flex_row,
|
||||||
|
a.gap_md,
|
||||||
|
a.border,
|
||||||
|
selected
|
||||||
|
? {borderColor: t.palette.primary_500}
|
||||||
|
: t.atoms.border_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<View style={[a.flex_1, a.gap_xs]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.flex_wrap,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_sm,
|
||||||
|
]}>
|
||||||
|
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}>
|
||||||
|
<Trans>Monthly plan</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}>
|
||||||
|
<Trans>$6/month</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_sm,
|
||||||
|
a.leading_normal,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
Billed monthly, cancel anytime. $8/month after 12 months
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Toggle.Item>
|
||||||
|
<Toggle.Item name="annual" label={_(msg`Annual plan`)}>
|
||||||
|
{({selected}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.p_sm,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.flex_row,
|
||||||
|
a.gap_md,
|
||||||
|
a.border,
|
||||||
|
selected
|
||||||
|
? {borderColor: t.palette.primary_500}
|
||||||
|
: t.atoms.border_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Toggle.Radio />
|
||||||
|
<View style={[a.flex_1, a.gap_xs]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.flex_wrap,
|
||||||
|
a.justify_between,
|
||||||
|
a.gap_sm,
|
||||||
|
]}>
|
||||||
|
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}>
|
||||||
|
<Trans>Annual plan</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}>
|
||||||
|
<Trans>$60/year</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_sm,
|
||||||
|
a.leading_normal,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>Billed annually, renews at $80/year</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Toggle.Item>
|
||||||
|
</Toggle.Group>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Subscribe`)}
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
onPress={onPressSubscribe}
|
||||||
|
disabled={isPending}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Subscribe</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon icon={isPending ? Loader : ChevronRightIcon} />
|
||||||
|
</Button>
|
||||||
|
<Text>
|
||||||
|
<InlineLinkText to="#" label="TODO REPLACE">
|
||||||
|
Terms and Conditions
|
||||||
|
</InlineLinkText>{' '}
|
||||||
|
·{' '}
|
||||||
|
<InlineLinkText to="#" label="TODO REPLACE">
|
||||||
|
Privacy Policy
|
||||||
|
</InlineLinkText>{' '}
|
||||||
|
·{' '}
|
||||||
|
<InlineLinkText to="#" label="TODO REPLACE">
|
||||||
|
EULA
|
||||||
|
</InlineLinkText>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.Inner>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MarketingBlurb() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BlueskyPlusLogo width={200} fill="white" style={[a.mt_5xl]} />
|
||||||
|
|
||||||
|
<Text style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
<Trans>
|
||||||
|
Here's the thing: we don't have any premium features yet because all
|
||||||
|
our servers are on fire. Support us now so we can put the fires out
|
||||||
|
and get working on those features!
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[a.text_lg, a.leading_normal, a.font_bold, {color: 'white'}]}>
|
||||||
|
<Trans>What you'll get now:</Trans>
|
||||||
|
</Text>
|
||||||
|
<View role="list" style={[a.gap_xs]}>
|
||||||
|
<Text
|
||||||
|
role="listitem"
|
||||||
|
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
{' '}
|
||||||
|
· <Trans>A supporter badge on your profile</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
role="listitem"
|
||||||
|
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
{' '}
|
||||||
|
· <Trans>A warm fuzzy feeling knowing you helped us out</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[a.text_lg, a.leading_normal, a.font_bold, {color: 'white'}]}>
|
||||||
|
<Trans>What you'll get soon: </Trans>
|
||||||
|
</Text>
|
||||||
|
<View role="list" style={[a.gap_xs]}>
|
||||||
|
<Text
|
||||||
|
role="listitem"
|
||||||
|
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
{' '}
|
||||||
|
· <Trans>Custom profile colors</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
role="listitem"
|
||||||
|
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
{' '}
|
||||||
|
· <Trans>Longer and higher quality videos</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
role="listitem"
|
||||||
|
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
||||||
|
{' '}
|
||||||
|
· <Trans>Custom app icons</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Pressable, View, ViewStyle} from 'react-native'
|
import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
|
||||||
import Animated, {LinearTransition} from 'react-native-reanimated'
|
import Animated, {LinearTransition} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
import {HITSLOP_10} from '#/lib/constants'
|
||||||
@@ -57,6 +57,7 @@ export type GroupProps = React.PropsWithChildren<{
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onChange: (value: string[]) => void
|
onChange: (value: string[]) => void
|
||||||
label: string
|
label: string
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type ItemProps = ViewStyleProp & {
|
export type ItemProps = ViewStyleProp & {
|
||||||
@@ -82,6 +83,7 @@ export function Group({
|
|||||||
type = 'checkbox',
|
type = 'checkbox',
|
||||||
maxSelections,
|
maxSelections,
|
||||||
label,
|
label,
|
||||||
|
style,
|
||||||
}: GroupProps) {
|
}: GroupProps) {
|
||||||
const groupRole = type === 'radio' ? 'radiogroup' : undefined
|
const groupRole = type === 'radio' ? 'radiogroup' : undefined
|
||||||
const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
|
const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
|
||||||
@@ -134,7 +136,7 @@ export function Group({
|
|||||||
return (
|
return (
|
||||||
<GroupContext.Provider value={context}>
|
<GroupContext.Provider value={context}>
|
||||||
<View
|
<View
|
||||||
style={[a.w_full]}
|
style={[a.w_full, style]}
|
||||||
role={groupRole}
|
role={groupRole}
|
||||||
{...(groupRole === 'radiogroup'
|
{...(groupRole === 'radiogroup'
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import Svg, {
|
|||||||
Stop,
|
Stop,
|
||||||
SvgProps,
|
SvgProps,
|
||||||
} from 'react-native-svg'
|
} from 'react-native-svg'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
|
|
||||||
@@ -24,15 +26,20 @@ export function Full(props: Props) {
|
|||||||
const {fill, ...rest} = props
|
const {fill, ...rest} = props
|
||||||
const gradient = fill === 'nordic'
|
const gradient = fill === 'nordic'
|
||||||
const styles = StyleSheet.flatten(props.style)
|
const styles = StyleSheet.flatten(props.style)
|
||||||
const _fill = gradient ? 'url(#nordic)' : fill || styles?.color || t.palette.primary_500
|
const _fill = gradient
|
||||||
|
? 'url(#nordic)'
|
||||||
|
: fill || styles?.color || t.palette.primary_500
|
||||||
// @ts-ignore it's fiiiiine
|
// @ts-ignore it's fiiiiine
|
||||||
const size = parseInt(rest.width || 100)
|
const size = parseInt(rest.width || 100)
|
||||||
const ratio = 90 / 448
|
const ratio = 90 / 448
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Svg
|
<Svg
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 448 90"
|
viewBox="0 0 448 90"
|
||||||
|
accessibilityLabel={_(msg`Bluesky Plus`)}
|
||||||
|
accessibilityHint=""
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[styles, {width: size, height: size * ratio}]}>
|
style={[styles, {width: size, height: size * ratio}]}>
|
||||||
{gradient && <Gradients />}
|
{gradient && <Gradients />}
|
||||||
@@ -54,15 +61,20 @@ export function Logotype(props: Props) {
|
|||||||
const {fill, ...rest} = props
|
const {fill, ...rest} = props
|
||||||
const gradient = fill === 'nordic'
|
const gradient = fill === 'nordic'
|
||||||
const styles = StyleSheet.flatten(props.style)
|
const styles = StyleSheet.flatten(props.style)
|
||||||
const _fill = gradient ? 'url(#nordic)' : fill || styles?.color || t.palette.primary_500
|
const _fill = gradient
|
||||||
|
? 'url(#nordic)'
|
||||||
|
: fill || styles?.color || t.palette.primary_500
|
||||||
// @ts-ignore it's fiiiiine
|
// @ts-ignore it's fiiiiine
|
||||||
const size = parseInt(rest.width || 100)
|
const size = parseInt(rest.width || 100)
|
||||||
const ratio = 78 / 330
|
const ratio = 78 / 330
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Svg
|
<Svg
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 330 78"
|
viewBox="0 0 330 78"
|
||||||
|
accessibilityLabel={_(msg`Bluesky Plus`)}
|
||||||
|
accessibilityHint=""
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[styles, {width: size, height: size * ratio}]}>
|
style={[styles, {width: size, height: size * ratio}]}>
|
||||||
{gradient && <Gradients />}
|
{gradient && <Gradients />}
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View, Linking} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
// import {isAndroid, isIOS} from '#/platform/detection'
|
|
||||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {SubscriptionsDialog} from '#/components/dialogs/SubscriptionsDialog'
|
||||||
|
import {Logotype} from '#/components/icons/BlueskyPlus'
|
||||||
|
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 {Button, ButtonText, ButtonIcon} from '#/components/Button'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
// import {Divider} from '#/components/Divider'
|
|
||||||
import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
|
||||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
|
||||||
import {Logotype} from '#/components/icons/BlueskyPlus'
|
|
||||||
import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements'
|
|
||||||
import {useCreateCheckout} from '#/state/purchases/subscriptions/useCreateCheckout'
|
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
|
|
||||||
export type ScreenProps = NativeStackScreenProps<
|
export type ScreenProps = NativeStackScreenProps<
|
||||||
CommonNavigatorParams,
|
CommonNavigatorParams,
|
||||||
@@ -26,54 +22,17 @@ export type ScreenProps = NativeStackScreenProps<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export function Subscriptions(_props: ScreenProps) {
|
export function Subscriptions(_props: ScreenProps) {
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {data: entitlements, isLoading: isEntitlementsLoading} =
|
||||||
const {data: entitlements, isLoading: isEntitlementsLoading} = useEntitlements()
|
useEntitlements()
|
||||||
const isSubscribed = entitlements?.some(e => e.id === 'core')
|
const isSubscribed = entitlements?.some(e => e.id === 'core')
|
||||||
|
const control = useDialogControl()
|
||||||
const {mutateAsync: createCheckout} = useCreateCheckout()
|
|
||||||
const isPending = false
|
|
||||||
|
|
||||||
const onPressMonthly = async () => {
|
|
||||||
try {
|
|
||||||
const data = await createCheckout({
|
|
||||||
did: currentAccount!.did,
|
|
||||||
email: currentAccount!.email!,
|
|
||||||
price: 'price_1QLsNLAwTlpRxHkAhuUwbnlL',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!data?.checkoutUrl) {
|
|
||||||
throw new Error('No checkout URL')
|
|
||||||
}
|
|
||||||
|
|
||||||
Linking.openURL(data.checkoutUrl)
|
|
||||||
} catch (e: any) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const onPressAnnual = async () => {
|
|
||||||
try {
|
|
||||||
const data = await createCheckout({
|
|
||||||
did: currentAccount!.did,
|
|
||||||
email: currentAccount!.email!,
|
|
||||||
price: 'price_1QLsOjAwTlpRxHkAcrV8DL2P',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!data?.checkoutUrl) {
|
|
||||||
throw new Error('No checkout URL')
|
|
||||||
}
|
|
||||||
|
|
||||||
Linking.openURL(data.checkoutUrl)
|
|
||||||
} catch (e: any) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={true} style={[a.util_screen_outer]}>
|
<CenteredView sideBorders={true} style={[a.util_screen_outer]}>
|
||||||
<View style={[a.px_xl, a.py_xl, a.gap_lg]}>
|
<View style={[a.px_xl, a.py_xl, a.gap_lg]}>
|
||||||
<Logotype width={200} fill='nordic' />
|
<Logotype width={200} fill="nordic" />
|
||||||
|
|
||||||
<View style={[a.pt_xl]}>
|
<View style={[a.pt_xl]}>
|
||||||
{isEntitlementsLoading ? (
|
{isEntitlementsLoading ? (
|
||||||
@@ -85,28 +44,18 @@ export function Subscriptions(_props: ScreenProps) {
|
|||||||
<Text>You're subscribed!</Text>
|
<Text>You're subscribed!</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label={_('Subscribe')}
|
label={_('Subscribe')}
|
||||||
onPress={onPressMonthly}
|
onPress={() => control.open()}
|
||||||
size='large'
|
size="large"
|
||||||
variant='solid'
|
variant="solid"
|
||||||
color='primary'
|
color="primary">
|
||||||
>
|
<ButtonText>Subscribe</ButtonText>
|
||||||
<ButtonText>Subscribe ($6/month)</ButtonText>
|
<ButtonIcon icon={Plus} />
|
||||||
<ButtonIcon icon={isPending ? Loader : Plus} />
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<SubscriptionsDialog control={control} />
|
||||||
label={_('Subscribe')}
|
</>
|
||||||
onPress={onPressAnnual}
|
|
||||||
size='large'
|
|
||||||
variant='solid'
|
|
||||||
color='secondary'
|
|
||||||
>
|
|
||||||
<ButtonText>Subscribe ($60/year)</ButtonText>
|
|
||||||
<ButtonIcon icon={isPending ? Loader : Plus} />
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import {useMutation} from '@tanstack/react-query'
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {IS_DEV} from '#/env'
|
|
||||||
import {api} from '#/state/purchases/subscriptions/api'
|
import {api} from '#/state/purchases/subscriptions/api'
|
||||||
|
import {IS_DEV} from '#/env'
|
||||||
|
|
||||||
export function useCreateCheckout() {
|
export function useCreateCheckout() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
async mutationFn(props: {
|
async mutationFn(props: {price: string; did: string; email: string}) {
|
||||||
price: string,
|
|
||||||
did: string,
|
|
||||||
email: string,
|
|
||||||
}) {
|
|
||||||
const {data, error} = await api<{
|
const {data, error} = await api<{
|
||||||
checkoutUrl: string
|
checkoutUrl: string
|
||||||
}>('/checkout/create', {
|
}>('/checkout/create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
json: {
|
json: {
|
||||||
...props,
|
...props,
|
||||||
redirectUrl: IS_DEV ? `http://localhost:19006/subscriptions` : `https://bsky.app/subscriptions`,
|
redirectUrl: IS_DEV
|
||||||
}
|
? `http://localhost:19006/subscriptions`
|
||||||
|
: `https://bsky.app/subscriptions`,
|
||||||
|
},
|
||||||
}).json()
|
}).json()
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -6,22 +6,26 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
|
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||||
|
import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {DesktopFeeds} from '#/view/shell/desktop/Feeds'
|
import {DesktopFeeds} from '#/view/shell/desktop/Feeds'
|
||||||
import {DesktopSearch} from '#/view/shell/desktop/Search'
|
import {DesktopSearch} from '#/view/shell/desktop/Search'
|
||||||
import {atoms as a, tokens,useTheme, web} from '#/alf'
|
import {atoms as a, tokens, useTheme, web} from '#/alf'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {SubscriptionsDialog} from '#/components/dialogs/SubscriptionsDialog'
|
||||||
import {GradientFill} from '#/components/GradientFill'
|
import {GradientFill} from '#/components/GradientFill'
|
||||||
import {Logotype} from '#/components/icons/BlueskyPlus'
|
import {Logotype} from '#/components/icons/BlueskyPlus'
|
||||||
import {InlineLinkText, Link} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
import {ProgressGuideList} from '#/components/ProgressGuide/List'
|
import {ProgressGuideList} from '#/components/ProgressGuide/List'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements'
|
|
||||||
|
|
||||||
export function DesktopRightNav({routeName}: {routeName: string}) {
|
export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {hasSession, currentAccount} = useSession()
|
const {hasSession, currentAccount} = useSession()
|
||||||
const {data: entitlements, isLoading: isEntitlementsLoading} = useEntitlements()
|
const subscriptionsDialogControl = useDialogControl()
|
||||||
|
const {data: entitlements} = useEntitlements()
|
||||||
const isSubscribed = entitlements?.some(e => e.id === 'core')
|
const isSubscribed = entitlements?.some(e => e.id === 'core')
|
||||||
|
|
||||||
const kawaii = useKawaiiMode()
|
const kawaii = useKawaiiMode()
|
||||||
@@ -66,11 +70,14 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
|||||||
{/* TODO NUX this */}
|
{/* TODO NUX this */}
|
||||||
{!isSubscribed && (
|
{!isSubscribed && (
|
||||||
<View style={[a.pb_lg]}>
|
<View style={[a.pb_lg]}>
|
||||||
<Link
|
<Button
|
||||||
to="/subscriptions"
|
onPress={() => subscriptionsDialogControl.open()}
|
||||||
label={_(msg`Subscribe to Bluesky Plus`)}
|
label={_(msg`Subscribe to Bluesky Plus`)}
|
||||||
style={[a.p_lg, a.overflow_hidden, a.rounded_md]}>
|
style={[a.p_lg, a.overflow_hidden, a.rounded_md]}>
|
||||||
<GradientFill gradient={tokens.gradients.nordic} rotate="270deg" />
|
<GradientFill
|
||||||
|
gradient={tokens.gradients.nordic}
|
||||||
|
rotate="270deg"
|
||||||
|
/>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
@@ -81,7 +88,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={[a.flex_1, a.relative, a.z_10]}>
|
<View style={[a.flex_1, a.relative, a.z_10]}>
|
||||||
<Logotype width={100} fill='white' />
|
<Logotype width={100} fill="white" />
|
||||||
|
|
||||||
<View style={[a.pt_xs]}>
|
<View style={[a.pt_xs]}>
|
||||||
<Text style={[a.text_sm, a.leading_tight, {color: 'white'}]}>
|
<Text style={[a.text_sm, a.leading_tight, {color: 'white'}]}>
|
||||||
@@ -91,7 +98,8 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Link>
|
</Button>
|
||||||
|
<SubscriptionsDialog control={subscriptionsDialogControl} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user