Some design

This commit is contained in:
Eric Bailey
2024-11-21 15:57:56 -06:00
parent 91568cc574
commit 0cf44f7749
8 changed files with 164 additions and 51 deletions
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#1185FE" d="M6.335 4.212c2.293 1.76 4.76 5.327 5.665 7.241.906-1.914 3.372-5.482 5.665-7.241C19.319 2.942 22 1.96 22 5.086c0 .624-.35 5.244-.556 5.994-.713 2.608-3.315 3.273-5.629 2.87 4.045.704 5.074 3.035 2.852 5.366-4.22 4.426-6.066-1.111-6.54-2.53-.086-.26-.126-.382-.127-.278 0-.104-.041.018-.128.278-.473 1.419-2.318 6.956-6.539 2.53-2.222-2.331-1.193-4.662 2.852-5.366-2.314.403-4.916-.262-5.63-2.87C2.35 10.33 2 5.71 2 5.086c0-3.126 2.68-2.144 4.335-.874Z"/></svg>

After

Width:  |  Height:  |  Size: 557 B

+6 -2
View File
@@ -108,8 +108,7 @@ export const gradients = {
nordic: { nordic: {
values: [ values: [
[0, '#083367'], [0, '#083367'],
[0.4, '#1A4972'], [0.4, '#386D84'],
[0.6, '#386D84'],
[1, '#9EE8C1'], [1, '#9EE8C1'],
], ],
hover_value: '#3A7085', hover_value: '#3A7085',
@@ -124,3 +123,8 @@ export const gradients = {
hover_value: '#755B62', hover_value: '#755B62',
}, },
} as const } as const
export const blueskyPlus = {
dark: '#083367',
light: '#9EE8C1',
}
+2 -2
View File
@@ -42,13 +42,13 @@ export function Logotype(props: Props) {
const {fill, size: _size, style, gradient, ...rest} = useCommonSVGProps(props) const {fill, size: _size, style, gradient, ...rest} = useCommonSVGProps(props)
// @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 = 88 / 330
const {_} = useLingui() const {_} = useLingui()
return ( return (
<Svg <Svg
fill="none" fill="none"
viewBox="0 0 330 78" viewBox="0 -10 330 88"
accessibilityLabel={_(msg`Bluesky Plus`)} accessibilityLabel={_(msg`Bluesky Plus`)}
accessibilityHint="" accessibilityHint=""
{...rest} {...rest}
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Mark = createSinglePathSVG({
path: 'M6.335 4.212c2.293 1.76 4.76 5.327 5.665 7.241.906-1.914 3.372-5.482 5.665-7.241C19.319 2.942 22 1.96 22 5.086c0 .624-.35 5.244-.556 5.994-.713 2.608-3.315 3.273-5.629 2.87 4.045.704 5.074 3.035 2.852 5.366-4.22 4.426-6.066-1.111-6.54-2.53-.086-.26-.126-.382-.127-.278 0-.104-.041.018-.128.278-.473 1.419-2.318 6.956-6.539 2.53-2.222-2.331-1.193-4.662 2.852-5.366-2.314.403-4.916-.262-5.63-2.87C2.35 10.33 2 5.71 2 5.086c0-3.126 2.68-2.144 4.335-.874Z',
})
+13 -6
View File
@@ -39,6 +39,7 @@ import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/compon
import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock'
import {Mark} from '#/components/icons/Logo'
import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller'
import { import {
Person_Stroke2_Corner2_Rounded as PersonIcon, Person_Stroke2_Corner2_Rounded as PersonIcon,
@@ -142,6 +143,18 @@ export function SettingsScreen({}: Props) {
<AddAccountRow /> <AddAccountRow />
)} )}
<SettingsList.Divider /> <SettingsList.Divider />
<SettingsList.LinkItem
to="/subscriptions"
label={_(msg`Subscriptions`)}>
<SettingsList.ItemIcon
icon={Mark}
iconProps={{gradient: 'nordic'}}
/>
<SettingsList.ItemText>
<Trans>Subscriptions</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.Divider />
<SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}> <SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}>
<SettingsList.ItemIcon icon={PersonIcon} /> <SettingsList.ItemIcon icon={PersonIcon} />
<SettingsList.ItemText> <SettingsList.ItemText>
@@ -162,12 +175,6 @@ export function SettingsScreen({}: Props) {
<Trans>Moderation</Trans> <Trans>Moderation</Trans>
</SettingsList.ItemText> </SettingsList.ItemText>
</SettingsList.LinkItem> </SettingsList.LinkItem>
<SettingsList.LinkItem to="/subscriptions" label={_(msg`Subscriptions`)}>
<SettingsList.ItemIcon icon={HandIcon} />
<SettingsList.ItemText>
<Trans>Subscriptions</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem <SettingsList.LinkItem
to="/settings/content-and-media" to="/settings/content-and-media"
label={_(msg`Content and media`)}> label={_(msg`Content and media`)}>
@@ -5,6 +5,7 @@ import {HITSLOP_10} from '#/lib/constants'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import * as Button from '#/components/Button' import * as Button from '#/components/Button'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/components/icons/Chevron'
import {Props as IconProps} from '#/components/icons/common'
import {Link, LinkProps} from '#/components/Link' import {Link, LinkProps} from '#/components/Link'
import {createPortalGroup} from '#/components/Portal' import {createPortalGroup} from '#/components/Portal'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -177,8 +178,10 @@ export function ItemIcon({
icon: Comp, icon: Comp,
size = 'xl', size = 'xl',
color: colorProp, color: colorProp,
iconProps,
}: Omit<React.ComponentProps<typeof Button.ButtonIcon>, 'position'> & { }: Omit<React.ComponentProps<typeof Button.ButtonIcon>, 'position'> & {
color?: string color?: string
iconProps?: Pick<IconProps, 'gradient'>
}) { }) {
const t = useTheme() const t = useTheme()
const {destructive, withinGroup} = useContext(ItemContext) const {destructive, withinGroup} = useContext(ItemContext)
@@ -201,7 +204,7 @@ export function ItemIcon({
const content = ( const content = (
<View style={[a.z_20, {width: iconSize, height: iconSize}]}> <View style={[a.z_20, {width: iconSize, height: iconSize}]}>
<Comp width={iconSize} style={[{color}]} /> <Comp width={iconSize} style={[{color}]} {...iconProps} />
</View> </View>
) )
+132 -40
View File
@@ -1,12 +1,18 @@
import React from 'react'
import {View} from 'react-native' 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 {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {CommonNavigatorParams} from '#/lib/routes/types' import {CommonNavigatorParams} from '#/lib/routes/types'
import {isAndroid, isIOS, isWeb} from '#/platform/detection'
import {PurchasesState, usePurchases} from '#/state/purchases' import {PurchasesState, usePurchases} from '#/state/purchases'
import {useManageSubscription} from '#/state/purchases/hooks/useManageSubscription' import {useManageSubscription} from '#/state/purchases/hooks/useManageSubscription'
import {SubscriptionGroupId} from '#/state/purchases/types' import {
PlatformId,
SubscriptionGroupId,
SubscriptionOfferingId,
} from '#/state/purchases/types'
import {APISubscription} from '#/state/purchases/types' import {APISubscription} from '#/state/purchases/types'
import {CenteredView} from '#/view/com/util/Views' import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, tokens, useTheme} from '#/alf' import {atoms as a, tokens, useTheme} from '#/alf'
@@ -14,15 +20,18 @@ import {Admonition} from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {BlueskyPlus} from '#/components/dialogs/BlueskyPlus' import {BlueskyPlus} from '#/components/dialogs/BlueskyPlus'
import {Divider} from '#/components/Divider'
import {GradientFill} from '#/components/GradientFill' import {GradientFill} from '#/components/GradientFill'
import {AndroidLogo} from '#/components/icons/AndroidLogo'
import {AppleLogo} from '#/components/icons/AppleLogo'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Rotate} from '#/components/icons/ArrowRotateCounterClockwise' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Rotate} from '#/components/icons/ArrowRotateCounterClockwise'
import {Full as BlueskyPlusLogo} from '#/components/icons/BlueskyPlus' import {Full as BlueskyPlusLogo, Logotype} from '#/components/icons/BlueskyPlus'
import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock' import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {createStaticClick, InlineLinkText, Link} from '#/components/Link' import {createStaticClick, Link} from '#/components/Link'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -40,7 +49,7 @@ export function Subscriptions(_props: ScreenProps) {
<Layout.Header title={_(msg`Subscriptions`)} /> <Layout.Header title={_(msg`Subscriptions`)} />
<Layout.Content> <Layout.Content>
<CenteredView sideBorders={true} style={[a.util_screen_outer]}> <CenteredView style={[a.util_screen_outer]}>
<View style={[a.px_xl, a.py_xl]}> <View style={[a.px_xl, a.py_xl]}>
{purchases.status === 'loading' ? ( {purchases.status === 'loading' ? (
<Loader /> <Loader />
@@ -113,13 +122,9 @@ function Core({
] ]
return ( return (
<View style={[a.pt_sm]}> <View style={[]}>
<BlueskyPlusLogo width={130} gradient="nordic" />
{isSubscribedToCore ? ( {isSubscribedToCore ? (
<View style={[a.pt_md]}> <CoreSubscriptions subscriptions={state.subscriptions} />
<CoreSubscriptions subscriptions={state.subscriptions} />
</View>
) : null} ) : null}
{!isSubscribedToCore && ( {!isSubscribedToCore && (
@@ -136,6 +141,8 @@ function Core({
</View> </View>
) : ( ) : (
<> <>
<BlueskyPlusLogo width={130} gradient="nordic" />
<Text style={[a.text_3xl, a.font_heavy, a.pt_md, a.pb_xs]}> <Text style={[a.text_3xl, a.font_heavy, a.pt_md, a.pb_xs]}>
<Trans>Building a better internet needs your support.</Trans> <Trans>Building a better internet needs your support.</Trans>
</Text> </Text>
@@ -221,6 +228,7 @@ function Core({
</> </>
)} )}
{/*
<View style={[a.pt_md]}> <View style={[a.pt_md]}>
<Text style={[a.mb_md, a.text_xs]}> <Text style={[a.mb_md, a.text_xs]}>
<InlineLinkText <InlineLinkText
@@ -245,6 +253,7 @@ function Core({
</InlineLinkText> </InlineLinkText>
</Text> </Text>
</View> </View>
*/}
</View> </View>
) )
} }
@@ -254,51 +263,134 @@ function CoreSubscriptions(props: {subscriptions: APISubscription[]}) {
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const {mutateAsync: manageSubscription} = useManageSubscription() const {mutateAsync: manageSubscription} = useManageSubscription()
const copy = React.useMemo(() => {
return {
[SubscriptionOfferingId.CoreMonthly]: {
title: _(msg`Monthly`),
},
[SubscriptionOfferingId.CoreAnnual]: {
title: _(msg`Annual`),
},
}
}, [_])
return props.subscriptions.map(sub => { return props.subscriptions.map(sub => {
const endDate = i18n.date(new Date(sub.periodEndsAt), {dateStyle: 'medium'}) const endDate = i18n.date(new Date(sub.periodEndsAt), {dateStyle: 'medium'})
const StatusIcon = sub.renews ? Rotate : Clock const StatusIcon = sub.renews ? Rotate : Clock
const c = copy[sub.offering]
const canManage =
(isWeb && sub.platform === PlatformId.Web) ||
(isIOS && sub.platform === PlatformId.Ios) ||
(isAndroid && sub.platform === PlatformId.Android)
const PlatformIcon = {
[PlatformId.Ios]: AppleLogo,
[PlatformId.Android]: AndroidLogo,
[PlatformId.Web]: Globe,
}[sub.platform]
return ( return (
<View <View
key={sub.purchasedAt} key={sub.purchasedAt}
style={[ style={[
a.p_lg, a.p_md,
a.py_md, a.px_lg,
a.rounded_md, a.rounded_md,
a.border, a.border,
a.gap_xs, a.gap_xs,
a.overflow_hidden, a.overflow_hidden,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
]}> ]}>
<GradientFill <View
gradient={tokens.gradients.nordic} style={[
style={{opacity: 0.1}} a.flex_row,
/> a.justify_between,
a.align_center,
{paddingBottom: 6},
]}>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
<Logotype width={75} fill={t.atoms.text.color} />
<View style={[a.flex_row, a.justify_between, a.align_center]}> <Text
<View> style={[
<View style={[a.flex_row, a.align_center, a.gap_xs]}> a.text_sm,
<Text a.rounded_full,
style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}> a.font_bold,
Active {
</Text> paddingVertical: 3,
</View> paddingHorizontal: 5,
backgroundColor: tokens.blueskyPlus.dark,
color: tokens.blueskyPlus.light,
},
]}>
{c.title}
</Text>
<View style={[a.flex_row, a.align_center, a.gap_xs]}> <Text
<StatusIcon size="sm" fill={t.atoms.text_contrast_low.color} /> style={[
<Text style={[a.text_sm]}>{endDate}</Text> a.text_sm,
</View> a.rounded_full,
a.font_bold,
{
paddingVertical: 3,
paddingHorizontal: 5,
borderWidth: sub.renews ? 0 : a.border.borderWidth,
borderColor: t.palette.negative_500,
backgroundColor: sub.renews
? tokens.blueskyPlus.light
: t.atoms.bg.backgroundColor,
color: sub.renews
? tokens.blueskyPlus.dark
: t.atoms.text.color,
},
]}>
{sub.renews ? <Trans>Active</Trans> : <Trans>Cancelled</Trans>}
</Text>
</View> </View>
<Link
label={_('Manage subscription')} <View style={[a.flex_row, a.align_center, a.gap_xs]}>
size="small" <StatusIcon size="sm" fill={t.atoms.text_contrast_low.color} />
variant="ghost" <Text style={[a.text_sm, a.font_bold]}>{endDate}</Text>
shape="round" </View>
{...createStaticClick(() => { </View>
manageSubscription()
})} <Divider />
style={[a.justify_center]}>
<ButtonIcon icon={Gear} size="lg" /> <View
</Link> style={[
a.flex_row,
a.justify_between,
a.align_center,
{paddingTop: 6},
]}>
<View
style={[
a.flex_row,
a.justify_between,
a.align_center,
a.gap_xs,
{left: -1},
]}>
<PlatformIcon size="md" fill={t.atoms.text_contrast_low.color} />
<Text
style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium]}>
<Trans>Managed via web</Trans>
</Text>
</View>
{canManage && (
<Link
label={_('Manage subscription')}
size="tiny"
variant="solid"
color="secondary_inverted"
{...createStaticClick(() => {
manageSubscription()
})}
style={[a.justify_center]}>
<ButtonText>
<Trans>Manage</Trans>
</ButtonText>
</Link>
)}
</View> </View>
</View> </View>
) )
+1
View File
@@ -48,6 +48,7 @@ export type SubscriptionOffering =
export type APISubscription = { export type APISubscription = {
group: SubscriptionGroupId group: SubscriptionGroupId
platform: PlatformId platform: PlatformId
offering: SubscriptionOfferingId
renews: boolean renews: boolean
periodDtartsAt: string periodDtartsAt: string
periodEndsAt: string periodEndsAt: string