Skip to content

Commit

Permalink
16: Initial posts UI for homepage & added callback to update posts wh…
Browse files Browse the repository at this point in the history
…en one is created (#57)

### Backend
- In `PostsSerializer` GET method, order posts by `published` descending
  - Some smol cleanup on Post model
### Frontend
  -  Refactor: moving homepage to a feed directory
- Adding interface and enums for typing in TypeScript, feel free to
change as you see fit
- Adding `<PostLists />` component, which is called in the home page to
display a list of posts given an author
- Note that the `AUTHOR_ID` is still hardcoded, added a TODO once able
to get `author_id` of current user
- Also added callback in `MakePostModal` comp to fetch posts once a post
is made
  - Added UI changes in existing HomePage component

### Example

https://github.com/uofa-cmput404/404f23project-404-team-not-found/assets/40973251/612221ad-ac32-4738-97e7-363fc7183953
  • Loading branch information
nluu175 authored Oct 24, 2023
2 parents 2de8514 + 938f9e9 commit 8792816
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 15 deletions.
2 changes: 1 addition & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

import { Routes, Route } from "react-router-dom";

import HomePage from "./components/HomePage";
import HomePage from "./components/feed/HomePage";
import Login from "./components/authentication/Login";
import SignUp from "./components/authentication/SignUp";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { Box, CssBaseline, Paper } from "@mui/material";
import AppBar from "@mui/material/AppBar";

import MakePostModal from "./post/MakePostModal";
import MakePostModal from "../post/MakePostModal";
import PostsList from "../post/PostsList";
import axios from "axios";
import {Post} from "../../interfaces/interfaces";

const APP_URI = process.env.REACT_APP_URI;

export default function HomePage() {
const [isMakePostModalOpen, setIsMakePostModalOpen] = useState(false);
const [posts, setPosts] = useState<Post[]>([]);

const openMakePostModal = () => {
setIsMakePostModalOpen(true);
};

const fetchPosts = async () => {
// TODO: replace hardcoded author id with AUTHOR_ID
const url = `${APP_URI}author/5ba6d758-257f-4f47-b0b7-d3d5f5e32561/posts/`;

try {
const response = await axios.get(url);
setPosts(response.data);
} catch (error) {
console.error("Error fetching posts:", error);
}
};

// https://react.dev/reference/react/useEffect
useEffect(() => {
fetchPosts();
}, []);

return (
<>
<CssBaseline />
<AppBar position="relative" style={{ color: "#FFFFFF" }}>
<AppBar position="fixed" style={{ color: "#FFFFFF", height: 60 }}>
<Typography
variant="h4"
align="left"
Expand All @@ -31,9 +54,19 @@ export default function HomePage() {
socialdistribution
</Typography>
</AppBar>
<Grid container style={{ width: "100%", margin: "0 auto" }}>
<Grid
container
style={{ width: "100%", margin: "0 auto", marginTop: 60 }}
>
<Grid item xs={3} style={{ height: "80vh" }}>
<Paper style={{ height: "100vh" }} variant="outlined">
<Paper style={{
height: "100vh",
position: "fixed",
width: "25vw"
}}
elevation={3}
variant="outlined"
square>
<Typography
variant="h5"
align="center"
Expand All @@ -53,11 +86,17 @@ export default function HomePage() {
</Box>
</Paper>
</Grid>
<Grid item xs={9}>
<Typography align="center">main</Typography>
<Grid item xs={6} justifyContent='center'>
<PostsList
posts={posts}
/>
</Grid>
<Grid item xs={3}>
<Typography align="center">side</Typography>
</Grid>
<MakePostModal
isModalOpen={isMakePostModalOpen}
onPostCreated={fetchPosts}
setIsModalOpen={setIsMakePostModalOpen}
/>
</Grid>
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/post/MakePostModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ const APP_URI = process.env.REACT_APP_URI;

const MakePostModal = ({
isModalOpen,
onPostCreated,
setIsModalOpen,
}: {
isModalOpen: boolean;
onPostCreated: () => void;
setIsModalOpen: (isOpen: boolean) => void;
}) => {
const [title, setTitle] = useState("");
Expand Down Expand Up @@ -55,6 +57,7 @@ const MakePostModal = ({

try {
await axios.post(url, payload);
onPostCreated();
handleClose();
} catch (error) {
console.error("Failed to post", error);
Expand Down
44 changes: 44 additions & 0 deletions client/src/components/post/PostsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import { Post } from "../../interfaces/interfaces";
import { Avatar, Card, CardContent, CardHeader, Typography } from "@mui/material";
import { theme } from "../../index";
import { formatDateTime } from "../../utils/dateUtils";

const PostsList = ({
posts,
}: {
posts: Post[];
}) => {
return (
<>
{ posts.map(post => (
<Card key={post.id}
style={{
margin: "auto",
width: "40vw",
borderRadius: 0,
borderBottom: 0
}}
variant='outlined'>
<CardHeader
avatar={
<Avatar sx={{ bgcolor: theme.palette.primary.main }} aria-label="recipe">
{post.author.displayName[0]}
</Avatar>
}
title={post.author.displayName}
subheader={formatDateTime(post.published)}
sx = {{marginTop:2}}
/>
<CardContent sx={{marginBottom:10}}>
<Typography variant="h6">{post.title}</Typography>
<Typography variant="body1">{post.description}</Typography>
<Typography variant="body1">{post.content}</Typography>
</CardContent>
</Card>
))}
</>
);
};

export default PostsList;
7 changes: 7 additions & 0 deletions client/src/enums/enums.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum ContentType {
BASE64 = "application/base64",
JPEG = "image/jpeg;base64",
MARKDOWN = "text/markdown",
PLAIN = "text/plain",
PNG = "image/png;base64",
}
2 changes: 1 addition & 1 deletion client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "./index.css";
import 'react-toastify/dist/ReactToastify.css';


const theme = createTheme({
export const theme = createTheme({
palette: {
primary: {
main: "#103f5b",
Expand Down
31 changes: 31 additions & 0 deletions client/src/interfaces/interfaces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ContentType } from "../enums/enums";

export interface Author {
id: string;
createdAt: string;
displayName: string;
github?: string | null;
host: string;
profileImage: string;
url: string;
}

export interface Category {
category: string;
}

export interface Post {
id: string;
author: Author;
categories: Category[];
content?: string | null;
contentType: ContentType;
description: string;
title: string;
source: string;
origin: string;
published: string;
updatedAt?: string | null;
visibility: string;
unlisted: boolean;
}
17 changes: 17 additions & 0 deletions client/src/utils/dateUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function formatDateTime(date_str: string): string {
// TypeScript Date Object: https://www.javatpoint.com/typescript-date-object
const date = new Date(date_str);
const months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];

const hours = date.getHours();
const minutes = String(date.getMinutes()).padStart(2, '0');

// Convert 24-hour format to 12-hour format and determine AM or PM
const twelveHour = hours % 12 || 12;
const amOrPm = hours < 12 ? "AM" : "PM";

return `${twelveHour}:${minutes} ${amOrPm}, ${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}
10 changes: 5 additions & 5 deletions server/socialdistribution/models/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ class Post(models.Model):
"""
# Types of content types
class ContentType(models.TextChoices):
BASE64 = 'application/base64' # images can need base 64 decoding
JPEG = 'image/jpeg;base64'
MARKDOWN = 'text/markdown'
PLAIN = 'text/plain'
PNG = 'image/png;base64'
BASE64 = "application/base64" # images can need base 64 decoding
JPEG = "image/jpeg;base64"
MARKDOWN = "text/markdown"
PLAIN = "text/plain"
PNG = "image/png;base64"

# Types of visibility for posts
class Visibility(models.TextChoices):
Expand Down
2 changes: 1 addition & 1 deletion server/socialdistribution/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def get(self, request, author_id):
get the recent posts from author AUTHOR_ID
TODO: paginate
"""
posts = Post.objects.filter(author__id=author_id)
posts = Post.objects.filter(author__id=author_id).order_by("-published")
serializer = PostSerializer(posts, many=True, context={"request": request})

return Response(serializer.data, status=status.HTTP_200_OK)
Expand Down

0 comments on commit 8792816

Please sign in to comment.