207 lines
4.8 KiB
TypeScript
207 lines
4.8 KiB
TypeScript
import {View} from 'react-native'
|
|
import {msg} from '@lingui/core/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {Plural, Trans} from '@lingui/react/macro'
|
|
|
|
import {getLabelingServiceTitle} from '#/lib/moderation'
|
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
|
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|
import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
|
|
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
|
|
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
|
import {RichText} from '#/components/RichText'
|
|
import {Text} from '#/components/Typography'
|
|
import {type app} from '#/lexicons'
|
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
|
|
|
|
type LabelingServiceProps = {
|
|
labeler: app.bsky.labeler.defs.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 emoji style={[a.text_md, a.font_semi_bold, a.leading_tight]}>
|
|
{value}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
export function Description({value, handle}: {value?: string; handle: string}) {
|
|
const {_} = useLingui()
|
|
return value ? (
|
|
<Text numberOfLines={2}>
|
|
<RichText value={value} />
|
|
</Text>
|
|
) : (
|
|
<Text emoji style={[a.leading_snug]}>
|
|
{_(msg`By ${sanitizeHandle(handle, '@')}`)}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
export function RegionalNotice() {
|
|
const t = useTheme()
|
|
return (
|
|
<View
|
|
style={[
|
|
a.flex_row,
|
|
a.align_center,
|
|
a.gap_xs,
|
|
a.pt_2xs,
|
|
{marginLeft: -2},
|
|
]}>
|
|
<Flag fill={t.atoms.text_contrast_low.color} size="sm" />
|
|
<Text style={[a.italic, a.leading_snug]}>
|
|
<Trans>Required in your region</Trans>
|
|
</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export function LikeCount({likeCount}: {likeCount: number}) {
|
|
const t = useTheme()
|
|
return (
|
|
<Text
|
|
style={[
|
|
a.mt_sm,
|
|
a.text_sm,
|
|
t.atoms.text_contrast_medium,
|
|
{fontWeight: '600'},
|
|
]}>
|
|
<Trans>
|
|
Liked by <Plural value={likeCount} one="# user" other="# users" />
|
|
</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_2xs, 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 likeCount={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.did,
|
|
},
|
|
}}
|
|
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: app.bsky.labeler.defs.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} />
|
|
)
|
|
}
|