Simplify the dialog

This commit is contained in:
Eric Bailey
2024-11-20 11:34:35 -06:00
parent 4db49edca6
commit b63cb9bf5d
3 changed files with 201 additions and 213 deletions
+3
View File
@@ -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>
+4 -3
View File
@@ -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]}
/> />
) )
} }
+154 -170
View File
@@ -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.flex_1,
a.gap_md,
a.rounded_xs,
a.relative,
a.overflow_hidden, a.overflow_hidden,
gtMobile ? {width: '100%', maxWidth: 400, minWidth: 200} : a.w_full,
]}> ]}>
<GradientFill gradient={tokens.gradients.nordic} rotate="270deg" /> <BlueskyPlusLogo width={100} fill="nordic" />
<View style={[a.px_xl, a.py_4xl, a.gap_md]}>
<MarketingBlurb /> <Text style={[a.text_3xl, a.font_heavy, a.pt_lg]}>
</View> <Trans>Let's build the social web.</Trans>
</View>
<View style={[a.justify_end, a.gap_md]}>
<Text style={[a.font_bold, a.text_3xl]}>
<Trans>Early bird discount!</Trans>
</Text> </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 <Text
style={[ style={[
a.text_sm, a.text_md,
a.leading_normal, a.leading_snug,
a.pt_xs,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
<Trans> <Trans>
Billed monthly, cancel anytime. $8/month after 12 months Support Bluesky and get access to exclusive features, custom app
icons, higher video quality, and more!
</Trans> </Trans>
</Text> </Text>
</View>
</View> <Toggle.Group
)} type="radio"
</Toggle.Item> label={_(msg`Choose a Bluesky Plus plan`)}
<Toggle.Item name={OfferingId.CoreAnnual} label={_(msg`Annual plan`)}> values={[offeringId]}
{({selected}) => ( onChange={values => setOfferingId(parseOfferingId(values[0]))}
style={[a.w_full, a.gap_sm, a.pt_lg]}>
{[OfferingId.CoreMonthly, OfferingId.CoreAnnual].map(id => {
return (
<Toggle.Item key={id} name={id} label={_(msg`Monthly plan`)}>
{({selected, hovered}) => (
<View <View
style={[ style={[
a.w_full, a.w_full,
a.p_sm, a.p_md,
a.rounded_sm, a.rounded_sm,
a.flex_row,
a.gap_md,
a.border, a.border,
a.overflow_hidden,
t.atoms.bg,
selected selected
? {borderColor: t.palette.primary_500} ? t.atoms.border_contrast_high
: t.atoms.border_contrast_medium, : t.atoms.border_contrast_low,
]}> ]}>
<Toggle.Radio /> {Boolean(selected || hovered) && (
<View style={[a.flex_1, a.gap_xs]}> <GradientFill
gradient={tokens.gradients.nordic}
style={{opacity: 0.1}}
/>
)}
<View <View
style={[ style={[
a.flex_row, a.flex_row,
a.flex_wrap,
a.justify_between, a.justify_between,
a.align_center,
a.gap_sm, a.gap_sm,
]}> ]}>
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}> <View style={[a.gap_xs]}>
<Trans>Annual plan</Trans> <Text style={[a.text_lg, a.leading_tight, a.font_heavy]}>
<Trans>{copy[id].title}</Trans>
</Text> </Text>
<Text style={[a.text_lg, a.leading_normal, a.font_bold]}>
<Trans>$80/year</Trans>
</Text>
</View>
<Text <Text
style={[ style={[
a.text_sm, a.text_sm,
a.leading_normal, a.leading_tight,
t.atoms.text_contrast_medium, t.atoms.text_contrast_medium,
]}> ]}>
<Trans>Billed annually, renews at $80/year</Trans> <Trans>{copy[id].price}</Trans>
</Text> </Text>
</View> </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> </View>
)} )}
</Toggle.Item> </Toggle.Item>
)
})}
</Toggle.Group> </Toggle.Group>
<View style={[a.pt_md]}>
<Button <Button
label={_(msg`Subscribe`)} label={_(msg`Subscribe`)}
variant="solid" variant="solid"
color="primary" color="secondary"
size="large" size="large"
onPress={onPressSubscribe} onPress={onPressSubscribe}
disabled={isPending}> disabled={isPending}
<ButtonText> style={[a.overflow_hidden]}>
<GradientFill gradient={tokens.gradients.nordic} />
<ButtonText style={[t.atoms.text]}>
<Trans>Subscribe</Trans> <Trans>Subscribe</Trans>
</ButtonText> </ButtonText>
<ButtonIcon icon={isPending ? Loader : ChevronRightIcon} /> <ButtonIcon
icon={isPending ? Loader : Plus}
position="right"
style={[t.atoms.text]}
/>
</Button> </Button>
<Text>
<InlineLinkText to="#" label="TODO REPLACE">
Terms and Conditions
</InlineLinkText>{' '}
&middot;{' '}
<InlineLinkText to="#" label="TODO REPLACE">
Privacy Policy
</InlineLinkText>{' '}
&middot;{' '}
<InlineLinkText to="#" label="TODO REPLACE">
EULA
</InlineLinkText>
</Text>
</View>
</View> </View>
<Dialog.Close /> <Dialog.Close />
</Dialog.Inner> </Dialog.ScrollableInner>
) )
} }
function MarketingBlurb() { function useCoreOfferingCopy() {
return ( const {_} = useLingui()
<> return React.useMemo(() => {
<BlueskyPlusLogo width={200} fill="white" style={[a.mt_5xl]} /> return {
[OfferingId.CoreMonthly]: {
<Text style={[a.text_lg, a.leading_normal, {color: 'white'}]}> title: _(msg`1 month`),
<Trans> price: _(msg`$8 / month`),
Here's the thing: we don't have any premium features yet because all discount: undefined,
our servers are on fire. Support us now so we can put the fires out },
and get working on those features! [OfferingId.CoreAnnual]: {
</Trans> title: _(msg`12 months`),
</Text> price: _(msg`$72 / year`),
discount: _(msg`Save 25%`),
<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'}]}>
{' '}
&middot; <Trans>A supporter badge on your profile</Trans>
</Text>
<Text
role="listitem"
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
{' '}
&middot; <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'}]}>
{' '}
&middot; <Trans>Custom profile colors</Trans>
</Text>
<Text
role="listitem"
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
{' '}
&middot; <Trans>Longer and higher quality videos</Trans>
</Text>
<Text
role="listitem"
style={[a.text_lg, a.leading_normal, {color: 'white'}]}>
{' '}
&middot; <Trans>Custom app icons</Trans>
</Text>
</View>
</>
)
} }