Skip to content

Commit 0a55780

Browse files
authored
Merge pull request #119 from Turing-Sandbox/SUMMA-22
SUMMA-22: Implement Tab System in Feed
2 parents c2cc998 + 8bba274 commit 0a55780

File tree

7 files changed

+280
-414
lines changed

7 files changed

+280
-414
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { useEffect, useState } from "react";
2+
import ContentTile from "../content/ContentTile";
3+
import { Content } from "../../models/Content";
4+
import { apiURL } from "../../scripts/api";
5+
import axios from "axios";
6+
import { normalizeContentDates } from "../../services/contentHelper";
7+
import { useAuth } from "../../hooks/useAuth";
8+
import ContentPreviewPopup from "../content/ContentPreviewPopup";
9+
10+
export default function ContentList({ tab }: { tab: string }) {
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [contents, setContents] = useState<Content[]>([]);
13+
14+
const [previewContent, setPreviewContent] = useState<Content | null>(null);
15+
const [error, setError] = useState<string | null>(null);
16+
17+
const auth = useAuth();
18+
19+
useEffect(() => {
20+
setIsLoading(true);
21+
22+
fetchContent();
23+
24+
setIsLoading(false);
25+
// eslint-disable-next-line react-hooks/exhaustive-deps
26+
}, [tab]);
27+
28+
useEffect(() => {
29+
// Function to reload the Mondiad script (ADS)
30+
const reloadMondiadScript = () => {
31+
const existingScript = document.querySelector(
32+
"script[src='https://ss.mrmnd.com/native.js']"
33+
);
34+
if (existingScript) {
35+
// Remove the existing script
36+
existingScript.remove();
37+
}
38+
39+
// Create a new script element
40+
const script = document.createElement("script");
41+
script.src = "https://ss.mrmnd.com/native.js";
42+
script.async = true;
43+
44+
// Append the script to the document head
45+
document.head.appendChild(script);
46+
};
47+
48+
// Reload the script whenever the component renders
49+
reloadMondiadScript();
50+
}, [contents]);
51+
52+
async function fetchContent(): Promise<boolean> {
53+
try {
54+
let response;
55+
56+
switch (tab) {
57+
case "personalized":
58+
if (!auth.user?.uid) {
59+
setContents([]);
60+
return false;
61+
}
62+
response = await axios.get(
63+
`${apiURL}/content/feed/${auth.user.uid}`,
64+
{ timeout: 5000, withCredentials: true }
65+
);
66+
break;
67+
case "latest":
68+
response = await axios.get(`${apiURL}/content`, {
69+
timeout: 5000,
70+
});
71+
break;
72+
case "trending":
73+
response = await axios.get(`${apiURL}/content/feed/trending`, {
74+
timeout: 5000,
75+
});
76+
break;
77+
default:
78+
return false;
79+
}
80+
81+
if (response.data && response.data.success) {
82+
let normalizedContent;
83+
84+
switch (tab) {
85+
case "personalized":
86+
normalizedContent = response.data.personalizedContent.map(
87+
(content: Content) => normalizeContentDates(content)
88+
);
89+
break;
90+
case "latest":
91+
normalizedContent = response.data.content.map((content: Content) =>
92+
normalizeContentDates(content)
93+
);
94+
break;
95+
case "trending":
96+
normalizedContent = response.data.trendingContent.map(
97+
(content: Content) => normalizeContentDates(content)
98+
);
99+
break;
100+
default:
101+
return false;
102+
}
103+
104+
setContents(normalizedContent);
105+
return true;
106+
} else {
107+
setContents([]);
108+
setError("No content found");
109+
}
110+
} catch {
111+
setContents([]);
112+
setError("Failed to fetch content. Please try again.");
113+
}
114+
115+
setIsLoading(false);
116+
return false;
117+
}
118+
119+
const openPreview = (content: Content) => {
120+
setPreviewContent(content);
121+
};
122+
123+
const closePreview = () => {
124+
setPreviewContent(null);
125+
};
126+
127+
return (
128+
<div>
129+
{/* Personalized Content Section */}
130+
<h2 className='feed-section-h2'>
131+
{tab === "personalized"
132+
? "Content For You"
133+
: tab === "latest"
134+
? "See the Latest Content"
135+
: "What's Trending"}
136+
</h2>
137+
{isLoading ? (
138+
<div className='content-list'>
139+
<div className='loading-tile' />
140+
<div className='loading-tile' />
141+
<div className='loading-tile' />
142+
<div className='loading-tile' />
143+
<div className='loading-tile' />
144+
<div className='loading-tile' />
145+
</div>
146+
) : contents.length === 0 ? (
147+
<h3>No content found</h3>
148+
) : (
149+
<div className='content-list'>
150+
{contents.map((content, index) => (
151+
<div key={`${content.uid || index}`}>
152+
{index % 8 === 0 ? (
153+
<div className='ad-tile' key={`ad-personalized-${index}`}>
154+
<div data-mndazid='ead3e00e-3a1a-42f1-b990-c294631f3d97'></div>
155+
</div>
156+
) : (
157+
<ContentTile
158+
key={`content-personalized-${content.uid || index}`}
159+
content={content}
160+
index={index}
161+
onPreview={(c) => openPreview(c)}
162+
/>
163+
)}
164+
</div>
165+
))}
166+
</div>
167+
)}
168+
{error && <p className='error'>{error}</p>}
169+
{previewContent && (
170+
<ContentPreviewPopup content={previewContent} onClose={closePreview} />
171+
)}
172+
</div>
173+
);
174+
}

