-
-
Save Neptunians/20248eed0d35b3bfdb95a97ddc0dd49e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, useCallback, useContext, createContext, useMemo, useRef, useLayoutEffect, forwardRef } from 'react'; | |
import ReactDOM from 'react-dom/client'; | |
import { BrowserRouter, Routes, Route, Link, NavLink, Form, useNavigate, useLocation, useResolvedPath, useMatches, Outlet, createPath, parsePath } from 'react-router-dom'; | |
import DOMPurify from 'dompurify'; // Assuming DOMPurify is imported | |
// --- Constants --- | |
const API_BASE_URL = "https://a-minecraft-movie-api.challs.umdctf.io"; // [cite: 2970] | |
// --- API Functions --- | |
// Function to initiate a session | |
async function startSessionIfNeeded() { | |
if (window.sessionNumber !== undefined) { | |
return; // Session already started | |
} | |
try { | |
const response = await fetch(`${API_BASE_URL}/start-session`, { // [cite: 2972] | |
method: "POST", | |
credentials: "include" | |
}); | |
if (!response.ok) { | |
console.error("Failed to start session:", response.statusText); | |
return; | |
} | |
const { sessionNumber } = await response.json(); // [cite: 2973, 2974] | |
window.sessionNumber = sessionNumber; // [cite: 2974] | |
} catch (error) { | |
console.error("Error starting session:", error); | |
} | |
} | |
// --- Context --- | |
const AccountContext = createContext(undefined); // [cite: 2974] | |
function useAccount() { | |
const context = useContext(AccountContext); // [cite: 2974] | |
if (!context) { | |
throw new Error("useAccount must be used within an AccountContext.Provider"); // [cite: 2975] | |
} | |
return context; // [cite: 2976] | |
} | |
// --- Components --- | |
// Login/Register Form Component | |
function LoginRegisterForm() { | |
const [mode, setMode] = useState("login"); // 'login' or 'register' [cite: 2976] | |
const [username, setUsername] = useState(""); // [cite: 2976] | |
const [password, setPassword] = useState(""); // [cite: 2977] | |
const [error, setError] = useState(undefined); // [cite: 2977] | |
const [, setIsLoggedIn] = useAccount(); // [cite: 2977] | |
const navigate = useNavigate(); // [cite: 2977] | |
const handleSubmit = useCallback(async (event) => { | |
event.preventDefault(); // [cite: 2977] | |
setError(undefined); // Clear previous errors | |
const endpoint = mode === "login" ? "/login" : "/register"; // [cite: 2976, 2977] | |
try { | |
const response = await fetch(`${API_BASE_URL}${endpoint}`, { // [cite: 2977] | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json" // [cite: 2977] | |
}, | |
credentials: "include", // [cite: 2977] | |
body: JSON.stringify({ username, password }) // [cite: 2977, 2978] | |
}); | |
if (!response.ok) { // [cite: 2977] | |
const errorText = await response.text(); | |
setError(errorText); // [cite: 2977] | |
return; | |
} | |
setIsLoggedIn(true); // [cite: 2978] | |
navigate("/"); // [cite: 2978] | |
} catch (err) { | |
setError("An error occurred. Please try again."); | |
console.error("Login/Register error:", err); | |
} | |
}, [username, password, mode, setIsLoggedIn, navigate]); // [cite: 2978] | |
return ( | |
<div className="flex justify-center items-center"> // [cite: 2979] | |
<div className="w-full max-w-md p-8 bg-white rounded-xl shadow-md"> // [cite: 2979] | |
<h2 className="text-2xl font-bold text-center text-[#52a535] mb-6"> // [cite: 2979] | |
{mode === "login" ? "Login" : "Register"} // [cite: 2979] | |
</h2> | |
<form className="space-y-4" onSubmit={handleSubmit}> // [cite: 2980] | |
<div> | |
<label className="block text-sm font-medium text-gray-700">Username</label> // [cite: 2981] | |
<input | |
id="username" // [cite: 2981] | |
type="text" // [cite: 2982] | |
value={username} // [cite: 2982] | |
onChange={(e) => setUsername(e.target.value)} // [cite: 2982] | |
className="mt-1 block w-full rounded-md border border-gray-300 shadow-sm px-3 py-2 focus:outline-none focus:ring-[#52a535] focus:border-[#52a535]" // [cite: 2982, 2983] | |
required // [cite: 2983] | |
/> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-gray-700">Password</label> // [cite: 2984] | |
<input | |
id="password" // [cite: 2984] | |
type="password" // [cite: 2985] | |
value={password} // [cite: 2985] | |
onChange={(e) => setPassword(e.target.value)} // [cite: 2985] | |
className="mt-1 block w-full rounded-md border border-gray-300 shadow-sm px-3 py-2 focus:outline-none focus:ring-[#52a535] focus:border-[#52a535]" // [cite: 2985] | |
required // [cite: 2986] | |
/> | |
</div> | |
{error && <p className="text-red-600 text-sm font-medium">{error}</p>} // [cite: 2986, 2987] | |
<button | |
type="submit" | |
className="cursor-pointer w-full py-2 px-4 bg-[#52a535] text-white rounded-md font-semibold hover:bg-green-600 transition" // [cite: 2987] | |
> | |
{mode === "login" ? "Log In" : "Sign Up"} // [cite: 2987, 2988] | |
</button> | |
</form> | |
<div className="mt-4 text-center"> // [cite: 2988] | |
<p className="text-sm text-gray-600"> // [cite: 2988] | |
{mode === "login" ? "Don't have an account?" : "Already registered?"} // [cite: 2989] | |
<button | |
type="button" | |
onClick={() => setMode(mode === "login" ? "register" : "login")} // [cite: 2989] | |
className="cursor-pointer ml-1 text-[#52a535] font-medium hover:underline" // [cite: 2990] | |
> | |
{mode === "login" ? "Register" : "Login"} // [cite: 2990] | |
</button> | |
</p> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
// Navigation Bar Component | |
function AppNavBar() { | |
const [isLoggedIn, setIsLoggedIn] = useAccount(); // [cite: 2991] | |
useEffect(() => { // [cite: 2992] | |
// Check login status on mount | |
(async () => { | |
try { | |
const response = await fetch(`${API_BASE_URL}/me`, { credentials: "include" }); // [cite: 2992] | |
if (response.ok) { | |
setIsLoggedIn(true); // [cite: 2992] | |
} | |
} catch (error) { | |
console.error("Failed to fetch login status:", error); | |
// Handle error appropriately, maybe set logged in to false | |
} | |
})(); | |
}, [setIsLoggedIn]); // [cite: 2992] | |
return ( | |
<nav className="bg-[#52a535] shadow-lg p-4 mb-8"> // [cite: 2992] | |
<div className="flex items-center justify-between"> // [cite: 2992] | |
<Link to="/" className="text-xl font-semibold tracking-wide text-white cursor-pointer"> // [cite: 2993] | |
Minecraft Movie Fanclub | |
</Link> | |
<div className="space-x-6"> // [cite: 2993] | |
<NavLink to="/create-post" className="text-white hover:underline cursor-pointer"> // [cite: 2994] | |
Create a Post | |
</NavLink> | |
{isLoggedIn ? ( | |
<NavLink to="/account" className="text-white hover:underline cursor-pointer"> // [cite: 2994, 2995] | |
Account | |
</NavLink> | |
) : ( | |
<NavLink to="/login" className="text-white hover:underline cursor-pointer"> // [cite: 2995, 2996] | |
Login / Register | |
</NavLink> | |
)} | |
</div> | |
</div> | |
</nav> | |
); | |
} | |
// Post Card Component | |
function PostCard({ postId, title, username, likes }) { | |
return ( | |
<Link | |
to={`/post?postId=${postId}`} | |
className="w-72 bg-white rounded-xl shadow-md border border-gray-200 hover:shadow-lg transition transform hover:scale-[1.01]" // [cite: 3199] | |
key={postId} // [cite: 3205] | |
> | |
<div className="bg-[#52a535] text-white px-4 py-2 rounded-t-xl font-bold"> // [cite: 3199] | |
{title} | |
</div> | |
<div className="p-4 space-y-2 text-sm text-gray-700"> // [cite: 3199] | |
<p><strong>By:</strong> {username}</p> // [cite: 3200] | |
<p><strong>Likes:</strong> {likes}</p> // [cite: 3200, 3201] | |
</div> | |
</Link> | |
); | |
} | |
// Top Posts Page Component | |
function TopPostsPage() { | |
const [posts, setPosts] = useState([]); // [cite: 3201] | |
const [error, setError] = useState(undefined); // [cite: 3201] | |
useEffect(() => { | |
fetch(`${API_BASE_URL}/top-posts`) // [cite: 3202] | |
.then(response => { | |
if (!response.ok) { | |
throw new Error("Failed to load posts"); // [cite: 3202] | |
} | |
return response.json(); // [cite: 3202] | |
}) | |
.then(data => { | |
// Sort posts by likes descending | |
setPosts(data.sort((a, b) => b.likes - a.likes)); // [cite: 3202] | |
}) | |
.catch(err => setError(err.message)); // [cite: 3203] | |
}, []); // [cite: 3203] | |
return ( | |
<div className="p-6 bg-white min-h-screen"> // [cite: 3203] | |
<h1 className="text-3xl font-extrabold text-[#52a535] mb-6 text-center"> // [cite: 3203] | |
Top Minecraft Movie Posts!!!!!! | |
</h1> | |
{error && <p className="text-red-600 text-center mb-4">{error}</p>} // [cite: 3203, 3204] | |
<div className="flex flex-wrap justify-start gap-6 pl-4"> // [cite: 3204] | |
{posts.map(post => ( // [cite: 3204] | |
<PostCard // [cite: 3204] | |
key={post.postId} // [cite: 3205] | |
postId={post.postId} // [cite: 3204] | |
title={post.title} // [cite: 3204, 3205] | |
username={post.username} // [cite: 3205] | |
likes={post.likes} // [cite: 3205] | |
/> | |
))} | |
</div> | |
</div> | |
); | |
} | |
// View Post Page Component | |
function ViewPostPage() { | |
const location = useLocation(); // [cite: 3162] | |
const postId = new URLSearchParams(location.search).get("postId"); // [cite: 3162] | |
const [post, setPost] = useState(undefined); // [cite: 3162] | |
const [error, setError] = useState(undefined); // [cite: 3162] | |
const [socialError, setSocialError] = useState(undefined); // [cite: 3162] | |
const fetchPost = useCallback(async () => { // [cite: 3162] | |
if (!postId || !Ym(postId)) return; // Avoid fetching if postId is invalid [cite: 3166] | |
setError(undefined); | |
setSocialError(undefined); | |
try { | |
const response = await fetch(`${API_BASE_URL}/post?postId=${postId}`); // [cite: 3162] | |
if (!response.ok) { // [cite: 3163] | |
setError(await response.text()); // [cite: 3163] | |
return; | |
} | |
setPost(await response.json()); // [cite: 3163] | |
} catch (err) { | |
setError("Failed to fetch post."); // [cite: 3163] | |
} | |
}, [postId]); // [cite: 3164] | |
const handleLikeDislike = useCallback(async (likesChange) => { // [cite: 3164] | |
await startSessionIfNeeded(); // [cite: 3164] | |
setSocialError(undefined); // Clear previous social errors | |
if (window.sessionNumber === undefined) { | |
setSocialError("Session not started. Cannot like/dislike."); | |
return; | |
} | |
try { | |
const response = await fetch(`${API_BASE_URL}/legacy-social`, { // [cite: 3164] | |
method: "POST", | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded" // [cite: 3164] | |
}, | |
body: `sessionNumber=${window.sessionNumber}&postId=${postId}&likes=${likesChange}`, // [cite: 3165] | |
credentials: "include" // [cite: 3165] | |
}); | |
if (!response.ok) { // [cite: 3165] | |
setSocialError(await response.text()); // [cite: 3165] | |
return; | |
} | |
await fetchPost(); // Refresh post data after like/dislike [cite: 3165] | |
} catch (err) { | |
setSocialError("Failed to update likes."); | |
console.error("Like/dislike error:", err); | |
} | |
}, [postId, fetchPost]); // [cite: 3165] | |
useEffect(() => { // [cite: 3166] | |
if (postId && Ym(postId)) { // [cite: 3166] | |
fetchPost(); // [cite: 3166] | |
} | |
}, [postId, fetchPost]); // [cite: 3166] | |
if (postId != null && !Ym(postId)) { // [cite: 3166] | |
return ( | |
<div className="flex justify-center items-center"> | |
<p className="text-lg font-semibold text-red-600">Invalid post id!</p> // [cite: 3166, 3167] | |
</div> | |
); | |
} | |
if (error) { // [cite: 3168] | |
return ( | |
<div className="flex justify-center items-center"> | |
<p className="text-lg font-semibold text-red-600">Error: {error}</p> // [cite: 3168] | |
</div> | |
); | |
} | |
if (!post) { // [cite: 3169] | |
return ( | |
<div className="flex justify-center items-center"> | |
<p className="text-lg font-semibold text-gray-600">Loading...</p> // [cite: 3169] | |
</div> | |
); | |
} | |
// Sanitize content - Allow specific iframe attributes needed for YouTube embeds | |
const sanitizedContent = DOMPurify.sanitize(post.content, { // [cite: 3170] | |
ADD_TAGS: ["iframe"], // [cite: 3170] | |
ADD_ATTR: ["allow", "allowfullscreen", "frameborder", "scrolling", "src", "width", "height"] // [cite: 3170] | |
}); | |
return ( | |
<div className="flex justify-center items-center"> // [cite: 3171] | |
<div className="w-xl rounded-xl shadow-md overflow-hidden"> // [cite: 3171] | |
{/* Post Header */} | |
<div className="bg-[#52a535] p-4"> // [cite: 3171] | |
<h2 className="text-white text-xl font-bold">{post.title}</h2> // [cite: 3171, 3172] | |
<p className="text-white text-sm"> // [cite: 3172] | |
Posted by <span className="font-semibold">@{post.username}</span> // [cite: 3172, 3173] | |
</p> | |
</div> | |
{/* Post Body */} | |
<div className="p-6 bg-white space-y-4"> // [cite: 3174] | |
{/* Use dangerouslySetInnerHTML for sanitized HTML */} | |
<div | |
className="text-gray-700" // [cite: 3174] | |
dangerouslySetInnerHTML={{ __html: sanitizedContent }} // [cite: 3174, 3175] | |
/> | |
{/* Admin Like Indicator */} | |
{post.likedByAdmin && ( // [cite: 3175] | |
<div className="bg-yellow-100 text-yellow-800 text-sm font-medium px-4 py-2 rounded-md shadow"> // [cite: 3175] | |
🌟 This post was liked by an admin! | |
</div> | |
)} | |
{/* Social Error Message */} | |
{socialError && ( // [cite: 3176] | |
<p className="text-sm text-red-600 font-medium">{socialError}</p> // [cite: 3176] | |
)} | |
{/* TODO Comment */} | |
<div dangerouslySetInnerHTML={{ __html: "" }} /> // [cite: 3177] | |
{/* Like/Dislike Section */} | |
<div className="flex items-center justify-between"> // [cite: 3178] | |
<span className="text-sm text-gray-600">{post.likes} Likes</span> // [cite: 3178] | |
<div className="flex items-center space-x-2"> // [cite: 3179] | |
<button | |
className="cursor-pointer px-3 py-1 text-sm font-medium text-white bg-[#52a535] rounded-md hover:bg-green-600 transition" // [cite: 3179] | |
onClick={() => handleLikeDislike(1)} // [cite: 3180] | |
> | |
👍 Like | |
</button> | |
<button | |
id="dislike-button" // [cite: 3180, 3181] | |
className="cursor-pointer px-3 py-1 text-sm font-medium text-white bg-red-500 rounded-md hover:bg-red-600 transition" // [cite: 3181] | |
onClick={() => handleLikeDislike(-1)} // [cite: 3181] | |
> | |
👎 Dislike | |
</button> // [cite: 3181, 3182] | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
// Create Post Page Component | |
function CreatePostPage() { | |
const [title, setTitle] = useState(""); // [cite: 3182] | |
const [content, setContent] = useState(""); // [cite: 3182, 3183] | |
const [error, setError] = useState(undefined); // [cite: 3183] | |
const navigate = useNavigate(); // [cite: 3183] | |
const handleSubmit = useCallback(async (event) => { // [cite: 3183] | |
event.preventDefault(); // [cite: 3183] | |
setError(undefined); // Clear previous errors | |
try { | |
const response = await fetch(`${API_BASE_URL}/create-post`, { // [cite: 3184] | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json" // [cite: 3184] | |
}, | |
credentials: "include", // [cite: 3184] | |
body: JSON.stringify({ title, content }) // [cite: 3184, 3185] | |
}); | |
if (!response.ok) { // [cite: 3186] | |
setError(await response.text()); // [cite: 3186] | |
return; // [cite: 3187] | |
} | |
const postId = await response.text(); // [cite: 3187] | |
navigate(`/post?postId=${postId}`); // [cite: 3188] | |
} catch (err) { | |
setError("An error occurred while creating the post."); | |
console.error("Create post error:", err); | |
} | |
}, [title, content, navigate]); // [cite: 3188] | |
return ( | |
<div className="flex justify-center items-center"> // [cite: 3189] | |
<form | |
className="w-full max-w-md rounded-xl shadow-md overflow-hidden bg-white" // [cite: 3189] | |
onSubmit={handleSubmit} // [cite: 3189] | |
> | |
<div className="bg-[#52a535] p-4"> // [cite: 3189] | |
<h2 className="text-white text-xl font-bold">Create a Post</h2> // [cite: 3190] | |
</div> | |
<div className="p-6 space-y-4"> // [cite: 3190] | |
{error && <p className="text-sm text-red-600 font-medium">{error}</p>} // [cite: 3191] | |
<div> | |
<label className="block text-sm font-medium text-gray-700 mb-1">Title</label> // [cite: 3192] | |
<input | |
type="text" // [cite: 3193] | |
className="w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-[#52a535] focus:border-[#52a535]" // [cite: 3193] | |
value={title} // [cite: 3193] | |
onChange={(e) => setTitle(e.target.value)} // [cite: 3193] | |
required // [cite: 3194] | |
/> | |
</div> | |
<div> | |
<label className="block text-sm font-medium text-gray-700 mb-1">Content</label> // [cite: 3194, 3195] | |
<textarea | |
className="w-full h-32 px-3 py-2 border rounded-md shadow-sm resize-none focus:outline-none focus:ring-[#52a535] focus:border-[#52a535]" // [cite: 3195] | |
value={content} // [cite: 3196] | |
onChange={(e) => setContent(e.target.value)} // [cite: 3196] | |
required // [cite: 3196] | |
/> | |
</div> | |
<div className="text-right"> // [cite: 3197] | |
<button | |
type="submit" | |
className="cursor-pointer px-4 py-2 bg-[#52a535] text-white text-sm font-medium rounded-md hover:bg-green-600 transition" // [cite: 3197, 3198] | |
> | |
📤 Submit Post | |
</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
); | |
} | |
// Account Page Component | |
function AccountPage() { | |
const [accountInfo, setAccountInfo] = useState(undefined); // [cite: 3205] | |
const [error, setError] = useState(undefined); // [cite: 3205] | |
useEffect(() => { // [cite: 3206] | |
fetch(`${API_BASE_URL}/me`, { credentials: "include" }) // [cite: 3206] | |
.then(response => { | |
if (!response.ok) { | |
throw new Error("Failed to load account info"); // [cite: 3206] | |
} | |
return response.json(); // [cite: 3206] | |
}) | |
.then(setAccountInfo) // [cite: 3207] | |
.catch(err => setError(err.message)); // [cite: 3207] | |
}, []); // [cite: 3207] | |
return ( | |
<div className="p-6 bg-white min-h-screen"> // [cite: 3207] | |
<h1 className="text-3xl font-extrabold text-[#52a535] mb-6 text-center"> // [cite: 3207] | |
Account Summary | |
</h1> | |
{error && <p className="text-red-600 text-center mb-4">{error}</p>} // [cite: 3207, 3208] | |
{accountInfo && ( // [cite: 3208] | |
<> | |
<div className="text-center text-gray-800 mb-8 space-y-2"> // [cite: 3208] | |
<p className="text-xl font-semibold">Username: {accountInfo.username}</p> // [cite: 3208, 3209] | |
<p className="text-lg"> // [cite: 3209] | |
Current Session Number: {window.sessionNumber !== undefined ? window.sessionNumber : "undefined"} {/* [cite: 3209, 3210] */} | |
</p> | |
{/* Display flag if present */} | |
{"flag" in accountInfo && accountInfo.flag && ( // [cite: 3210] | |
<div className="mt-4 text-green-700 font-medium"> // [cite: 3210] | |
<p>Wow, an admin liked your post! ⛏️</p> // [cite: 3210, 3211] | |
<p>Your flag is: {accountInfo.flag}</p> // [cite: 3211] | |
</div> | |
)} | |
</div> | |
<h2 className="text-2xl font-bold text-[#52a535] mb-4 pl-4">Your Posts</h2> // [cite: 3212] | |
{accountInfo.posts.length === 0 ? ( // [cite: 3212] | |
<p className="pl-4 text-gray-600 italic">You haven't posted anything yet.</p> // [cite: 3213] | |
) : ( | |
<div className="flex flex-wrap justify-start gap-6 pl-4"> // [cite: 3213] | |
{accountInfo.posts.map(post => ( // [cite: 3213] | |
<PostCard // [cite: 3213] | |
key={post.postId} // [cite: 3214] | |
postId={post.postId} // [cite: 3214] | |
title={post.title} // [cite: 3214] | |
username={accountInfo.username} // [cite: 3214] | |
likes={post.likes} // [cite: 3214] | |
/> | |
))} | |
</div> // [cite: 3214, 3215] | |
)} | |
</> | |
)} | |
</div> // [cite: 3215] | |
); | |
} | |
// Main Application Component | |
function App() { | |
const [isLoggedIn, setIsLoggedIn] = useState(false); // [cite: 3215] | |
// DOMPurify hook to sanitize iframes (only allow specific youtube embeds) | |
useEffect(() => { // [cite: 3216] | |
DOMPurify.addHook("uponSanitizeElement", (node, data) => { // [cite: 3216] | |
if (data.tagName === "iframe" && node instanceof Element) { // [cite: 3216] | |
const src = node.getAttribute("src") || ""; | |
// Allow only specific googleusercontent URLs that likely proxy YouTube | |
if (!src.startsWith("https://www.youtube.com/embed/")) { // [cite: 3216] | |
// Attempt to remove the node if the src is not allowed | |
// Note: DOMPurify hooks run *during* sanitization. Direct removal might | |
// interfere. A better approach might be to forbid the element entirely | |
// if the src doesn't match, but this mimics the original logic. | |
try { | |
if (node.parentNode) { | |
node.parentNode.removeChild(node); // [cite: 3216] | |
} | |
} catch (e) { | |
console.warn("Could not remove invalid iframe during sanitization:", e); | |
} | |
} | |
} | |
}); | |
// Cleanup hook if necessary, though DOMPurify hooks are usually global | |
// return () => DOMPurify.removeHook('uponSanitizeElement'); | |
}, []); // [cite: 3217] | |
return ( | |
<div className="min-h-screen bg-gray-50 text-gray-900"> // [cite: 3216, 3217] | |
{/* Wrap with AccountContext Provider */} | |
<AccountContext.Provider value={[isLoggedIn, setIsLoggedIn]}> // [cite: 3217] | |
<AppNavBar /> // [cite: 3217] | |
<Routes> // [cite: 3217] | |
<Route path="/" element={<TopPostsPage />} /> // [cite: 3217, 3218] | |
<Route path="/login" element={<LoginRegisterForm />} /> // [cite: 3218] | |
<Route path="/post" element={<ViewPostPage />} /> // [cite: 3218, 3219] | |
<Route path="/create-post" element={<CreatePostPage />} /> // [cite: 3219] | |
<Route path="/account" element={<AccountPage />} /> // [cite: 3219, 3220] | |
</Routes> | |
</AccountContext.Provider> | |
</div> | |
); | |
} | |
// --- Entry Point --- | |
const rootElement = document.getElementById("root"); | |
if (!rootElement) { | |
throw new Error("Failed to find the root element"); | |
} | |
const root = ReactDOM.createRoot(rootElement); | |
root.render( // [cite: 3220] | |
<React.StrictMode> // [cite: 3220] | |
<BrowserRouter> // [cite: 3220] | |
<App /> // [cite: 3221] | |
</BrowserRouter> | |
</React.StrictMode> | |
); // [cite: 3221] | |
// Initial session start attempt | |
startSessionIfNeeded(); // [cite: 2971] | |
// Note: The initial module preloading logic and React/Router library source code | |
// have been omitted as requested. DOMPurify's source is also omitted, | |
// assuming it's included via import. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment