diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts index a2253237..eeb86be7 100644 --- a/src/state/models/suggested-actors-view.ts +++ b/src/state/models/suggested-actors-view.ts @@ -1,22 +1,9 @@ import {makeAutoObservable} from 'mobx' +import * as GetSuggestions from '../../third-party/api/src/client/types/app/bsky/actor/getSuggestions' import {RootStoreModel} from './root-store' import {Declaration} from './_common' -interface Response { - data: { - suggestions: ResponseSuggestedActor[] - } -} -export type ResponseSuggestedActor = { - did: string - handle: string - declaration: Declaration - displayName?: string - description?: string - createdAt?: string - indexedAt: string -} - +type ResponseSuggestedActor = GetSuggestions.OutputSchema['actors'][number] export type SuggestedActor = ResponseSuggestedActor & { _reactKey: string } @@ -90,14 +77,7 @@ export class SuggestedActorsViewModel { private async _fetch(isRefreshing = false) { this._xLoading(isRefreshing) try { - const debugRes = await this.rootStore.api.app.bsky.graph.getFollowers({ - user: 'alice.test', - }) - const res = { - data: { - suggestions: debugRes.data.followers, - }, - } + const res = await this.rootStore.api.app.bsky.actor.getSuggestions() this._replaceAll(res) this._xIdle() } catch (e: any) { @@ -105,10 +85,10 @@ export class SuggestedActorsViewModel { } } - private _replaceAll(res: Response) { + private _replaceAll(res: GetSuggestions.Response) { this.suggestions.length = 0 let counter = 0 - for (const item of res.data.suggestions) { + for (const item of res.data.actors) { this._append({ _reactKey: `item-${counter++}`, ...item, diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index e73d9a7d..4f6ac042 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useEffect} from 'react' +import React, {useMemo, useEffect, useState} from 'react' import { ActivityIndicator, FlatList, @@ -10,9 +10,12 @@ import { import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' +import _omit from 'lodash.omit' import {ErrorScreen} from '../util/ErrorScreen' import {UserAvatar} from '../util/UserAvatar' +import Toast from '../util/Toast' import {useStores} from '../../../state' +import * as apilib from '../../../state/lib/api' import { SuggestedActorsViewModel, SuggestedActor, @@ -22,6 +25,7 @@ import {s, colors, gradients} from '../../lib/styles' export const SuggestedFollows = observer( ({onNoSuggestions}: {onNoSuggestions?: () => void}) => { const store = useStores() + const [follows, setFollows] = useState>({}) const view = useMemo( () => new SuggestedActorsViewModel(store), @@ -46,7 +50,39 @@ export const SuggestedFollows = observer( .setup() .catch((err: any) => console.error('Failed to fetch suggestions', err)) - const renderItem = ({item}: {item: SuggestedActor}) => + const onPressFollow = async (item: SuggestedActor) => { + try { + const res = await apilib.follow(store, item.did, item.declaration.cid) + setFollows({[item.did]: res.uri, ...follows}) + } catch (e) { + console.log(e) + Toast.show('An issue occurred, please try again.', { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + }) + } + } + const onPressUnfollow = async (item: SuggestedActor) => { + try { + await apilib.unfollow(store, follows[item.did]) + setFollows(_omit(follows, [item.did])) + } catch (e) { + console.log(e) + Toast.show('An issue occurred, please try again.', { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + }) + } + } + + const renderItem = ({item}: {item: SuggestedActor}) => ( + + ) return ( {view.isLoading ? ( @@ -75,7 +111,17 @@ export const SuggestedFollows = observer( }, ) -const User = ({item}: {item: SuggestedActor}) => { +const User = ({ + item, + follow, + onPressFollow, + onPressUnfollow, +}: { + item: SuggestedActor + follow: string | undefined + onPressFollow: (item: SuggestedActor) => void + onPressUnfollow: (item: SuggestedActor) => void +}) => { return ( @@ -88,23 +134,35 @@ const User = ({item}: {item: SuggestedActor}) => { - {item.displayName} + {item.displayName || item.handle} @{item.handle} - - - - Follow - - + {follow ? ( + onPressUnfollow(item)}> + + Unfollow + + + ) : ( + onPressFollow(item)}> + + + Follow + + + )} {item.description ? ( diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index db3555f7..759efd43 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -15,6 +15,7 @@ import * as EmailValidator from 'email-validator' import {observer} from 'mobx-react-lite' import {Picker} from '../com/util/Picker' import {s, colors} from '../lib/styles' +import {makeValidHandle, createFullHandle} from '../lib/strings' import {useStores, DEFAULT_SERVICE} from '../../state' import {ServiceDescription} from '../../state/models/session' @@ -256,7 +257,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { await store.session.createAccount({ service: DEFAULT_SERVICE, email, - handle: `${handle}.${userDomain}`, + handle: createFullHandle(handle, userDomain), password, inviteCode, }) @@ -264,15 +265,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const errMsg = e.toString() console.log(e) setIsProcessing(false) - // if (errMsg.includes('Authentication Required')) { - // setError('Invalid username or password') - // } else if (errMsg.includes('Network request failed')) { - // setError( - // 'Unable to contact your service. Please check your Internet connection.', - // ) - // } else { setError(errMsg.replace(/^Error:/, '')) - // } } } @@ -380,7 +373,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { placeholderTextColor={colors.blue0} autoCapitalize="none" value={handle} - onChangeText={v => setHandle(cleanUsername(v))} + onChangeText={v => setHandle(makeValidHandle(v))} editable={!isProcessing} /> @@ -405,7 +398,7 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { Your full username will be{' '} - @{handle}.{userDomain} + @{createFullHandle(handle, userDomain)} @@ -431,14 +424,6 @@ const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { ) } -function cleanUsername(v: string): string { - v = v.trim() - if (v.length > 63) { - v = v.slice(0, 63) - } - return v.toLowerCase().replace(/[^a-z0-9-]/g, '') -} - export const Login = observer( (/*{navigation}: RootTabsScreenProps<'Login'>*/) => { const [screenState, setScreenState] = useState( diff --git a/todos.txt b/todos.txt index 142b57c0..fbb20c4e 100644 --- a/todos.txt +++ b/todos.txt @@ -36,7 +36,6 @@ Paul's todo list - Follows list - Members list - Bugs - > Create account broken > Auth token refresh seems broken - Check that sub components arent reloading too much - Titles are getting screwed up (possibly swipe related) \ No newline at end of file