diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx
index 7057819e1f..5df967a8a5 100644
--- a/src/components/contacts/screens/GetContacts.tsx
+++ b/src/components/contacts/screens/GetContacts.tsx
@@ -1,15 +1,27 @@
+import {useContext} from 'react'
import {Alert, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import * as Contacts from 'expo-contacts'
-import {AppBskyContactImportContacts} from '@atproto/api'
+import type AtpAgent from '@atproto/api'
+import {
+ type AppBskyActorProfile,
+ AppBskyContactImportContacts,
+ type Un$Typed,
+} from '@atproto/api'
import {msg, t, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMutation, useQueryClient} from '@tanstack/react-query'
+import {uploadBlob} from '#/lib/api'
import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {findContactsStatusQueryKey} from '#/state/queries/find-contacts'
import {useAgent} from '#/state/session'
+import {
+ Context as OnboardingContext,
+ type OnboardingAction,
+ type OnboardingState,
+} from '#/screens/Onboarding/state'
import {atoms as a, ios, tokens, useGutters} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Layout from '#/components/Layout'
@@ -43,9 +55,26 @@ export function GetContacts({
const insets = useSafeAreaInsets()
const gutters = useGutters([0, 'wide'])
const queryClient = useQueryClient()
+ const maybeOnboardingContext = useContext(OnboardingContext)
const {mutate: uploadContacts, isPending: isUploadPending} = useMutation({
mutationFn: async (contacts: Contacts.ExistingContact[]) => {
+ /**
+ * `importContacts` triggers a notification for the people you match with,
+ * however we prevent notifications coming from users without profiles.
+ * If you're using this as the onboarding flow, we need to create a profile
+ * record before this.
+ *
+ * When you finish onboarding, we'll upsert again - bit wasteful but fine.
+ */
+ if (context === 'Onboarding' && maybeOnboardingContext) {
+ try {
+ await createProfileRecord(agent, maybeOnboardingContext)
+ } catch (error) {
+ logger.debug('Error creating profile record:', {safeMessage: error})
+ }
+ }
+
const {phoneNumbers, indexToContactId} = normalizeContactBook(
contacts,
state.phoneCountryCode,
@@ -202,11 +231,11 @@ export function GetContacts({
Bluesky helps friends find each other by creating an encoded digital
- fingerprint, called a "hash," and then looking for matching hashes.
+ fingerprint, called a "hash", and then looking for matching hashes.
- • We never store plain phone numbers
+ • We never keep plain phone numbers
• We delete hashes after matches are made
@@ -288,3 +317,35 @@ function showPermissionDeniedAlert() {
],
)
}
+
+/**
+ * Copied from `#/screens/Onboarding/StepFinished/index.tsx`
+ */
+async function createProfileRecord(
+ agent: AtpAgent,
+ onboardingContext: {
+ state: OnboardingState
+ dispatch: React.Dispatch
+ },
+) {
+ const profileStepResults = onboardingContext.state.profileStepResults
+ const {imageUri, imageMime} = profileStepResults
+ const blobPromise =
+ imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined
+
+ await agent.upsertProfile(async existing => {
+ let next: Un$Typed = existing ?? {}
+
+ if (blobPromise) {
+ const res = await blobPromise
+ if (res.data.blob) {
+ next.avatar = res.data.blob
+ }
+ }
+
+ next.displayName = ''
+
+ next.createdAt = new Date().toISOString()
+ return next
+ })
+}
diff --git a/src/screens/Onboarding/StepFinished/index.tsx b/src/screens/Onboarding/StepFinished/index.tsx
index abf21d51d5..2c6dfd3198 100644
--- a/src/screens/Onboarding/StepFinished/index.tsx
+++ b/src/screens/Onboarding/StepFinished/index.tsx
@@ -161,11 +161,10 @@ export function StepFinished() {
}
next.displayName = ''
- // HACKFIX
- // creating a bunch of identical profile objects is breaking the relay
- // tossing this unspecced field onto it to reduce the size of the problem
- // -prf
- next.createdAt = new Date().toISOString()
+
+ if (!next.createdAt) {
+ next.createdAt = new Date().toISOString()
+ }
return next
})