this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

start notifications page + popular feed

+142 -28
+74 -6
apps/expo/src/app/(tabs)/notifications.tsx
··· 1 - import { Text, View } from "react-native"; 1 + import { ActivityIndicator, Text, View } from "react-native"; 2 + import { Stack } from "expo-router"; 3 + import { FlashList } from "@shopify/flash-list"; 4 + import { useInfiniteQuery } from "@tanstack/react-query"; 5 + 6 + import { Button } from "../../components/button"; 7 + import { useAuthedAgent } from "../../lib/agent"; 2 8 3 9 export default function NotificationsPage() { 4 - return ( 5 - <View className="flex-1 justify-center"> 6 - <Text className="text-center text-xl">Coming soon</Text> 7 - </View> 8 - ); 10 + const agent = useAuthedAgent(); 11 + const notifications = useInfiniteQuery({ 12 + queryKey: ["notifications"], 13 + queryFn: async ({ pageParam }) => { 14 + const notifs = await agent.listNotifications({ 15 + cursor: pageParam as string | undefined, 16 + }); 17 + return notifs.data; 18 + }, 19 + getNextPageParam: (lastPage) => lastPage.cursor, 20 + }); 21 + 22 + switch (notifications.status) { 23 + case "loading": 24 + return ( 25 + <View className="flex-1 items-center justify-center"> 26 + <Stack.Screen options={{ headerShown: true }} /> 27 + <ActivityIndicator /> 28 + </View> 29 + ); 30 + 31 + case "error": 32 + return ( 33 + <View className="flex-1 items-center justify-center p-4"> 34 + <Stack.Screen options={{ headerShown: true }} /> 35 + <Text className="text-center text-xl"> 36 + {(notifications.error as Error).message || "An error occurred"} 37 + </Text> 38 + <Button 39 + variant="outline" 40 + onPress={() => void notifications.refetch()} 41 + > 42 + Retry 43 + </Button> 44 + </View> 45 + ); 46 + 47 + case "success": 48 + return ( 49 + <> 50 + <Stack.Screen options={{ headerShown: true }} /> 51 + <FlashList 52 + data={notifications.data.pages.flatMap( 53 + (page) => page.notifications, 54 + )} 55 + renderItem={({ item }) => ( 56 + <View className="w-full border-b p-4"> 57 + <Text>{JSON.stringify(item, null, 2)}</Text> 58 + </View> 59 + )} 60 + onEndReachedThreshold={0.5} 61 + onEndReached={() => void notifications.fetchNextPage()} 62 + onRefresh={() => { 63 + if (!notifications.isRefetching) void notifications.refetch(); 64 + }} 65 + refreshing={notifications.isRefetching} 66 + ListFooterComponent={ 67 + notifications.isFetching ? ( 68 + <View className="w-full items-center py-4"> 69 + <ActivityIndicator /> 70 + </View> 71 + ) : null 72 + } 73 + /> 74 + </> 75 + ); 76 + } 9 77 }
+3
apps/expo/src/app/(tabs)/search.tsx
··· 1 1 import { Text, View } from "react-native"; 2 2 3 + import { useAuthedAgent } from "../../lib/agent"; 4 + 3 5 export default function SearchPage() { 6 + const agent = useAuthedAgent(); 4 7 return ( 5 8 <View className="flex-1 justify-center"> 6 9 <Text className="text-center text-xl">Coming soon</Text>
+61 -19
apps/expo/src/app/(tabs)/timeline.tsx
··· 1 - import { useMemo } from "react"; 2 - import { ActivityIndicator, Text, View } from "react-native"; 1 + import { useMemo, useState } from "react"; 2 + import { ActivityIndicator, Text, TouchableOpacity, View } from "react-native"; 3 3 import { Stack } from "expo-router"; 4 4 import { FlashList } from "@shopify/flash-list"; 5 5 import { useInfiniteQuery } from "@tanstack/react-query"; ··· 7 7 import { Button } from "../../components/button"; 8 8 import { FeedPost } from "../../components/feed-post"; 9 9 import { useAuthedAgent } from "../../lib/agent"; 10 + import { cx } from "../../lib/utils/cx"; 10 11 11 12 export default function Timeline() { 13 + const [mode, setMode] = useState<"popular" | "following">("following"); 12 14 const agent = useAuthedAgent(); 13 15 const timeline = useInfiniteQuery({ 14 - queryKey: ["timeline"], 16 + queryKey: ["timeline", mode], 15 17 queryFn: async ({ pageParam }) => { 16 - const timeline = await agent.getTimeline({ 17 - cursor: pageParam as string | undefined, 18 - }); 19 - return timeline.data; 18 + switch (mode) { 19 + case "popular": 20 + const popular = await agent.app.bsky.unspecced.getPopular({ 21 + cursor: pageParam as string | undefined, 22 + }); 23 + return popular.data; 24 + case "following": 25 + const timeline = await agent.getTimeline({ 26 + cursor: pageParam as string | undefined, 27 + }); 28 + return timeline.data; 29 + } 20 30 }, 21 31 getNextPageParam: (lastPage) => lastPage.cursor, 22 32 }); ··· 36 46 .flat(); 37 47 }, [timeline]); 38 48 49 + const header = ( 50 + <> 51 + <Stack.Screen options={{ headerShown: true }} /> 52 + <View className="w-full flex-row border-b border-neutral-200 bg-white"> 53 + <TouchableOpacity 54 + onPress={() => setMode("following")} 55 + className={cx( 56 + "ml-4 border-y-2 border-transparent py-3 text-xl", 57 + mode === "following" && "border-b-black", 58 + )} 59 + > 60 + <Text className="font-medium">Following</Text> 61 + </TouchableOpacity> 62 + <TouchableOpacity 63 + onPress={() => setMode("popular")} 64 + className={cx( 65 + "ml-4 border-y-2 border-transparent py-3 text-xl", 66 + mode === "popular" && "border-b-black", 67 + )} 68 + > 69 + <Text>What&apos;s Hot</Text> 70 + </TouchableOpacity> 71 + </View> 72 + </> 73 + ); 74 + 39 75 switch (timeline.status) { 40 76 case "loading": 41 77 return ( 42 - <View className="flex-1 items-center justify-center"> 43 - <ActivityIndicator /> 44 - </View> 78 + <> 79 + {header} 80 + <View className="flex-1 items-center justify-center"> 81 + <ActivityIndicator /> 82 + </View> 83 + </> 45 84 ); 46 85 47 86 case "error": 48 87 return ( 49 - <View className="flex-1 items-center justify-center p-4"> 50 - <Text className="text-center text-xl"> 51 - {(timeline.error as Error).message || "An error occurred"} 52 - </Text> 53 - <Button variant="outline" onPress={() => void timeline.refetch()}> 54 - Retry 55 - </Button> 56 - </View> 88 + <> 89 + {header} 90 + <View className="flex-1 items-center justify-center p-4"> 91 + <Text className="text-center text-xl"> 92 + {(timeline.error as Error).message || "An error occurred"} 93 + </Text> 94 + <Button variant="outline" onPress={() => void timeline.refetch()}> 95 + Retry 96 + </Button> 97 + </View> 98 + </> 57 99 ); 58 100 59 101 case "success": 60 102 return ( 61 103 <> 62 - <Stack.Screen options={{ headerShown: true }} /> 104 + {header} 63 105 <FlashList 64 106 data={data} 65 107 renderItem={({ item: { hasReply, item } }) => (
+4 -3
apps/nextjs/src/pages/index.tsx
··· 1 1 import { useState } from "react"; 2 2 import Head from "next/head"; 3 3 4 + import { api } from "~/utils/api"; 5 + 4 6 export default function HomePage() { 5 - const [count, setCount] = useState(0); 7 + const query = api.useless.ping.useQuery(); 6 8 7 9 return ( 8 10 <div> ··· 12 14 13 15 <main> 14 16 <h1>Home</h1> 15 - <p>Count: {count}</p> 16 - <button onClick={() => setCount((c) => c + 1)} /> 17 + <p>{query.data}</p> 17 18 </main> 18 19 </div> 19 20 );