Add blocking announcement dialog feature
This commit is contained in:
@@ -22,6 +22,7 @@ export const Context = createContext<DialogContextProps>({
|
|||||||
disableDrag: false,
|
disableDrag: false,
|
||||||
setDisableDrag: () => {},
|
setDisableDrag: () => {},
|
||||||
isWithinDialog: false,
|
isWithinDialog: false,
|
||||||
|
preventDismiss: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
export function useDialogContext() {
|
export function useDialogContext() {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export function Outer({
|
|||||||
control,
|
control,
|
||||||
onClose,
|
onClose,
|
||||||
nativeOptions,
|
nativeOptions,
|
||||||
|
preventDismiss,
|
||||||
testID,
|
testID,
|
||||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
const themeName = useThemeName()
|
const themeName = useThemeName()
|
||||||
@@ -165,6 +166,7 @@ export function Outer({
|
|||||||
cornerRadius={20}
|
cornerRadius={20}
|
||||||
backgroundColor={t.atoms.bg.backgroundColor}
|
backgroundColor={t.atoms.bg.backgroundColor}
|
||||||
{...nativeOptions}
|
{...nativeOptions}
|
||||||
|
preventDismiss={preventDismiss ?? nativeOptions?.preventDismiss}
|
||||||
onSnapPointChange={onSnapPointChange}
|
onSnapPointChange={onSnapPointChange}
|
||||||
onStateChange={onStateChange}
|
onStateChange={onStateChange}
|
||||||
disableDrag={disableDrag}>
|
disableDrag={disableDrag}>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function Outer({
|
|||||||
control,
|
control,
|
||||||
onClose,
|
onClose,
|
||||||
webOptions,
|
webOptions,
|
||||||
|
preventDismiss,
|
||||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
@@ -52,7 +53,9 @@ export function Outer({
|
|||||||
}, [setIsOpen, setDialogIsOpen, control.id])
|
}, [setIsOpen, setDialogIsOpen, control.id])
|
||||||
|
|
||||||
const close = React.useCallback<DialogControlProps['close']>(
|
const close = React.useCallback<DialogControlProps['close']>(
|
||||||
cb => {
|
(cb, force) => {
|
||||||
|
if (preventDismiss && !force) return
|
||||||
|
|
||||||
setDialogIsOpen(control.id, false)
|
setDialogIsOpen(control.id, false)
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
|
|
||||||
@@ -72,7 +75,7 @@ export function Outer({
|
|||||||
|
|
||||||
onClose?.()
|
onClose?.()
|
||||||
},
|
},
|
||||||
[control.id, onClose, setDialogIsOpen],
|
[control.id, onClose, setDialogIsOpen, preventDismiss],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleBackgroundPress = React.useCallback(async () => {
|
const handleBackgroundPress = React.useCallback(async () => {
|
||||||
@@ -96,8 +99,9 @@ export function Outer({
|
|||||||
disableDrag: false,
|
disableDrag: false,
|
||||||
setDisableDrag: () => {},
|
setDisableDrag: () => {},
|
||||||
isWithinDialog: true,
|
isWithinDialog: true,
|
||||||
|
preventDismiss: preventDismiss ?? false,
|
||||||
}),
|
}),
|
||||||
[close],
|
[close, preventDismiss],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -239,7 +243,10 @@ export const InnerFlatList = React.forwardRef<
|
|||||||
|
|
||||||
export function Close() {
|
export function Close() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {close} = React.useContext(Context)
|
const {close, preventDismiss} = React.useContext(Context)
|
||||||
|
|
||||||
|
if (preventDismiss) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React from 'react'
|
import {
|
||||||
import type {
|
type AccessibilityProps,
|
||||||
AccessibilityProps,
|
type GestureResponderEvent,
|
||||||
GestureResponderEvent,
|
type ScrollViewProps,
|
||||||
ScrollViewProps,
|
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {ViewStyle} from 'react-native'
|
import {type ViewStyle} from 'react-native'
|
||||||
import {StyleProp} from 'react-native'
|
import {type StyleProp} from 'react-native'
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
import {ViewStyleProp} from '#/alf'
|
import {type ViewStyleProp} from '#/alf'
|
||||||
import {BottomSheetViewProps} from '../../../modules/bottom-sheet'
|
import {type BottomSheetViewProps} from '../../../modules/bottom-sheet'
|
||||||
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
import {type BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
||||||
|
|
||||||
type A11yProps = Required<AccessibilityProps>
|
type A11yProps = Required<AccessibilityProps>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export type DialogControlRefProps = {
|
|||||||
open: (
|
open: (
|
||||||
options?: DialogControlOpenOptions & Partial<GestureResponderEvent>,
|
options?: DialogControlOpenOptions & Partial<GestureResponderEvent>,
|
||||||
) => void
|
) => void
|
||||||
close: (callback?: () => void) => void
|
close: (callback?: () => void, force?: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +46,7 @@ export type DialogContextProps = {
|
|||||||
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
|
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
// in the event that the hook is used outside of a dialog
|
// in the event that the hook is used outside of a dialog
|
||||||
isWithinDialog: boolean
|
isWithinDialog: boolean
|
||||||
|
preventDismiss: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DialogControlOpenOptions = {
|
export type DialogControlOpenOptions = {
|
||||||
@@ -62,6 +63,7 @@ export type DialogOuterProps = {
|
|||||||
control: DialogControlProps
|
control: DialogControlProps
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
nativeOptions?: Omit<BottomSheetViewProps, 'children'>
|
nativeOptions?: Omit<BottomSheetViewProps, 'children'>
|
||||||
|
preventDismiss?: boolean
|
||||||
webOptions?: {
|
webOptions?: {
|
||||||
alignCenter?: boolean
|
alignCenter?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import {type ReactNode, useCallback} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {Logo} from '#/view/icons/Logo'
|
||||||
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {Span, Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function BlockingAlertDialog() {
|
||||||
|
const {enabled} = usePolicyUpdate20250801()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{enabled && (
|
||||||
|
<BlockingAnnouncementDialogOuter>
|
||||||
|
<PolicyUpdate20250801 />
|
||||||
|
</BlockingAnnouncementDialogOuter>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useForceClose() {
|
||||||
|
const {close} = Dialog.useDialogContext()
|
||||||
|
return useCallback(
|
||||||
|
(cb?: () => void) => {
|
||||||
|
close(cb, true)
|
||||||
|
},
|
||||||
|
[close],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BlockingAnnouncementDialogOuter({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
}) {
|
||||||
|
const {signinDialogControl: control} = useGlobalDialogsControlContext()
|
||||||
|
|
||||||
|
Dialog.useAutoOpen(control, 1e3)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Outer preventDismiss control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
{children}
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnnouncementBadge() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View style={[a.align_start]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.pl_md,
|
||||||
|
a.pr_lg,
|
||||||
|
a.py_sm,
|
||||||
|
a.rounded_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_xs,
|
||||||
|
{
|
||||||
|
backgroundColor: t.palette.primary_25,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Logo fill={t.palette.primary_600} width={14} />
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.font_bold,
|
||||||
|
{
|
||||||
|
color: t.palette.primary_600,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Trans>Announcement</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function usePolicyUpdate20250801() {
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function PolicyUpdate20250801() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const forceClose = useForceClose()
|
||||||
|
|
||||||
|
const linkStyle = [a.text_md]
|
||||||
|
const links = {
|
||||||
|
terms: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Terms of Service`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Privacy Policy`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
copyright: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Copyright Policy`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
guidelines: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Community Guidelines`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Our blog post`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
forceClose(() => {
|
||||||
|
console.log('closed')
|
||||||
|
})
|
||||||
|
}, [forceClose])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(msg`Sign in to Bluesky or create a new account`)}
|
||||||
|
style={[web({maxWidth: 420})]}>
|
||||||
|
<View style={[a.align_start, a.gap_xl]}>
|
||||||
|
<AnnouncementBadge />
|
||||||
|
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold, a.leading_snug]}>
|
||||||
|
<Trans>Hey there 👋</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text emoji style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
We’re updating our{' '}
|
||||||
|
<InlineLinkText {...links.terms}>Terms of Service</InlineLinkText>
|
||||||
|
,{' '}
|
||||||
|
<InlineLinkText {...links.privacy}>Privacy Policy</InlineLinkText>
|
||||||
|
, and{' '}
|
||||||
|
<InlineLinkText {...links.copyright}>
|
||||||
|
Copyright Policy
|
||||||
|
</InlineLinkText>
|
||||||
|
, <Span style={[]}>effective September 1st, 2025.</Span>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
We're also updating our{' '}
|
||||||
|
<InlineLinkText {...links.guidelines}>
|
||||||
|
Community Guidelines
|
||||||
|
</InlineLinkText>
|
||||||
|
, <Span style={[]}>and we want your input!</Span> These new
|
||||||
|
guidelines will take effect on{' '}
|
||||||
|
<Span style={[]}>October 1st, 2025.</Span>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text emoji style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
Learn more about these changes and how to share your thoughts with
|
||||||
|
us by{' '}
|
||||||
|
<InlineLinkText {...links.blog}>
|
||||||
|
reading our blog post.
|
||||||
|
</InlineLinkText>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.w_full, a.gap_md]}>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Continue`)}
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
onPress={handleClose}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Continue</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.leading_snug,
|
||||||
|
a.text_sm,
|
||||||
|
a.italic,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
By clicking "Continue" you acknowledge that you understand and
|
||||||
|
agree to these updates.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import {useCallback} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {Nux} from '#/state/queries/nuxs'
|
||||||
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {
|
||||||
|
AnnouncementBadge,
|
||||||
|
useAnnouncementState,
|
||||||
|
useForceClose,
|
||||||
|
} from '#/components/dialogs/BlockingAnnouncements/common'
|
||||||
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {Span, Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function useLocalState() {
|
||||||
|
return useAnnouncementState({
|
||||||
|
id: Nux.BlockingAnnouncementPolicyUpdate20250801,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Announcement() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const forceClose = useForceClose()
|
||||||
|
const {complete} = useLocalState()
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
forceClose(() => complete())
|
||||||
|
}, [forceClose, complete])
|
||||||
|
|
||||||
|
const linkStyle = [a.text_md]
|
||||||
|
const links = {
|
||||||
|
terms: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Terms of Service`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Privacy Policy`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
copyright: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Copyright Policy`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
guidelines: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Community Guidelines`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
overridePresentation: true,
|
||||||
|
to: `https://bsky.social/about/support`,
|
||||||
|
label: _(msg`Our blog post`),
|
||||||
|
style: linkStyle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(msg`We’re updating our policies`)}
|
||||||
|
style={[web({maxWidth: 420})]}>
|
||||||
|
<View style={[a.align_start, a.gap_xl]}>
|
||||||
|
<AnnouncementBadge />
|
||||||
|
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold, a.leading_snug]}>
|
||||||
|
<Trans>Hey there 👋</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text emoji style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
We’re updating our{' '}
|
||||||
|
<InlineLinkText {...links.terms}>Terms of Service</InlineLinkText>
|
||||||
|
,{' '}
|
||||||
|
<InlineLinkText {...links.privacy}>Privacy Policy</InlineLinkText>
|
||||||
|
, and{' '}
|
||||||
|
<InlineLinkText {...links.copyright}>
|
||||||
|
Copyright Policy
|
||||||
|
</InlineLinkText>
|
||||||
|
, <Span style={[]}>effective September 1st, 2025.</Span>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
We're also updating our{' '}
|
||||||
|
<InlineLinkText {...links.guidelines}>
|
||||||
|
Community Guidelines
|
||||||
|
</InlineLinkText>
|
||||||
|
, <Span style={[]}>and we want your input!</Span> These new
|
||||||
|
guidelines will take effect on{' '}
|
||||||
|
<Span style={[]}>October 1st, 2025.</Span>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text emoji style={[a.leading_snug, a.text_md]}>
|
||||||
|
<Trans>
|
||||||
|
Learn more about these changes and how to share your thoughts with
|
||||||
|
us by{' '}
|
||||||
|
<InlineLinkText {...links.blog}>
|
||||||
|
reading our blog post.
|
||||||
|
</InlineLinkText>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.w_full, a.gap_md]}>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Continue`)}
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
onPress={handleClose}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Continue</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.leading_snug,
|
||||||
|
a.text_sm,
|
||||||
|
a.italic,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
By clicking "Continue" you acknowledge that you understand and
|
||||||
|
agree to these updates.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import {type ReactNode, useCallback, useMemo} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {Trans} from '@lingui/macro'
|
||||||
|
|
||||||
|
import {type Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
|
||||||
|
import {Logo} from '#/view/icons/Logo'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function useForceClose() {
|
||||||
|
const {close} = Dialog.useDialogContext()
|
||||||
|
return useCallback(
|
||||||
|
(cb?: () => void) => {
|
||||||
|
close(cb, true)
|
||||||
|
},
|
||||||
|
[close],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAnnouncementState({id}: {id: Nux}) {
|
||||||
|
const nux = useNux(id)
|
||||||
|
const {mutate: save, variables} = useSaveNux()
|
||||||
|
const optimisticallyCompleted = !!variables?.completed
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
/**
|
||||||
|
* Until data has loaded, assumed completed
|
||||||
|
*/
|
||||||
|
completed:
|
||||||
|
optimisticallyCompleted || nux.status === 'ready'
|
||||||
|
? nux.nux?.completed === true
|
||||||
|
: true,
|
||||||
|
complete() {
|
||||||
|
save({
|
||||||
|
id,
|
||||||
|
completed: true,
|
||||||
|
data: undefined,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[id, nux, save, optimisticallyCompleted],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BlockingAnnouncementDialogOuter({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
}) {
|
||||||
|
const {signinDialogControl: control} = useGlobalDialogsControlContext()
|
||||||
|
|
||||||
|
Dialog.useAutoOpen(control, 1e3)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Outer preventDismiss control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
{children}
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnnouncementBadge() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View style={[a.align_start]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.pl_md,
|
||||||
|
a.pr_lg,
|
||||||
|
a.py_sm,
|
||||||
|
a.rounded_full,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_xs,
|
||||||
|
{
|
||||||
|
backgroundColor: t.palette.primary_25,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Logo fill={t.palette.primary_600} width={14} />
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.font_bold,
|
||||||
|
{
|
||||||
|
color: t.palette.primary_600,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Trans>Announcement</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {BlockingAnnouncementDialogOuter} from '#/components/dialogs/BlockingAnnouncements/common'
|
||||||
|
import * as PolicyUpdate20250801 from '#/components/dialogs/BlockingAnnouncements/PolicyUpdate20250801'
|
||||||
|
|
||||||
|
export function BlockingAnnouncements() {
|
||||||
|
const policyUpdate20250801 = PolicyUpdate20250801.useLocalState()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See `window.clearNux` example in `/state/queries/nuxs` for a way to clear
|
||||||
|
* NUX state for local testing and debugging.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!policyUpdate20250801.completed && (
|
||||||
|
<BlockingAnnouncementDialogOuter>
|
||||||
|
<PolicyUpdate20250801.Announcement />
|
||||||
|
</BlockingAnnouncementDialogOuter>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,6 +9,11 @@ export enum Nux {
|
|||||||
ActivitySubscriptions = 'ActivitySubscriptions',
|
ActivitySubscriptions = 'ActivitySubscriptions',
|
||||||
AgeAssuranceDismissibleNotice = 'AgeAssuranceDismissibleNotice',
|
AgeAssuranceDismissibleNotice = 'AgeAssuranceDismissibleNotice',
|
||||||
AgeAssuranceDismissibleFeedBanner = 'AgeAssuranceDismissibleFeedBanner',
|
AgeAssuranceDismissibleFeedBanner = 'AgeAssuranceDismissibleFeedBanner',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Blocking announcements. New IDs are required for each new announcement.
|
||||||
|
*/
|
||||||
|
BlockingAnnouncementPolicyUpdate20250801 = 'BlockingAnnouncementPolicyUpdate20250801',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nuxNames = new Set(Object.values(Nux))
|
export const nuxNames = new Set(Object.values(Nux))
|
||||||
@@ -38,6 +43,10 @@ export type AppNux = BaseNux<
|
|||||||
id: Nux.AgeAssuranceDismissibleFeedBanner
|
id: Nux.AgeAssuranceDismissibleFeedBanner
|
||||||
data: undefined
|
data: undefined
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
id: Nux.BlockingAnnouncementPolicyUpdate20250801
|
||||||
|
data: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
||||||
@@ -47,4 +56,5 @@ export const NuxSchemas: Record<Nux, zod.ZodObject<any> | undefined> = {
|
|||||||
[Nux.ActivitySubscriptions]: undefined,
|
[Nux.ActivitySubscriptions]: undefined,
|
||||||
[Nux.AgeAssuranceDismissibleNotice]: undefined,
|
[Nux.AgeAssuranceDismissibleNotice]: undefined,
|
||||||
[Nux.AgeAssuranceDismissibleFeedBanner]: undefined,
|
[Nux.AgeAssuranceDismissibleFeedBanner]: undefined,
|
||||||
|
[Nux.BlockingAnnouncementPolicyUpdate20250801]: undefined,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
|||||||
import {atoms as a, select, useTheme} from '#/alf'
|
import {atoms as a, select, useTheme} from '#/alf'
|
||||||
import {setSystemUITheme} from '#/alf/util/systemUI'
|
import {setSystemUITheme} from '#/alf/util/systemUI'
|
||||||
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
|
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
|
||||||
|
import {BlockingAnnouncements} from '#/components/dialogs/BlockingAnnouncements'
|
||||||
import {EmailDialog} from '#/components/dialogs/EmailDialog'
|
import {EmailDialog} from '#/components/dialogs/EmailDialog'
|
||||||
import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent'
|
import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent'
|
||||||
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
|
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
|
||||||
@@ -155,6 +156,7 @@ function ShellInner() {
|
|||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<MutedWordsDialog />
|
<MutedWordsDialog />
|
||||||
<SigninDialog />
|
<SigninDialog />
|
||||||
|
<BlockingAnnouncements />
|
||||||
<EmailDialog />
|
<EmailDialog />
|
||||||
<AgeAssuranceRedirectDialog />
|
<AgeAssuranceRedirectDialog />
|
||||||
<InAppBrowserConsentDialog />
|
<InAppBrowserConsentDialog />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {ModalsContainer} from '#/view/com/modals/Modal'
|
|||||||
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
||||||
import {atoms as a, select, useTheme} from '#/alf'
|
import {atoms as a, select, useTheme} from '#/alf'
|
||||||
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
|
import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog'
|
||||||
|
import {BlockingAnnouncements} from '#/components/dialogs/BlockingAnnouncements'
|
||||||
import {EmailDialog} from '#/components/dialogs/EmailDialog'
|
import {EmailDialog} from '#/components/dialogs/EmailDialog'
|
||||||
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
|
import {LinkWarningDialog} from '#/components/dialogs/LinkWarning'
|
||||||
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
|
||||||
@@ -70,6 +71,7 @@ function ShellInner() {
|
|||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<MutedWordsDialog />
|
<MutedWordsDialog />
|
||||||
<SigninDialog />
|
<SigninDialog />
|
||||||
|
<BlockingAnnouncements />
|
||||||
<EmailDialog />
|
<EmailDialog />
|
||||||
<AgeAssuranceRedirectDialog />
|
<AgeAssuranceRedirectDialog />
|
||||||
<LinkWarningDialog />
|
<LinkWarningDialog />
|
||||||
|
|||||||
Reference in New Issue
Block a user