Connecting features #2

Merged
innocuous-symmetry merged 7 commits from connecting-features into master 2022-01-27 20:21:54 +00:00
12 changed files with 255 additions and 9 deletions

3
package-lock.json generated
View File

@@ -16,6 +16,9 @@
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-scripts": "5.0.0"
},
"devDependencies": {
"uuid": "^8.3.2"
}
},
"node_modules/@babel/code-frame": {

View File

@@ -32,5 +32,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"uuid": "^8.3.2"
}
}

View File

@@ -1,5 +1,7 @@
.App {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 0;
}
@@ -9,4 +11,55 @@
flex-direction: row;
justify-content: space-between;
align-items: baseline;
width: 100%;
height: 5rem;
border-bottom: 1px solid black;
}
.navbar > * {
padding: 0 2rem;
}
.content-container {
display: inline-flex;
flex-direction: row;
}
.feed {
display: inline-flex;
flex-direction: column;
align-items: center;
width: 90vw;
}
.about-the-app {
display: hidden;
flex-direction: column;
align-items: center;
}
.sidebar {
display: flex;
flex-direction: column;
width: 12rem;
position: fixed;
background-color: black;
color: white;
right: 0;
top: 5rem;
padding: 1.5rem;
transition: right 0.6s ease-out;
}
.sidebar-hidden {
display: flex;
flex-direction: column;
width: 12rem;
position: fixed;
background-color: black;
color: white;
right: -15rem;
top: 5rem;
padding: 1.5rem;
transition: right 0.6s ease-out;
}

View File

@@ -1,13 +1,29 @@
import React from 'react';
import './App.css';
import Navbar from './features/navbar/Navbar';
import redditSlice from './features/reddit/redditSlice';
import Post from './features/posts/Post';
import Feed from './features/posts/Feed';
function App() {
return (
<div className="App">
<Navbar />
<p>Stuff</p>
<div className="content-container">
<div className="feed">
{/* To do: import posts from post directory */}
{/* Map post data onto individual post cards, handle undefined values */}
<Feed />
</div>
<div className="about-the-site">
{/* To do: add mutable state to class name for this div, */}
{/* determining whether or not it's active based on the state of */}
{/* The action dispatched from the searchbar slice(?) */}
{/* Do I need a searchbar slice? */}
</div>
</div>
</div>
);
}

View File

@@ -1,11 +1,24 @@
import React from "react";
import React, { useState } from "react";
import SearchBar from "../searchBar/searchBar";
import Sidebar from "../sidebar/Sidebar";
export default function Navbar() {
const [collapsed, setCollapsed] = useState(true);
const handleCollapse = () => {
setCollapsed(!collapsed);
}
return (
<>
<div className="navbar">
<h1>Reddit but it's all cats</h1>
<p>Search bar here</p>
<p>Expand sidebar here</p>
<SearchBar />
<button onClick={handleCollapse}>Sidebar</button>
</div>
<div className="sidebar-container">
<Sidebar isCollapsed={collapsed} />
</div>
</>
)
}

View File

@@ -0,0 +1,55 @@
import React, { useState, useEffect } from "react";
import postsSlice, { fetchBySub, updatePosts, selectPosts } from "./postsSlice";
import { selectAllSubs } from "../reddit/redditSlice";
import { useSelector, useDispatch } from "react-redux";
import { v4 } from "uuid";
import Post from "./Post";
export default function Feed() {
const [feed, setFeed] = useState(null);
const dispatch = useDispatch();
useEffect(() => {
let isActive = true;
const getPosts = async() => {
let myPosts = await dispatch(fetchBySub('https://www.reddit.com/r/cats.json'));
myPosts = myPosts.payload;
console.log(myPosts);
if (typeof myPosts === 'object' && isActive) {
let newFeed = [];
for (let post of myPosts) {
newFeed.push(
<Post
title={post.data.title}
author={post.data.author}
subreddit={post.data.subreddit}
ups={post.data.ups}
comments={post.data.num_comments}
time={post.data.created_utc}
id={v4()}
media={post.data.post_hint === 'image' && post.data.url}
permalink={post.data.permalink}
selftext={post.data.selftext}
video={post.data.is_video ? post.data.media.reddit_video.fallback_url : null}
/>
);
}
setFeed(newFeed);
}
};
getPosts();
return () => {
isActive = false;
}
}, [dispatch])
return (
<>
{feed ? feed : <h1>Loading cats for you...</h1>}
</>
);
}