frontend/src/hooks/AuthProvider.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@ import { User } from "../models/User";
44
import { fetchUser } from "../services/userService";
55
import axios from "axios";
66
import { apiURL } from "../scripts/api";
7+
import LoadingPage from "../pages/loading/LoadingPage";
78

8-
// TODO: REVIEW THE AUTH PROVIDER TO IMPROVE SECURITY
99
export default function AuthProvider({
1010
children,
1111
}: React.PropsWithChildren<object>) {
1212
const [user, setUser] = useState<User | null>(null);
13+
const [loading, setLoading] = useState(true);
1314

1415
const login = async (userUID: string) => {
15-
console.log("Logging in user...");
16-
17-
getUserData(userUID);
16+
await getUserData(userUID);
1817
};
1918

2019
const logout = async () => {
@@ -29,7 +28,6 @@ export default function AuthProvider({
2928
}
3029
};
3130

32-
// Automatically refresh the user token and fetch user data
3331
useEffect(() => {
3432
const initializeUser = async () => {
3533
try {
@@ -56,22 +54,20 @@ export default function AuthProvider({
5654
}
5755
}
5856
} catch (err) {
59-
// This is expected if the user is not logged in, so we don't need to show an error
60-
console.log("Not currently logged in or refresh token expired");
61-
// Clear any stale user data
57+
console.error("Auto-login failed", err);
6258
setUser(null);
59+
} finally {
60+
setLoading(false);
6361
}
6462
};
6563

6664
initializeUser();
6765
}, []);
6866

6967
async function getUserData(userUID: string) {
70-
// Fetch user data from the server
7168
console.log("Fetching user data... for UID:", userUID);
7269
const userData = await fetchUser(userUID);
7370

74-
console.log("User data:", userData);
7571
if (!(userData instanceof Error)) {
7672
setUser(userData || null);
7773
} else {
@@ -80,6 +76,11 @@ export default function AuthProvider({
8076
}
8177
}
8278

79+
if (loading) {
80+
// Render a fallback while loading
81+
return <LoadingPage />;
82+
}
83+
8384
return (
8485
<AuthContext.Provider
8586
value={{ user, login, logout, isAuthenticated: !!user }}

0 commit comments

Comments
 (0)