Implement fetching blog cards and blog entries

This commit is contained in:
Curt Spark 2024-05-08 20:58:18 +01:00
parent d4a963b162
commit 961a4cda5d
8 changed files with 87 additions and 54 deletions

6
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@babel/core": "^7.24.4", "@babel/core": "^7.24.4",
"@babel/preset-env": "^7.24.4", "@babel/preset-env": "^7.24.4",
"@babel/preset-react": "^7.24.1", "@babel/preset-react": "^7.24.1",
"dompurify": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
@ -3336,6 +3337,11 @@
"url": "https://github.com/fb55/domhandler?sponsor=1" "url": "https://github.com/fb55/domhandler?sponsor=1"
} }
}, },
"node_modules/dompurify": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.2.tgz",
"integrity": "sha512-hLGGBI1tw5N8qTELr3blKjAML/LY4ANxksbS612UiJyDfyf/2D092Pvm+S7pmeTGJRqvlJkFzBoHBQKgQlOQVg=="
},
"node_modules/domutils": { "node_modules/domutils": {
"version": "2.8.0", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",

View File

@ -31,6 +31,7 @@
"@babel/core": "^7.24.4", "@babel/core": "^7.24.4",
"@babel/preset-env": "^7.24.4", "@babel/preset-env": "^7.24.4",
"@babel/preset-react": "^7.24.1", "@babel/preset-react": "^7.24.1",
"dompurify": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
} }

View File