View File

@@ -0,0 +1,30 @@
.post-body {
display: inline-flex;
flex-direction: column;
width: 75%;
padding: 2rem;
border: 1px solid black;
margin: 3rem 0;
}
.image-placeholder {
display: inline-flex;
background-color: grey;
width: 30%;
height: 15rem;
}
a {
font-size: 2rem;
}
img, video {
max-height: 45rem;
object-fit: contain;
}
.post-metadata {
display: inline-flex;
flex-direction: row;
justify-content: space-between;
}

View File

@@ -0,0 +1,31 @@
import React, { useState, useEffect } from "react";
import './Post.css';
export default function Post({title,author,subreddit,ups,comments,time,id,media,permalink,selftext,video}) {
const limit = 300;
const [body, setBody] = useState(selftext);
useEffect(() => {
if (selftext.length > limit) {
setBody(selftext.substring(0,limit) + '...');
} else {
return;
}
})
return (
<>
<div className="post-body">
<a className="title" href={`https://reddit.com${permalink}`}>{title ? title : 'title'}</a>
{media ? <img alt={title} src={media} /> : ''}
{video ? <video controls type="video/mp4" src={video}></video> : ''}
<p>{body}</p>
<div className="post-metadata">
<p className="user">{author ? 'u/' + author : 'u/username'}</p>
<p className="time-posted">{time ? time : ''}</p>
<p className="num-comments">{comments ? comments : 'comments'}</p>
</div>
</div>
</>
);
}

View File

@@ -1,7 +1,7 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const fetchBySub = createAsyncThunk(
'reddit/fetchBySub',
'posts/fetchBySub',
async(subreddit) => { // expects an argument corresponding to the url, in json format, of a given subreddit
try {
const myRequest = new Request(subreddit); // initializes request
@@ -25,7 +25,10 @@ export const postsSlice = createSlice({
reducers: {
filterPosts(state,action) { // Expects action.payload to be the searchterm imported from the state of searchBar
state.posts.filter(post => (post.data.title !== action.payload) && (post.data.selftext !== action.payload));
}
},
updatePosts(state,action) {
state.posts = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(fetchBySub.pending, (state,action) => {
@@ -47,4 +50,6 @@ export const postsSlice = createSlice({
});
export default postsSlice.reducer;
export const { filterPosts } = postsSlice.actions;
export const selectPosts = state => state.postsSlice.posts;
export const { filterPosts, updatePosts } = postsSlice.actions;
// exports also includes fetchBySub (takes argument of a sub)

View File

@@ -73,4 +73,5 @@ export const redditSlice = createSlice({
});
export default redditSlice.reducer;
export const selectAllSubs = state => state.redditSlice.subreddits;
export const { updateSubVisibility } = redditSlice.actions;

View File

@@ -0,0 +1,24 @@
import React, { useState, useEffect } from "react";
export default function SearchBar() {
const [term, setTerm] = useState('');
const handleChange = (e) => {
e.preventDefault();
setTerm(e.target.value);
}
useEffect(() => {
if (term) {
// dispatch an action which filters content by {term}
} else {
return;
}
}, [term])
return (
<>
<input type="text" className="searchbar" placeholder="Search posts" value={term ? term : ''} onChange={handleChange} />
</>
);
}

View File

@@ -0,0 +1,12 @@
import React from "react";
export default function Sidebar({isCollapsed}) {
return (
<div className={isCollapsed ? 'sidebar-hidden' : 'sidebar'}>
<p>Hard coded subreddit</p>
<p>Hard coded subreddit</p>
<p>Hard coded subreddit</p>
<p>Hard coded subreddit</p>
</div>
);
}