diff --git a/src/App.native.tsx b/src/App.native.tsx
index 780d4058f..95625bdff 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -63,6 +63,7 @@ import {Provider as PortalProvider} from '#/components/Portal'
import {Splash} from '#/Splash'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import {AudioCategory, PlatformInfo} from '../modules/expo-bluesky-swiss-army'
+import {NudgeDialogs} from '#/components/dialogs/nudges'
SplashScreen.preventAutoHideAsync()
@@ -131,6 +132,7 @@ function InnerApp() {
style={s.h100pct}>
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 3017a3a26..79120ffdb 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -50,6 +50,7 @@ import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
+import {NudgeDialogs} from '#/components/dialogs/nudges'
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
@@ -113,6 +114,7 @@ function InnerApp() {
+
diff --git a/src/components/dialogs/nudges/TenMillion.tsx b/src/components/dialogs/nudges/TenMillion.tsx
new file mode 100644
index 000000000..9b5d5eae6
--- /dev/null
+++ b/src/components/dialogs/nudges/TenMillion.tsx
@@ -0,0 +1,100 @@
+import React from 'react'
+import {useLingui} from '@lingui/react'
+import {msg} from '@lingui/macro'
+import {View} from 'react-native'
+import ViewShot from 'react-native-view-shot'
+
+import {atoms as a, useBreakpoints, tokens} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {Text} from '#/components/Typography'
+import {GradientFill} from '#/components/GradientFill'
+import {Button, ButtonText} from '#/components/Button'
+import {useComposerControls} from 'state/shell'
+
+import {useContext} from '#/components/dialogs/nudges'
+
+export function TenMillion() {
+ const {_} = useLingui()
+ const {controls} = useContext()
+ const {gtMobile} = useBreakpoints()
+ const {openComposer} = useComposerControls()
+
+ const imageRef = React.useRef(null)
+
+ const share = () => {
+ if (imageRef.current && imageRef.current.capture) {
+ imageRef.current.capture().then(uri => {
+ controls.tenMillion.close(() => {
+ setTimeout(() => {
+ openComposer({
+ text: '10 milly, babyyy',
+ imageUris: [
+ {
+ uri,
+ width: 1000,
+ height: 1000,
+ },
+ ],
+ })
+ }, 1e3)
+ })
+ })
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ 10 milly, babyyy
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/dialogs/nudges/index.tsx b/src/components/dialogs/nudges/index.tsx
new file mode 100644
index 000000000..357d4e2b4
--- /dev/null
+++ b/src/components/dialogs/nudges/index.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+
+import * as Dialog from '#/components/Dialog'
+
+import {TenMillion} from '#/components/dialogs/nudges/TenMillion'
+
+type Context = {
+ controls: {
+ tenMillion: Dialog.DialogOuterProps['control']
+ }
+}
+
+const Context = React.createContext({
+ // @ts-ignore
+ controls: {}
+})
+
+export function useContext() {
+ return React.useContext(Context)
+}
+
+let SHOWN = false
+
+export function NudgeDialogs() {
+ const tenMillion = Dialog.useDialogControl()
+
+ const ctx = React.useMemo(() => {
+ return {
+ controls: {
+ tenMillion
+ }
+ }
+ }, [tenMillion])
+
+ React.useEffect(() => {
+ const t = setTimeout(() => {
+ if (!SHOWN) {
+ SHOWN = true
+ ctx.controls.tenMillion.open()
+ }
+ }, 2e3)
+
+ return () => {
+ clearTimeout(t)
+ }
+ }, [ctx])
+
+ return (
+
+
+
+ )
+}
diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts
index 8cccda48f..67f1c2c38 100644
--- a/src/lib/hooks/useIntentHandler.ts
+++ b/src/lib/hooks/useIntentHandler.ts
@@ -71,7 +71,7 @@ export function useIntentHandler() {
}, [incomingUrl, composeIntent, verifyEmailIntent])
}
-function useComposeIntent() {
+export function useComposeIntent() {
const closeAllActiveElements = useCloseAllActiveElements()
const {openComposer} = useComposerControls()
const {hasSession} = useSession()
@@ -97,6 +97,10 @@ function useComposeIntent() {
if (part.includes('https://') || part.includes('http://')) {
return false
}
+ console.log({
+ part,
+ text: VALID_IMAGE_REGEX.test(part),
+ })
// We also should just filter out cases that don't have all the info we need
return VALID_IMAGE_REGEX.test(part)
})
diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx
index 42696139e..ee1ed6622 100644
--- a/src/view/shell/Composer.web.tsx
+++ b/src/view/shell/Composer.web.tsx
@@ -63,6 +63,7 @@ export function Composer({}: {winHeight: number}) {
mention={state.mention}
openEmojiPicker={onOpenPicker}
text={state.text}
+ imageUris={state.imageUris}
/>
--
2.51.2
From 3c8b3b47823475b93a92dcf82a4cabbda625c323 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 10 Sep 2024 14:23:30 -0500
Subject: [PATCH 02/24] Progress on desktoip
---
src/alf/index.tsx | 18 +-
src/components/dialogs/nudges/TenMillion.tsx | 330 ++++++++++++++++---
src/view/icons/Logomark.tsx | 29 ++
3 files changed, 331 insertions(+), 46 deletions(-)
create mode 100644 src/view/icons/Logomark.tsx
diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index 5fa7d3b1a..d699de6a5 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -18,9 +18,17 @@ export * from '#/alf/util/themeSelector'
export const Context = React.createContext<{
themeName: ThemeName
theme: Theme
+ themes: ReturnType
}>({
themeName: 'light',
theme: defaultTheme,
+ themes: createThemes({
+ hues: {
+ primary: BLUE_HUE,
+ negative: RED_HUE,
+ positive: GREEN_HUE,
+ },
+ }),
})
export function ThemeProvider({
@@ -42,18 +50,22 @@ export function ThemeProvider({
({
+ themes,
themeName: themeName,
theme: theme,
}),
- [theme, themeName],
+ [theme, themeName, themes],
)}>
{children}
)
}
-export function useTheme() {
- return React.useContext(Context).theme
+export function useTheme(theme?: ThemeName) {
+ const ctx = React.useContext(Context)
+ return React.useMemo(() => {
+ return theme ? ctx.themes[theme] : ctx.theme
+ }, [theme, ctx])
}
export function useBreakpoints() {
diff --git a/src/components/dialogs/nudges/TenMillion.tsx b/src/components/dialogs/nudges/TenMillion.tsx
index 9b5d5eae6..869056977 100644
--- a/src/components/dialogs/nudges/TenMillion.tsx
+++ b/src/components/dialogs/nudges/TenMillion.tsx
@@ -1,25 +1,74 @@
import React from 'react'
-import {useLingui} from '@lingui/react'
-import {msg} from '@lingui/macro'
import {View} from 'react-native'
import ViewShot from 'react-native-view-shot'
+import {moderateProfile} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
-import {atoms as a, useBreakpoints, tokens} from '#/alf'
+import {sanitizeDisplayName} from '#/lib/strings/display-names'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {isNative} from '#/platform/detection'
+import {useModerationOpts} from '#/state/preferences/moderation-opts'
+import {useProfileQuery} from '#/state/queries/profile'
+import {useSession} from '#/state/session'
+import {useComposerControls} from 'state/shell'
+import {formatCount} from '#/view/com/util/numeric/format'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {Logomark} from '#/view/icons/Logomark'
+import {
+ atoms as a,
+ ThemeProvider,
+ tokens,
+ useBreakpoints,
+ useTheme,
+} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
-import {Text} from '#/components/Typography'
+import {useContext} from '#/components/dialogs/nudges'
+import {Divider} from '#/components/Divider'
import {GradientFill} from '#/components/GradientFill'
-import {Button, ButtonText} from '#/components/Button'
-import {useComposerControls} from 'state/shell'
+import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
+import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
+import {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
-import {useContext} from '#/components/dialogs/nudges'
+const RATIO = 8 / 10
+const WIDTH = 2000
+const HEIGHT = WIDTH * RATIO
+
+function getFontSize(count: number) {
+ const length = count.toString().length
+ if (length < 7) {
+ return 80
+ } else if (length < 5) {
+ return 100
+ } else {
+ return 70
+ }
+}
export function TenMillion() {
- const {_} = useLingui()
+ const t = useTheme()
+ const lightTheme = useTheme('light')
+ const {_, i18n} = useLingui()
const {controls} = useContext()
const {gtMobile} = useBreakpoints()
const {openComposer} = useComposerControls()
-
const imageRef = React.useRef(null)
+ const {currentAccount} = useSession()
+ const {isLoading: isProfileLoading, data: profile} = useProfileQuery({
+ did: currentAccount!.did,
+ }) // TODO PWI
+ const moderationOpts = useModerationOpts()
+ const moderation = React.useMemo(() => {
+ return profile && moderationOpts
+ ? moderateProfile(profile, moderationOpts)
+ : undefined
+ }, [profile, moderationOpts])
+
+ const isLoading = isProfileLoading || !moderation || !profile
+
+ const userNumber = 56738
const share = () => {
if (imageRef.current && imageRef.current.capture) {
@@ -31,8 +80,8 @@ export function TenMillion() {
imageUris: [
{
uri,
- width: 1000,
- height: 1000,
+ width: WIDTH,
+ height: HEIGHT,
},
],
})
@@ -48,52 +97,247 @@ export function TenMillion() {
+ style={[
+ {
+ padding: 0,
+ },
+ // gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
+ ]}>
-
+
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+
+
+
+
+ {/* Centered content */}
+
+
+
+ Celebrating {formatCount(i18n, 10000000)} users
+ {' '}
+ 🎉
+
+
+
+ #
+
+ {i18n.number(userNumber)}
+
+
+ {/* End centered content */}
+
+
+
+
+
+
+ {sanitizeDisplayName(
+ profile.displayName ||
+ sanitizeHandle(profile.handle),
+ moderation.ui('displayName'),
+ )}
+
+
+
+ {sanitizeHandle(profile.handle, '@')}
+
+
+ {profile.createdAt && (
+
+ {i18n.date(profile.createdAt, {
+ dateStyle: 'long',
+ })}
+
+ )}
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
-
+ You're part of the next wave of the internet.
+
+
+
+ Online culture is too important to be controlled by a few
+ corporations.{' '}
+
+ We’re dedicated to building an open foundation for the social
+ internet so that we can all shape its future.
+
+
+
+
+
+
+
+ Brag a little ;)
+
- 10 milly, babyyy
+
+
-
+
-
+
)
diff --git a/src/view/icons/Logomark.tsx b/src/view/icons/Logomark.tsx
new file mode 100644
index 000000000..5715a1a40
--- /dev/null
+++ b/src/view/icons/Logomark.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+import Svg, {Path, PathProps, SvgProps} from 'react-native-svg'
+
+import {usePalette} from '#/lib/hooks/usePalette'
+
+const ratio = 54 / 61
+
+export function Logomark({
+ fill,
+ ...rest
+}: {fill?: PathProps['fill']} & SvgProps) {
+ const pal = usePalette('default')
+ // @ts-ignore it's fiiiiine
+ const size = parseInt(rest.width || 32)
+
+ return (
+
+ )
+}
--
2.51.2
From eaf0081623154df995e81f2ae430a723539df800 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Tue, 10 Sep 2024 16:20:19 -0500
Subject: [PATCH 03/24] WIP, avi not working on web
---
src/components/dialogs/nudges/TenMillion.tsx | 423 +++++++++++--------
src/lib/canvas.ts | 15 +
src/view/com/util/UserAvatar.tsx | 4 +
3 files changed, 266 insertions(+), 176 deletions(-)
create mode 100644 src/lib/canvas.ts
diff --git a/src/components/dialogs/nudges/TenMillion.tsx b/src/components/dialogs/nudges/TenMillion.tsx
index 869056977..2be5e3491 100644
--- a/src/components/dialogs/nudges/TenMillion.tsx
+++ b/src/components/dialogs/nudges/TenMillion.tsx
@@ -1,10 +1,12 @@
import React from 'react'
import {View} from 'react-native'
import ViewShot from 'react-native-view-shot'
+import {Image} from 'expo-image'
import {moderateProfile} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {getCanvas} from '#/lib/canvas'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {isNative} from '#/platform/detection'
@@ -32,6 +34,7 @@ import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Ima
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
+const DEBUG = false
const RATIO = 8 / 10
const WIDTH = 2000
const HEIGHT = WIDTH * RATIO
@@ -47,6 +50,22 @@ function getFontSize(count: number) {
}
}
+function Frame({children}: {children: React.ReactNode}) {
+ return (
+
+ {children}
+
+ )
+}
+
export function TenMillion() {
const t = useTheme()
const lightTheme = useTheme('light')
@@ -54,7 +73,6 @@ export function TenMillion() {
const {controls} = useContext()
const {gtMobile} = useBreakpoints()
const {openComposer} = useComposerControls()
- const imageRef = React.useRef(null)
const {currentAccount} = useSession()
const {isLoading: isProfileLoading, data: profile} = useProfileQuery({
did: currentAccount!.did,
@@ -65,220 +83,273 @@ export function TenMillion() {
? moderateProfile(profile, moderationOpts)
: undefined
}, [profile, moderationOpts])
+ const [uri, setUri] = React.useState(null)
- const isLoading = isProfileLoading || !moderation || !profile
+ const isLoadingData = isProfileLoading || !moderation || !profile
+ const isLoadingImage = !uri
- const userNumber = 56738
+ const userNumber = 56738 // TODO
+
+ const captureInProgress = React.useRef(false)
+ const imageRef = React.useRef(null)
const share = () => {
- if (imageRef.current && imageRef.current.capture) {
- imageRef.current.capture().then(uri => {
- controls.tenMillion.close(() => {
- setTimeout(() => {
- openComposer({
- text: '10 milly, babyyy',
- imageUris: [
- {
- uri,
- width: WIDTH,
- height: HEIGHT,
- },
- ],
- })
- }, 1e3)
- })
+ if (uri) {
+ controls.tenMillion.close(() => {
+ setTimeout(() => {
+ openComposer({
+ text: '10 milly, babyyy',
+ imageUris: [
+ {
+ uri,
+ width: WIDTH,
+ height: HEIGHT,
+ },
+ ],
+ })
+ }, 1e3)
})
}
}
- return (
-
-
+ const onCanvasReady = async () => {
+ if (
+ imageRef.current &&
+ imageRef.current.capture &&
+ !captureInProgress.current
+ ) {
+ captureInProgress.current = true
+ const uri = await imageRef.current.capture()
+ setUri(uri)
+ }
+ }
-
- {
+ if (uri) {
+ const canvas = await getCanvas(uri)
+ const imgHref = canvas
+ .toDataURL('image/png')
+ .replace('image/png', 'image/octet-stream')
+ const link = document.createElement('a')
+ link.setAttribute('download', `Bluesky 10M Users.png`)
+ link.setAttribute('href', imgHref)
+ link.click()
+ }
+ }
+
+ const canvas = isLoadingData ? null : (
+
-
-
-
+ ]}>
+
+
+
+
+
+
+
-
+
+
+
- {isLoading ? (
-
- ) : (
-
+
+
+ Celebrating {formatCount(i18n, 10000000)} users
+ {' '}
+ 🎉
+
+
-
-
-
+ #
+
+ {i18n.number(userNumber)}
+
+
+ {/* End centered content */}
- {/* Centered content */}
-
-
-
- Celebrating {formatCount(i18n, 10000000)} users
- {' '}
- 🎉
+
+
+
+
+
+ {sanitizeDisplayName(
+ profile.displayName ||
+ sanitizeHandle(profile.handle),
+ moderation.ui('displayName'),
+ )}
-
+
- #
+ {sanitizeHandle(profile.handle, '@')}
- {i18n.number(userNumber)}
-
-
- {/* End centered content */}
-
-
-
-
-
- {sanitizeDisplayName(
- profile.displayName ||
- sanitizeHandle(profile.handle),
- moderation.ui('displayName'),
- )}
+ {profile.createdAt && (
+
+ {i18n.date(profile.createdAt, {
+ dateStyle: 'long',
+ })}
-
-
- {sanitizeHandle(profile.handle, '@')}
-
-
- {profile.createdAt && (
-
- {i18n.date(profile.createdAt, {
- dateStyle: 'long',
- })}
-
- )}
-
-
+ )}
- )}
+
-
+
+
+
+
+
+
+ )
+
+ return (
+
+
+
+
+
+
+
+
+ {isLoadingData || isLoadingImage ? (
+
+ ) : (
+
+ )}
-
+
+
+ {canvas}
+ onPress={download}>
-
- Online culture is too important to be controlled by a few
- corporations.{' '}
+
+
+ Online culture is too important to be controlled by a few
+ corporations.
+ {' '}
- We’re dedicated to building an open foundation for the social
- internet so that we can all shape its future.
+
+ We’re dedicated to building an open foundation for the social
+ internet so that we can all shape its future.
+
+
+ Congratulations. We're glad you're here.
+
+
Date: Wed, 11 Sep 2024 09:51:40 -0500
Subject: [PATCH 06/24] Copy
---
.../dialogs/nudges/TenMillion/index.tsx | 22 +++++--------------
1 file changed, 6 insertions(+), 16 deletions(-)
diff --git a/src/components/dialogs/nudges/TenMillion/index.tsx b/src/components/dialogs/nudges/TenMillion/index.tsx
index fdac91f4f..f42cd2282 100644
--- a/src/components/dialogs/nudges/TenMillion/index.tsx
+++ b/src/components/dialogs/nudges/TenMillion/index.tsx
@@ -440,24 +440,14 @@ export function TenMillion() {
fontWeight: '900',
},
]}>
- You're part of the next wave of the internet.
+ Thanks for being an early part of Bluesky.
-
+
- Online culture is too important to be controlled by a few
- corporations.
+ We're rebuilding the social internet together. Congratulations,
+ we're glad you're here.
{' '}
-
-
- We’re dedicated to building an open foundation for the social
- internet so that we can all shape its future.
-
-
-
-
-
- Congratulations. We're glad you're here.
@@ -471,7 +461,7 @@ export function TenMillion() {
a.pt_xl,
]}>
- Brag a little ;)
+ Brag a little!
- {_(msg`Share post`)}
+ {_(msg`Share`)}
--
2.51.2
From f8edd11bc5788666e0e6d55c1082971c901f089e Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 09:57:37 -0500
Subject: [PATCH 07/24] Don't open for logged out users
---
src/components/dialogs/nudges/TenMillion/index.tsx | 7 ++++++-
src/components/dialogs/nudges/index.tsx | 13 ++++++++-----
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/components/dialogs/nudges/TenMillion/index.tsx b/src/components/dialogs/nudges/TenMillion/index.tsx
index f42cd2282..c2c6926f7 100644
--- a/src/components/dialogs/nudges/TenMillion/index.tsx
+++ b/src/components/dialogs/nudges/TenMillion/index.tsx
@@ -87,6 +87,11 @@ function Frame({children}: {children: React.ReactNode}) {
}
export function TenMillion() {
+ const {hasSession} = useSession()
+ return hasSession ? : null
+}
+
+export function TenMillionInner() {
const t = useTheme()
const lightTheme = useTheme('light')
const {_, i18n} = useLingui()
@@ -96,7 +101,7 @@ export function TenMillion() {
const {currentAccount} = useSession()
const {isLoading: isProfileLoading, data: profile} = useProfileQuery({
did: currentAccount!.did,
- }) // TODO PWI
+ })
const moderationOpts = useModerationOpts()
const moderation = React.useMemo(() => {
return profile && moderationOpts
diff --git a/src/components/dialogs/nudges/index.tsx b/src/components/dialogs/nudges/index.tsx
index 357d4e2b4..eabe60c17 100644
--- a/src/components/dialogs/nudges/index.tsx
+++ b/src/components/dialogs/nudges/index.tsx
@@ -1,7 +1,7 @@
import React from 'react'
+import {useSession} from '#/state/session'
import * as Dialog from '#/components/Dialog'
-
import {TenMillion} from '#/components/dialogs/nudges/TenMillion'
type Context = {
@@ -12,7 +12,7 @@ type Context = {
const Context = React.createContext({
// @ts-ignore
- controls: {}
+ controls: {},
})
export function useContext() {
@@ -22,17 +22,20 @@ export function useContext() {
let SHOWN = false
export function NudgeDialogs() {
+ const {hasSession} = useSession()
const tenMillion = Dialog.useDialogControl()
const ctx = React.useMemo(() => {
return {
controls: {
- tenMillion
- }
+ tenMillion,
+ },
}
}, [tenMillion])
React.useEffect(() => {
+ if (!hasSession) return
+
const t = setTimeout(() => {
if (!SHOWN) {
SHOWN = true
@@ -43,7 +46,7 @@ export function NudgeDialogs() {
return () => {
clearTimeout(t)
}
- }, [ctx])
+ }, [ctx, hasSession])
return (
--
2.51.2
From 77d60a5b8047c2a4b18206e99d89d25222b4601c Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 18:18:04 -0500
Subject: [PATCH 08/24] Hook up data
---
.../dialogs/nudges/TenMillion/index.tsx | 78 ++++++++++++++++++-
1 file changed, 74 insertions(+), 4 deletions(-)
diff --git a/src/components/dialogs/nudges/TenMillion/index.tsx b/src/components/dialogs/nudges/TenMillion/index.tsx
index c2c6926f7..e110ed1ff 100644
--- a/src/components/dialogs/nudges/TenMillion/index.tsx
+++ b/src/components/dialogs/nudges/TenMillion/index.tsx
@@ -6,6 +6,7 @@ import {moderateProfile} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {networkRetry} from '#/lib/async/retry'
import {getCanvas} from '#/lib/canvas'
import {shareUrl} from '#/lib/sharing'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
@@ -13,7 +14,7 @@ import {sanitizeHandle} from '#/lib/strings/handles'
import {isNative} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile'
-import {useSession} from '#/state/session'
+import {useAgent, useSession} from '#/state/session'
import {useComposerControls} from 'state/shell'
import {formatCount} from '#/view/com/util/numeric/format'
// import {UserAvatar} from '#/view/com/util/UserAvatar'
@@ -109,14 +110,56 @@ export function TenMillionInner() {
: undefined
}, [profile, moderationOpts])
const [uri, setUri] = React.useState(null)
+ const [userNumber, setUserNumber] = React.useState(0)
+ const [error, setError] = React.useState('')
- const isLoadingData = isProfileLoading || !moderation || !profile
+ const isLoadingData =
+ isProfileLoading || !moderation || !profile || !userNumber
const isLoadingImage = !uri
- const userNumber = 56_738 // TODO
const percent = userNumber / 10_000_000
const Badge = getPercentBadge(percent)
+ const agent = useAgent()
+ React.useEffect(() => {
+ async function fetchUserNumber() {
+ if (agent.session?.accessJwt) {
+ const res = await fetch(
+ `https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`,
+ {
+ headers: {
+ Authorization: `Bearer ${agent.session.accessJwt}`,
+ },
+ },
+ )
+
+ if (!res.ok) {
+ throw new Error('Network request failed')
+ }
+
+ const data = await res.json()
+
+ if (data.number) {
+ setUserNumber(data.number)
+ }
+ }
+ }
+
+ networkRetry(3, fetchUserNumber).catch(() => {
+ setError(
+ _(
+ msg`Oh no! We couldn't fetch your user number. Rest assured, we're glad you're here ❤️`,
+ ),
+ )
+ })
+ }, [
+ _,
+ agent.session?.accessJwt,
+ setUserNumber,
+ controls.tenMillion,
+ setError,
+ ])
+
const sharePost = () => {
if (uri) {
controls.tenMillion.close(() => {
@@ -421,7 +464,34 @@ export function TenMillionInner() {
- {isLoadingData || isLoadingImage ? (
+ {error ? (
+
+
+ (╯°□°)╯︵ ┻━┻
+
+
+ {error}
+
+
+ ) : isLoadingData || isLoadingImage ? (
) : (
Date: Wed, 11 Sep 2024 20:01:27 -0500
Subject: [PATCH 09/24] Rename
---
src/App.native.tsx | 4 ++--
src/App.web.tsx | 4 ++--
.../{nudges => nuxs}/TenMillion/icons/OnePercent.tsx | 0
.../TenMillion/icons/PointOnePercent.tsx | 0
.../{nudges => nuxs}/TenMillion/icons/TenPercent.tsx | 0
.../TenMillion/icons/TwentyFivePercent.tsx | 0
.../dialogs/{nudges => nuxs}/TenMillion/index.tsx | 10 +++++-----
src/components/dialogs/{nudges => nuxs}/index.tsx | 4 ++--
8 files changed, 11 insertions(+), 11 deletions(-)
rename src/components/dialogs/{nudges => nuxs}/TenMillion/icons/OnePercent.tsx (100%)
rename src/components/dialogs/{nudges => nuxs}/TenMillion/icons/PointOnePercent.tsx (100%)
rename src/components/dialogs/{nudges => nuxs}/TenMillion/icons/TenPercent.tsx (100%)
rename src/components/dialogs/{nudges => nuxs}/TenMillion/icons/TwentyFivePercent.tsx (100%)
rename src/components/dialogs/{nudges => nuxs}/TenMillion/index.tsx (97%)
rename src/components/dialogs/{nudges => nuxs}/index.tsx (90%)
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 95625bdff..83f133e99 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -57,13 +57,13 @@ import * as Toast from '#/view/com/util/Toast'
import {Shell} from '#/view/shell'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
+import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {Splash} from '#/Splash'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import {AudioCategory, PlatformInfo} from '../modules/expo-bluesky-swiss-army'
-import {NudgeDialogs} from '#/components/dialogs/nudges'
SplashScreen.preventAutoHideAsync()
@@ -132,7 +132,7 @@ function InnerApp() {
style={s.h100pct}>
-
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 79120ffdb..ff9944fa4 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -46,11 +46,11 @@ import {ToastContainer} from '#/view/com/util/Toast.web'
import {Shell} from '#/view/shell/index'
import {ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
+import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
-import {NudgeDialogs} from '#/components/dialogs/nudges'
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
@@ -114,7 +114,7 @@ function InnerApp() {
-
+
diff --git a/src/components/dialogs/nudges/TenMillion/icons/OnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx
similarity index 100%
rename from src/components/dialogs/nudges/TenMillion/icons/OnePercent.tsx
rename to src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx
diff --git a/src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx
similarity index 100%
rename from src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx
rename to src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx
diff --git a/src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx
similarity index 100%
rename from src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx
rename to src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx
diff --git a/src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx
similarity index 100%
rename from src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx
rename to src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx
diff --git a/src/components/dialogs/nudges/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
similarity index 97%
rename from src/components/dialogs/nudges/TenMillion/index.tsx
rename to src/components/dialogs/nuxs/TenMillion/index.tsx
index e110ed1ff..663c09560 100644
--- a/src/components/dialogs/nudges/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -28,10 +28,10 @@ import {
} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
-import {useContext} from '#/components/dialogs/nudges'
-import {OnePercent} from '#/components/dialogs/nudges/TenMillion/icons/OnePercent'
-import {PointOnePercent} from '#/components/dialogs/nudges/TenMillion/icons/PointOnePercent'
-import {TenPercent} from '#/components/dialogs/nudges/TenMillion/icons/TenPercent'
+import {useContext} from '#/components/dialogs/nuxs'
+import {OnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/OnePercent'
+import {PointOnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/PointOnePercent'
+import {TenPercent} from '#/components/dialogs/nuxs/TenMillion/icons/TenPercent'
import {Divider} from '#/components/Divider'
import {GradientFill} from '#/components/GradientFill'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
@@ -39,7 +39,7 @@ import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/D
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
-// import {TwentyFivePercent} from '#/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent'
+// import {TwentyFivePercent} from '#/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent'
const DEBUG = false
const RATIO = 8 / 10
diff --git a/src/components/dialogs/nudges/index.tsx b/src/components/dialogs/nuxs/index.tsx
similarity index 90%
rename from src/components/dialogs/nudges/index.tsx
rename to src/components/dialogs/nuxs/index.tsx
index eabe60c17..401dd3e66 100644
--- a/src/components/dialogs/nudges/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {useSession} from '#/state/session'
import * as Dialog from '#/components/Dialog'
-import {TenMillion} from '#/components/dialogs/nudges/TenMillion'
+import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
type Context = {
controls: {
@@ -21,7 +21,7 @@ export function useContext() {
let SHOWN = false
-export function NudgeDialogs() {
+export function NuxDialogs() {
const {hasSession} = useSession()
const tenMillion = Dialog.useDialogControl()
--
2.51.2
From 9bb385a4dd54aca2b21533b7dd919ac8d0b4aeef Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 21:20:39 -0500
Subject: [PATCH 10/24] Refactor, integrate nux, snoozing
---
.../dialogs/nuxs/TenMillion/index.tsx | 180 ++++++++++--------
src/components/dialogs/nuxs/index.tsx | 80 +++++---
src/components/dialogs/nuxs/snoozing.ts | 18 ++
src/state/queries/nuxs/definitions.ts | 25 +--
src/storage/schema.ts | 4 +-
5 files changed, 179 insertions(+), 128 deletions(-)
create mode 100644 src/components/dialogs/nuxs/snoozing.ts
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index 663c09560..d96456d47 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -1,5 +1,6 @@
import React from 'react'
import {View} from 'react-native'
+import Animated, {FadeIn} from 'react-native-reanimated'
import ViewShot from 'react-native-view-shot'
import {Image} from 'expo-image'
import {moderateProfile} from '@atproto/api'
@@ -17,7 +18,6 @@ import {useProfileQuery} from '#/state/queries/profile'
import {useAgent, useSession} from '#/state/session'
import {useComposerControls} from 'state/shell'
import {formatCount} from '#/view/com/util/numeric/format'
-// import {UserAvatar} from '#/view/com/util/UserAvatar'
import {Logomark} from '#/view/icons/Logomark'
import {
atoms as a,
@@ -28,7 +28,7 @@ import {
} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
-import {useContext} from '#/components/dialogs/nuxs'
+import {useNuxDialogContext} from '#/components/dialogs/nuxs'
import {OnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/OnePercent'
import {PointOnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/PointOnePercent'
import {TenPercent} from '#/components/dialogs/nuxs/TenMillion/icons/TenPercent'
@@ -39,7 +39,6 @@ import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/D
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
-// import {TwentyFivePercent} from '#/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent'
const DEBUG = false
const RATIO = 8 / 10
@@ -65,9 +64,6 @@ function getPercentBadge(percent: number) {
} else if (percent <= 0.1) {
return TenPercent
}
- // else if (percent <= 0.25) {
- // return TwentyFivePercent
- // }
return null
}
@@ -88,41 +84,13 @@ function Frame({children}: {children: React.ReactNode}) {
}
export function TenMillion() {
- const {hasSession} = useSession()
- return hasSession ? : null
-}
-
-export function TenMillionInner() {
- const t = useTheme()
- const lightTheme = useTheme('light')
- const {_, i18n} = useLingui()
- const {controls} = useContext()
- const {gtMobile} = useBreakpoints()
- const {openComposer} = useComposerControls()
- const {currentAccount} = useSession()
- const {isLoading: isProfileLoading, data: profile} = useProfileQuery({
- did: currentAccount!.did,
- })
- const moderationOpts = useModerationOpts()
- const moderation = React.useMemo(() => {
- return profile && moderationOpts
- ? moderateProfile(profile, moderationOpts)
- : undefined
- }, [profile, moderationOpts])
- const [uri, setUri] = React.useState(null)
+ const agent = useAgent()
+ const nuxDialogs = useNuxDialogContext()
const [userNumber, setUserNumber] = React.useState(0)
- const [error, setError] = React.useState('')
-
- const isLoadingData =
- isProfileLoading || !moderation || !profile || !userNumber
- const isLoadingImage = !uri
-
- const percent = userNumber / 10_000_000
- const Badge = getPercentBadge(percent)
- const agent = useAgent()
React.useEffect(() => {
async function fetchUserNumber() {
+ // TODO check for 3p PDS
if (agent.session?.accessJwt) {
const res = await fetch(
`https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`,
@@ -146,26 +114,83 @@ export function TenMillionInner() {
}
networkRetry(3, fetchUserNumber).catch(() => {
- setError(
- _(
- msg`Oh no! We couldn't fetch your user number. Rest assured, we're glad you're here ❤️`,
- ),
- )
+ nuxDialogs.dismissActiveNux()
})
}, [
- _,
agent.session?.accessJwt,
setUserNumber,
- controls.tenMillion,
- setError,
+ nuxDialogs.dismissActiveNux,
+ nuxDialogs,
])
- const sharePost = () => {
+ return userNumber ? : null
+}
+
+export function TenMillionInner({userNumber}: {userNumber: number}) {
+ const t = useTheme()
+ const lightTheme = useTheme('light')
+ const {_, i18n} = useLingui()
+ const control = Dialog.useDialogControl()
+ const {gtMobile} = useBreakpoints()
+ const {openComposer} = useComposerControls()
+ const {currentAccount} = useSession()
+ const {
+ isLoading: isProfileLoading,
+ data: profile,
+ error: profileError,
+ } = useProfileQuery({
+ did: currentAccount!.did,
+ })
+ const moderationOpts = useModerationOpts()
+ const nuxDialogs = useNuxDialogContext()
+ const moderation = React.useMemo(() => {
+ return profile && moderationOpts
+ ? moderateProfile(profile, moderationOpts)
+ : undefined
+ }, [profile, moderationOpts])
+ const [uri, setUri] = React.useState(null)
+ const percent = userNumber / 10_000_000
+ const Badge = getPercentBadge(percent)
+ const isLoadingData = isProfileLoading || !moderation || !profile
+ const isLoadingImage = !uri
+
+ const error: string = React.useMemo(() => {
+ if (profileError) {
+ return _(
+ msg`Oh no! We weren't able to generate an image for you to share. Rest assured, we're glad you're here 🦋`,
+ )
+ }
+ return ''
+ }, [_, profileError])
+
+ /*
+ * Opening and closing
+ */
+ React.useEffect(() => {
+ const timeout = setTimeout(() => {
+ control.open()
+ }, 3e3)
+ return () => {
+ clearTimeout(timeout)
+ }
+ }, [control])
+ const onClose = React.useCallback(() => {
+ nuxDialogs.dismissActiveNux()
+ }, [nuxDialogs])
+
+ /*
+ * Actions
+ */
+ const sharePost = React.useCallback(() => {
if (uri) {
- controls.tenMillion.close(() => {
+ control.close(() => {
setTimeout(() => {
openComposer({
- text: '10 milly, babyyy',
+ text: _(
+ msg`I'm user #${i18n.number(
+ userNumber,
+ )} out of 10M. What a ride 😎`,
+ ), // TODO
imageUris: [
{
uri,
@@ -177,17 +202,15 @@ export function TenMillionInner() {
}, 1e3)
})
}
- }
-
- const onNativeShare = () => {
+ }, [_, i18n, control, openComposer, uri, userNumber])
+ const onNativeShare = React.useCallback(() => {
if (uri) {
- controls.tenMillion.close(() => {
+ control.close(() => {
shareUrl(uri)
})
}
- }
-
- const download = async () => {
+ }, [uri, control])
+ const download = React.useCallback(async () => {
if (uri) {
const canvas = await getCanvas(uri)
const imgHref = canvas
@@ -198,32 +221,24 @@ export function TenMillionInner() {
link.setAttribute('href', imgHref)
link.click()
}
- }
+ }, [uri])
+ /*
+ * Canvas stuff
+ */
const imageRef = React.useRef(null)
- // const captureInProgress = React.useRef(false)
- // const [cavasRelayout, setCanvasRelayout] = React.useState('key')
- // const onCanvasReady = async () => {
- // if (
- // imageRef.current &&
- // imageRef.current.capture &&
- // !captureInProgress.current
- // ) {
- // captureInProgress.current = true
- // setCanvasRelayout('updated')
- // }
- // }
- const onCanvasLayout = async () => {
+ const captureInProgress = React.useRef(false)
+ const onCanvasReady = React.useCallback(async () => {
if (
imageRef.current &&
- imageRef.current.capture // &&
- // cavasRelayout === 'updated'
+ imageRef.current.capture &&
+ !captureInProgress.current
) {
+ captureInProgress.current = true
const uri = await imageRef.current.capture()
setUri(uri)
}
- }
-
+ }, [setUri])
const canvas = isLoadingData ? null : (
+
) : (
-
+
+
+
)}
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index 401dd3e66..6c4598cdb 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -1,56 +1,80 @@
import React from 'react'
+import {Nux, useNuxs, useUpsertNuxMutation} from '#/state/queries/nuxs'
import {useSession} from '#/state/session'
-import * as Dialog from '#/components/Dialog'
+import {isSnoozed, snooze} from '#/components/dialogs/nuxs/snoozing'
import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
type Context = {
- controls: {
- tenMillion: Dialog.DialogOuterProps['control']
- }
+ activeNux: Nux | undefined
+ dismissActiveNux: () => void
}
+const queuedNuxs = [Nux.TenMillionDialog]
+
const Context = React.createContext({
- // @ts-ignore
- controls: {},
+ activeNux: undefined,
+ dismissActiveNux: () => {},
})
-export function useContext() {
+export function useNuxDialogContext() {
return React.useContext(Context)
}
-let SHOWN = false
-
export function NuxDialogs() {
const {hasSession} = useSession()
- const tenMillion = Dialog.useDialogControl()
+ return hasSession ? : null
+}
- const ctx = React.useMemo(() => {
- return {
- controls: {
- tenMillion,
- },
- }
- }, [tenMillion])
+function Inner() {
+ const {nuxs} = useNuxs()
+ const [snoozed, setSnoozed] = React.useState(() => {
+ return isSnoozed()
+ })
+ const [activeNux, setActiveNux] = React.useState()
+ const {mutate: upsertNux} = useUpsertNuxMutation()
+
+ const snoozeNuxDialog = React.useCallback(() => {
+ snooze()
+ setSnoozed(true)
+ }, [setSnoozed])
+
+ const dismissActiveNux = React.useCallback(() => {
+ setActiveNux(undefined)
+ upsertNux({
+ id: activeNux!,
+ completed: true,
+ data: undefined,
+ })
+ }, [activeNux, setActiveNux, upsertNux])
React.useEffect(() => {
- if (!hasSession) return
+ if (snoozed) return
+ if (!nuxs) return
+
+ for (const id of queuedNuxs) {
+ const nux = nuxs.find(nux => nux.id === id)
- const t = setTimeout(() => {
- if (!SHOWN) {
- SHOWN = true
- ctx.controls.tenMillion.open()
- }
- }, 2e3)
+ if (nux && nux.completed) continue
- return () => {
- clearTimeout(t)
+ setActiveNux(id)
+ // snooze immediately upon enabling
+ snoozeNuxDialog()
+
+ break
+ }
+ }, [nuxs, snoozed, snoozeNuxDialog])
+
+ const ctx = React.useMemo(() => {
+ return {
+ activeNux,
+ dismissActiveNux,
}
- }, [ctx, hasSession])
+ }, [activeNux, dismissActiveNux])
return (
-
+ {activeNux === Nux.TenMillionDialog && }
)
}
diff --git a/src/components/dialogs/nuxs/snoozing.ts b/src/components/dialogs/nuxs/snoozing.ts
new file mode 100644
index 000000000..a36efd8ed
--- /dev/null
+++ b/src/components/dialogs/nuxs/snoozing.ts
@@ -0,0 +1,18 @@
+import {simpleAreDatesEqual} from '#/lib/strings/time'
+import {device} from '#/storage'
+
+export function snooze() {
+ device.set(['lastNuxDialog'], new Date().toISOString())
+}
+
+export function isSnoozed() {
+ const lastNuxDialog = device.get(['lastNuxDialog'])
+ if (!lastNuxDialog) return false
+ const last = new Date(lastNuxDialog)
+ const now = new Date()
+ // already snoozed today
+ if (simpleAreDatesEqual(last, now)) {
+ return true
+ }
+ return false
+}
diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts
index c5cb1e9d9..865967d37 100644
--- a/src/state/queries/nuxs/definitions.ts
+++ b/src/state/queries/nuxs/definitions.ts
@@ -3,27 +3,16 @@ import zod from 'zod'
import {BaseNux} from '#/state/queries/nuxs/types'
export enum Nux {
- One = 'one',
- Two = 'two',
+ TenMillionDialog = 'TenMillionDialog',
}
export const nuxNames = new Set(Object.values(Nux))
-export type AppNux =
- | BaseNux<{
- id: Nux.One
- data: {
- likes: number
- }
- }>
- | BaseNux<{
- id: Nux.Two
- data: undefined
- }>
+export type AppNux = BaseNux<{
+ id: Nux.TenMillionDialog
+ data: undefined
+}>
-export const NuxSchemas = {
- [Nux.One]: zod.object({
- likes: zod.number(),
- }),
- [Nux.Two]: undefined,
+export const NuxSchemas: Record | undefined> = {
+ [Nux.TenMillionDialog]: undefined,
}
diff --git a/src/storage/schema.ts b/src/storage/schema.ts
index 6522d75a3..be074db43 100644
--- a/src/storage/schema.ts
+++ b/src/storage/schema.ts
@@ -1,4 +1,6 @@
/**
* Device data that's specific to the device and does not vary based account
*/
-export type Device = {}
+export type Device = {
+ lastNuxDialog: string
+}
--
2.51.2
From c8b133863df5c6b417562f71f8a3c6feef280139 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 21:28:34 -0500
Subject: [PATCH 11/24] Fix some nux types
---
src/components/dialogs/nuxs/index.tsx | 9 ++++++---
src/state/queries/nuxs/types.ts | 4 +---
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index 6c4598cdb..36db7764d 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -40,13 +40,16 @@ function Inner() {
}, [setSnoozed])
const dismissActiveNux = React.useCallback(() => {
+ if (!activeNux) return
setActiveNux(undefined)
+ const nux = nuxs?.find(nux => nux.id === activeNux)
upsertNux({
- id: activeNux!,
+ id: activeNux,
completed: true,
- data: undefined,
+ data: nux?.data,
+ expiresAt: nux?.expiresAt,
})
- }, [activeNux, setActiveNux, upsertNux])
+ }, [activeNux, setActiveNux, upsertNux, nuxs])
React.useEffect(() => {
if (snoozed) return
diff --git a/src/state/queries/nuxs/types.ts b/src/state/queries/nuxs/types.ts
index 5b7918470..2331582a1 100644
--- a/src/state/queries/nuxs/types.ts
+++ b/src/state/queries/nuxs/types.ts
@@ -4,6 +4,4 @@ export type Data = Record | undefined
export type BaseNux<
T extends Pick & {data: Data},
-> = T & {
- completed: boolean
-}
+> = Pick & T
--
2.51.2
From 6e78ce53d74e79e2349ab357c7270e30742d33a5 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 21:47:25 -0500
Subject: [PATCH 12/24] Dev helpers, string cleanup
---
.../dialogs/nuxs/TenMillion/index.tsx | 9 ++++-----
src/components/dialogs/nuxs/index.tsx | 20 +++++++++++++++++--
src/components/dialogs/nuxs/snoozing.ts | 4 ++++
src/storage/schema.ts | 2 +-
4 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index d96456d47..5da295ab6 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -430,7 +430,6 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
style={[
a.text_sm,
a.font_semibold,
- ,
a.leading_tight,
lightTheme.atoms.text_contrast_low,
]}>
@@ -533,13 +532,13 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
fontWeight: '900',
},
]}>
- Thanks for being an early part of Bluesky.
+ You're part of the next wave of the internet.
- We're rebuilding the social internet together. Congratulations,
- we're glad you're here.
+ Thanks for being part of our first 10 million users. We're glad
+ you're here.
{' '}
@@ -554,7 +553,7 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
a.pt_xl,
]}>
- Brag a little!
+ Brag a little!
()
const {mutate: upsertNux} = useUpsertNuxMutation()
+ const {mutate: removeNuxs} = useRemoveNuxsMutation()
const snoozeNuxDialog = React.useCallback(() => {
snooze()
@@ -51,6 +58,15 @@ function Inner() {
})
}, [activeNux, setActiveNux, upsertNux, nuxs])
+ if (IS_DEV && typeof window !== 'undefined') {
+ // @ts-ignore
+ window.clearNuxDialog = (id: Nux) => {
+ if (!IS_DEV || !id) return
+ removeNuxs([id])
+ unsnooze()
+ }
+ }
+
React.useEffect(() => {
if (snoozed) return
if (!nuxs) return
diff --git a/src/components/dialogs/nuxs/snoozing.ts b/src/components/dialogs/nuxs/snoozing.ts
index a36efd8ed..91effd050 100644
--- a/src/components/dialogs/nuxs/snoozing.ts
+++ b/src/components/dialogs/nuxs/snoozing.ts
@@ -5,6 +5,10 @@ export function snooze() {
device.set(['lastNuxDialog'], new Date().toISOString())
}
+export function unsnooze() {
+ device.set(['lastNuxDialog'], undefined)
+}
+
export function isSnoozed() {
const lastNuxDialog = device.get(['lastNuxDialog'])
if (!lastNuxDialog) return false
diff --git a/src/storage/schema.ts b/src/storage/schema.ts
index be074db43..bc41fd3ed 100644
--- a/src/storage/schema.ts
+++ b/src/storage/schema.ts
@@ -2,5 +2,5 @@
* Device data that's specific to the device and does not vary based account
*/
export type Device = {
- lastNuxDialog: string
+ lastNuxDialog: string | undefined
}
--
2.51.2
From 45c8d89d9212bf7160317185f8418bdebbf3b617 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 21:52:26 -0500
Subject: [PATCH 13/24] Protect against 3p PDSs and bad responses
---
src/components/dialogs/nuxs/TenMillion/index.tsx | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index 5da295ab6..f5c8435de 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -90,8 +90,11 @@ export function TenMillion() {
React.useEffect(() => {
async function fetchUserNumber() {
- // TODO check for 3p PDS
- if (agent.session?.accessJwt) {
+ const isBlueskyHosted = agent.sessionManager.pdsUrl
+ ?.toString()
+ .includes('bsky.network')
+
+ if (isBlueskyHosted && agent.session?.accessJwt) {
const res = await fetch(
`https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`,
{
@@ -109,6 +112,9 @@ export function TenMillion() {
if (data.number) {
setUserNumber(data.number)
+ } else {
+ // should be rare
+ nuxDialogs.dismissActiveNux()
}
}
}
@@ -117,6 +123,7 @@ export function TenMillion() {
nuxDialogs.dismissActiveNux()
})
}, [
+ agent.sessionManager.pdsUrl,
agent.session?.accessJwt,
setUserNumber,
nuxDialogs.dismissActiveNux,
@@ -187,9 +194,9 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
setTimeout(() => {
openComposer({
text: _(
- msg`I'm user #${i18n.number(
+ msg`Bluesky now has over 10 million users, and I was #${i18n.number(
userNumber,
- )} out of 10M. What a ride 😎`,
+ )}!`,
), // TODO
imageUris: [
{
--
2.51.2
From c99e43d6c625ed7d0bb4f1d25026512460445bef Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Wed, 11 Sep 2024 21:56:20 -0500
Subject: [PATCH 14/24] Protect against other exit methods, protect against
multiple fetches
---
.../dialogs/nuxs/TenMillion/index.tsx | 10 +++++++---
src/components/dialogs/nuxs/index.tsx | 19 +++++++++----------
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index f5c8435de..c77755145 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -87,6 +87,7 @@ export function TenMillion() {
const agent = useAgent()
const nuxDialogs = useNuxDialogContext()
const [userNumber, setUserNumber] = React.useState(0)
+ const fetching = React.useRef(false)
React.useEffect(() => {
async function fetchUserNumber() {
@@ -119,9 +120,12 @@ export function TenMillion() {
}
}
- networkRetry(3, fetchUserNumber).catch(() => {
- nuxDialogs.dismissActiveNux()
- })
+ if (!fetching.current) {
+ fetching.current = true
+ networkRetry(3, fetchUserNumber).catch(() => {
+ nuxDialogs.dismissActiveNux()
+ })
+ }
}, [
agent.sessionManager.pdsUrl,
agent.session?.accessJwt,
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index c02af59fc..f60c9e8d0 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -49,14 +49,7 @@ function Inner() {
const dismissActiveNux = React.useCallback(() => {
if (!activeNux) return
setActiveNux(undefined)
- const nux = nuxs?.find(nux => nux.id === activeNux)
- upsertNux({
- id: activeNux,
- completed: true,
- data: nux?.data,
- expiresAt: nux?.expiresAt,
- })
- }, [activeNux, setActiveNux, upsertNux, nuxs])
+ }, [activeNux, setActiveNux])
if (IS_DEV && typeof window !== 'undefined') {
// @ts-ignore
@@ -77,12 +70,18 @@ function Inner() {
if (nux && nux.completed) continue
setActiveNux(id)
- // snooze immediately upon enabling
+ // immediately snooze (in memory)
snoozeNuxDialog()
+ // immediately update remote data
+ upsertNux({
+ id,
+ completed: true,
+ data: undefined,
+ })
break
}
- }, [nuxs, snoozed, snoozeNuxDialog])
+ }, [nuxs, snoozed, snoozeNuxDialog, upsertNux])
const ctx = React.useMemo(() => {
return {
--
2.51.2
From 63f85f649392a834f81a629902b971e72bea0eb8 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 08:03:58 -0500
Subject: [PATCH 15/24] Copy
---
src/components/dialogs/nuxs/TenMillion/index.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index c77755145..db5e19da7 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -543,14 +543,14 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
fontWeight: '900',
},
]}>
- You're part of the next wave of the internet.
+ Thanks for being one of our first 10 million users.
- Thanks for being part of our first 10 million users. We're glad
+ Together, we're rebuilding the social internet. We're glad
you're here.
- {' '}
+
--
2.51.2
From 46402fd01074764d49c1cc52b25ecf6dc5b274b2 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 13:14:51 -0500
Subject: [PATCH 16/24] Add gate
---
src/components/dialogs/nuxs/index.tsx | 24 ++++++++++++++++++++----
src/lib/statsig/gates.ts | 5 ++++-
2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index f60c9e8d0..a0063ce76 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -1,5 +1,6 @@
import React from 'react'
+import {useGate} from '#/lib/statsig/statsig'
import {
Nux,
useNuxs,
@@ -16,7 +17,17 @@ type Context = {
dismissActiveNux: () => void
}
-const queuedNuxs = [Nux.TenMillionDialog]
+const queuedNuxs: {
+ id: Nux
+ enabled(props: {gate: ReturnType}): boolean
+}[] = [
+ {
+ id: Nux.TenMillionDialog,
+ enabled({gate}) {
+ return gate('ten_million_dialog')
+ },
+ },
+]
const Context = React.createContext({
activeNux: undefined,
@@ -33,6 +44,7 @@ export function NuxDialogs() {
}
function Inner() {
+ const gate = useGate()
const {nuxs} = useNuxs()
const [snoozed, setSnoozed] = React.useState(() => {
return isSnoozed()
@@ -64,15 +76,19 @@ function Inner() {
if (snoozed) return
if (!nuxs) return
- for (const id of queuedNuxs) {
+ for (const {id, enabled} of queuedNuxs) {
const nux = nuxs.find(nux => nux.id === id)
+ // check if completed first
if (nux && nux.completed) continue
+ // then check gate (track exposure)
+ if (!enabled({gate})) continue
+ // we have a winner
setActiveNux(id)
// immediately snooze (in memory)
snoozeNuxDialog()
- // immediately update remote data
+ // immediately update remote data (affects next reload)
upsertNux({
id,
completed: true,
@@ -81,7 +97,7 @@ function Inner() {
break
}
- }, [nuxs, snoozed, snoozeNuxDialog, upsertNux])
+ }, [nuxs, snoozed, snoozeNuxDialog, upsertNux, gate])
const ctx = React.useMemo(() => {
return {
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index df9daab44..9b6c036b9 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -1,3 +1,6 @@
export type Gate =
// Keep this alphabetic please.
- 'debug_show_feedcontext' | 'suggested_feeds_interstitial' | 'video_upload' // upload videos
+ | 'debug_show_feedcontext'
+ | 'suggested_feeds_interstitial'
+ | 'ten_million_dialog'
+ | 'video_upload' // upload videos
--
2.51.2
From bd79ce7ea00602db5914e54f8191b9429c024777 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 13:45:06 -0500
Subject: [PATCH 17/24] Ensure dialog shows for all accounts without snoozing
---
src/components/dialogs/nuxs/index.tsx | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index a0063ce76..ba693329f 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -20,12 +20,18 @@ type Context = {
const queuedNuxs: {
id: Nux
enabled(props: {gate: ReturnType}): boolean
+ /**
+ * TEMP only intended for use with the 10Milly dialog rn, since there are no
+ * other NUX dialogs configured
+ */
+ unsafe_disableSnooze: boolean
}[] = [
{
id: Nux.TenMillionDialog,
enabled({gate}) {
return gate('ten_million_dialog')
},
+ unsafe_disableSnooze: true,
},
]
@@ -76,18 +82,27 @@ function Inner() {
if (snoozed) return
if (!nuxs) return
- for (const {id, enabled} of queuedNuxs) {
+ for (const {id, enabled, unsafe_disableSnooze} of queuedNuxs) {
const nux = nuxs.find(nux => nux.id === id)
// check if completed first
if (nux && nux.completed) continue
+
// then check gate (track exposure)
if (!enabled({gate})) continue
// we have a winner
setActiveNux(id)
- // immediately snooze (in memory)
- snoozeNuxDialog()
+
+ /**
+ * TEMP only intended for use with the 10Milly dialog rn, since there are no
+ * other NUX dialogs configured
+ */
+ if (!unsafe_disableSnooze) {
+ // immediately snooze (in memory)
+ snoozeNuxDialog()
+ }
+
// immediately update remote data (affects next reload)
upsertNux({
id,
--
2.51.2
From 7bba213e1ac791a3284a48dc0e0f493f54d9c933 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 13:45:58 -0500
Subject: [PATCH 18/24] Add retry
---
src/state/queries/nuxs/index.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/state/queries/nuxs/index.ts b/src/state/queries/nuxs/index.ts
index 2945e67eb..e183bcfad 100644
--- a/src/state/queries/nuxs/index.ts
+++ b/src/state/queries/nuxs/index.ts
@@ -57,6 +57,7 @@ export function useUpsertNuxMutation() {
const agent = useAgent()
return useMutation({
+ retry: 3,
mutationFn: async (nux: AppNux) => {
await agent.bskyAppUpsertNux(serializeAppNux(nux))
// triggers a refetch
@@ -72,6 +73,7 @@ export function useRemoveNuxsMutation() {
const agent = useAgent()
return useMutation({
+ retry: 3,
mutationFn: async (ids: string[]) => {
await agent.bskyAppRemoveNuxs(ids)
// triggers a refetch
--
2.51.2
From 1f2e4b26c0e6a01bf6a297b333ba7c7f96149006 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 13:54:20 -0500
Subject: [PATCH 19/24] Handle overflow of bottom text
---
.../dialogs/nuxs/TenMillion/index.tsx | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index db5e19da7..786c635c1 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -424,13 +424,15 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
moderation.ui('displayName'),
)}
-
+
{sanitizeHandle(profile.handle, '@')}
@@ -438,16 +440,22 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
{profile.createdAt && (
Joined{' '}
{i18n.date(profile.createdAt, {
- dateStyle: 'long',
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
})}
--
2.51.2
From cc8e7b5ae5686cfc51ee015d94cd726997d3c01a Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 13:56:25 -0500
Subject: [PATCH 20/24] Handle display name
---
src/components/dialogs/nuxs/TenMillion/index.tsx | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index 786c635c1..6d80dd12a 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -417,7 +417,15 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
/>
*/}
-
+
{sanitizeDisplayName(
profile.displayName ||
sanitizeHandle(profile.handle),
--
2.51.2
From 7acf0e1284be7ca5a5e7a55e1823ec0be1255308 Mon Sep 17 00:00:00 2001
From: Paul Frazee
Date: Thu, 12 Sep 2024 12:57:08 -0700
Subject: [PATCH 21/24] On android, change ten milly nux secondary action to
save instead of share
---
.../dialogs/nuxs/TenMillion/index.tsx | 43 ++++++++++++++++---
1 file changed, 38 insertions(+), 5 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index 6d80dd12a..29256502d 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -3,6 +3,8 @@ import {View} from 'react-native'
import Animated, {FadeIn} from 'react-native-reanimated'
import ViewShot from 'react-native-view-shot'
import {Image} from 'expo-image'
+import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
+import * as MediaLibrary from 'expo-media-library'
import {moderateProfile} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -12,13 +14,14 @@ import {getCanvas} from '#/lib/canvas'
import {shareUrl} from '#/lib/sharing'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
-import {isNative} from '#/platform/detection'
+import {isIOS, isNative} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile'
import {useAgent, useSession} from '#/state/session'
import {useComposerControls} from 'state/shell'
import {formatCount} from '#/view/com/util/numeric/format'
import {Logomark} from '#/view/icons/Logomark'
+import * as Toast from 'view/com/util/Toast'
import {
atoms as a,
ThemeProvider,
@@ -221,7 +224,31 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
})
}
}, [uri, control])
- const download = React.useCallback(async () => {
+ const onNativeDownload = React.useCallback(async () => {
+ if (uri) {
+ const res = await requestMediaLibraryPermissionsAsync()
+
+ if (!res) {
+ Toast.show(
+ _(
+ msg`You must grant access to your photo library to save the image.`,
+ ),
+ 'xmark',
+ )
+ return
+ }
+
+ try {
+ await MediaLibrary.createAssetAsync(uri)
+ Toast.show(_(msg`Image saved to your camera roll!`))
+ } catch (e: unknown) {
+ console.log(e)
+ Toast.show(_(msg`An error occurred while saving the image!`), 'xmark')
+ return
+ }
+ }
+ }, [_, uri])
+ const onWebDownload = React.useCallback(async () => {
if (uri) {
const canvas = await getCanvas(uri)
const imgHref = canvas
@@ -586,7 +613,7 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
-
+ onPress={
+ isNative
+ ? isIOS
+ ? onNativeShare
+ : onNativeDownload
+ : onWebDownload
+ }>
+
Date: Thu, 12 Sep 2024 13:11:30 -0700
Subject: [PATCH 22/24] Add ja and pt-BR translations
---
src/locale/locales/ja/messages.po | 224 +++++++++++++++++----------
src/locale/locales/pt-BR/messages.po | 77 +++++++--
2 files changed, 204 insertions(+), 97 deletions(-)
diff --git a/src/locale/locales/ja/messages.po b/src/locale/locales/ja/messages.po
index 61dc1394d..b1c03b59e 100644
--- a/src/locale/locales/ja/messages.po
+++ b/src/locale/locales/ja/messages.po
@@ -115,7 +115,7 @@ msgstr "<0><1>テキストとタグ1>中の0>{0}"
msgid "{0} joined this week"
msgstr "今週、{0}人が参加しました"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:593
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:637
msgid "{0} of {1}"
msgstr "{0} / {1}"
@@ -123,7 +123,7 @@ msgstr "{0} / {1}"
msgid "{0} people have used this starter pack!"
msgstr "{0}人がこのスターターパックを使用しました!"
-#: src/view/com/util/UserAvatar.tsx:419
+#: src/view/com/util/UserAvatar.tsx:423
msgid "{0}'s avatar"
msgstr "{0}のアバター"
@@ -372,7 +372,7 @@ msgstr "アカウントを追加"
msgid "Add alt text"
msgstr "ALTテキストを追加"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:107
+#: src/view/com/composer/videos/SubtitleDialog.tsx:109
msgid "Add alt text (optional)"
msgstr "ALTテキストを追加(オプション)"
@@ -493,9 +493,9 @@ msgid "ALT"
msgstr "ALT"
#: src/view/com/composer/GifAltText.tsx:144
-#: src/view/com/composer/videos/SubtitleDialog.tsx:54
-#: src/view/com/composer/videos/SubtitleDialog.tsx:102
-#: src/view/com/composer/videos/SubtitleDialog.tsx:106
+#: src/view/com/composer/videos/SubtitleDialog.tsx:56
+#: src/view/com/composer/videos/SubtitleDialog.tsx:104
+#: src/view/com/composer/videos/SubtitleDialog.tsx:108
#: src/view/com/modals/EditImage.tsx:316
#: src/view/screens/AccessibilitySettings.tsx:87
msgid "Alt text"
@@ -522,11 +522,11 @@ msgstr "以前のメールアドレス{0}にメールが送信されました。
msgid "An error has occurred"
msgstr "エラーが発生しました"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:369
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:413
msgid "An error occurred"
msgstr "エラーが発生しました"
-#: src/state/queries/video/video.ts:227
+#: src/state/queries/video/video.ts:232
msgid "An error occurred while compressing the video."
msgstr "ビデオの圧縮中にエラーが発生しました。"
@@ -534,11 +534,11 @@ msgstr "ビデオの圧縮中にエラーが発生しました。"
msgid "An error occurred while generating your starter pack. Want to try again?"
msgstr "スターターパックの生成中にエラーが発生しました。再度試しますか?"
-#: src/view/com/util/post-embeds/VideoEmbed.tsx:213
+#: src/view/com/util/post-embeds/VideoEmbed.tsx:215
msgid "An error occurred while loading the video. Please try again later."
msgstr "ビデオの読み込み時にエラーが発生しました。時間をおいてもう一度お試しください。"
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:170
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:174
msgid "An error occurred while loading the video. Please try again."
msgstr "ビデオの読み込み時にエラーが発生しました。もう一度お試しください。"
@@ -547,7 +547,7 @@ msgstr "ビデオの読み込み時にエラーが発生しました。もう一
msgid "An error occurred while saving the QR code!"
msgstr "QRコードの保存中にエラーが発生しました!"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:61
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:68
msgid "An error occurred while selecting the video"
msgstr "ビデオの選択中にエラーが発生しました"
@@ -556,7 +556,7 @@ msgstr "ビデオの選択中にエラーが発生しました"
msgid "An error occurred while trying to follow all"
msgstr "すべてフォローしようとしたらエラーが発生しました"
-#: src/state/queries/video/video.ts:194
+#: src/state/queries/video/video.ts:199
msgid "An error occurred while uploading the video."
msgstr "ビデオのアップロード中にエラーが発生しました。"
@@ -702,7 +702,7 @@ msgstr "あなたのフィードから{0}を削除してもよろしいですか
msgid "Are you sure you want to remove this from your feeds?"
msgstr "本当にこのフィードをあなたのフィードから削除したいですか?"
-#: src/view/com/composer/Composer.tsx:837
+#: src/view/com/composer/Composer.tsx:841
msgid "Are you sure you'd like to discard this draft?"
msgstr "本当にこの下書きを破棄しますか?"
@@ -844,6 +844,10 @@ msgstr "Bluesky は、ホスティング プロバイダーを選択できるオ
msgid "Bluesky is better with friends!"
msgstr "Blueskyは友達と一緒のほうが楽しい!"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:201
+msgid "Bluesky now has over 10 million users, and I was #{0}!"
+msgstr "Bluesky のユーザー数は現在 1,000 万人を超えており、私は #{0} でした。"
+
#: src/components/StarterPack/ProfileStarterPacks.tsx:282
msgid "Bluesky will choose a set of recommended accounts from people in your network."
msgstr "Blueskyはあなたのつながっているユーザーからおすすめのアカウントを選びます。"
@@ -865,6 +869,10 @@ msgstr "画像のぼかしとフィードからのフィルタリング"
msgid "Books"
msgstr "書籍"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:583
+msgid "Brag a little!"
+msgstr "ちょっと自慢してみよう!"
+
#: src/components/FeedInterstitials.tsx:346
msgid "Browse more accounts on the Explore page"
msgstr "検索ページでさらにアカウントを見る"
@@ -927,8 +935,8 @@ msgstr "英数字、スペース、ハイフン、アンダースコアのみが
#: src/components/Prompt.tsx:124
#: src/components/TagMenu/index.tsx:282
#: src/screens/Deactivated.tsx:161
-#: src/view/com/composer/Composer.tsx:590
-#: src/view/com/composer/Composer.tsx:605
+#: src/view/com/composer/Composer.tsx:595
+#: src/view/com/composer/Composer.tsx:610
#: src/view/com/modals/ChangeEmail.tsx:213
#: src/view/com/modals/ChangeEmail.tsx:215
#: src/view/com/modals/ChangeHandle.tsx:148
@@ -996,14 +1004,18 @@ msgstr "リンク先のウェブサイトを開くことをキャンセル"
msgid "Cannot interact with a blocked user"
msgstr "ブロックしたユーザーとはやりとりできません"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:133
+#: src/view/com/composer/videos/SubtitleDialog.tsx:135
msgid "Captions (.vtt)"
msgstr "キャプション(.vtt)"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:54
+#: src/view/com/composer/videos/SubtitleDialog.tsx:56
msgid "Captions & alt text"
msgstr "キャプション&ALTテキスト"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:336
+msgid "Celebrating {0} users"
+msgstr "{0} 人のユーザーを祝福"
+
#: src/view/com/modals/VerifyEmail.tsx:160
msgid "Change"
msgstr "変更"
@@ -1238,7 +1250,7 @@ msgstr "下部のナビゲーションバーを閉じる"
msgid "Closes password update alert"
msgstr "パスワード更新アラートを閉じる"
-#: src/view/com/composer/Composer.tsx:602
+#: src/view/com/composer/Composer.tsx:607
msgid "Closes post composer and discards post draft"
msgstr "投稿の編集画面を閉じて下書きを削除する"
@@ -1277,7 +1289,7 @@ msgstr "初期設定を完了してアカウントを使い始める"
msgid "Complete the challenge"
msgstr "テストをクリアしてください"
-#: src/view/com/composer/Composer.tsx:710
+#: src/view/com/composer/Composer.tsx:715
msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length"
msgstr "{MAX_GRAPHEME_LENGTH}文字までの投稿を作成"
@@ -1495,7 +1507,7 @@ msgstr "リストの読み込みに失敗しました"
msgid "Could not mute chat"
msgstr "チャットのミュートに失敗しました"
-#: src/view/com/composer/videos/VideoPreview.web.tsx:45
+#: src/view/com/composer/videos/VideoPreview.web.tsx:56
msgid "Could not process your video"
msgstr "ビデオを処理できませんでした"
@@ -1604,7 +1616,7 @@ msgstr "ダークモード"
msgid "Dark theme"
msgstr "ダークテーマ"
-#: src/screens/Signup/StepInfo/index.tsx:191
+#: src/screens/Signup/StepInfo/index.tsx:192
msgid "Date of birth"
msgstr "生年月日"
@@ -1738,7 +1750,7 @@ msgstr "引用投稿を切り離しますか?"
msgid "Dialog: adjust who can interact with this post"
msgstr "ダイアログ:この投稿に誰が反応できるか調整"
-#: src/view/com/composer/Composer.tsx:351
+#: src/view/com/composer/Composer.tsx:356
msgid "Did you want to say anything?"
msgstr "なにか言いたいことはあった?"
@@ -1763,7 +1775,7 @@ msgstr "メールでの2要素認証を無効化"
msgid "Disable haptic feedback"
msgstr "触覚フィードバックを無効化"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:335
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:379
msgid "Disable subtitles"
msgstr "サブタイトル(字幕)を無効にする"
@@ -1776,11 +1788,11 @@ msgstr "サブタイトル(字幕)を無効にする"
msgid "Disabled"
msgstr "無効"
-#: src/view/com/composer/Composer.tsx:839
+#: src/view/com/composer/Composer.tsx:843
msgid "Discard"
msgstr "破棄"
-#: src/view/com/composer/Composer.tsx:836
+#: src/view/com/composer/Composer.tsx:840
msgid "Discard draft?"
msgstr "下書きを削除しますか?"
@@ -1806,7 +1818,7 @@ msgstr "新しいフィードを探す"
#~ msgid "Dismiss"
#~ msgstr "消す"
-#: src/view/com/composer/Composer.tsx:1106
+#: src/view/com/composer/Composer.tsx:1110
msgid "Dismiss error"
msgstr "エラーを消す"
@@ -1858,8 +1870,8 @@ msgstr "ドメインを確認しました!"
#: src/screens/Onboarding/StepProfile/index.tsx:325
#: src/view/com/auth/server-input/index.tsx:169
#: src/view/com/auth/server-input/index.tsx:170
-#: src/view/com/composer/videos/SubtitleDialog.tsx:167
-#: src/view/com/composer/videos/SubtitleDialog.tsx:177
+#: src/view/com/composer/videos/SubtitleDialog.tsx:171
+#: src/view/com/composer/videos/SubtitleDialog.tsx:181
#: src/view/com/modals/AddAppPasswords.tsx:243
#: src/view/com/modals/AltImage.tsx:141
#: src/view/com/modals/crop-image/CropImage.web.tsx:177
@@ -1891,6 +1903,10 @@ msgstr "Blueskyをダウンロード"
msgid "Download CAR file"
msgstr "CARファイルをダウンロード"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:591
+msgid "Download image"
+msgstr "画像をダウンロード"
+
#: src/view/com/composer/text-input/TextInput.web.tsx:269
msgid "Drop to add images"
msgstr "ドロップして画像を追加する"
@@ -1952,7 +1968,7 @@ msgctxt "action"
msgid "Edit"
msgstr "編集"
-#: src/view/com/util/UserAvatar.tsx:328
+#: src/view/com/util/UserAvatar.tsx:332
#: src/view/com/util/UserBanner.tsx:92
msgid "Edit avatar"
msgstr "アバターを編集"
@@ -2112,7 +2128,7 @@ msgstr "有効にするメディアプレイヤー"
msgid "Enable priority notifications"
msgstr "優先通知を有効にする"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:336
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:380
msgid "Enable subtitles"
msgstr "サブタイトル(字幕)を有効にする"
@@ -2130,7 +2146,7 @@ msgstr "有効"
msgid "End of feed"
msgstr "フィードの終わり"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:157
+#: src/view/com/composer/videos/SubtitleDialog.tsx:161
msgid "Ensure you have selected a language for each subtitle file."
msgstr "各字幕ファイルに言語が選択されてることを確認してください。"
@@ -2232,7 +2248,7 @@ msgstr "フォローしているユーザーは除外"
msgid "Excludes users you follow"
msgstr "フォローしているユーザーは除外"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:353
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:397
msgid "Exit fullscreen"
msgstr "全画面表示を終了"
@@ -2667,7 +2683,7 @@ msgctxt "from-feed"
msgid "From <0/>"
msgstr "<0/>から"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:354
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:398
msgid "Fullscreen"
msgstr "全画面表示"
@@ -2696,7 +2712,7 @@ msgstr "開始"
msgid "Getting started"
msgstr "入門"
-#: src/components/MediaPreview.tsx:119
+#: src/components/MediaPreview.tsx:120
msgid "GIF"
msgstr "GIF"
@@ -3098,7 +3114,7 @@ msgstr "招待、ただし個人的なもの"
msgid "It's just you right now! Add more people to your starter pack by searching above."
msgstr "今はあなただけ!上で検索してスターターパックにより多くのユーザーを追加してください。"
-#: src/view/com/composer/Composer.tsx:1125
+#: src/view/com/composer/Composer.tsx:1129
msgid "Job ID: {0}"
msgstr "ジョブID:{0}"
@@ -3117,6 +3133,10 @@ msgstr "Blueskyに参加"
msgid "Join the conversation"
msgstr "会話に参加"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:461
+msgid "Joined {0}"
+msgstr "{0} 参加"
+
#: src/screens/Onboarding/index.tsx:21
#: src/screens/Onboarding/state.ts:89
msgid "Journalism"
@@ -3580,8 +3600,12 @@ msgstr "映画"
msgid "Music"
msgstr "音楽"
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:389
+msgctxt "video"
+msgid "Mute"
+msgstr ""
+
#: src/components/TagMenu/index.tsx:263
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:345
msgid "Mute"
msgstr "ミュート"
@@ -4042,6 +4066,10 @@ msgstr "ちょっと!"
msgid "Oh no! Something went wrong."
msgstr "ちょっと!何らかの問題が発生したようです。"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:171
+msgid "Oh no! We weren't able to generate an image for you to share. Rest assured, we're glad you're here 🦋"
+msgstr "ああ、残念!共有するための画像を生成できませんでした。ご安心ください、ここに来てくれて嬉しいです🦋"
+
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:339
msgid "OK"
msgstr "OK"
@@ -4062,7 +4090,7 @@ msgstr "<0><1/><2><3/>2>0>"
msgid "Onboarding reset"
msgstr "オンボーディングのリセット"
-#: src/view/com/composer/Composer.tsx:667
+#: src/view/com/composer/Composer.tsx:672
msgid "One or more images is missing alt text."
msgstr "1つもしくは複数の画像にALTテキストがありません。"
@@ -4113,8 +4141,8 @@ msgid "Open conversation options"
msgstr "会話のオプションを開く"
#: src/screens/Messages/Conversation/MessageInput.web.tsx:165
-#: src/view/com/composer/Composer.tsx:819
-#: src/view/com/composer/Composer.tsx:820
+#: src/view/com/composer/Composer.tsx:823
+#: src/view/com/composer/Composer.tsx:824
msgid "Open emoji picker"
msgstr "絵文字を入力"
@@ -4283,11 +4311,11 @@ msgid "Opens the threads preferences"
msgstr "スレッドの設定を開く"
#: src/view/com/notifications/FeedItem.tsx:551
-#: src/view/com/util/UserAvatar.tsx:420
+#: src/view/com/util/UserAvatar.tsx:424
msgid "Opens this profile"
msgstr "プロフィールを開く"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:81
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:88
msgid "Opens video picker"
msgstr "ビデオの選択画面を開く"
@@ -4365,11 +4393,11 @@ msgid "Password updated!"
msgstr "パスワードが更新されました!"
#: src/view/com/util/post-embeds/GifEmbed.tsx:44
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:322
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:366
msgid "Pause"
msgstr "一時停止"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:275
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:319
msgid "Pause video"
msgstr "ビデオを一時停止"
@@ -4429,7 +4457,7 @@ msgid "Pinned to your feeds"
msgstr "フィードにピン留めしました"
#: src/view/com/util/post-embeds/GifEmbed.tsx:44
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:323
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:367
msgid "Play"
msgstr "再生"
@@ -4441,8 +4469,8 @@ msgstr "{0}を再生"
msgid "Play or pause the GIF"
msgstr "GIFの再生や一時停止"
-#: src/view/com/util/post-embeds/VideoEmbed.tsx:187
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:276
+#: src/view/com/util/post-embeds/VideoEmbed.tsx:189
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:320
msgid "Play video"
msgstr "ビデオを再生"
@@ -4514,7 +4542,7 @@ msgstr "@{0}としてサインインしてください"
msgid "Please Verify Your Email"
msgstr "メールアドレスを確認してください"
-#: src/view/com/composer/Composer.tsx:355
+#: src/view/com/composer/Composer.tsx:360
msgid "Please wait for your link card to finish loading"
msgstr "リンクカードが読み込まれるまでお待ちください"
@@ -4527,8 +4555,8 @@ msgstr "政治"
msgid "Porn"
msgstr "ポルノ"
-#: src/view/com/composer/Composer.tsx:642
-#: src/view/com/composer/Composer.tsx:649
+#: src/view/com/composer/Composer.tsx:647
+#: src/view/com/composer/Composer.tsx:654
msgctxt "action"
msgid "Post"
msgstr "投稿"
@@ -4698,11 +4726,11 @@ msgstr "ユーザーを一括でミュートまたはブロックする、公開
msgid "Public, shareable lists which can drive feeds."
msgstr "フィードとして利用できる、公開された共有可能なリスト。"
-#: src/view/com/composer/Composer.tsx:627
+#: src/view/com/composer/Composer.tsx:632
msgid "Publish post"
msgstr "投稿を公開"
-#: src/view/com/composer/Composer.tsx:627
+#: src/view/com/composer/Composer.tsx:632
msgid "Publish reply"
msgstr "返信を公開"
@@ -4832,7 +4860,7 @@ msgstr "アカウントを削除"
msgid "Remove attachment"
msgstr "添付を削除"
-#: src/view/com/util/UserAvatar.tsx:387
+#: src/view/com/util/UserAvatar.tsx:391
msgid "Remove Avatar"
msgstr "アバターを削除"
@@ -4900,7 +4928,7 @@ msgstr "引用を削除"
msgid "Remove repost"
msgstr "リポストを削除"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:260
+#: src/view/com/composer/videos/SubtitleDialog.tsx:264
msgid "Remove subtitle file"
msgstr "字幕ファイルを削除"
@@ -4961,7 +4989,7 @@ msgstr "返信できません"
msgid "Replies to this post are disabled."
msgstr "この投稿への返信は無効化されています。"
-#: src/view/com/composer/Composer.tsx:640
+#: src/view/com/composer/Composer.tsx:645
msgctxt "action"
msgid "Reply"
msgstr "返信"
@@ -5415,7 +5443,7 @@ msgstr "Blueskyの求人を見る"
msgid "See this guide"
msgstr "ガイドを見る"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:587
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:631
msgid "Seek slider"
msgstr "シークバー"
@@ -5455,7 +5483,7 @@ msgstr "GIF「{0}」を選ぶ"
msgid "Select how long to mute this word for."
msgstr "このワードをどのくらいの間ミュートするのかを選択。"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:245
+#: src/view/com/composer/videos/SubtitleDialog.tsx:249
msgid "Select language..."
msgstr "言語を選択…"
@@ -5487,7 +5515,7 @@ msgstr "報告先のモデレーションサービスを選んでください"
msgid "Select the service that hosts your data."
msgstr "データをホストするサービスを選択します。"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:80
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:87
msgid "Select video"
msgstr "ビデオを選択"
@@ -5503,7 +5531,7 @@ msgstr "登録されたフィードに含める言語を選択します。選択
msgid "Select your app language for the default text to display in the app."
msgstr "アプリに表示されるデフォルトのテキストの言語を選択"
-#: src/screens/Signup/StepInfo/index.tsx:192
+#: src/screens/Signup/StepInfo/index.tsx:193
msgid "Select your date of birth"
msgstr "生年月日を選択"
@@ -5643,6 +5671,7 @@ msgstr "性的行為または性的なヌード。"
msgid "Sexually Suggestive"
msgstr "性的にきわどい"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:607
#: src/components/StarterPack/QrCodeDialog.tsx:177
#: src/screens/StarterPack/StarterPackScreen.tsx:411
#: src/screens/StarterPack/StarterPackScreen.tsx:582
@@ -5679,6 +5708,14 @@ msgstr "とにかく共有"
msgid "Share feed"
msgstr "フィードを共有"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:590
+msgid "Share image externally"
+msgstr "画像を外部に共有する"
+
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:602
+msgid "Share image in post"
+msgstr "投稿で画像を共有"
+
#: src/components/StarterPack/ShareDialog.tsx:124
#: src/components/StarterPack/ShareDialog.tsx:131
#: src/screens/StarterPack/StarterPackScreen.tsx:586
@@ -5950,8 +5987,8 @@ msgstr "何らかの問題が発生したようなので、もう一度お試し
msgid "Something went wrong!"
msgstr "何らかの問題が発生したようです!"
-#: src/App.native.tsx:102
-#: src/App.web.tsx:83
+#: src/App.native.tsx:103
+#: src/App.web.tsx:84
msgid "Sorry! Your session expired. Please log in again."
msgstr "大変申し訳ありません!セッションの有効期限が切れました。もう一度ログインしてください。"
@@ -6153,6 +6190,10 @@ msgstr "ジョークを言って!"
msgid "Tell us a little more"
msgstr "もう少し教えて"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:487
+msgid "Ten Million"
+msgstr "1000万"
+
#: src/view/shell/desktop/RightNav.tsx:90
msgid "Terms"
msgstr "条件"
@@ -6186,6 +6227,10 @@ msgstr "テキストの入力フィールド"
msgid "Thank you. Your report has been sent."
msgstr "ありがとうございます。あなたの報告は送信されました。"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:562
+msgid "Thanks for being one of our first 10 million users."
+msgstr "最初の 1,000 万人のユーザーの 1 人になっていただきありがとうございます。"
+
#: src/components/intents/VerifyEmailIntentDialog.tsx:74
msgid "Thanks, you have successfully verified your email address."
msgstr ""
@@ -6271,7 +6316,7 @@ msgstr "投稿が削除された可能性があります。"
msgid "The Privacy Policy has been moved to <0/>"
msgstr "プライバシーポリシーは<0/>に移動しました"
-#: src/state/queries/video/video.ts:222
+#: src/state/queries/video/video.ts:227
msgid "The selected video is larger than 50MB."
msgstr "選択したビデオのサイズが50MBを超えています。"
@@ -6608,7 +6653,7 @@ msgstr "メールでの2要素認証を無効にするには、メールアド
msgid "To report a conversation, please report one of its messages via the conversation screen. This lets our moderators understand the context of your issue."
msgstr "会話を報告するには、会話の画面からメッセージのうちの一つを報告してください。それによって問題の文脈をモデレーターが理解できるようになります。"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:106
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:113
msgid "To upload videos to Bluesky, you must first verify your email."
msgstr "Blueskyにビデオをアップロードするには、まずメールアドレスを確認しなくてはなりません。"
@@ -6616,6 +6661,10 @@ msgstr "Blueskyにビデオをアップロードするには、まずメール
msgid "To whom would you like to send this report?"
msgstr "この報告を誰に送りたいですか?"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:566
+msgid "Together, we're rebuilding the social internet. We're glad you're here."
+msgstr "私たちは一緒にソーシャル インターネットを再構築しています。ご参加いただきありがとうございます。"
+
#: src/view/com/util/forms/DropdownButton.tsx:255
msgid "Toggle dropdown"
msgstr "ドロップダウンを切り替え"
@@ -6739,8 +6788,12 @@ msgstr "アカウントのフォローを解除"
msgid "Unlike this feed"
msgstr "このフィードからいいねを外す"
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:388
+msgctxt "video"
+msgid "Unmute"
+msgstr ""
+
#: src/components/TagMenu/index.tsx:263
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:344
#: src/view/screens/ProfileList.tsx:689
msgid "Unmute"
msgstr "ミュートを解除"
@@ -6767,7 +6820,7 @@ msgstr "会話のミュートを解除"
msgid "Unmute thread"
msgstr "スレッドのミュートを解除"
-#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:273
+#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:317
msgid "Unmute video"
msgstr "ビデオのミュートを解除"
@@ -6809,7 +6862,7 @@ msgstr "このラベラーの登録を解除"
msgid "Unsubscribed from list"
msgstr "リストの登録を解除しました"
-#: src/state/queries/video/video.ts:240
+#: src/state/queries/video/video.ts:245
msgid "Unsupported video type: {mimeType}"
msgstr "サポートしていないビデオ形式:{mimeType}"
@@ -6846,20 +6899,20 @@ msgstr "代わりに写真をアップロード"
msgid "Upload a text file to:"
msgstr "テキストファイルのアップロード先:"
-#: src/view/com/util/UserAvatar.tsx:355
-#: src/view/com/util/UserAvatar.tsx:358
+#: src/view/com/util/UserAvatar.tsx:359
+#: src/view/com/util/UserAvatar.tsx:362
#: src/view/com/util/UserBanner.tsx:123
#: src/view/com/util/UserBanner.tsx:126
msgid "Upload from Camera"
msgstr "カメラからアップロード"
-#: src/view/com/util/UserAvatar.tsx:372
+#: src/view/com/util/UserAvatar.tsx:376
#: src/view/com/util/UserBanner.tsx:140
msgid "Upload from Files"
msgstr "ファイルからアップロード"
-#: src/view/com/util/UserAvatar.tsx:366
#: src/view/com/util/UserAvatar.tsx:370
+#: src/view/com/util/UserAvatar.tsx:374
#: src/view/com/util/UserBanner.tsx:134
#: src/view/com/util/UserBanner.tsx:138
msgid "Upload from Library"
@@ -6988,7 +7041,7 @@ msgstr "このコンテンツやプロフィールにいいねをしているユ
msgid "Value:"
msgstr "値:"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:104
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:111
msgid "Verified email required"
msgstr "メールアドレスの確認が必要"
@@ -7017,7 +7070,7 @@ msgstr "メールアドレスを確認"
msgid "Verify New Email"
msgstr "新しいメールアドレスを確認"
-#: src/view/com/composer/videos/SelectVideoBtn.tsx:108
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:115
msgid "Verify now"
msgstr "確認する"
@@ -7047,11 +7100,11 @@ msgstr "ビデオの処理に失敗"
msgid "Video Games"
msgstr "ビデオゲーム"
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:163
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:167
msgid "Video not found."
msgstr "ビデオが見つかりません。"
-#: src/view/com/composer/videos/SubtitleDialog.tsx:99
+#: src/view/com/composer/videos/SubtitleDialog.tsx:101
msgid "Video settings"
msgstr "ビデオの設定"
@@ -7059,6 +7112,11 @@ msgstr "ビデオの設定"
msgid "Video: {0}"
msgstr "ビデオ:{0}"
+#: src/view/com/composer/videos/SelectVideoBtn.tsx:58
+#: src/view/com/composer/videos/VideoPreview.web.tsx:44
+msgid "Videos must be less than 60 seconds long"
+msgstr ""
+
#: src/screens/Profile/Header/Shell.tsx:113
msgid "View {0}'s avatar"
msgstr "{0}のアバターを表示"
@@ -7224,7 +7282,7 @@ msgstr "大変申し訳ありませんが、現在ミュートされたワード
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
msgstr "大変申し訳ありませんが、検索を完了できませんでした。数分後に再試行してください。"
-#: src/view/com/composer/Composer.tsx:417
+#: src/view/com/composer/Composer.tsx:422
msgid "We're sorry! The post you are replying to has been deleted."
msgstr "大変申し訳ありません!返信しようとしている投稿は削除されました。"
@@ -7255,7 +7313,7 @@ msgstr "あなたのスターターパックを何と呼びたいですか?"
#: src/view/com/auth/SplashScreen.tsx:40
#: src/view/com/auth/SplashScreen.web.tsx:86
-#: src/view/com/composer/Composer.tsx:512
+#: src/view/com/composer/Composer.tsx:517
msgid "What's up?"
msgstr "最近どう?"
@@ -7322,11 +7380,11 @@ msgstr "ワイド"
msgid "Write a message"
msgstr "メッセージを書く"
-#: src/view/com/composer/Composer.tsx:708
+#: src/view/com/composer/Composer.tsx:713
msgid "Write post"
msgstr "投稿を書く"
-#: src/view/com/composer/Composer.tsx:511
+#: src/view/com/composer/Composer.tsx:516
#: src/view/com/post-thread/PostThreadComposePrompt.tsx:42
msgid "Write your reply"
msgstr "返信を書く"
@@ -7646,15 +7704,19 @@ msgstr "あなたのアカウント"
msgid "Your account has been deleted"
msgstr "あなたのアカウントは削除されました"
+#: src/state/queries/video/video.ts:185
+msgid "Your account is not yet old enough to upload videos. Please try again later."
+msgstr ""
+
#: src/view/screens/Settings/ExportCarDialog.tsx:65
msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately."
msgstr "あなたのアカウントの公開データの全記録を含むリポジトリは、「CAR」ファイルとしてダウンロードできます。このファイルには、画像などのメディア埋め込み、また非公開のデータは含まれていないため、それらは個別に取得する必要があります。"
-#: src/screens/Signup/StepInfo/index.tsx:180
+#: src/screens/Signup/StepInfo/index.tsx:181
msgid "Your birth date"
msgstr "生年月日"
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:167
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:171
msgid "Your browser does not support the video format. Please try a different browser."
msgstr "利用中のブラウザがこのビデオ形式をサポートしていません。他のブラウザをお試しください。"
@@ -7705,7 +7767,7 @@ msgstr "ミュートしたワード"
msgid "Your password has been changed successfully!"
msgstr "パスワードの変更が完了しました!"
-#: src/view/com/composer/Composer.tsx:463
+#: src/view/com/composer/Composer.tsx:468
msgid "Your post has been published"
msgstr "投稿を公開しました"
@@ -7721,7 +7783,7 @@ msgstr "あなたのプロフィール"
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
msgstr "あなたのプロフィール、投稿、フィード、そしてリストは他のBlueskyユーザーに見えなくなります。ログインすることでいつでもアカウントを再有効化できます。"
-#: src/view/com/composer/Composer.tsx:462
+#: src/view/com/composer/Composer.tsx:467
msgid "Your reply has been published"
msgstr "返信を公開しました"
diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po
index c51e3e826..d49528e9b 100644
--- a/src/locale/locales/pt-BR/messages.po
+++ b/src/locale/locales/pt-BR/messages.po
@@ -139,7 +139,7 @@ msgstr "{0} pessoas já usaram este pacote inicial!"
#~ msgid "{0} your feeds"
#~ msgstr "{0} seus feeds"
-#: src/view/com/util/UserAvatar.tsx:419
+#: src/view/com/util/UserAvatar.tsx:423
msgid "{0}'s avatar"
msgstr "Avatar de {0}"
@@ -656,7 +656,7 @@ msgstr "Ocorreu um erro ao gerar seu pacote inicial. Quer tentar novamente?"
msgid "An error occurred while loading the video. Please try again later."
msgstr "Ocorreu um erro ao carregar o vídeo. Tente novamente mais tarde."
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:171
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:174
msgid "An error occurred while loading the video. Please try again."
msgstr "Ocorreu um erro ao carregar o vídeo. Tente novamente."
@@ -1005,6 +1005,10 @@ msgstr "Bluesky é melhor com amigos!"
#~ msgid "Bluesky is public."
#~ msgstr "Bluesky é público."
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:201
+msgid "Bluesky now has over 10 million users, and I was #{0}!"
+msgstr "O Bluesky agora tem mais de 10 milhões de usuários, e eu tinha #{0}!"
+
#: src/components/StarterPack/ProfileStarterPacks.tsx:282
msgid "Bluesky will choose a set of recommended accounts from people in your network."
msgstr "O Bluesky escolherá um conjunto de contas recomendadas dentre as pessoas em sua rede."
@@ -1026,6 +1030,10 @@ msgstr "Desfocar imagens e filtrar dos feeds"
msgid "Books"
msgstr "Livros"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:583
+msgid "Brag a little!"
+msgstr "Gabe-se um pouco!"
+
#: src/components/FeedInterstitials.tsx:346
msgid "Browse more accounts on the Explore page"
msgstr "Navegue por mais contas na página Explorar"
@@ -1173,6 +1181,10 @@ msgstr "Legendas (.vtt)"
msgid "Captions & alt text"
msgstr "Legendas e texto alt"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:336
+msgid "Celebrating {0} users"
+msgstr "Comemorando {0} usuários"
+
#: src/view/com/modals/VerifyEmail.tsx:160
msgid "Change"
msgstr "Alterar"
@@ -1867,7 +1879,7 @@ msgstr "Tema escuro"
#~ msgid "Dark Theme"
#~ msgstr "Modo Escuro"
-#: src/screens/Signup/StepInfo/index.tsx:191
+#: src/screens/Signup/StepInfo/index.tsx:192
msgid "Date of birth"
msgstr "Data de nascimento"
@@ -2174,6 +2186,10 @@ msgstr "Baixe o Bluesky"
msgid "Download CAR file"
msgstr "Baixar arquivo CAR"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:591
+msgid "Download image"
+msgstr "Baixar imagem"
+
#: src/view/com/composer/text-input/TextInput.web.tsx:269
msgid "Drop to add images"
msgstr "Solte para adicionar imagens"
@@ -2239,7 +2255,7 @@ msgctxt "action"
msgid "Edit"
msgstr "Editar"
-#: src/view/com/util/UserAvatar.tsx:328
+#: src/view/com/util/UserAvatar.tsx:332
#: src/view/com/util/UserBanner.tsx:92
msgid "Edit avatar"
msgstr "Editar avatar"
@@ -3545,6 +3561,10 @@ msgstr "Crie uma conta no Bluesky"
msgid "Join the conversation"
msgstr "Participe da conversa"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:461
+msgid "Joined {0}"
+msgstr "Juntou-se {0}"
+
#: src/screens/Onboarding/index.tsx:21
#: src/screens/Onboarding/state.ts:89
msgid "Journalism"
@@ -4551,6 +4571,10 @@ msgstr "Opa!"
msgid "Oh no! Something went wrong."
msgstr "Opa! Algo deu errado."
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:171
+msgid "Oh no! We weren't able to generate an image for you to share. Rest assured, we're glad you're here 🦋"
+msgstr "Ah, não! Não conseguimos gerar uma imagem para você compartilhar. Fique tranquilo, estamos felizes que você esteja aqui 🦋"
+
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:339
msgid "OK"
msgstr "OK"
@@ -4821,7 +4845,7 @@ msgid "Opens the threads preferences"
msgstr "Abre as preferências de threads"
#: src/view/com/notifications/FeedItem.tsx:551
-#: src/view/com/util/UserAvatar.tsx:420
+#: src/view/com/util/UserAvatar.tsx:424
msgid "Opens this profile"
msgstr "Abre este perfil"
@@ -5410,7 +5434,7 @@ msgstr "Remover conta"
msgid "Remove attachment"
msgstr ""
-#: src/view/com/util/UserAvatar.tsx:387
+#: src/view/com/util/UserAvatar.tsx:391
msgid "Remove Avatar"
msgstr "Remover avatar"
@@ -6153,7 +6177,7 @@ msgstr "Selecione quais idiomas você deseja ver nos seus feeds. Se nenhum for s
msgid "Select your app language for the default text to display in the app."
msgstr "Selecione o idioma do seu aplicativo"
-#: src/screens/Signup/StepInfo/index.tsx:192
+#: src/screens/Signup/StepInfo/index.tsx:193
msgid "Select your date of birth"
msgstr "Selecione sua data de nascimento"
@@ -6321,6 +6345,7 @@ msgstr "Atividade sexual ou nudez erótica."
msgid "Sexually Suggestive"
msgstr "Sexualmente Sugestivo"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:607
#: src/components/StarterPack/QrCodeDialog.tsx:177
#: src/screens/StarterPack/StarterPackScreen.tsx:411
#: src/screens/StarterPack/StarterPackScreen.tsx:582
@@ -6357,6 +6382,14 @@ msgstr "Compartilhar assim"
msgid "Share feed"
msgstr "Compartilhar feed"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:590
+msgid "Share image externally"
+msgstr "Compartilhar imagem externamente"
+
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:602
+msgid "Share image in post"
+msgstr "Compartilhe a imagem na postagem"
+
#: src/components/StarterPack/ShareDialog.tsx:124
#: src/components/StarterPack/ShareDialog.tsx:131
#: src/screens/StarterPack/StarterPackScreen.tsx:586
@@ -6668,8 +6701,8 @@ msgstr "Algo deu errado. Por favor, tente novamente."
msgid "Something went wrong!"
msgstr "Algo deu errado!"
-#: src/App.native.tsx:102
-#: src/App.web.tsx:83
+#: src/App.native.tsx:103
+#: src/App.web.tsx:84
msgid "Sorry! Your session expired. Please log in again."
msgstr "Opa! Sua sessão expirou. Por favor, entre novamente."
@@ -6912,6 +6945,10 @@ msgstr "Conte uma piada!"
msgid "Tell us a little more"
msgstr "Conte-nos um pouco mais"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:487
+msgid "Ten Million"
+msgstr "Dez milhões"
+
#: src/view/shell/desktop/RightNav.tsx:90
msgid "Terms"
msgstr "Termos"
@@ -6949,6 +6986,10 @@ msgstr "Campo de entrada de texto"
msgid "Thank you. Your report has been sent."
msgstr "Obrigado. Sua denúncia foi enviada."
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:562
+msgid "Thanks for being one of our first 10 million users."
+msgstr "Obrigado por ser um dos nossos primeiros 10 milhões de usuários."
+
#: src/components/intents/VerifyEmailIntentDialog.tsx:74
msgid "Thanks, you have successfully verified your email address."
msgstr ""
@@ -7437,6 +7478,10 @@ msgstr "Para enviar vídeos para o Bluesky, você deve primeiro verificar seu e-
msgid "To whom would you like to send this report?"
msgstr "Para quem você gostaria de enviar esta denúncia?"
+#: src/components/dialogs/nuxs/TenMillion/index.tsx:566
+msgid "Together, we're rebuilding the social internet. We're glad you're here."
+msgstr "Juntos, estamos reconstruindo a internet social. Estamos felizes que você esteja aqui."
+
#: src/components/dialogs/MutedWords.tsx:112
#~ msgid "Toggle between muted word options."
#~ msgstr "Alternar entre opções de uma palavra silenciada"
@@ -7691,20 +7736,20 @@ msgstr "Enviar uma foto"
msgid "Upload a text file to:"
msgstr "Carregar um arquivo de texto para:"
-#: src/view/com/util/UserAvatar.tsx:355
-#: src/view/com/util/UserAvatar.tsx:358
+#: src/view/com/util/UserAvatar.tsx:359
+#: src/view/com/util/UserAvatar.tsx:362
#: src/view/com/util/UserBanner.tsx:123
#: src/view/com/util/UserBanner.tsx:126
msgid "Upload from Camera"
msgstr "Tirar uma foto"
-#: src/view/com/util/UserAvatar.tsx:372
+#: src/view/com/util/UserAvatar.tsx:376
#: src/view/com/util/UserBanner.tsx:140
msgid "Upload from Files"
msgstr "Carregar um arquivo"
-#: src/view/com/util/UserAvatar.tsx:366
#: src/view/com/util/UserAvatar.tsx:370
+#: src/view/com/util/UserAvatar.tsx:374
#: src/view/com/util/UserBanner.tsx:134
#: src/view/com/util/UserBanner.tsx:138
msgid "Upload from Library"
@@ -7904,7 +7949,7 @@ msgstr "Falha no processamento do vídeo"
msgid "Video Games"
msgstr "Games"
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:164
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:167
msgid "Video not found."
msgstr "Vídeo não encontrado."
@@ -8572,11 +8617,11 @@ msgstr "Sua conta ainda não tem idade suficiente para enviar vídeos. Por favor
msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately."
msgstr "O repositório da sua conta, contendo todos os seus dados públicos, pode ser baixado como um arquivo \"CAR\". Este arquivo não inclui imagens ou dados privados, estes devem ser exportados separadamente."
-#: src/screens/Signup/StepInfo/index.tsx:180
+#: src/screens/Signup/StepInfo/index.tsx:181
msgid "Your birth date"
msgstr "Sua data de nascimento"
-#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:168
+#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:171
msgid "Your browser does not support the video format. Please try a different browser."
msgstr "Seu navegador não suporta o formato de vídeo. Por favor, tente um navegador diferente."
--
2.51.2
From 003f9e06d40cebac812e4d0200b305727b5621e4 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 21:18:11 -0500
Subject: [PATCH 23/24] Fallback snoozing
---
src/components/dialogs/nuxs/index.tsx | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index ba693329f..781696070 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -1,6 +1,7 @@
import React from 'react'
import {useGate} from '#/lib/statsig/statsig'
+import {logger} from '#/logger'
import {
Nux,
useNuxs,
@@ -17,6 +18,16 @@ type Context = {
dismissActiveNux: () => void
}
+/**
+ * If we fail to complete a NUX here, it may show again on next reload,
+ * or if prefs state updates. If `true`, this fallback ensures that the last
+ * shown NUX won't show again, at least for this session.
+ *
+ * This is temporary, and only needed for the 10Milly dialog rn, since we
+ * aren't snoozing that one in device storage.
+ */
+let __isSnoozedFallback = false
+
const queuedNuxs: {
id: Nux
enabled(props: {gate: ReturnType}): boolean
@@ -56,7 +67,7 @@ function Inner() {
return isSnoozed()
})
const [activeNux, setActiveNux] = React.useState()
- const {mutate: upsertNux} = useUpsertNuxMutation()
+ const {mutateAsync: upsertNux} = useUpsertNuxMutation()
const {mutate: removeNuxs} = useRemoveNuxsMutation()
const snoozeNuxDialog = React.useCallback(() => {
@@ -79,6 +90,7 @@ function Inner() {
}
React.useEffect(() => {
+ if (__isSnoozedFallback) return
if (snoozed) return
if (!nuxs) return
@@ -99,7 +111,7 @@ function Inner() {
* other NUX dialogs configured
*/
if (!unsafe_disableSnooze) {
- // immediately snooze (in memory)
+ // immediately snooze for a day
snoozeNuxDialog()
}
@@ -108,6 +120,16 @@ function Inner() {
id,
completed: true,
data: undefined,
+ }).catch(e => {
+ logger.error(`NUX dialogs: failed to upsert '${id}' NUX`, {
+ safeMessage: e.message,
+ })
+ /*
+ * TEMP only intended for use with the 10Milly dialog rn
+ */
+ if (unsafe_disableSnooze) {
+ __isSnoozedFallback = true
+ }
})
break
--
2.51.2
From 4637c66390193a50628ae23a7891381da92abd24 Mon Sep 17 00:00:00 2001
From: Eric Bailey
Date: Thu, 12 Sep 2024 21:23:18 -0500
Subject: [PATCH 24/24] Let display name wrap
---
src/components/dialogs/nuxs/TenMillion/index.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
index 6d80dd12a..28384c3c0 100644
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ b/src/components/dialogs/nuxs/TenMillion/index.tsx
@@ -418,12 +418,11 @@ export function TenMillionInner({userNumber}: {userNumber: number}) {
*/}
{sanitizeDisplayName(