Merge conflicts

This commit is contained in:
Eric Bailey
2026-01-14 15:19:25 -06:00
parent 85eac1f505
commit d5a383bc58
15 changed files with 361 additions and 58 deletions
+1
View File
@@ -557,6 +557,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
a.align_center, a.align_center,
a.justify_center, a.justify_center,
a.curve_continuous, a.curve_continuous,
a.overflow_hidden,
baseStyles, baseStyles,
style, style,
...(state.hovered || state.pressed ...(state.hovered || state.pressed
+8 -2
View File
@@ -359,7 +359,13 @@ export function FlatListFooter({children}: {children: React.ReactNode}) {
) )
} }
export function Handle({difference = false}: {difference?: boolean}) { export function Handle({
difference = false,
fill,
}: {
difference?: boolean
fill?: string
}) {
const t = useTheme() const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {screenReaderEnabled} = useA11y() const {screenReaderEnabled} = useA11y()
@@ -390,7 +396,7 @@ export function Handle({difference = false}: {difference?: boolean}) {
opacity: 0.75, opacity: 0.75,
} }
: { : {
backgroundColor: t.palette.contrast_975, backgroundColor: fill || t.palette.contrast_975,
opacity: 0.5, opacity: 0.5,
}, },
]} ]}
+5 -3
View File
@@ -167,6 +167,7 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
a.gap_sm, a.gap_sm,
a.px_md, a.px_md,
a.rounded_md, a.rounded_md,
a.overflow_hidden,
a.border, a.border,
t.atoms.bg_contrast_25, t.atoms.bg_contrast_25,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
@@ -193,7 +194,6 @@ export function ItemText({children, style}: ItemTextProps) {
a.text_md, a.text_md,
a.font_semi_bold, a.font_semi_bold,
t.atoms.text_contrast_high, t.atoms.text_contrast_high,
{paddingTop: 3},
style, style,
disabled && t.atoms.text_contrast_low, disabled && t.atoms.text_contrast_low,
]}> ]}>
@@ -202,14 +202,16 @@ export function ItemText({children, style}: ItemTextProps) {
) )
} }
export function ItemIcon({icon: Comp}: ItemIconProps) { export function ItemIcon({icon: Comp, fill}: ItemIconProps) {
const t = useTheme() const t = useTheme()
const {disabled} = useMenuItemContext() const {disabled} = useMenuItemContext()
return ( return (
<Comp <Comp
size="lg" size="lg"
fill={ fill={
disabled fill
? fill({disabled})
: disabled
? t.atoms.text_contrast_low.color ? t.atoms.text_contrast_low.color
: t.atoms.text_contrast_medium.color : t.atoms.text_contrast_medium.color
} }
+5 -2
View File
@@ -262,6 +262,7 @@ export function Item({children, label, onPress, style, ...rest}: ItemProps) {
a.gap_lg, a.gap_lg,
a.py_sm, a.py_sm,
a.rounded_xs, a.rounded_xs,
a.overflow_hidden,
{minHeight: 32, paddingHorizontal: 10}, {minHeight: 32, paddingHorizontal: 10},
web({outline: 0}), web({outline: 0}),
(hovered || focused) && (hovered || focused) &&
@@ -302,7 +303,7 @@ export function ItemText({children, style}: ItemTextProps) {
) )
} }
export function ItemIcon({icon: Comp, position = 'left'}: ItemIconProps) { export function ItemIcon({icon: Comp, position = 'left', fill}: ItemIconProps) {
const t = useTheme() const t = useTheme()
const {disabled} = useMenuItemContext() const {disabled} = useMenuItemContext()
return ( return (
@@ -319,7 +320,9 @@ export function ItemIcon({icon: Comp, position = 'left'}: ItemIconProps) {
<Comp <Comp
size="md" size="md"
fill={ fill={
disabled fill
? fill({disabled})
: disabled
? t.atoms.text_contrast_low.color ? t.atoms.text_contrast_low.color
: t.atoms.text_contrast_medium.color : t.atoms.text_contrast_medium.color
} }
+1
View File
@@ -107,6 +107,7 @@ export type ItemTextProps = React.PropsWithChildren<TextStyleProp & {}>
export type ItemIconProps = React.PropsWithChildren<{ export type ItemIconProps = React.PropsWithChildren<{
icon: React.ComponentType<SVGIconProps> icon: React.ComponentType<SVGIconProps>
position?: 'left' | 'right' position?: 'left' | 'right'
fill?: (props: {disabled: boolean}) => string
}> }>
export type GroupProps = React.PropsWithChildren<ViewStyleProp & {}> export type GroupProps = React.PropsWithChildren<ViewStyleProp & {}>
@@ -0,0 +1,201 @@
import {useCallback} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {LinearGradient} from 'expo-linear-gradient'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme, utils, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
import {
createIsEnabledCheck,
isExistingUserAsOf,
} from '#/components/dialogs/nuxs/utils'
import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
import {Text} from '#/components/Typography'
import {IS_E2E} from '#/env'
export const enabled = createIsEnabledCheck(props => {
return (
!IS_E2E &&
isExistingUserAsOf(
'2026-01-16T00:00:00.000Z',
props.currentProfile.createdAt,
) &&
props.gate('live_now_beta')
)
})
export function LiveNowBetaDialog() {
const t = useTheme()
const {_} = useLingui()
const nuxDialogs = useNuxDialogContext()
const control = Dialog.useDialogControl()
Dialog.useAutoOpen(control)
const onClose = useCallback(() => {
nuxDialogs.dismissActiveNux()
}, [nuxDialogs])
return (
<Dialog.Outer
control={control}
onClose={onClose}
nativeOptions={{preventExpansion: true}}>
<Dialog.Handle fill={t.palette.primary_700} />
<Dialog.ScrollableInner
label={_(msg`Show when youre live`)}
style={[web({maxWidth: 440})]}
contentContainerStyle={[
{
paddingTop: 0,
paddingLeft: 0,
paddingRight: 0,
},
]}>
<View
style={[
a.align_center,
a.overflow_hidden,
{
gap: 16,
paddingTop: isWeb ? 24 : 40,
borderTopLeftRadius: a.rounded_md.borderRadius,
borderTopRightRadius: a.rounded_md.borderRadius,
},
]}>
<LinearGradient
colors={[
t.palette.primary_100,
utils.alpha(t.palette.primary_100, 0),
]}
locations={[0, 1]}
start={{x: 0, y: 0}}
end={{x: 0, y: 1}}
style={[a.absolute, a.inset_0]}
/>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<BeakerIcon fill={t.palette.primary_700} size="sm" />
<Text
style={[
a.font_semi_bold,
{
color: t.palette.primary_700,
},
]}>
<Trans>Beta Feature</Trans>
</Text>
</View>
<View
style={[
a.relative,
a.w_full,
{
paddingTop: 8,
paddingHorizontal: 32,
paddingBottom: 32,
},
]}>
<View
style={[
{
borderRadius: 24,
aspectRatio: 326 / 105,
},
isWeb
? [
{
boxShadow: `0px 10px 15px -3px ${utils.alpha(t.palette.primary_900, 0.4)}`,
},
]
: [
t.atoms.shadow_md,
{
shadowColor: utils.alpha(t.palette.primary_900, 0.4),
shadowOpacity: 0.2,
shadowOffset: {
width: 0,
height: 10,
},
},
],
]}>
<Image
accessibilityIgnoresInvertColors
source={require('../../../../assets/images/bookmarks_announcement_nux.webp')}
style={[
a.w_full,
{
aspectRatio: 326 / 105,
},
]}
alt={_(
msg({
message: `A screenshot of a post from @rtgame.bsky.social, showing the user is currently livestreaming content on Twitch. The post reads: "New stream. New energy. Same chaos. Tap in now — we're just getting started!"`,
comment:
'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.',
}),
)}
/>
</View>
</View>
</View>
<View style={[a.align_center, a.px_xl, a.gap_2xl, a.pb_sm]}>
<View style={[a.gap_sm, a.align_center]}>
<Text
style={[
a.text_3xl,
a.leading_tight,
a.font_bold,
a.text_center,
{
fontSize: isWeb ? 28 : 32,
maxWidth: 360,
},
]}>
<Trans>Show when youre live</Trans>
</Text>
<Text
style={[
a.text_md,
a.leading_snug,
a.text_center,
{
maxWidth: 340,
},
]}>
<Trans>
Streaming on Twitch? Set your live status on Bluesky to add a
badge to your avatar. Tapping it takes people straight to your
stream.
</Trans>
</Text>
</View>
{!isWeb && (
<Button
label={_(msg`Close`)}
size="large"
color="primary"
onPress={() => {
control.close()
}}
style={[a.w_full]}>
<ButtonText>
<Trans>Close</Trans>
</ButtonText>
</Button>
)}
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
+6 -8
View File
@@ -20,9 +20,9 @@ import {useProfileQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session' import {type SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell' import {useOnboardingState} from '#/state/shell'
import { import {
enabled as isFindContactsAnnouncementEnabled, enabled as isLiveNowBetaDialogEnabled,
FindContactsAnnouncement, LiveNowBetaDialog,
} from '#/components/dialogs/nuxs/FindContactsAnnouncement' } from '#/components/dialogs/nuxs/LiveNowBetaDialog'
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils' import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
@@ -37,8 +37,8 @@ const queuedNuxs: {
enabled?: (props: EnabledCheckProps) => boolean enabled?: (props: EnabledCheckProps) => boolean
}[] = [ }[] = [
{ {
id: Nux.FindContactsAnnouncement, id: Nux.LiveNowBetaDialog,
enabled: isFindContactsAnnouncementEnabled, enabled: isLiveNowBetaDialogEnabled,
}, },
] ]
@@ -186,9 +186,7 @@ function Inner({
return ( return (
<Context.Provider value={ctx}> <Context.Provider value={ctx}>
{/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/} {/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
{activeNux === Nux.FindContactsAnnouncement && ( {activeNux === Nux.LiveNowBetaDialog && <LiveNowBetaDialog />}
<FindContactsAnnouncement />
)}
</Context.Provider> </Context.Provider>
) )
} }
+31
View File
@@ -0,0 +1,31 @@
import {View, type ViewStyle} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
/**
* The little blue dot used to nudge a user towards a certain feature. The dot
* is absolutely positioned, and is intended to be configured by passing in
* positional styles via `top`, `bottom`, `left`, and `right` props.
*/
export function Dot({
top,
bottom,
left,
right,
}: Pick<ViewStyle, 'top' | 'bottom' | 'left' | 'right'>) {
const t = useTheme()
return (
<View style={[a.absolute, {top, bottom, left, right}]}>
<View
style={[
a.rounded_full,
{
height: 8,
width: 8,
backgroundColor: t.palette.primary_500,
},
]}
/>
</View>
)
}
+19
View File
@@ -0,0 +1,19 @@
import {LinearGradient} from 'expo-linear-gradient'
import {atoms as a, useTheme, utils} from '#/alf'
export function Gradient() {
const t = useTheme()
return (
<LinearGradient
colors={[
utils.alpha(t.palette.primary_500, 0.2),
utils.alpha(t.palette.primary_500, 0.1),
]}
locations={[0, 1]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={[a.absolute, a.inset_0]}
/>
)
}
+4 -9
View File
@@ -7,7 +7,7 @@ import {
import {isAfter, parseISO} from 'date-fns' import {isAfter, parseISO} from 'date-fns'
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
import {useLiveNowConfig} from '#/state/service-config' import {type LiveNowConfig, useLiveNowConfig} from '#/state/service-config'
import {useTickEveryMinute} from '#/state/shell' import {useTickEveryMinute} from '#/state/shell'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
@@ -20,7 +20,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
tick! // revalidate every minute tick! // revalidate every minute
if (shadowed && 'status' in shadowed && shadowed.status) { if (shadowed && 'status' in shadowed && shadowed.status) {
const isValid = validateStatus(shadowed.did, shadowed.status, config) const isValid = validateStatus(shadowed.status, config)
const isDisabled = shadowed.status.isDisabled || false const isDisabled = shadowed.status.isDisabled || false
const isActive = isStatusStillActive(shadowed.status.expiresAt) const isActive = isStatusStillActive(shadowed.status.expiresAt)
if (isValid && !isDisabled && isActive) { if (isValid && !isDisabled && isActive) {
@@ -65,19 +65,14 @@ export function isStatusStillActive(timeStr: string | undefined) {
} }
export function validateStatus( export function validateStatus(
did: string,
status: AppBskyActorDefs.StatusView, status: AppBskyActorDefs.StatusView,
config: {did: string; domains: string[]}[], config: LiveNowConfig,
) { ) {
if (status.status !== 'app.bsky.actor.status#live') return false if (status.status !== 'app.bsky.actor.status#live') return false
const sources = config.find(cfg => cfg.did === did)
if (!sources) {
return false
}
try { try {
if (AppBskyEmbedExternal.isView(status.embed)) { if (AppBskyEmbedExternal.isView(status.embed)) {
const url = new URL(status.embed.external.uri) const url = new URL(status.embed.external.uri)
return sources.domains.includes(url.hostname) return config.allowedDomains.includes(url.hostname)
} else { } else {
return false return false
} }
+1
View File
@@ -8,6 +8,7 @@ export type Gate =
| 'explore_show_suggested_feeds' | 'explore_show_suggested_feeds'
| 'feed_reply_button_open_thread' | 'feed_reply_button_open_thread'
| 'is_bsky_team_member' // special, do not remove | 'is_bsky_team_member' // special, do not remove
| 'live_now_beta'
| 'old_postonboarding' | 'old_postonboarding'
| 'onboarding_add_video_feed' | 'onboarding_add_video_feed'
| 'onboarding_suggested_starterpacks' | 'onboarding_suggested_starterpacks'
+12
View File
@@ -12,6 +12,8 @@ export enum Nux {
BookmarksAnnouncement = 'BookmarksAnnouncement', BookmarksAnnouncement = 'BookmarksAnnouncement',
FindContactsAnnouncement = 'FindContactsAnnouncement', FindContactsAnnouncement = 'FindContactsAnnouncement',
FindContactsDismissibleBanner = 'FindContactsDismissibleBanner', FindContactsDismissibleBanner = 'FindContactsDismissibleBanner',
LiveNowBetaDialog = 'LiveNowBetaDialog',
LiveNowBetaNudge = 'LiveNowBetaNudge',
/* /*
* Blocking announcements. New IDs are required for each new announcement. * Blocking announcements. New IDs are required for each new announcement.
@@ -62,6 +64,14 @@ export type AppNux = BaseNux<
id: Nux.FindContactsDismissibleBanner id: Nux.FindContactsDismissibleBanner
data: undefined data: undefined
} }
| {
id: Nux.LiveNowBetaDialog
data: undefined
}
| {
id: Nux.LiveNowBetaNudge
data: undefined
}
> >
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = { export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
@@ -75,4 +85,6 @@ export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
[Nux.BookmarksAnnouncement]: undefined, [Nux.BookmarksAnnouncement]: undefined,
[Nux.FindContactsAnnouncement]: undefined, [Nux.FindContactsAnnouncement]: undefined,
[Nux.FindContactsDismissibleBanner]: undefined, [Nux.FindContactsDismissibleBanner]: undefined,
[Nux.LiveNowBetaDialog]: undefined,
[Nux.LiveNowBetaNudge]: undefined,
} }
+3 -11
View File
@@ -93,16 +93,10 @@ export function useLiveNowConfig(): LiveNowConfig {
const ctx = useContext(LiveNowContext) const ctx = useContext(LiveNowContext)
const canGoLive = useCanGoLive() const canGoLive = useCanGoLive()
const {currentAccount} = useSession() const {currentAccount} = useSession()
if (!canGoLive) return {allowedDomains: []} if (!currentAccount?.did || !canGoLive) return {allowedDomains: []}
if (!currentAccount?.did) return {allowedDomains: []}
const vip = ctx.find(live => live.did === currentAccount.did) const vip = ctx.find(live => live.did === currentAccount.did)
if (vip) {
return { return {
allowedDomains: vip.domains.concat(DEFAULT_LIVE_ALLOWED_DOMAINS), allowedDomains: DEFAULT_LIVE_ALLOWED_DOMAINS.concat(vip ? vip.domains : []),
}
}
return {
allowedDomains: DEFAULT_LIVE_ALLOWED_DOMAINS,
} }
} }
@@ -110,9 +104,7 @@ export function useCanGoLive() {
const gate = useGate() const gate = useGate()
const {hasSession} = useSession() const {hasSession} = useSession()
if (!hasSession) return false if (!hasSession) return false
const isInBeta = IS_DEV ? true : gate('live_now_beta') return IS_DEV ? true : gate('live_now_beta')
if (!isInBeta) return false
return true
} }
export function useCheckEmailConfirmed() { export function useCheckEmailConfirmed() {
+1 -1
View File
@@ -954,7 +954,7 @@ let PostFeed = ({
const actor = post.author const actor = post.author
if ( if (
actor.status && actor.status &&
validateStatus(actor.did, actor.status, liveNowConfig) && validateStatus(actor.status, liveNowConfig) &&
isStatusStillActive(actor.status.expiresAt) isStatusStillActive(actor.status.expiresAt)
) { ) {
if (!seenActorWithStatusRef.current.has(actor.did)) { if (!seenActorWithStatusRef.current.has(actor.did)) {
+48 -7
View File
@@ -15,6 +15,7 @@ import {logger} from '#/logger'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
import { import {
RQKEY as profileQueryKey, RQKEY as profileQueryKey,
useProfileBlockMutationQueue, useProfileBlockMutationQueue,
@@ -25,6 +26,7 @@ import {useCanGoLive} from '#/state/service-config'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {EventStopper} from '#/view/com/util/EventStopper' import {EventStopper} from '#/view/com/util/EventStopper'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button' import {Button, ButtonIcon} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog' import {StarterPackDialog} from '#/components/dialogs/StarterPackDialog'
@@ -59,6 +61,8 @@ import * as Prompt from '#/components/Prompt'
import {useFullVerificationState} from '#/components/verification' import {useFullVerificationState} from '#/components/verification'
import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt'
import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt'
import {Dot} from '#/features/nuxs/components/Dot'
import {Gradient} from '#/features/nuxs/components/Gradient'
import {useDevMode} from '#/storage/hooks/dev-mode' import {useDevMode} from '#/storage/hooks/dev-mode'
let ProfileMenu = ({ let ProfileMenu = ({
@@ -66,6 +70,7 @@ let ProfileMenu = ({
}: { }: {
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount, hasSession} = useSession() const {currentAccount, hasSession} = useSession()
const {openModal} = useModalControls() const {openModal} = useModalControls()
@@ -79,8 +84,12 @@ let ProfileMenu = ({
const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked
const [devModeEnabled] = useDevMode() const [devModeEnabled] = useDevMode()
const verification = useFullVerificationState({profile}) const verification = useFullVerificationState({profile})
const canGoLive = useCanGoLive(currentAccount?.did) const canGoLive = useCanGoLive()
const status = useActorStatus(profile) const status = useActorStatus(profile)
const statusNudge = useNux(Nux.LiveNowBetaNudge)
const statusNudgeActive =
canGoLive && statusNudge.status === 'ready' && !statusNudge.nux?.completed
const {mutate: saveNux} = useSaveNux()
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
@@ -229,6 +238,7 @@ let ProfileMenu = ({
<Menu.Trigger label={_(msg`More options`)}> <Menu.Trigger label={_(msg`More options`)}>
{({props}) => { {({props}) => {
return ( return (
<>
<Button <Button
{...props} {...props}
testID="profileHeaderDropdownBtn" testID="profileHeaderDropdownBtn"
@@ -238,8 +248,12 @@ let ProfileMenu = ({
color="secondary" color="secondary"
size="small" size="small"
shape="round"> shape="round">
<Gradient />
<ButtonIcon icon={Ellipsis} size="sm" /> <ButtonIcon icon={Ellipsis} size="sm" />
</Button> </Button>
{statusNudgeActive && <Dot top={1} right={1} />}
</>
) )
}} }}
</Menu.Trigger> </Menu.Trigger>
@@ -337,11 +351,19 @@ let ProfileMenu = ({
? _(msg`Edit live status`) ? _(msg`Edit live status`)
: _(msg`Go live`) : _(msg`Go live`)
} }
onPress={ onPress={() => {
status.isDisabled if (status.isDisabled) {
? goLiveDisabledDialogControl.open goLiveDisabledDialogControl.open()
: goLiveDialogControl.open } else {
}> goLiveDialogControl.open()
}
saveNux({
id: Nux.LiveNowBetaNudge,
data: undefined,
completed: true,
})
}}>
<Gradient />
<Menu.ItemText> <Menu.ItemText>
{status.isDisabled ? ( {status.isDisabled ? (
<Trans>Go live (disabled)</Trans> <Trans>Go live (disabled)</Trans>
@@ -351,7 +373,26 @@ let ProfileMenu = ({
<Trans>Go live</Trans> <Trans>Go live</Trans>
)} )}
</Menu.ItemText> </Menu.ItemText>
<Menu.ItemIcon icon={LiveIcon} /> {statusNudgeActive && (
<Menu.ItemText
style={[
a.flex_0,
{
color: t.palette.primary_500,
right: isWeb ? -8 : -4,
},
]}>
<Trans>New</Trans>
</Menu.ItemText>
)}
<Menu.ItemIcon
icon={LiveIcon}
fill={
statusNudgeActive
? () => t.palette.primary_500
: undefined
}
/>
</Menu.Item> </Menu.Item>
)} )}
{verification.viewer.role === 'verifier' && {verification.viewer.role === 'verifier' &&