Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor api to simplify code and add proper errors #238

Merged
merged 30 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
75a4fb9
fix lint
Simon-Laux Jan 26, 2025
e257f2f
log and present error dialog when DM chat message failed to send
Simon-Laux Jan 26, 2025
557e73f
introduce ApiContext (simplify/refactor api)
Simon-Laux Jan 26, 2025
6299aad
api: getAccountById
Simon-Laux Jan 26, 2025
246dc72
fix issue from last commit
Simon-Laux Jan 26, 2025
1cc3fe6
run biome lint
Simon-Laux Jan 26, 2025
8a7dc1b
some smaller fixes and typings
Simon-Laux Jan 26, 2025
d121388
remove `_` prefix from method names -> in js this means not private, …
Simon-Laux Jan 27, 2025
b5dba48
type `useLocalSearchParams`
Simon-Laux Jan 27, 2025
2438ddc
commit file that I forgot to commit in last commit
Simon-Laux Jan 27, 2025
8f485cb
api: `getAccountByUsername`
Simon-Laux Jan 27, 2025
4de1319
add global `API` method for testing the api from the dev console
Simon-Laux Jan 27, 2025
19fcf6e
detect account not found error
Simon-Laux Jan 27, 2025
66fddfb
display error and offer a button to search for the username instead
Simon-Laux Jan 27, 2025
9d07d4d
fix lint
Simon-Laux Jan 27, 2025
3006435
remove log line that we don't need anymore, because we know the code …
Simon-Laux Jan 27, 2025
884e75d
api: convert getAccountStatusesById to ApiContext and type it
Simon-Laux Jan 27, 2025
fc87be0
ui: make profile tab content fill whole screen when there are not eno…
Simon-Laux Jan 27, 2025
6ea05a5
fix fmt
Simon-Laux Jan 27, 2025
dcda863
type `reportProfile` api params and `profile/report/[id]`
Simon-Laux Jan 27, 2025
8efbe56
use API context in: reportProfile, reportStatus
Simon-Laux Jan 27, 2025
b7d6882
unify reportStatus and reportProfile and redoce some more code duplic…
Simon-Laux Jan 27, 2025
8517e93
fix error message extraction in ApiContext
Simon-Laux Jan 27, 2025
649bf24
add error alert on failed report
Simon-Laux Jan 27, 2025
40ac075
fix fmt
Simon-Laux Jan 27, 2025
8409fc5
log more of the error to console
Simon-Laux Jan 27, 2025
9150c20
api: followAccountById, unfollowAccountById
Simon-Laux Jan 27, 2025
5a068d8
fix some lint issues
Simon-Laux Jan 27, 2025
5fcac80
fix login problem
Simon-Laux Jan 28, 2025
068565b
Merge branch 'main' into simon/api-context
dansup Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions src/app/(auth)/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ import { Text, View, XStack, Spinner, YStack } from 'tamagui'
import FeedPost from 'src/components/post/FeedPost'
import { StatusBar } from 'expo-status-bar'
import { SafeAreaView } from 'react-native-safe-area-context'
import {
type ErrorBoundaryProps,
Stack,
useLocalSearchParams,
useNavigation,
useRouter,
} from 'expo-router'
import { Stack, useLocalSearchParams, useNavigation, useRouter } from 'expo-router'
import {
useInfiniteQuery,
useMutation,
Expand All @@ -41,6 +35,8 @@ import { useVideo } from 'src/hooks/useVideoProvider'
import { useFocusEffect } from '@react-navigation/native'
import { useUserCache } from 'src/state/AuthProvider'
import type { Status } from 'src/lib/api-types'
import type { ListRenderItemInfo } from 'react-native'
import type { ErrorBoundaryProps } from 'expo-router'

export function ErrorBoundary(props: ErrorBoundaryProps) {
return (
Expand Down Expand Up @@ -159,12 +155,12 @@ export default function HomeScreen() {

const keyExtractor = useCallback((item) => item?.id, [])

const onDeletePost = (id) => {
const onDeletePost = (id: string) => {
deletePostMutation.mutate(id)
}

const deletePostMutation = useMutation({
mutationFn: async (id) => {
mutationFn: async (id: string) => {
return await deleteStatusV1(id)
},
onSuccess: (data, variables) => {
Expand All @@ -182,16 +178,16 @@ export default function HomeScreen() {
})

const bookmarkMutation = useMutation({
mutationFn: async (id) => {
mutationFn: async (id: string) => {
return await postBookmark(id)
},
})

const onBookmark = (id) => {
const onBookmark = (id: string) => {
bookmarkMutation.mutate(id)
}

const onShare = (id, state) => {
const onShare = (id: string, state) => {
try {
shareMutation.mutate({ type: state == true ? 'unreblog' : 'reblog', id: id })
} catch (error) {
Expand Down Expand Up @@ -220,22 +216,22 @@ export default function HomeScreen() {
router.push(`/post/likes/${id}`)
}

const handleGotoProfile = (id) => {
const handleGotoProfile = (id: string) => {
bottomSheetModalRef.current?.close()
router.push(`/profile/${id}`)
}

const handleGotoUsernameProfile = (id) => {
const handleGotoUsernameProfile = (username: string) => {
bottomSheetModalRef.current?.close()
router.push(`/profile/0?byUsername=${id}`)
router.push(`/profile/0?byUsername=${username}`)
}

const gotoHashtag = (id) => {
const gotoHashtag = (id: string) => {
bottomSheetModalRef.current?.close()
router.push(`/hashtag/${id}`)
}

const handleCommentReport = (id) => {
const handleCommentReport = (id: string) => {
bottomSheetModalRef.current?.close()
router.push(`/post/report/${id}`)
}
Expand Down
6 changes: 5 additions & 1 deletion src/app/(auth)/(tabs)/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export default function ProfileScreen() {
} = useInfiniteQuery({
queryKey: ['statusesById', userId],
queryFn: async ({ pageParam }) => {
const data = await getAccountStatusesById(userId, pageParam)
if (!userId) {
throw new Error('getAccountStatusesById: user id missing')
}
const data = await getAccountStatusesById(userId, { max_id: pageParam })
return data.filter((p) => {
return (
['photo', 'photo:album', 'video'].includes(p.pf_type) &&
Expand Down Expand Up @@ -111,6 +114,7 @@ export default function ProfileScreen() {
<ActivityIndicator color={'#000'} />
</View>
)}

<FlatList
data={feed?.pages.flat()}
keyExtractor={(item, index) => item?.id.toString()}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/admin/users/show/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UserAvatar from 'src/components/common/UserAvatar'
import { PressableOpacity } from 'react-native-pressable-opacity'

export default function Screen() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const router = useRouter()
const instance = Storage.getString('app.instance')

Expand Down
54 changes: 33 additions & 21 deletions src/app/(auth)/chats/conversation/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import { Stack, useLocalSearchParams, useNavigation, useRouter } from 'expo-rout
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { fetchChatThread, sendChatMessage, deleteChatMessage } from 'src/lib/api'
import { _timeAgo, enforceLen } from 'src/utils'
import {
GiftedChat,
Bubble,
Send,
type BubbleProps,
type IMessage,
} from 'react-native-gifted-chat'
import { GiftedChat, Bubble, Send } from 'react-native-gifted-chat'

import { Feather } from '@expo/vector-icons'
import { useUserCache } from 'src/state/AuthProvider'

import type { BubbleProps, IMessage } from 'react-native-gifted-chat'

function renderBubble<TMessage extends IMessage>(props: BubbleProps<TMessage>) {
return (
<Bubble
Expand All @@ -27,7 +24,7 @@ function renderBubble<TMessage extends IMessage>(props: BubbleProps<TMessage>) {
}

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const router = useRouter()
const queryClient = useQueryClient()
const navigation = useNavigation()
Expand All @@ -45,20 +42,35 @@ export default function Page() {

const sendMutation = useMutation({
mutationFn: async (message) => {
const res = await sendChatMessage(id, message[0].text)
const msg = {
_id: res.id,
id: res.id,
createdAt: new Date(),
sent: true,
text: message[0].text,
user: {
_id: selfUser.id,
name: selfUser.username,
avatar: selfUser.avatar,
},
try {
const res = await sendChatMessage(id, message[0].text)
console.log('send message - server answered:', { res })

if (typeof res.error !== 'undefined') {
throw new Error(res.error)
}

const msg = {
_id: res.id,
id: res.id,
createdAt: new Date(),
sent: true,
text: message[0].text,
user: {
_id: selfUser.id,
name: selfUser.username,
avatar: selfUser.avatar,
},
}
setMessages((previousMessages) => GiftedChat.append(previousMessages, msg))
} catch (err: any) {
console.log('Failed to send message', err, err?.msg || err?.message)
Alert.alert('Failed to send message', err?.message || err)
}
setMessages((previousMessages) => GiftedChat.append(previousMessages, msg))
},
onError: (err) => {
console.log('sendMutation: Failed to send message', err)
Alert.alert('Failed to send message', err.message)
},
})

Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/hashtag/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const RenderItem = ({ item }) =>
) : null

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const queryClient = useQueryClient()

const RelatedTags = useCallback(
Expand Down
4 changes: 2 additions & 2 deletions src/app/(auth)/post/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import CommentFeed from 'src/components/post/CommentFeed'
import { useUserCache } from 'src/state/AuthProvider'

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const navigation = useNavigation()

useLayoutEffect(() => {
Expand Down Expand Up @@ -83,7 +83,7 @@ export default function Page() {

const { isPending, isError, data, error } = useQuery({
queryKey: ['getStatusById', id],
queryFn: getStatusById,
queryFn: () => getStatusById(id),
})
if (isPending) {
return (
Expand Down
4 changes: 3 additions & 1 deletion src/app/(auth)/post/edit/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const RenderItem = React.memo(({ item, onUpdateMediaAlt }) => (
))

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const navigation = useNavigation()
const [caption, setCaption] = useState('')
const [isSensitive, setSensitive] = useState(false)
Expand Down Expand Up @@ -111,6 +111,8 @@ export default function Page() {
return m.description !== ogm.description
})

// TODO: error handling
// TODO: invalidate react query cache of this post so it is updated in the UI
await Promise.all(mediaChanges.map(updateMedia))
.then(async (res) => {
return await putEditPost(data?.id, {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/post/history/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { _timeAgo, htmlToTextWithLineBreaks } from 'src/utils'
import ReadMore from 'src/components/common/ReadMore'

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const navigation = useNavigation()

useLayoutEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/app/(auth)/post/likes/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getStatusById, getStatusLikes } from 'src/lib/api'
import UserAvatar from 'src/components/common/UserAvatar'

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()

const RenderItem = ({ item }) => {
return (
Expand Down Expand Up @@ -35,7 +35,7 @@ export default function Page() {

const { data: status } = useQuery({
queryKey: ['getStatusById', id],
queryFn: getStatusById,
queryFn: () => getStatusById(id),
})

const statusId = status?.id
Expand Down
35 changes: 15 additions & 20 deletions src/app/(auth)/post/report/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,18 @@ import { SafeAreaView } from 'react-native-safe-area-context'
import { ScrollView, Text, XStack, YStack } from 'tamagui'
import { Feather } from '@expo/vector-icons'
import { useMutation } from '@tanstack/react-query'
import { reportStatus } from 'src/lib/api'
import { ActivityIndicator, Pressable } from 'react-native'
import { report } from 'src/lib/api'
import { ActivityIndicator, Alert, Pressable } from 'react-native'
import { reportTypes } from 'src/lib/reportTypes'

import type { NewReport } from 'src/lib/api'
import type { ReportType } from 'src/lib/reportTypes'

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const router = useRouter()

const reportTypes = [
{ name: 'spam', title: "It's spam" },
{ name: 'sensitive', title: 'Nudity or sexual activity' },
{ name: 'abusive', title: 'Bullying or harassment' },
{ name: 'underage', title: 'I think this account is underage' },
{ name: 'violence', title: 'Violence or dangerous organizations' },
{ name: 'copyright', title: 'Copyright infringement' },
{ name: 'impersonation', title: 'Impersonation' },
{ name: 'scam', title: 'Scam or fraud' },
{ name: 'terrorism', title: 'Terrorism or terrorism-related content' },
]

const RenderOption = ({ title, name }) => (
const RenderOption = ({ title, name }: ReportType) => (
<Pressable onPress={() => handleAction(name)}>
<XStack
px="$5"
Expand All @@ -39,17 +31,20 @@ export default function Page() {
</Pressable>
)

const handleAction = (type) => {
mutation.mutate({ id: id, type: type })
const handleAction = (type: string) => {
mutation.mutate({ object_id: id, object_type: 'post', report_type: type })
}

const mutation = useMutation({
mutationFn: (newReport) => {
return reportStatus(newReport)
mutationFn: (newReport: NewReport) => {
return report(newReport)
},
onSuccess: (data, variables, context) => {
router.replace('/post/report/sent')
},
onError: (err) => {
Alert.alert('Report Failed', err.message)
},
})

return (
Expand Down
4 changes: 2 additions & 2 deletions src/app/(auth)/post/shares/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getStatusById, getStatusReblogs } from 'src/lib/api'
import UserAvatar from 'src/components/common/UserAvatar'

export default function Page() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()

const RenderItem = ({ item }) => {
return (
Expand Down Expand Up @@ -35,7 +35,7 @@ export default function Page() {

const { data: status } = useQuery({
queryKey: ['getStatusById', id],
queryFn: getStatusById,
queryFn: () => getStatusById(id),
})

const statusId = status?.id
Expand Down
Loading
Loading