about summary refs log tree commit diff
path: root/src/components/dialogs
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-07-02 00:36:04 +0300
committerGitHub <noreply@github.com>2025-07-01 14:36:04 -0700
commitbc072570d27e1f397406daea355570f5aec95647 (patch)
tree0d698c0bababd9b5e221df763a1ab15744ebdb71 /src/components/dialogs
parent8f9a8ddce022e328b07b793c3f1500e1c423ef73 (diff)
downloadvoidsky-bc072570d27e1f397406daea355570f5aec95647.tar.zst
Activity notification settings (#8485)
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: hailey <me@haileyok.com>
Diffstat (limited to 'src/components/dialogs')
-rw-r--r--src/components/dialogs/nuxs/ActivitySubscriptions.tsx177
-rw-r--r--src/components/dialogs/nuxs/index.tsx19
-rw-r--r--src/components/dialogs/nuxs/utils.ts15
3 files changed, 202 insertions, 9 deletions
diff --git a/src/components/dialogs/nuxs/ActivitySubscriptions.tsx b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx
new file mode 100644
index 000000000..b9f3979ed
--- /dev/null
+++ b/src/components/dialogs/nuxs/ActivitySubscriptions.tsx
@@ -0,0 +1,177 @@
+import {useCallback} from 'react'
+import {View} from 'react-native'
+import {Image} from 'expo-image'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {isWeb} from '#/platform/detection'
+import {atoms as a, useTheme, web} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {useNuxDialogContext} from '#/components/dialogs/nuxs'
+import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle'
+import {Text} from '#/components/Typography'
+
+export function ActivitySubscriptionsNUX() {
+  const t = useTheme()
+  const {_} = useLingui()
+  const nuxDialogs = useNuxDialogContext()
+  const control = Dialog.useDialogControl()
+
+  Dialog.useAutoOpen(control)
+
+  const onClose = useCallback(() => {
+    nuxDialogs.dismissActiveNux()
+  }, [nuxDialogs])
+
+  return (
+    <Dialog.Outer control={control} onClose={onClose}>
+      <Dialog.Handle />
+
+      <Dialog.ScrollableInner
+        label={_(msg`Introducing activity notifications`)}
+        style={[web({maxWidth: 400})]}
+        contentContainerStyle={[
+          {
+            paddingTop: 0,
+            paddingLeft: 0,
+            paddingRight: 0,
+          },
+        ]}>
+        <View
+          style={[
+            a.align_center,
+            a.overflow_hidden,
+            t.atoms.bg_contrast_25,
+            {
+              gap: isWeb ? 16 : 24,
+              paddingTop: isWeb ? 24 : 48,
+              borderTopLeftRadius: a.rounded_md.borderRadius,
+              borderTopRightRadius: a.rounded_md.borderRadius,
+            },
+          ]}>
+          <View
+            style={[
+              a.pl_sm,
+              a.pr_md,
+              a.py_sm,
+              a.rounded_full,
+              a.flex_row,
+              a.align_center,
+              a.gap_xs,
+              {
+                backgroundColor: t.palette.primary_100,
+              },
+            ]}>
+            <SparkleIcon fill={t.palette.primary_800} size="sm" />
+            <Text
+              style={[
+                a.font_bold,
+                {
+                  color: t.palette.primary_800,
+                },
+              ]}>
+              <Trans>New Feature</Trans>
+            </Text>
+          </View>
+
+          <View style={[a.relative, a.w_full]}>
+            <View
+              style={[
+                a.absolute,
+                t.atoms.bg_contrast_25,
+                t.atoms.shadow_md,
+                {
+                  shadowOpacity: 0.4,
+                  top: 5,
+                  bottom: 0,
+                  left: '17%',
+                  right: '17%',
+                  width: '66%',
+                  borderTopLeftRadius: 40,
+                  borderTopRightRadius: 40,
+                },
+              ]}
+            />
+            <View
+              style={[
+                a.overflow_hidden,
+                {
+                  aspectRatio: 398 / 228,
+                },
+              ]}>
+              <Image
+                accessibilityIgnoresInvertColors
+                source={require('../../../../assets/images/activity_notifications_announcement.webp')}
+                style={[
+                  a.w_full,
+                  {
+                    aspectRatio: 398 / 268,
+                  },
+                ]}
+                alt={_(
+                  msg`A screenshot of a profile page with a bell icon next to the follow button, indicating the new activity notifications feature.`,
+                )}
+              />
+            </View>
+          </View>
+        </View>
+        <View
+          style={[
+            a.align_center,
+            a.px_xl,
+            isWeb ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_3xl, a.gap_3xl],
+          ]}>
+          <View style={[a.gap_md, a.align_center]}>
+            <Text
+              style={[
+                a.text_3xl,
+                a.leading_tight,
+                a.font_heavy,
+                a.text_center,
+                {
+                  fontSize: isWeb ? 28 : 32,
+                  maxWidth: 300,
+                },
+              ]}>
+              <Trans>Get notified when someone posts</Trans>
+            </Text>
+            <Text
+              style={[
+                a.text_md,
+                a.leading_snug,
+                a.text_center,
+                {
+                  maxWidth: 340,
+                },
+              ]}>
+              <Trans>
+                You can now choose to be notified when specific people post. If
+                there’s someone you want timely updates from, go to their
+                profile and find the new bell icon near the follow button.
+              </Trans>
+            </Text>
+          </View>
+
+          {!isWeb && (
+            <Button
+              label={_(msg`Close`)}
+              size="large"
+              variant="solid"
+              color="primary"
+              onPress={() => {
+                control.close()
+              }}
+              style={[a.w_full, {maxWidth: 280}]}>
+              <ButtonText>
+                <Trans>Close</Trans>
+              </ButtonText>
+            </Button>
+          )}
+        </View>
+
+        <Dialog.Close />
+      </Dialog.ScrollableInner>
+    </Dialog.Outer>
+  )
+}
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index 11377e1de..8096a0141 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -11,12 +11,12 @@ import {
 import {useProfileQuery} from '#/state/queries/profile'
 import {type SessionAccount, useSession} from '#/state/session'
 import {useOnboardingState} from '#/state/shell'
-import {InitialVerificationAnnouncement} from '#/components/dialogs/nuxs/InitialVerificationAnnouncement'
+import {ActivitySubscriptionsNUX} from '#/components/dialogs/nuxs/ActivitySubscriptions'
 /*
  * NUXs
  */
 import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
-import {isDaysOld} from '#/components/dialogs/nuxs/utils'
+import {isExistingUserAsOf} from '#/components/dialogs/nuxs/utils'
 
 type Context = {
   activeNux: Nux | undefined
@@ -33,9 +33,12 @@ const queuedNuxs: {
   }) => boolean
 }[] = [
   {
-    id: Nux.InitialVerificationAnnouncement,
+    id: Nux.ActivitySubscriptions,
     enabled: ({currentProfile}) => {
-      return isDaysOld(2, currentProfile.createdAt)
+      return isExistingUserAsOf(
+        '2025-07-03T00:00:00.000Z',
+        currentProfile.createdAt,
+      )
     },
   },
 ]
