From 76c584d981f195a580e132b786e101b3d0d32380 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 9 Sep 2024 20:57:32 -0500 Subject: WIP --- src/App.native.tsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/App.native.tsx') 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}> + -- cgit 1.4.1 From 63444052e8db9d2333b887a5ba5fd261e7df52db Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 11 Sep 2024 20:01:27 -0500 Subject: Rename --- src/App.native.tsx | 4 +- src/App.web.tsx | 4 +- .../dialogs/nudges/TenMillion/icons/OnePercent.tsx | 15 - .../nudges/TenMillion/icons/PointOnePercent.tsx | 15 - .../dialogs/nudges/TenMillion/icons/TenPercent.tsx | 15 - .../nudges/TenMillion/icons/TwentyFivePercent.tsx | 15 - src/components/dialogs/nudges/TenMillion/index.tsx | 574 --------------------- src/components/dialogs/nudges/index.tsx | 56 -- .../dialogs/nuxs/TenMillion/icons/OnePercent.tsx | 15 + .../nuxs/TenMillion/icons/PointOnePercent.tsx | 15 + .../dialogs/nuxs/TenMillion/icons/TenPercent.tsx | 15 + .../nuxs/TenMillion/icons/TwentyFivePercent.tsx | 15 + src/components/dialogs/nuxs/TenMillion/index.tsx | 574 +++++++++++++++++++++ src/components/dialogs/nuxs/index.tsx | 56 ++ 14 files changed, 694 insertions(+), 694 deletions(-) delete mode 100644 src/components/dialogs/nudges/TenMillion/icons/OnePercent.tsx delete mode 100644 src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx delete mode 100644 src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx delete mode 100644 src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx delete mode 100644 src/components/dialogs/nudges/TenMillion/index.tsx delete mode 100644 src/components/dialogs/nudges/index.tsx create mode 100644 src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx create mode 100644 src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx create mode 100644 src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx create mode 100644 src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx create mode 100644 src/components/dialogs/nuxs/TenMillion/index.tsx create mode 100644 src/components/dialogs/nuxs/index.tsx (limited to 'src/App.native.tsx') 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/nudges/TenMillion/icons/OnePercent.tsx deleted file mode 100644 index 9c8d47afd..000000000 --- a/src/components/dialogs/nudges/TenMillion/icons/OnePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function OnePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx b/src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx deleted file mode 100644 index 1f9467e44..000000000 --- a/src/components/dialogs/nudges/TenMillion/icons/PointOnePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function PointOnePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx b/src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx deleted file mode 100644 index 4197be835..000000000 --- a/src/components/dialogs/nudges/TenMillion/icons/TenPercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function TenPercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx b/src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx deleted file mode 100644 index 0d3797141..000000000 --- a/src/components/dialogs/nudges/TenMillion/icons/TwentyFivePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function TwentyFivePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nudges/TenMillion/index.tsx b/src/components/dialogs/nudges/TenMillion/index.tsx deleted file mode 100644 index e110ed1ff..000000000 --- a/src/components/dialogs/nudges/TenMillion/index.tsx +++ /dev/null @@ -1,574 +0,0 @@ -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 {networkRetry} from '#/lib/async/retry' -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 {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 {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 {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 {Divider} from '#/components/Divider' -import {GradientFill} from '#/components/GradientFill' -import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' -import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download' -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' - -const DEBUG = false -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 - } -} - -function getPercentBadge(percent: number) { - if (percent <= 0.001) { - return PointOnePercent - } else if (percent <= 0.01) { - return OnePercent - } else if (percent <= 0.1) { - return TenPercent - } - // else if (percent <= 0.25) { - // return TwentyFivePercent - // } - return null -} - -function Frame({children}: {children: React.ReactNode}) { - return ( - - {children} - - ) -} - -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 [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() { - 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(() => { - setTimeout(() => { - openComposer({ - text: '10 milly, babyyy', - imageUris: [ - { - uri, - width: WIDTH, - height: HEIGHT, - }, - ], - }) - }, 1e3) - }) - } - } - - const onNativeShare = () => { - if (uri) { - controls.tenMillion.close(() => { - shareUrl(uri) - }) - } - } - - const download = async () => { - 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 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 () => { - if ( - imageRef.current && - imageRef.current.capture // && - // cavasRelayout === 'updated' - ) { - const uri = await imageRef.current.capture() - setUri(uri) - } - } - - const canvas = isLoadingData ? null : ( - - - - - - - - - - - - - - {/* Centered content */} - - - - Celebrating {formatCount(i18n, 10000000)} users - {' '} - 🎉 - - - - # - - - {i18n.number(userNumber)} - - - - {Badge && ( - - - - )} - - {/* End centered content */} - - - - {/* - - */} - - - {sanitizeDisplayName( - profile.displayName || - sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} - - - - {sanitizeHandle(profile.handle, '@')} - - - {profile.createdAt && ( - - - Joined{' '} - {i18n.date(profile.createdAt, { - dateStyle: 'long', - })} - - - )} - - - - - - - - - - - - ) - - return ( - - - - - - - {error ? ( - - - (╯°□°)╯︵ ┻━┻ - - - {error} - - - ) : isLoadingData || isLoadingImage ? ( - - ) : ( - - )} - - - - {canvas} - - - - Thanks for being an early part of Bluesky. - - - - - We're rebuilding the social internet together. Congratulations, - we're glad you're here. - {' '} - - - - - - - Brag a little! - - - - - - - - - - - - ) -} diff --git a/src/components/dialogs/nudges/index.tsx b/src/components/dialogs/nudges/index.tsx deleted file mode 100644 index eabe60c17..000000000 --- a/src/components/dialogs/nudges/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react' - -import {useSession} from '#/state/session' -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 {hasSession} = useSession() - const tenMillion = Dialog.useDialogControl() - - const ctx = React.useMemo(() => { - return { - controls: { - tenMillion, - }, - } - }, [tenMillion]) - - React.useEffect(() => { - if (!hasSession) return - - const t = setTimeout(() => { - if (!SHOWN) { - SHOWN = true - ctx.controls.tenMillion.open() - } - }, 2e3) - - return () => { - clearTimeout(t) - } - }, [ctx, hasSession]) - - return ( - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx new file mode 100644 index 000000000..9c8d47afd --- /dev/null +++ b/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import Svg, {Path} from 'react-native-svg' + +export function OnePercent({fill}: {fill?: string}) { + return ( + + + + ) +} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx new file mode 100644 index 000000000..1f9467e44 --- /dev/null +++ b/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import Svg, {Path} from 'react-native-svg' + +export function PointOnePercent({fill}: {fill?: string}) { + return ( + + + + ) +} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx new file mode 100644 index 000000000..4197be835 --- /dev/null +++ b/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import Svg, {Path} from 'react-native-svg' + +export function TenPercent({fill}: {fill?: string}) { + return ( + + + + ) +} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx new file mode 100644 index 000000000..0d3797141 --- /dev/null +++ b/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import Svg, {Path} from 'react-native-svg' + +export function TwentyFivePercent({fill}: {fill?: string}) { + return ( + + + + ) +} diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx new file mode 100644 index 000000000..663c09560 --- /dev/null +++ b/src/components/dialogs/nuxs/TenMillion/index.tsx @@ -0,0 +1,574 @@ +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 {networkRetry} from '#/lib/async/retry' +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 {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 {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 {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' +import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download' +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 +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 + } +} + +function getPercentBadge(percent: number) { + if (percent <= 0.001) { + return PointOnePercent + } else if (percent <= 0.01) { + return OnePercent + } else if (percent <= 0.1) { + return TenPercent + } + // else if (percent <= 0.25) { + // return TwentyFivePercent + // } + return null +} + +function Frame({children}: {children: React.ReactNode}) { + return ( + + {children} + + ) +} + +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 [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() { + 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(() => { + setTimeout(() => { + openComposer({ + text: '10 milly, babyyy', + imageUris: [ + { + uri, + width: WIDTH, + height: HEIGHT, + }, + ], + }) + }, 1e3) + }) + } + } + + const onNativeShare = () => { + if (uri) { + controls.tenMillion.close(() => { + shareUrl(uri) + }) + } + } + + const download = async () => { + 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 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 () => { + if ( + imageRef.current && + imageRef.current.capture // && + // cavasRelayout === 'updated' + ) { + const uri = await imageRef.current.capture() + setUri(uri) + } + } + + const canvas = isLoadingData ? null : ( + + + + + + + + + + + + + + {/* Centered content */} + + + + Celebrating {formatCount(i18n, 10000000)} users + {' '} + 🎉 + + + + # + + + {i18n.number(userNumber)} + + + + {Badge && ( + + + + )} + + {/* End centered content */} + + + + {/* + + */} + + + {sanitizeDisplayName( + profile.displayName || + sanitizeHandle(profile.handle), + moderation.ui('displayName'), + )} + + + + {sanitizeHandle(profile.handle, '@')} + + + {profile.createdAt && ( + + + Joined{' '} + {i18n.date(profile.createdAt, { + dateStyle: 'long', + })} + + + )} + + + + + + + + + + + + ) + + return ( + + + + + + + {error ? ( + + + (╯°□°)╯︵ ┻━┻ + + + {error} + + + ) : isLoadingData || isLoadingImage ? ( + + ) : ( + + )} + + + + {canvas} + + + + Thanks for being an early part of Bluesky. + + + + + We're rebuilding the social internet together. Congratulations, + we're glad you're here. + {' '} + + + + + + + Brag a little! + + + + + + + + + + + + ) +} diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx new file mode 100644 index 000000000..401dd3e66 --- /dev/null +++ b/src/components/dialogs/nuxs/index.tsx @@ -0,0 +1,56 @@ +import React from 'react' + +import {useSession} from '#/state/session' +import * as Dialog from '#/components/Dialog' +import {TenMillion} from '#/components/dialogs/nuxs/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 NuxDialogs() { + const {hasSession} = useSession() + const tenMillion = Dialog.useDialogControl() + + const ctx = React.useMemo(() => { + return { + controls: { + tenMillion, + }, + } + }, [tenMillion]) + + React.useEffect(() => { + if (!hasSession) return + + const t = setTimeout(() => { + if (!SHOWN) { + SHOWN = true + ctx.controls.tenMillion.open() + } + }, 2e3) + + return () => { + clearTimeout(t) + } + }, [ctx, hasSession]) + + return ( + + + + ) +} -- cgit 1.4.1