about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--jest/jestSetup.js2
-rw-r--r--package.json2
-rw-r--r--src/components/ReportDialog/SelectLabelerView.tsx115
-rw-r--r--src/components/ReportDialog/SelectReportOptionView.tsx18
-rw-r--r--src/components/ReportDialog/SubmitView.tsx8
-rw-r--r--src/components/ReportDialog/index.tsx54
-rw-r--r--src/state/queries/labeler.ts8
-rw-r--r--yarn.lock60
8 files changed, 210 insertions, 57 deletions
diff --git a/jest/jestSetup.js b/jest/jestSetup.js
index d8cee9bfd..e690e813a 100644
--- a/jest/jestSetup.js
+++ b/jest/jestSetup.js
@@ -88,3 +88,5 @@ jest.mock('sentry-expo', () => ({
     ReactNavigationInstrumentation: jest.fn(),
   },
 }))
+
+jest.mock('crypto', () => ({}))
diff --git a/package.json b/package.json
index 30df66950..b526825fb 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "update-extensions": "scripts/updateExtensions.sh"
   },
   "dependencies": {
-    "@atproto/api": "^0.11.2",
+    "@atproto/api": "^0.12.0",
     "@bam.tech/react-native-image-resizer": "^3.0.4",
     "@braintree/sanitize-url": "^6.0.2",
     "@emoji-mart/react": "^1.1.1",
diff --git a/src/components/ReportDialog/SelectLabelerView.tsx b/src/components/ReportDialog/SelectLabelerView.tsx
new file mode 100644
index 000000000..300114fc4
--- /dev/null
+++ b/src/components/ReportDialog/SelectLabelerView.tsx
@@ -0,0 +1,115 @@
+import React from 'react'
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {AppBskyLabelerDefs} from '@atproto/api'
+
+export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
+
+import {atoms as a, useTheme} from '#/alf'
+import {Text} from '#/components/Typography'
+import {Button, useButtonContext} from '#/components/Button'
+import {Divider} from '#/components/Divider'
+import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
+
+import {ReportDialogProps} from './types'
+
+export function SelectLabelerView({
+  ...props
+}: ReportDialogProps & {
+  labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
+  onSelectLabeler: (v: string) => void
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+
+  return (
+    <View style={[a.gap_lg]}>
+      <View style={[a.justify_center, a.gap_sm]}>
+        <Text style={[a.text_2xl, a.font_bold]}>
+          <Trans>Select moderation service</Trans>
+        </Text>
+        <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
+          <Trans>Who do you want to send this report to?</Trans>
+        </Text>
+      </View>
+
+      <Divider />
+
+      <View style={[a.gap_sm, {marginHorizontal: a.p_md.padding * -1}]}>
+        {props.labelers.map(labeler => {
+          return (
+            <Button
+              key={labeler.creator.did}
+              label={_(msg`Send report to ${labeler.creator.displayName}`)}
+              onPress={() => props.onSelectLabeler(labeler.creator.did)}>
+              <LabelerButton
+                title={labeler.creator.displayName || labeler.creator.handle}
+                description={labeler.creator.description || ''}
+              />
+            </Button>
+          )
+        })}
+      </View>
+    </View>
+  )
+}
+
+function LabelerButton({
+  title,
+  description,
+}: {
+  title: string
+  description: string
+}) {
+  const t = useTheme()
+  const {hovered, pressed} = useButtonContext()
+  const interacted = hovered || pressed
+
+  const styles = React.useMemo(() => {
+    return {
+      interacted: {
+        backgroundColor: t.palette.contrast_50,
+      },
+    }
+  }, [t])
+
+  return (
+    <View
+      style={[
+        a.w_full,
+        a.flex_row,
+        a.align_center,
+        a.justify_between,
+        a.p_md,
+        a.rounded_md,
+        {paddingRight: 70},
+        interacted && styles.interacted,
+      ]}>
+      <View style={[a.flex_1, a.gap_xs]}>
+        <Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
+          {title}
+        </Text>
+        <Text style={[a.leading_tight, {maxWidth: 400}]} numberOfLines={3}>
+          {description}
+        </Text>
+      </View>
+
+      <View
+        style={[
+          a.absolute,
+          a.inset_0,
+          a.justify_center,
+          a.pr_md,
+          {left: 'auto'},
+        ]}>
+        <ChevronRight
+          size="md"
+          fill={
+            hovered ? t.palette.primary_500 : t.atoms.text_contrast_low.color
+          }
+        />
+      </View>
+    </View>
+  )
+}
diff --git a/src/components/ReportDialog/SelectReportOptionView.tsx b/src/components/ReportDialog/SelectReportOptionView.tsx
index 8ae0b52ec..037a62fce 100644
--- a/src/components/ReportDialog/SelectReportOptionView.tsx
+++ b/src/components/ReportDialog/SelectReportOptionView.tsx
@@ -18,7 +18,10 @@ import {
   useButtonContext,
 } from '#/components/Button'
 import {Divider} from '#/components/Divider'
-import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
+import {
+  ChevronRight_Stroke2_Corner0_Rounded as ChevronRight,
+  ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft,
+} from '#/components/icons/Chevron'
 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight'
 
 import {ReportDialogProps} from './types'
@@ -28,6 +31,7 @@ export function SelectReportOptionView({
 }: ReportDialogProps & {
   labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
   onSelectReportOption: (reportOption: ReportOption) => void
+  goBack: () => void
 }) {
   const t = useTheme()
   const {_} = useLingui()
@@ -60,6 +64,18 @@ export function SelectReportOptionView({
 
   return (
     <View style={[a.gap_lg]}>
+      {props.labelers?.length > 1 ? (
+        <Button
+          size="small"
+          variant="solid"
+          color="secondary"
+          shape="round"
+          label={_(msg`Go back to previous step`)}
+          onPress={props.goBack}>
+          <ButtonIcon icon={ChevronLeft} />
+        </Button>
+      ) : null}
+
       <View style={[a.justify_center, a.gap_sm]}>
         <Text style={[a.text_2xl, a.font_bold]}>{i18n.title}</Text>
         <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
diff --git a/src/components/ReportDialog/SubmitView.tsx b/src/components/ReportDialog/SubmitView.tsx
index 99af64a2a..d47211c81 100644
--- a/src/components/ReportDialog/SubmitView.tsx
+++ b/src/components/ReportDialog/SubmitView.tsx
@@ -24,11 +24,13 @@ import {getAgent} from '#/state/session'
 export function SubmitView({
   params,
   labelers,
+  selectedLabeler,
   selectedReportOption,
   goBack,
   onSubmitComplete,
 }: ReportDialogProps & {
   labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
+  selectedLabeler: string
   selectedReportOption: ReportOption
   goBack: () => void
   onSubmitComplete: () => void
@@ -37,9 +39,9 @@ export function SubmitView({
   const {_} = useLingui()
   const [details, setDetails] = React.useState<string>('')
   const [submitting, setSubmitting] = React.useState<boolean>(false)
-  const [selectedServices, setSelectedServices] = React.useState<string[]>(
-    labelers?.map(labeler => labeler.creator.did) || [],
-  )
+  const [selectedServices, setSelectedServices] = React.useState<string[]>([
+    selectedLabeler,
+  ])
   const [error, setError] = React.useState('')
 
   const submit = React.useCallback(async () => {
diff --git a/src/components/ReportDialog/index.tsx b/src/components/ReportDialog/index.tsx
index b35727c7d..f01ff3f3b 100644
--- a/src/components/ReportDialog/index.tsx
+++ b/src/components/ReportDialog/index.tsx
@@ -12,9 +12,11 @@ import * as Dialog from '#/components/Dialog'
 import {Text} from '#/components/Typography'
 
 import {ReportDialogProps} from './types'
+import {SelectLabelerView} from './SelectLabelerView'
 import {SelectReportOptionView} from './SelectReportOptionView'
 import {SubmitView} from './SubmitView'
 import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
+import {AppBskyLabelerDefs} from '@atproto/api'
 
 export function ReportDialog(props: ReportDialogProps) {
   return (
@@ -33,9 +35,6 @@ function ReportDialogInner(props: ReportDialogProps) {
     error,
   } = useMyLabelersQuery()
   const isLoading = useDelayedLoading(500, isLabelerLoading)
-  const [selectedReportOption, setSelectedReportOption] = React.useState<
-    ReportOption | undefined
-  >()
 
   return (
     <Dialog.ScrollableInner label="Report Dialog">
@@ -51,23 +50,46 @@ function ReportDialogInner(props: ReportDialogProps) {
             <Trans>Something went wrong, please try again.</Trans>
           </Text>
         </View>
-      ) : selectedReportOption ? (
-        <SubmitView
-          {...props}
-          labelers={labelers}
-          selectedReportOption={selectedReportOption}
-          goBack={() => setSelectedReportOption(undefined)}
-          onSubmitComplete={() => props.control.close()}
-        />
       ) : (
-        <SelectReportOptionView
-          {...props}
-          labelers={labelers}
-          onSelectReportOption={setSelectedReportOption}
-        />
+        <ReportDialogLoaded labelers={labelers} {...props} />
       )}
 
       <Dialog.Close />
     </Dialog.ScrollableInner>
   )
 }
+
+function ReportDialogLoaded(
+  props: ReportDialogProps & {
+    labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
+  },
+) {
+  const [selectedLabeler, setSelectedLabeler] = React.useState<
+    string | undefined
+  >(props.labelers.length === 1 ? props.labelers[0].creator.did : undefined)
+  const [selectedReportOption, setSelectedReportOption] = React.useState<
+    ReportOption | undefined
+  >()
+
+  if (selectedReportOption && selectedLabeler) {
+    return (
+      <SubmitView
+        {...props}
+        selectedLabeler={selectedLabeler}
+        selectedReportOption={selectedReportOption}
+        goBack={() => setSelectedReportOption(undefined)}
+        onSubmitComplete={() => props.control.close()}
+      />
+    )
+  }
+  if (selectedLabeler) {
+    return (
+      <SelectReportOptionView
+        {...props}
+        goBack={() => setSelectedLabeler(undefined)}
+        onSelectReportOption={setSelectedReportOption}
+      />
+    )
+  }
+  return <SelectLabelerView {...props} onSelectLabeler={setSelectedLabeler} />
+}
diff --git a/src/state/queries/labeler.ts b/src/state/queries/labeler.ts
index c405a6b57..b2f93c4a4 100644
--- a/src/state/queries/labeler.ts
+++ b/src/state/queries/labeler.ts
@@ -4,7 +4,7 @@ import {AppBskyLabelerDefs} from '@atproto/api'
 
 import {getAgent} from '#/state/session'
 import {preferencesQueryKey} from '#/state/queries/preferences'
-import {STALE, PUBLIC_BSKY_AGENT} from '#/state/queries'
+import {STALE} from '#/state/queries'
 
 export const labelerInfoQueryKey = (did: string) => ['labeler-info', did]
 export const labelersInfoQueryKey = (dids: string[]) => [
@@ -27,7 +27,7 @@ export function useLabelerInfoQuery({
     enabled: !!did && enabled !== false,
     queryKey: labelerInfoQueryKey(did as string),
     queryFn: async () => {
-      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({
+      const res = await getAgent().app.bsky.labeler.getServices({
         dids: [did as string],
         detailed: true,
       })
@@ -41,7 +41,7 @@ export function useLabelersInfoQuery({dids}: {dids: string[]}) {
     enabled: !!dids.length,
     queryKey: labelersInfoQueryKey(dids),
     queryFn: async () => {
-      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({dids})
+      const res = await getAgent().app.bsky.labeler.getServices({dids})
       return res.data.views as AppBskyLabelerDefs.LabelerView[]
     },
   })
@@ -54,7 +54,7 @@ export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
     gcTime: 1000 * 60 * 60 * 6, // 6 hours
     staleTime: STALE.MINUTES.ONE,
     queryFn: async () => {
-      const res = await PUBLIC_BSKY_AGENT.app.bsky.labeler.getServices({
+      const res = await getAgent().app.bsky.labeler.getServices({
         dids,
         detailed: true,
       })
diff --git a/yarn.lock b/yarn.lock
index 83dee81cd..266734cdc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -34,19 +34,17 @@
     jsonpointer "^5.0.0"
     leven "^3.1.0"
 
-"@atproto/api@^0.11.2":
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.11.2.tgz#33432cdaeff674d8b21a28601542884bbe1473b2"
-  integrity sha512-VYpavKaEfuXhce9mP/+boZ7BNglnKSReFCRNXhFpa1KnAvWFHGsf+2jP69skEEnZ1XY4/lQhXdDzZdUIqXn2aA==
-  dependencies:
-    "@atproto/common-web" "^0.2.4"
-    "@atproto/lexicon" "^0.3.3"
-    "@atproto/syntax" "^0.2.1"
-    "@atproto/xrpc" "^0.4.3"
+"@atproto/api@^0.12.0":
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.0.tgz#69e52f8761dc7d76c675fa7284bd49240bb0df64"
+  integrity sha512-nSWiad1Z6IC/oVFSVxD5gZLhkD+J4EW2CFqAqIhklJNc0cjFKdmf8D56Pac6Ktm1sJoM6TVZ8GEeuEG6bJS/aQ==
+  dependencies:
+    "@atproto/common-web" "^0.3.0"
+    "@atproto/lexicon" "^0.4.0"
+    "@atproto/syntax" "^0.3.0"
+    "@atproto/xrpc" "^0.5.0"
     multiformats "^9.9.0"
     tlds "^1.234.0"
-    typed-emitter "^2.1.0"
-    zod "^3.21.4"
 
 "@atproto/api@^0.9.5":
   version "0.9.5"
@@ -144,10 +142,10 @@
     uint8arrays "3.0.0"
     zod "^3.21.4"
 
-"@atproto/common-web@^0.2.4":
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.2.4.tgz#a77e0a7f8f025115b3db4f7c530b934b10ab3d82"
-  integrity sha512-6+DOhQcTklFmeiSkZRx6iFeqi4OFtGl4yEDGATk00q4tEcPoPvyOBtYHN6+G9lrfJIfx5RfmggamvXlJv1PxxA==
+"@atproto/common-web@^0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb"
+  integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA==
   dependencies:
     graphemer "^1.4.0"
     multiformats "^9.9.0"
@@ -255,13 +253,13 @@
     multiformats "^9.9.0"
     zod "^3.21.4"
 
-"@atproto/lexicon@^0.3.3":
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.3.3.tgz#e242bf8b024661b3312a8da5b84fe2cd627a6ab1"
-  integrity sha512-6FOjdc3V05JKrtkhjfhHMS7f/4hMJOeHNtoE3Na7iFMpzBz0Lw5sw8kIFKY8pc8IG79qGcFgELyHLsljZYX+5A==
+"@atproto/lexicon@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.0.tgz#63e8829945d80c25524882caa8ed27b1151cc576"
+  integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ==
   dependencies:
-    "@atproto/common-web" "^0.2.4"
-    "@atproto/syntax" "^0.2.1"
+    "@atproto/common-web" "^0.3.0"
+    "@atproto/syntax" "^0.3.0"
     iso-datestring-validator "^2.2.2"
     multiformats "^9.9.0"
     zod "^3.21.4"
@@ -361,12 +359,10 @@
   dependencies:
     "@atproto/common-web" "^0.2.3"
 
-"@atproto/syntax@^0.2.1":
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.2.1.tgz#3abcb050c122e7ce80fc25b49cd7d86c63dc6c2e"
-  integrity sha512-ImOuiICtB5h78j90hAYOfTYzr5q5Wut0irNdELiogA3i74a8EXThe+j6Tj8snanYggrShbu5c6BDc1tVj477Yw==
-  dependencies:
-    "@atproto/common-web" "^0.2.4"
+"@atproto/syntax@^0.3.0":
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.0.tgz#fafa2dbea9add37253005cb663e7373e05e618b3"
+  integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
 
 "@atproto/xrpc-server@^0.4.2":
   version "0.4.2"
@@ -393,12 +389,12 @@
     "@atproto/lexicon" "^0.3.1"
     zod "^3.21.4"
 
-"@atproto/xrpc@^0.4.3":
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.4.3.tgz#19d85cb7f343369363e664917945dec1f89fde97"
-  integrity sha512-0rn3abHORG0T93mci8WW97Cpg2ClU2aCtTq5rxdCPRsl9P4tyP+8F4snbkrIaMbVO05Rd9D9gFwtWs5Z473pCQ==
+"@atproto/xrpc@^0.5.0":
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032"
+  integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog==
   dependencies:
-    "@atproto/lexicon" "^0.3.3"
+    "@atproto/lexicon" "^0.4.0"
     zod "^3.21.4"
 
 "@aws-crypto/crc32@3.0.0":