Simplify the dialog
This commit is contained in:
@@ -654,10 +654,12 @@ export function ButtonIcon({
|
|||||||
icon: Comp,
|
icon: Comp,
|
||||||
position,
|
position,
|
||||||
size,
|
size,
|
||||||
|
style,
|
||||||
}: {
|
}: {
|
||||||
icon: React.ComponentType<SVGIconProps>
|
icon: React.ComponentType<SVGIconProps>
|
||||||
position?: 'left' | 'right'
|
position?: 'left' | 'right'
|
||||||
size?: SVGIconProps['size']
|
size?: SVGIconProps['size']
|
||||||
|
style?: SVGIconProps['style']
|
||||||
}) {
|
}) {
|
||||||
const {size: buttonSize, disabled} = useButtonContext()
|
const {size: buttonSize, disabled} = useButtonContext()
|
||||||
const textStyles = useSharedButtonTextStyles()
|
const textStyles = useSharedButtonTextStyles()
|
||||||
@@ -742,6 +744,7 @@ export function ButtonIcon({
|
|||||||
color: textStyles.color,
|
color: textStyles.color,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
},
|
},
|
||||||
|
style,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
|
|
||||||
import {atoms as a, tokens} from '#/alf'
|
import {atoms as a, tokens, ViewStyleProp} from '#/alf'
|
||||||
|
|
||||||
export function GradientFill({
|
export function GradientFill({
|
||||||
gradient,
|
gradient,
|
||||||
rotate,
|
rotate,
|
||||||
}: {
|
style,
|
||||||
|
}: ViewStyleProp & {
|
||||||
gradient:
|
gradient:
|
||||||
| typeof tokens.gradients.primary
|
| typeof tokens.gradients.primary
|
||||||
| typeof tokens.gradients.sky
|
| typeof tokens.gradients.sky
|
||||||
@@ -39,7 +40,7 @@ export function GradientFill({
|
|||||||
locations={gradient.values.map(c => c[0])}
|
locations={gradient.values.map(c => c[0])}
|
||||||
start={start}
|
start={start}
|
||||||
end={end}
|
end={end}
|
||||||
style={[a.absolute, a.inset_0]}
|
style={[a.absolute, a.inset_0, style]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,42 +3,48 @@ import {View} from 'react-native'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {GradientFill} from '#/components/GradientFill'
|
import {GradientFill} from '#/components/GradientFill'
|
||||||
import {Full as BlueskyPlusLogo} from '#/components/icons/BlueskyPlus'
|
import {Full as BlueskyPlusLogo} from '#/components/icons/BlueskyPlus'
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron'
|
import {CheckThick_Stroke2_Corner0_Rounded as CheckThink} from '#/components/icons/Check'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSubscriptionGroup, usePurchaseOffering} from '#/state/purchases/subscriptions/useSubscriptionGroup'
|
|
||||||
import {OfferingId, SubscriptionGroupId} from '#/state/purchases/subscriptions/types'
|
|
||||||
import {parseOfferingId} from '#/state/purchases/subscriptions/util'
|
|
||||||
|
|
||||||
export function BlueskyPlus({
|
export function BlueskyPlus({control}: {control: Dialog.DialogControlProps}) {
|
||||||
control,
|
|
||||||
}: {
|
|
||||||
control: Dialog.DialogControlProps
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Outer control={control}>
|
<Dialog.Outer control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
<DialogInner />
|
<DialogInner />
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogInner() {
|
function DialogInner() {
|
||||||
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const t = useTheme()
|
const copy = useCoreOfferingCopy()
|
||||||
|
|
||||||
const [offeringId, setOfferingId] = React.useState<OfferingId>(OfferingId.CoreMonthly)
|
const [offeringId, setOfferingId] = React.useState<OfferingId>(
|
||||||
const {data: coreOffering, error} = useSubscriptionGroup(SubscriptionGroupId.Core)
|
OfferingId.CoreAnnual,
|
||||||
|
)
|
||||||
|
const {data: coreOffering} = useSubscriptionGroup(SubscriptionGroupId.Core)
|
||||||
const {mutateAsync: purchaseOffering, isPending} = usePurchaseOffering()
|
const {mutateAsync: purchaseOffering, isPending} = usePurchaseOffering()
|
||||||
|
|
||||||
const onPressSubscribe = async () => {
|
const onPressSubscribe = async () => {
|
||||||
@@ -61,209 +67,187 @@ function DialogInner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Inner label={_(msg`Bluesky Plus`)} style={[web({maxWidth: 1000})]}>
|
<Dialog.ScrollableInner
|
||||||
<View style={[a.gap_xl, gtMobile && a.flex_row]}>
|
label={_(msg`Bluesky Plus`)}
|
||||||
<View
|
style={[
|
||||||
style={[
|
a.overflow_hidden,
|
||||||
a.flex_1,
|
gtMobile ? {width: '100%', maxWidth: 400, minWidth: 200} : a.w_full,
|
||||||
a.gap_md,
|
]}>
|
||||||
a.rounded_xs,
|
<BlueskyPlusLogo width={100} fill="nordic" />
|
||||||
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.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={[offeringId]}
|
|
||||||
onChange={values => setOfferingId(parseOfferingId(values[0]))}
|
|
||||||
style={[a.w_full, a.gap_md, a.my_xl]}>
|
|
||||||
<Toggle.Item name={OfferingId.CoreMonthly} 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>$8/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={OfferingId.CoreAnnual} 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>$80/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() {
|
<Text style={[a.text_3xl, a.font_heavy, a.pt_lg]}>
|
||||||
return (
|
<Trans>Let's build the social web.</Trans>
|
||||||
<>
|
</Text>
|
||||||
<BlueskyPlusLogo width={200} fill="white" style={[a.mt_5xl]} />
|
|
||||||
|
|
||||||
<Text style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
a.pt_xs,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Here's the thing: we don't have any premium features yet because all
|
Support Bluesky and get access to exclusive features, custom app
|
||||||
our servers are on fire. Support us now so we can put the fires out
|
icons, higher video quality, and more!
|
||||||
and get working on those features!
|
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Toggle.Group
|
||||||
style={[a.text_lg, a.leading_normal, a.font_bold, {color: 'white'}]}>
|
type="radio"
|
||||||
<Trans>What you'll get now:</Trans>
|
label={_(msg`Choose a Bluesky Plus plan`)}
|
||||||
</Text>
|
values={[offeringId]}
|
||||||
<View role="list" style={[a.gap_xs]}>
|
onChange={values => setOfferingId(parseOfferingId(values[0]))}
|
||||||
<Text
|
style={[a.w_full, a.gap_sm, a.pt_lg]}>
|
||||||
role="listitem"
|
{[OfferingId.CoreMonthly, OfferingId.CoreAnnual].map(id => {
|
||||||
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
return (
|
||||||
{' '}
|
<Toggle.Item key={id} name={id} label={_(msg`Monthly plan`)}>
|
||||||
· <Trans>A supporter badge on your profile</Trans>
|
{({selected, hovered}) => (
|
||||||
</Text>
|
<View
|
||||||
<Text
|
style={[
|
||||||
role="listitem"
|
a.w_full,
|
||||||
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
|
a.p_md,
|
||||||
{' '}
|
a.rounded_sm,
|
||||||
· <Trans>A warm fuzzy feeling knowing you helped us out</Trans>
|
a.border,
|
||||||
</Text>
|
a.overflow_hidden,
|
||||||
|
t.atoms.bg,
|
||||||
|
selected
|
||||||
|
? t.atoms.border_contrast_high
|
||||||
|
: t.atoms.border_contrast_low,
|
||||||
|
]}>
|
||||||
|
{Boolean(selected || hovered) && (
|
||||||
|
<GradientFill
|
||||||
|
gradient={tokens.gradients.nordic}
|
||||||
|
style={{opacity: 0.1}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_between,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
]}>
|
||||||
|
<View style={[a.gap_xs]}>
|
||||||
|
<Text style={[a.text_lg, a.leading_tight, a.font_heavy]}>
|
||||||
|
<Trans>{copy[id].title}</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_sm,
|
||||||
|
a.leading_tight,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>{copy[id].price}</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_end,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
]}>
|
||||||
|
{copy[id].discount && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_full,
|
||||||
|
{
|
||||||
|
backgroundColor: t.atoms.text.color,
|
||||||
|
paddingVertical: 3,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_xs,
|
||||||
|
a.font_bold,
|
||||||
|
{
|
||||||
|
color: t.atoms.bg.backgroundColor,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{copy[id].discount}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_full,
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
{height: 24, width: 24},
|
||||||
|
]}>
|
||||||
|
{selected ? (
|
||||||
|
<>
|
||||||
|
<GradientFill gradient={tokens.gradients.nordic} />
|
||||||
|
<CheckThink
|
||||||
|
fill="white"
|
||||||
|
size="xs"
|
||||||
|
style={[a.z_10]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
a.inset_0,
|
||||||
|
a.border,
|
||||||
|
a.rounded_full,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Toggle.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Toggle.Group>
|
||||||
|
|
||||||
|
<View style={[a.pt_md]}>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Subscribe`)}
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onPress={onPressSubscribe}
|
||||||
|
disabled={isPending}
|
||||||
|
style={[a.overflow_hidden]}>
|
||||||
|
<GradientFill gradient={tokens.gradients.nordic} />
|
||||||
|
<ButtonText style={[t.atoms.text]}>
|
||||||
|
<Trans>Subscribe</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon
|
||||||
|
icon={isPending ? Loader : Plus}
|
||||||
|
position="right"
|
||||||
|
style={[t.atoms.text]}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text
|
<Dialog.Close />
|
||||||
style={[a.text_lg, a.leading_normal, a.font_bold, {color: 'white'}]}>
|
</Dialog.ScrollableInner>
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useCoreOfferingCopy() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
return React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
[OfferingId.CoreMonthly]: {
|
||||||
|
title: _(msg`1 month`),
|
||||||
|
price: _(msg`$8 / month`),
|
||||||
|
discount: undefined,
|
||||||
|
},
|
||||||
|
[OfferingId.CoreAnnual]: {
|
||||||
|
title: _(msg`12 months`),
|
||||||
|
price: _(msg`$72 / year`),
|
||||||
|
discount: _(msg`Save 25%`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [_])
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user