@@ -111,7 +114,7 @@ function Inner({
   }
 
   React.useEffect(() => {
-    if (snoozed) return
+    if (snoozed) return // comment this out to test
     if (!nuxs) return
 
     for (const {id, enabled} of queuedNuxs) {
@@ -119,7 +122,7 @@ function Inner({
 
       // check if completed first
       if (nux && nux.completed) {
-        continue
+        continue // comment this out to test
       }
 
       // then check gate (track exposure)
@@ -172,9 +175,7 @@ function Inner({
   return (
     <Context.Provider value={ctx}>
       {/*For example, activeNux === Nux.NeueTypography && <NeueTypography />*/}
-      {activeNux === Nux.InitialVerificationAnnouncement && (
-        <InitialVerificationAnnouncement />
-      )}
+      {activeNux === Nux.ActivitySubscriptions && <ActivitySubscriptionsNUX />}
     </Context.Provider>
   )
 }
diff --git a/src/components/dialogs/nuxs/utils.ts b/src/components/dialogs/nuxs/utils.ts
index 0cc510484..ba8f0169d 100644
--- a/src/components/dialogs/nuxs/utils.ts
+++ b/src/components/dialogs/nuxs/utils.ts
@@ -16,3 +16,18 @@ export function isDaysOld(days: number, createdAt?: string) {
   if (isOldEnough) return true
   return false
 }
+
+export function isExistingUserAsOf(date: string, createdAt?: string) {
+  /*
+   * Should never happen because we gate NUXs to only accounts with a valid
+   * profile and a `createdAt` (see `nuxs/index.tsx`). But if it ever did, the
+   * account is either old enough to be pre-onboarding, or some failure happened
+   * during account creation. Fail closed. - esb
+   */
+  if (!createdAt) return false
+
+  const threshold = Date.parse(date)
+  const then = new Date(createdAt).getTime()
+
+  return then < threshold
+}