Skip to content

Commit aaff15d

Browse files
authored
Web Forum Improvements (#545)
1 parent 09e2b2f commit aaff15d

File tree

21 files changed

+1294
-189
lines changed

21 files changed

+1294
-189
lines changed

apps/expo/src/app/forum.tsx

Lines changed: 135 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { useEffect, useState } from "react";
22
import {
33
FlatList,
44
RefreshControl,
@@ -8,6 +8,7 @@ import {
88
} from "react-native";
99
import { SafeAreaView } from "react-native-safe-area-context";
1010
import { Stack, useRouter } from "expo-router";
11+
import { MaterialIcons } from "@expo/vector-icons";
1112

1213
import Discussion from "~/components/Discussion";
1314
import FloatingButton from "~/components/FloatingButton";
@@ -19,31 +20,117 @@ import RedDot from "../../assets/reddot.svg";
1920

2021
const Forum = () => {
2122
const [selectedTab, setSelectedTab] = useState(1);
22-
const router = useRouter();
23+
const [currentPage, setCurrentPage] = useState(1);
2324
const [isRefreshing, setIsRefreshing] = useState(false);
25+
const pageSize = 10;
26+
const router = useRouter();
27+
28+
useEffect(() => {
29+
setCurrentPage(1);
30+
}, [selectedTab]);
31+
32+
const { data: forumData, refetch: refetchForum } =
33+
api.discussion.getDiscussions.useQuery({
34+
page: currentPage,
35+
pageSize,
36+
});
37+
38+
const { data: myPostsData, refetch: refetchMyPosts } =
39+
api.discussion.getDiscussionsByUser.useQuery({
40+
page: currentPage,
41+
pageSize,
42+
});
2443

25-
const { data: forumPosts, refetch: refetchForumPosts } =
26-
api.discussion.getDiscussions.useQuery();
27-
const { data: myPosts, refetch: refetchMyPosts } =
28-
api.discussion.getDiscussionsByUser.useQuery();
2944
const { data: replies, refetch: refetchReplies } =
3045
api.discussion.getReplies.useQuery();
31-
const replyLength = replies?.length;
3246

33-
const dataToDisplay =
34-
selectedTab === 1 ? forumPosts : selectedTab === 2 ? myPosts : [];
47+
const currentData =
48+
selectedTab === 1 ? forumData : selectedTab === 2 ? myPostsData : null;
49+
50+
const totalPages: number = currentData?.totalPages ?? 0;
51+
const posts = currentData?.posts ?? [];
52+
53+
const PaginationControls = () => {
54+
if (totalPages <= 1) return null;
55+
56+
const getPageNumbers = () => {
57+
const pages = [];
58+
const maxVisible = 5;
59+
let start = Math.max(1, currentPage - 2);
60+
let end = Math.min(totalPages, currentPage + 2);
61+
62+
if (totalPages > maxVisible) {
63+
if (currentPage <= 3) {
64+
start = 1;
65+
end = maxVisible;
66+
} else if (currentPage >= totalPages - 2) {
67+
start = totalPages - maxVisible + 1;
68+
end = totalPages;
69+
}
70+
}
3571

36-
const onRefresh = async () => {
72+
if (start > 1) pages.push(1, "...");
73+
for (let i = start; i <= end; i++) pages.push(i);
74+
if (end < totalPages) pages.push("...", totalPages);
75+
76+
return pages;
77+
};
78+
79+
return (
80+
<View className="my-4 flex-row items-center justify-center">
81+
<TouchableOpacity
82+
onPress={() => setCurrentPage(Math.max(1, currentPage - 1))}
83+
disabled={currentPage === 1}
84+
>
85+
<MaterialIcons
86+
name="chevron-left"
87+
size={24}
88+
color={currentPage === 1 ? "#ccc" : "#000"}
89+
/>
90+
</TouchableOpacity>
91+
92+
{getPageNumbers().map((page, index) =>
93+
page === "..." ? (
94+
<Text key={`ellipsis-${index}`} className="mx-2">
95+
...
96+
</Text>
97+
) : (
98+
<TouchableOpacity
99+
key={page}
100+
className={`mx-1 h-8 w-8 items-center justify-center rounded-full
101+
${currentPage === page ? "bg-blue-500" : "bg-gray-200"}`}
102+
onPress={() => setCurrentPage(Number(page))}
103+
>
104+
<Text
105+
className={`${currentPage === page ? "text-white" : "text-gray-700"}`}
106+
>
107+
{page}
108+
</Text>
109+
</TouchableOpacity>
110+
),
111+
)}
112+
113+
<TouchableOpacity
114+
onPress={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
115+
disabled={currentPage === totalPages}
116+
>
117+
<MaterialIcons
118+
name="chevron-right"
119+
size={24}
120+
color={currentPage === totalPages ? "#ccc" : "#000"}
121+
/>
122+
</TouchableOpacity>
123+
</View>
124+
);
125+
};
126+
127+
const handleRefresh = async () => {
37128
setIsRefreshing(true);
38129
try {
39-
await Promise.all([
40-
refetchForumPosts(),
41-
refetchMyPosts(),
42-
refetchReplies(),
43-
]);
130+
await Promise.all([refetchForum(), refetchMyPosts(), refetchReplies()]);
44131
} catch (error) {
45-
console.error("Failed to refresh forum:", error);
46-
alert("Error refreshing forum. Please try again.");
132+
console.error("Refresh failed:", error);
133+
alert("Refresh failed. Please try again.");
47134
} finally {
48135
setIsRefreshing(false);
49136
}
@@ -56,28 +143,12 @@ const Forum = () => {
56143
</View>
57144
<View className="ml-4 mr-4 mt-4 flex-1 items-center justify-center">
58145
<TabNav currentTab={selectedTab}>
59-
<TabNav.Tab
60-
onPress={() => {
61-
setSelectedTab(1);
62-
}}
63-
>
64-
Feed
65-
</TabNav.Tab>
66-
<TabNav.Tab
67-
onPress={() => {
68-
setSelectedTab(2);
69-
}}
70-
>
71-
My Posts
72-
</TabNav.Tab>
73-
<TabNav.Tab
74-
onPress={() => {
75-
setSelectedTab(3);
76-
}}
77-
>
146+
<TabNav.Tab onPress={() => setSelectedTab(1)}>Feed</TabNav.Tab>
147+
<TabNav.Tab onPress={() => setSelectedTab(2)}>My Posts</TabNav.Tab>
148+
<TabNav.Tab onPress={() => setSelectedTab(3)}>
78149
<View className="relative">
79-
<Bell width={20} height={20}></Bell>
80-
<View className="absolute left-3.5 top-0 ">
150+
<Bell width={20} height={20} />
151+
<View className="absolute left-3.5 top-0">
81152
<RedDot width={6} height={6} />
82153
</View>
83154
</View>
@@ -97,37 +168,31 @@ const Forum = () => {
97168
ListHeaderComponent={renderHeader}
98169
data={replies}
99170
keyExtractor={(item) => item.id.toString()}
100-
renderItem={({ item, index }) => {
101-
if (index == 0) {
102-
return (
103-
<View className="bg-p-90 ml-3 mr-3 mt-2 rounded-tl-md rounded-tr-md p-4">
104-
<Text className="font-headline-md pb-3">
105-
New Notifications
106-
</Text>
107-
<ReplyNotification comment={item}></ReplyNotification>
108-
</View>
109-
);
110-
} else if (index == replyLength! - 1) {
111-
return (
112-
<View className="bg-p-90 ml-3 mr-3 rounded-bl-lg rounded-br-lg p-4">
113-
<ReplyNotification comment={item}></ReplyNotification>
114-
</View>
115-
);
116-
} else {
117-
return (
118-
<View className="bg-p-90 ml-3 mr-3 p-4">
119-
<ReplyNotification comment={item}></ReplyNotification>
120-
</View>
121-
);
122-
}
123-
}}
171+
renderItem={({ item, index }) => (
172+
<View
173+
className={`bg-p-90 ml-3 mr-3 p-4 ${
174+
index === 0
175+
? "mt-2 rounded-tl-md rounded-tr-md"
176+
: index === (replies?.length ?? 0) - 1
177+
? "rounded-bl-lg rounded-br-lg"
178+
: ""
179+
}`}
180+
>
181+
{index === 0 && (
182+
<Text className="font-headline-md pb-3">
183+
New Notifications
184+
</Text>
185+
)}
186+
<ReplyNotification comment={item} />
187+
</View>
188+
)}
124189
/>
125190
</View>
126191
) : (
127192
<View className="h-vh mb-16">
128193
<FlatList
129194
ListHeaderComponent={renderHeader}
130-
data={dataToDisplay}
195+
data={posts}
131196
keyExtractor={(item) => item.id.toString()}
132197
renderItem={({ item }) => (
133198
<View className="mt-2">
@@ -137,24 +202,23 @@ const Forum = () => {
137202
refreshControl={
138203
<RefreshControl
139204
refreshing={isRefreshing}
140-
onRefresh={onRefresh}
205+
onRefresh={handleRefresh}
141206
/>
142207
}
208+
ListFooterComponent={<PaginationControls />}
143209
/>
144210
</View>
145211
)}
146212

147-
{/* Floating Button */}
148213
<TouchableOpacity
149-
style={{
150-
position: "absolute",
151-
bottom: 100,
152-
right: 20,
153-
}}
214+
style={{ position: "absolute", bottom: 100, right: 20 }}
215+
onPress={() => router.push("/createpost")}
154216
>
155217
<FloatingButton
156-
onPress={() => router.push("/createpost")}
157218
icon={true}
219+
onPress={function (): void {
220+
throw new Error("Function not implemented.");
221+
}}
158222
/>
159223
</TouchableOpacity>
160224
</View>

apps/expo/src/components/Discussion.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ AWS.config.update({
2727
secretAccessKey: String(Constants.expoConfig?.extra?.awsSecretAccessKey),
2828
region: String(Constants.expoConfig?.extra?.awsRegion),
2929
});
30-
// const s3 = new AWS.S3();
3130

32-
type DiscussionProps = RouterOutputs["discussion"]["getDiscussions"][number];
31+
type PaginatedDiscussions = RouterOutputs["discussion"]["getDiscussions"];
32+
type DiscussionPost = PaginatedDiscussions["posts"][number];
3333

3434
interface RenderItemProps {
35-
item: RouterOutputs["discussion"]["getDiscussions"][number]["comments"][number];
35+
item: RouterOutputs["discussion"]["getDiscussions"]["posts"][number]["comments"][number];
3636
index: number;
3737
totalComments: number;
3838
}
@@ -41,7 +41,7 @@ export default function Discussion({
4141
discussion,
4242
canEdit,
4343
}: {
44-
discussion: DiscussionProps;
44+
discussion: DiscussionPost;
4545
canEdit: boolean;
4646
}) {
4747
const router = useRouter();

apps/nextjs/next.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const config = {
99
/** We already do linting and typechecking as separate tasks in CI */
1010
eslint: { ignoreDuringBuilds: true },
1111
typescript: { ignoreBuildErrors: true },
12+
images: {
13+
domains: ["i.pinimg.com"],
14+
},
1215
};
1316

1417
export default config;

0 commit comments

Comments
 (0)