about summary refs log tree commit diff
path: root/src/components/LabelingServiceCard/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/LabelingServiceCard/index.tsx')
-rw-r--r--src/components/LabelingServiceCard/index.tsx182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx
new file mode 100644
index 000000000..f924f0f59
--- /dev/null
+++ b/src/components/LabelingServiceCard/index.tsx
@@ -0,0 +1,182 @@
+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'
+
+import {getLabelingServiceTitle} from '#/lib/moderation'
+import {Link as InternalLink, LinkProps} from '#/components/Link'
+import {Text} from '#/components/Typography'
+import {useLabelerInfoQuery} from '#/state/queries/labeler'
+import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
+import {RichText} from '#/components/RichText'
+import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {sanitizeHandle} from '#/lib/strings/handles'
+import {pluralize} from '#/lib/strings/helpers'
+
+type LabelingServiceProps = {
+  labeler: AppBskyLabelerDefs.LabelerViewDetailed
+}
+
+export function Outer({
+  children,
+  style,
+}: React.PropsWithChildren<ViewStyleProp>) {
+  return (
+    <View
+      style={[
+        a.flex_row,
+        a.gap_md,
+        a.w_full,
+        a.p_lg,
+        a.pr_md,
+        a.overflow_hidden,
+        style,
+      ]}>
+      {children}
+    </View>
+  )
+}
+
+export function Avatar({avatar}: {avatar?: string}) {
+  return <UserAvatar type="labeler" size={40} avatar={avatar} />
+}
+
+export function Title({value}: {value: string}) {
+  return <Text style={[a.text_md, a.font_bold]}>{value}</Text>
+}
+
+export function Description({value, handle}: {value?: string; handle: string}) {
+  return value ? (
+    <Text numberOfLines={2}>
+      <RichText value={value} style={[]} />
+    </Text>
+  ) : (
+    <Text>
+      <Trans>By {sanitizeHandle(handle, '@')}</Trans>
+    </Text>
+  )
+}
+
+export function LikeCount({count}: {count: number}) {
+  const t = useTheme()
+  return (
+    <Text
+      style={[
+        a.mt_sm,
+        a.text_sm,
+        t.atoms.text_contrast_medium,
+        {fontWeight: '500'},
+      ]}>
+      <Trans>
+        Liked by {count} {pluralize(count, 'user')}
+      </Trans>
+    </Text>
+  )
+}
+
+export function Content({children}: React.PropsWithChildren<{}>) {
+  const t = useTheme()
+
+  return (
+    <View
+      style={[
+        a.flex_1,
+        a.flex_row,
+        a.gap_md,
+        a.align_center,
+        a.justify_between,
+      ]}>
+      <View style={[a.gap_xs, a.flex_1]}>{children}</View>
+
+      <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
+    </View>
+  )
+}
+
+/**
+ * The canonical view for a labeling service. Use this or compose your own.
+ */
+export function Default({
+  labeler,
+  style,
+}: LabelingServiceProps & ViewStyleProp) {
+  return (
+    <Outer style={style}>
+      <Avatar avatar={labeler.creator.avatar} />
+      <Content>
+        <Title
+          value={getLabelingServiceTitle({
+            displayName: labeler.creator.displayName,
+            handle: labeler.creator.handle,
+          })}
+        />
+        <Description
+          value={labeler.creator.description}
+          handle={labeler.creator.handle}
+        />
+        {labeler.likeCount ? <LikeCount count={labeler.likeCount} /> : null}
+      </Content>
+    </Outer>
+  )
+}
+
+export function Link({
+  children,
+  labeler,
+}: LabelingServiceProps & Pick<LinkProps, 'children'>) {
+  const {_} = useLingui()
+
+  return (
+    <InternalLink
+      to={{
+        screen: 'Profile',
+        params: {
+          name: labeler.creator.handle,
+        },
+      }}
+      label={_(
+        msg`View the labeling service provided by @${labeler.creator.handle}`,
+      )}>
+      {children}
+    </InternalLink>
+  )
+}
+
+// TODO not finished yet
+export function DefaultSkeleton() {
+  return (
+    <View>
+      <Text>Loading</Text>
+    </View>
+  )
+}
+
+export function Loader({
+  did,
+  loading: LoadingComponent = DefaultSkeleton,
+  error: ErrorComponent,
+  component: Component,
+}: {
+  did: string
+  loading?: React.ComponentType<{}>
+  error?: React.ComponentType<{error: string}>
+  component: React.ComponentType<{
+    labeler: AppBskyLabelerDefs.LabelerViewDetailed
+  }>
+}) {
+  const {isLoading, data, error} = useLabelerInfoQuery({did})
+
+  return isLoading ? (
+    LoadingComponent ? (
+      <LoadingComponent />
+    ) : null
+  ) : error || !data ? (
+    ErrorComponent ? (
+      <ErrorComponent error={error?.message || 'Unknown error'} />
+    ) : null
+  ) : (
+    <Component labeler={data} />
+  )
+}