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 {_} = useLingui()
31 const control = useDialogControl()
32
33 const createdAt = profile.createdAt
34
35 const [now] = useState(() => Date.now())
36 const daysOld = useMemo(() => {
37 if (!createdAt) return Infinity
38 return differenceInSeconds(now, new Date(createdAt)) / 86400
39 }, [createdAt, now])
40
41 if (!createdAt || daysOld > 7) return null
42
43 return (
44 <View style={[a.pr_2xs]}>
45 <Button
46 disabled={disabled}
47 label={_(
48 msg`This user is new here. Press for more info about when they joined.`,
49 )}
50 hitSlop={HITSLOP_10}
51 onPress={control.open}>
52 {({hovered, pressed}) => (
53 <Newskie
54 size="lg"
55 fill="#FFC404"
56 style={{
57 opacity: hovered || pressed ? 0.5 : 1,
58 }}
59 />
60 )}
61 </Button>
62
63 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
64 <Dialog.Handle />
65 <DialogInner profile={profile} createdAt={createdAt} now={now} />
66 </Dialog.Outer>
67 </View>
68 )
69}
70
71function DialogInner({
72 profile,
73 createdAt,
74 now,
75}: {
76 profile: AppBskyActorDefs.ProfileViewDetailed
77 createdAt: string
78 now: number
79}) {
80 const control = Dialog.useDialogContext()
81 const {_} = useLingui()
82 const t = useTheme()
83 const moderationOpts = useModerationOpts()
84 const {currentAccount} = useSession()
85 const timeAgo = useGetTimeAgo()
86 const isMe = profile.did === currentAccount?.did
87
88 const profileName = useMemo(() => {
89 if (!moderationOpts) return profile.displayName || profile.handle
90 const moderation = moderateProfile(profile, moderationOpts)
91 return sanitizeDisplayName(
92 profile.displayName || profile.handle,
93 moderation.ui('displayName'),
94 )
95 }, [moderationOpts, profile])
96
97 const getJoinMessage = () => {
98 const timeAgoString = timeAgo(createdAt, now, {format: 'long'})
99
100 if (isMe) {
101 if (profile.joinedViaStarterPack) {
102 return _(
103 msg`You joined Bluesky using a starter pack ${timeAgoString} ago`,
104 )
105 } else {
106 return _(msg`You joined Bluesky ${timeAgoString} ago`)
107 }
108 } else {
109 if (profile.joinedViaStarterPack) {
110 return _(
111 msg`${profileName} joined Bluesky using a starter pack ${timeAgoString} ago`,
112 )
113 } else {
114 return _(msg`${profileName} joined Bluesky ${timeAgoString} ago`)
115 }
116 }
117 }
118
119 return (
120 <Dialog.ScrollableInner
121 label={_(msg`New user info dialog`)}
122 style={web({maxWidth: 400})}>
123 <View style={[a.gap_md]}>
124 <View style={[a.align_center]}>
125 <View
126 style={[
127 {
128 height: 60,
129 width: 64,
130 },
131 ]}>
132 <Newskie
133 width={64}
134 height={64}
135 fill="#FFC404"
136 style={[a.absolute, a.inset_0]}
137 />
138 </View>
139 <Text style={[a.font_semi_bold, a.text_xl]}>
140 {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>}
141 </Text>
142 </View>
143 <Text style={[a.text_md, a.text_center, a.leading_snug]}>
144 {getJoinMessage()}
145 </Text>
146 {profile.joinedViaStarterPack ? (
147 <StarterPackCard.Link
148 starterPack={profile.joinedViaStarterPack}
149 onPress={() => control.close()}>
150 <View
151 style={[
152 a.w_full,
153 a.mt_sm,
154 a.p_lg,
155 a.border,
156 a.rounded_sm,
157 t.atoms.border_contrast_low,
158 ]}>
159 <StarterPackCard.Card
160 starterPack={profile.joinedViaStarterPack}
161 />
162 </View>
163 </StarterPackCard.Link>
164 ) : null}
165
166 {IS_NATIVE && (
167 <Button
168 label={_(msg`Close`)}
169 color="secondary"
170 size="small"
171 style={[a.mt_sm]}
172 onPress={() => control.close()}>
173 <ButtonText>
174 <Trans>Close</Trans>
175 </ButtonText>
176 </Button>
177 )}
178 </View>
179
180 <Dialog.Close />
181 </Dialog.ScrollableInner>
182 )
183}