@ -6,6 +6,7 @@ import NavbarButton from "./components/NavbarButton.tsx";
import NavbarUserInfo from "./components/NavbarUserInfo.tsx"; import NavbarUserInfo from "./components/NavbarUserInfo.tsx";
import Home from "./pages/Home.tsx"; import Home from "./pages/Home.tsx";
import Blog from "./pages/Blog.tsx";
import Login from "./pages/Login.tsx"; import Login from "./pages/Login.tsx";
import Logout from "./pages/Logout.tsx"; import Logout from "./pages/Logout.tsx";
import About from "./pages/About.tsx"; import About from "./pages/About.tsx";
@ -73,6 +74,7 @@ export default function App() {
<Routes> <Routes>
<Route path="/" exact element={<Home></Home>} /> <Route path="/" exact element={<Home></Home>} />
<Route path="/home" exact element={<Home></Home>} /> <Route path="/home" exact element={<Home></Home>} />
<Route path="/blog/*" exact element={<Blog></Blog>} />
<Route path="/about" exact element={<About></About>} /> <Route path="/about" exact element={<About></About>} />
<Route <Route
path="/blogeditor" path="/blogeditor"

View File

@ -1,31 +1,37 @@
import * as React from "react"; import * as React from "react";
import { Link } from "react-router-dom";
interface Props { interface Props {
blogID: number;
blogImage: string; // This needs a placeholder/default variable blogImage: string; // This needs a placeholder/default variable
blogTitle: string; blogTitle: string;
blogDatePosted: string; blogDatePosted: string;
blogAuthor: string;
blogDescription: string; blogDescription: string;
} }
export default function BlogEntryCard({ export default function BlogEntryCard({
blogID,
blogImage, blogImage,
blogTitle, blogTitle,
blogDatePosted, blogDatePosted,
blogAuthor,
blogDescription, blogDescription,
}: Props) { }: Props) {
return ( return (
<> <>
<div className="blogEntryCard"> <div className="blogEntryCard">
<a href="#"> <Link to={"/blog?ID=" + blogID.toString()}>
<div className="blogEntry"> <div className="blogEntry">
<img src={blogImage} className="blogEntryImage" alt="..."></img> <img src={blogImage} className="blogEntryImage" alt="..."></img>
<div className="blogEntryBody"> <div className="blogEntryBody">
<h4 className="blogEntryTitle">{blogTitle}</h4> <h4 className="blogEntryTitle">{blogTitle}</h4>
<p className="blogEntryDatePosted">{blogDatePosted}</p> <p className="blogEntryDatePosted">{blogDatePosted}</p>
<p className="blogEntryDatePosted">{blogAuthor}</p>
<p className="blogEntryDescription">{blogDescription}</p> <p className="blogEntryDescription">{blogDescription}</p>
</div> </div>
</div> </div>
</a> </Link>
</div> </div>
</> </>
); );

View File

@ -12,7 +12,8 @@ body,
font-family: "consolas", sans-serif; font-family: "consolas", sans-serif;
background-color: white; background-color: white;
--background-image: url(https://source.unsplash.com/random/1920x1080/?blurry); /*--background-image: url(https://source.unsplash.com/random/1920x1080/?blurry);*/
--background-image: url(https://images.unsplash.com/photo-1542254503-d802f00c2342?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=1080&ixid=MnwxfDB8MXxyYW5kb218MHx8Ymx1cnJ5fHx8fHx8MTcxNTE3MDY0OQ&ixlib=rb-4.0.3&q=80&w=1920);
--nav-bar-height: 25px; --nav-bar-height: 25px;
--blog-card-width: 18rem; --blog-card-width: 18rem;
--blog-card-height: 20rem; --blog-card-height: 20rem;

View File

@ -1,38 +1,50 @@
import * as React from "react"; import * as React from "react";
import * as DOMPurify from "dompurify";
import BlogEntryCard from "./../components/BlogEntryCard.tsx"; import { Navigate } from "react-router-dom";
import TilingItem from "../components/TilingItem.tsx";
export default function Home() { export default function Home() {
// A mock of what the data from the getBlogEntries JSON return would look like const [blogContents, setBlogContents] = React.useState(
const blogEntries = [ "Fetching blog contents...",
{ );
blogImage: const blogQuery = new URLSearchParams(window.location.search);
"https://git.cspark.dev/avatars/1c230bfe7494b1a62932d94ed8558dc61189437b1b6e0cecdb0c45c3d899bea4?size=512", const blogID = blogQuery.get("ID");
blogTitle: "Test Blog Entry 1",
blogDatePosted: "12/04/2024 4PM", // In reality this would probably be an actual correct format/standardised timestamp React.useEffect(() => {
blogDescription: "This is a first blog entry test", // If no description, we'd want to get a small snippet of the intro of the blog if (blogID) {
}, fetch(
{ "http://localhost:8000/api/blog/contents/HTML/" + blogID.toString(),
blogImage: {
"https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", method: "GET",
blogTitle: "Test Blog Entry 2", headers: {
blogDatePosted: "8/04/2024 6PM", // In reality this would probably be an actual correct format/standardised timestamp "Content-Type": "application/json",
blogDescription: "This is a test blog entry number 2", // If no description, we'd want to get a small snippet of the intro of the blog },
}, },
]; )
.then((response) => response.json())
.then((responseParsed: Object) => {
if (responseParsed.success) {
setBlogContents(responseParsed.contents);
} else {
setBlogContents(responseParsed.message);
}
});
}
}, []);
return ( return (
<> <>
<div className="blogEntryGrid"> <div className="standardHorizontalTilingGrid">
{blogEntries.map((item, index) => ( <TilingItem>
<BlogEntryCard <h1>Now on blog page!</h1>
key={index} <div
blogImage={item["blogImage"]} dangerouslySetInnerHTML={{
blogTitle={item["blogTitle"]} __html: DOMPurify.sanitize(blogContents),
blogDatePosted={item["blogDatePosted"]} }}
blogDescription={item["blogDescription"]} />
></BlogEntryCard> </TilingItem>
))}
</div> </div>
</> </>
); );

View File

@ -3,34 +3,39 @@ import * as React from "react";
import BlogEntryCard from "./../components/BlogEntryCard.tsx"; import BlogEntryCard from "./../components/BlogEntryCard.tsx";
export default function Home() { export default function Home() {
// A mock of what the data from the getBlogEntries JSON return would look like const [blogEntries, setBlogEntries] = React.useState({});
const blogEntries = [
{ React.useEffect(() => {
blogImage: fetch(
"https://git.cspark.dev/avatars/1c230bfe7494b1a62932d94ed8558dc61189437b1b6e0cecdb0c45c3d899bea4?size=512", "http://127.0.0.1:8000/api/user/blog/cardInfo/?rangeStart=1&rangeEnd=25&sortByLatest=true",
blogTitle: "Test Blog Entry 1", {
blogDatePosted: "12/04/2024 4PM", // In reality this would probably be an actual correct format/standardised timestamp method: "GET",
blogDescription: "This is a first blog entry test", // If no description, we'd want to get a small snippet of the intro of the blog headers: {
}, "Content-Type": "application/json",
{ },
blogImage: },
"https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", )
blogTitle: "Test Blog Entry 2", .then((response) => response.json())
blogDatePosted: "8/04/2024 6PM", // In reality this would probably be an actual correct format/standardised timestamp .then((responseParsed: Object) => {
blogDescription: "This is a test blog entry number 2", // If no description, we'd want to get a small snippet of the intro of the blog if (responseParsed["0"].success) {
}, delete responseParsed["0"];
]; setBlogEntries(responseParsed);
}
});
}, []);
return ( return (
<> <>
<div className="blogEntryGrid"> <div className="blogEntryGrid">
{blogEntries.map((item, index) => ( {Object.entries(blogEntries).map((item, index) => (
<BlogEntryCard <BlogEntryCard
key={index} key={index}
blogImage={item["blogImage"]} blogID={item["1"]["blogID"]}
blogTitle={item["blogTitle"]} blogImage={item["1"]["image"]}
blogDatePosted={item["blogDatePosted"]} blogTitle={item["1"]["title"]}
blogDescription={item["blogDescription"]} blogDatePosted={item["1"]["datePosted"]}
blogAuthor={item["1"]["authorInfo"]["username"]}
blogDescription={item["1"]["description"]}
></BlogEntryCard> ></BlogEntryCard>
))} ))}
</div> </div>

View File

@ -31,7 +31,7 @@ export default function Login({ authTokenStorageHandler }: Props) {
rememberMe: formDataObject.rememberMe ? true : false, rememberMe: formDataObject.rememberMe ? true : false,
}; };
fetch("http://127.0.0.1:8000/api/login", { fetch("http://127.0.0.1:8000/api/user/login", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",