Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {useMemo, useState} from 'react'
2import {View} from 'react-native'
3import {type AppBskyActorDefs, moderateProfile} from '@atproto/api'
4import {msg} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6import {Trans} from '@lingui/react/macro'
7import {differenceInSeconds} from 'date-fns'
8
9import {HITSLOP_10} from '#/lib/constants'
10import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
11import {sanitizeDisplayName} from '#/lib/strings/display-names'
12import {useModerationOpts} from '#/state/preferences/moderation-opts'
13import {useSession} from '#/state/session'
14import {atoms as a, useTheme, web} from '#/alf'
15import {Button, ButtonText} from '#/components/Button'
16import * as Dialog from '#/components/Dialog'
17import {useDialogControl} from '#/components/Dialog'
18import {Newskie} from '#/components/icons/Newskie'
19import * as StarterPackCard from '#/components/StarterPack/StarterPackCard'
20import {Text} from '#/components/Typography'
21import {IS_NATIVE} from '#/env'
22
23export function NewskieDialog({
24 profile,
25 disabled,
26}: {
27 profile: AppBskyActorDefs.ProfileViewDetailed
28 disabled?: boolean
29}) {
30 const t = useTheme()
31 const {_} = useLingui()
32 const control = useDialogControl()
33
34 const createdAt = profile.createdAt
35
36 const [now] = useState(() => Date.now())
37 const daysOld = useMemo(() => {
38 if (!createdAt) return Infinity
39 return differenceInSeconds(now, new Date(createdAt)) / 86400
40 }, [createdAt, now])
41
42 if (!createdAt || daysOld > 7) return null
43
44 return (
45 <View style={[a.pr_2xs]}>
46 <Button
47 disabled={disabled}
48 label={_(
49 msg`This user is new here. Press for more info about when they joined.`,
50 )}
51 hitSlop={HITSLOP_10}
52 onPress={control.open}>
53 {({hovered, pressed}) => (
54 <Newskie
55 size="lg"
56 fill={t.palette.yellow}
57 style={{
58 opacity: hovered || pressed ? 0.5 : 1,
59 }}
60 />
61 )}
62 </Button>
63
64 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
65 <Dialog.Handle />
66 <DialogInner profile={profile} createdAt={createdAt} now={now} />
67 </Dialog.Outer>
68 </View>
69 )
70}
71
72function DialogInner({
73 profile,
74 createdAt,
75 now,
76}: {
77 profile: AppBskyActorDefs.ProfileViewDetailed
78 createdAt: string
79 now: number
80}) {
81 const control = Dialog.useDialogContext()
82 const {_} = useLingui()
83 const t = useTheme()
84 const moderationOpts = useModerationOpts()
85 const {currentAccount} = useSession()
86 const timeAgo = useGetTimeAgo()
87 const isMe = profile.did === currentAccount?.did
88
89 const profileName = useMemo(() => {
90 if (!moderationOpts) return profile.displayName || profile.handle
91 const moderation = moderateProfile(profile, moderationOpts)
92 return sanitizeDisplayName(
93 profile.displayName || profile.handle,
94 moderation.ui('displayName'),
95 )
96 }, [moderationOpts, profile])
97
98 const getJoinMessage = () => {
99 const timeAgoString = timeAgo(createdAt, now, {format: 'long'})
100
101 if (isMe) {
102 if (profile.joinedViaStarterPack) {
103 return _(
104 msg`You joined Bluesky using a starter pack ${timeAgoString} ago`,
105 )
106 } else {
107 return _(msg`You joined Bluesky ${timeAgoString} ago`)
108 }
109 } else {
110 if (profile.joinedViaStarterPack) {
111 return _(
112 msg`${profileName} joined Bluesky using a starter pack ${timeAgoString} ago`,
113 )
114 } else {
115 return _(msg`${profileName} joined Bluesky ${timeAgoString} ago`)
116 }
117 }
118 }
119
120 return (
121 <Dialog.ScrollableInner
122 label={_(msg`New user info dialog`)}
123 style={web({maxWidth: 400})}>
124 <View style={[a.gap_md]}>
125 <View style={[a.align_center]}>
126 <View
127 style={[
128 {
129 height: 60,
130 width: 64,
131 },
132 ]}>
133 <Newskie
134 width={64}
135 height={64}
136 fill={t.palette.yellow}
137 style={[a.absolute, a.inset_0]}
138 />
139 </View>
140 <Text style={[a.font_semi_bold, a.text_xl]}>
141 {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>}
142 </Text>
143 </View>
144 <Text style={[a.text_md, a.text_center, a.leading_snug]}>
145 {getJoinMessage()}
146 </Text>
147 {profile.joinedViaStarterPack ? (
148 <StarterPackCard.Link
149 starterPack={profile.joinedViaStarterPack}
150 onPress={() => control.close()}>
151 <View
152 style={[
153 a.w_full,
154 a.mt_sm,
155 a.p_lg,
156 a.border,
157 a.rounded_sm,
158 t.atoms.border_contrast_low,
159 ]}>
160 <StarterPackCard.Card
161 starterPack={profile.joinedViaStarterPack}
162 />
163 </View>
164 </StarterPackCard.Link>
165 ) : null}
166
167 {IS_NATIVE && (
168 <Button
169 label={_(msg`Close`)}
170 color="secondary"
171 size="small"
172 style={[a.mt_sm]}
173 onPress={() => control.close()}>
174 <ButtonText>
175 <Trans>Close</Trans>
176 </ButtonText>
177 </Button>
178 )}
179 </View>
180
181 <Dialog.Close />
182 </Dialog.ScrollableInner>
183 )
184}