Files
bsky-social-app/src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx
T
Eric Bailey 5922c6622f [APP-1767] Live event feeds (#9696)
* Update env config for new worker setup

* Add liveEvents context

* Add live event feed card to Explore

* Add Discover banner/trending handling, device storage

* Add prefs methods, hook up to Discover banner

* Add settings toggle

* Fix up metrics and mutations

* Simplify undo logic

* Handle previews

* Update overlay color and text color handling

* Update metrics

* Handle live event in feed card

* Show multiple feeds on Explore for bsky team

* Fix dev/preview handling bug

* Add sidebar cards, update types

* Fix type error

* Fix type error

* Feedback

* Blurhash fix

* Copy update

* Dev only debug hook
2026-01-14 15:09:01 -06:00

172 lines
4.8 KiB
TypeScript

import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useCleanError} from '#/lib/hooks/useCleanError'
import {isNative} from '#/platform/detection'
import {atoms as a, web} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Loader} from '#/components/Loader'
import * as toast from '#/components/Toast'
import {Span, Text} from '#/components/Typography'
import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences'
import {
type LiveEventFeed,
type LiveEventFeedMetricContext,
} from '#/features/liveEvents/types'
export {useDialogControl} from '#/components/Dialog'
export function LiveEventFeedOptionsMenu({
control,
feed,
metricContext,
}: {
control: Dialog.DialogControlProps
feed: LiveEventFeed
metricContext: LiveEventFeedMetricContext
}) {
const {_} = useLingui()
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Configure live event banner`)}
style={[web({maxWidth: 400})]}>
<Inner control={control} feed={feed} metricContext={metricContext} />
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
function Inner({
control,
feed,
metricContext,
}: {
control: Dialog.DialogControlProps
feed: LiveEventFeed
metricContext: LiveEventFeedMetricContext
}) {
const {_} = useLingui()
const {
isPending,
mutate: update,
error: rawError,
variables,
} = useUpdateLiveEventPreferences({
feed,
metricContext,
onUpdateSuccess({undoAction}) {
toast.show(
<toast.Outer>
<toast.Icon />
<toast.Text>
<Trans>Your live event preferences have been updated.</Trans>
</toast.Text>
{undoAction && (
<toast.Action
label={_(msg`Undo`)}
onPress={() => {
if (undoAction) {
update(undoAction)
}
}}>
<Trans>Undo</Trans>
</toast.Action>
)}
</toast.Outer>,
{
type: 'success',
},
)
/*
* If there is no `undoAction`, it means that the action was already
* undone, and therefore the menu would have been closed prior to the
* undo happening.
*/
if (undoAction) {
control.close()
}
},
})
const cleanError = useCleanError()
const error = rawError ? cleanError(rawError) : undefined
const isHidingFeed = variables?.type === 'hideFeed' && isPending
const isHidingAllFeeds = variables?.type === 'toggleHideAllFeeds' && isPending
return (
<View style={[a.gap_lg]}>
<View style={[a.gap_sm]}>
<Text style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
<Trans>Live event options</Trans>
</Text>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
Live events appear occasionally when something exciting is
happening. If you'd like, you can hide this particular event, or all
events for this placement in your app interface.
</Trans>
</Text>
<Text style={[a.text_md, a.leading_snug]}>
<Trans>
If you choose to hide all events, you can always re-enable them from{' '}
<Span style={[a.font_semi_bold]}>Settings → Content & Media</Span>.
</Trans>
</Text>
</View>
<View style={[a.gap_sm]}>
<Button
label={_(msg`Hide this event`)}
size="large"
color="primary_subtle"
onPress={() => {
update({type: 'hideFeed', id: feed.id})
}}>
<ButtonText>
<Trans>Hide this event</Trans>
</ButtonText>
{isHidingFeed && <ButtonIcon icon={Loader} />}
</Button>
<Button
label={_(msg`Hide all events`)}
size="large"
color="secondary"
onPress={() => {
update({type: 'toggleHideAllFeeds'})
}}>
<ButtonText>
<Trans>Hide all events</Trans>
</ButtonText>
{isHidingAllFeeds && <ButtonIcon icon={Loader} />}
</Button>
{isNative && (
<Button
label={_(msg`Cancel`)}
size="large"
color="secondary_inverted"
onPress={() => control.close()}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
)}
</View>
{error && (
<Admonition type="error">
{error.clean || error.raw || _(msg`An unknown error occurred.`)}
</Admonition>
)}
</View>
)
}