mirror of
https://github.com/rjNemo/devbook_ts
synced 2026-06-12 13:36:43 +00:00
refactor: Post folder
This commit is contained in:
parent
01a5c61e81
commit
abf0ba5b0c
5 changed files with 162 additions and 115 deletions
|
|
@ -1,115 +0,0 @@
|
||||||
import React, {FC} from 'react';
|
|
||||||
// Routing
|
|
||||||
import {useParams, Link} from 'react-router-dom';
|
|
||||||
import Routes from '../constants/routes';
|
|
||||||
// Redux
|
|
||||||
import {compose} from '@reduxjs/toolkit';
|
|
||||||
import {connect, useSelector} from 'react-redux';
|
|
||||||
import {firestoreConnect, WithFirestoreProps} from 'react-redux-firebase';
|
|
||||||
import {RootState} from '../store';
|
|
||||||
import {selectProfile} from '../store/firebase';
|
|
||||||
import Collections from '../constants/collections';
|
|
||||||
// Typing
|
|
||||||
import Post from '../models/Post';
|
|
||||||
import Comment from '../types/Comment';
|
|
||||||
// Form
|
|
||||||
import useForm from '../hooks';
|
|
||||||
|
|
||||||
interface IProps extends WithFirestoreProps {
|
|
||||||
post: Post;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a Post and the related comments. Shows a form to add a comment.
|
|
||||||
*/
|
|
||||||
const PostPage: FC<IProps> = ({post, firestore}) => {
|
|
||||||
const {avatarUrl, displayName} = useSelector(selectProfile);
|
|
||||||
const newComment: Comment = {
|
|
||||||
name: displayName ?? 'error',
|
|
||||||
avatarUrl: avatarUrl ?? 'error',
|
|
||||||
text: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const {formData, handleChange, resetForm} = useForm(newComment);
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
firestore
|
|
||||||
.update(`${Collections.POSTS}/${post.id}`, {
|
|
||||||
comments: [...post.comments, formData],
|
|
||||||
})
|
|
||||||
.then(() => resetForm())
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="container">
|
|
||||||
<Link to={Routes.POSTS} className="btn btn-light">
|
|
||||||
Back To Posts
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="post bg-white p-1 my-1">
|
|
||||||
<div>
|
|
||||||
<Link to={`${Routes.PROFILE}/${post.userID}`}>
|
|
||||||
<img className="round-img" src={post.avatarUrl} alt={post.name} />
|
|
||||||
<h4>{post.name}</h4>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="my-1">{post.text}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="post-form">
|
|
||||||
<div className="post-form-header bg-primary">
|
|
||||||
<h3>Leave A Comment</h3>
|
|
||||||
</div>
|
|
||||||
<form className="form my-1" onSubmit={handleSubmit}>
|
|
||||||
<textarea
|
|
||||||
name="text"
|
|
||||||
cols={30}
|
|
||||||
rows={5}
|
|
||||||
placeholder="Comment on this post"
|
|
||||||
value={formData.text}
|
|
||||||
onChange={handleChange}
|
|
||||||
></textarea>
|
|
||||||
<input type="submit" className="btn btn-dark my-1" value="Submit" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="posts">
|
|
||||||
{post.comments?.map((c: Comment, i: number) => (
|
|
||||||
<div className="post bg-white p-1 my-1" key={i}>
|
|
||||||
<div>
|
|
||||||
<a href="profile.html">
|
|
||||||
<img className="round-img" src={c.avatarUrl} alt={c.name} />
|
|
||||||
<h4>{c.name}</h4>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="my-1">{c.text}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Container to fetch id params from thr URI and pass it to Profile page
|
|
||||||
*/
|
|
||||||
const PostPageContainer: FC = () => {
|
|
||||||
const {id} = useParams();
|
|
||||||
const Component = compose<FC>(
|
|
||||||
firestoreConnect(() => [`${Collections.POSTS}/${id}`]),
|
|
||||||
connect(({firestore: {data}}: RootState) => ({
|
|
||||||
post: data.posts && {...data.posts[id], id},
|
|
||||||
})),
|
|
||||||
)(PostPage);
|
|
||||||
|
|
||||||
return <Component />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PostPageContainer;
|
|
||||||
26
src/pages/Post/Comments.tsx
Normal file
26
src/pages/Post/Comments.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
import Comment from '../../types/Comment';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
comments: Comment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostComments: FC<IProps> = ({comments}) => (
|
||||||
|
<div className="posts">
|
||||||
|
{comments?.map(({name, avatarUrl, text}: Comment, i: number) => (
|
||||||
|
<div className="post bg-white p-1 my-1" key={i}>
|
||||||
|
<div>
|
||||||
|
<a href="profile.html">
|
||||||
|
<img className="round-img" src={avatarUrl} alt={name} />
|
||||||
|
<h4>{name}</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="my-1">{text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PostComments;
|
||||||
24
src/pages/Post/Display.tsx
Normal file
24
src/pages/Post/Display.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
// Routing
|
||||||
|
import {Link} from 'react-router-dom';
|
||||||
|
import Routes from '../../constants/routes';
|
||||||
|
|
||||||
|
import Post from '../../models/Post';
|
||||||
|
|
||||||
|
const PostDisplay: FC<{post: Post}> = ({
|
||||||
|
post: {name, userID, avatarUrl, text},
|
||||||
|
}) => (
|
||||||
|
<div className="post bg-white p-1 my-1">
|
||||||
|
<div>
|
||||||
|
<Link to={`${Routes.PROFILE}/${userID}`}>
|
||||||
|
<img className="round-img" src={avatarUrl} alt={name} />
|
||||||
|
<h4>{name}</h4>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="my-1">{text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PostDisplay;
|
||||||
28
src/pages/Post/Form.tsx
Normal file
28
src/pages/Post/Form.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostForm: FC<IProps> = ({text, handleChange, handleSubmit}) => (
|
||||||
|
<div className="post-form">
|
||||||
|
<div className="post-form-header bg-primary">
|
||||||
|
<h3>Leave A Comment</h3>
|
||||||
|
</div>
|
||||||
|
<form className="form my-1" onSubmit={handleSubmit}>
|
||||||
|
<textarea
|
||||||
|
name="text"
|
||||||
|
cols={30}
|
||||||
|
rows={5}
|
||||||
|
placeholder="Comment on this post"
|
||||||
|
value={text}
|
||||||
|
onChange={handleChange}
|
||||||
|
></textarea>
|
||||||
|
<input type="submit" className="btn btn-dark my-1" value="Submit" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PostForm;
|
||||||
84
src/pages/Post/index.tsx
Normal file
84
src/pages/Post/index.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
// Routing
|
||||||
|
import {useParams, Link} from 'react-router-dom';
|
||||||
|
import Routes from '../../constants/routes';
|
||||||
|
// Redux
|
||||||
|
import {compose} from '@reduxjs/toolkit';
|
||||||
|
import {connect, useSelector} from 'react-redux';
|
||||||
|
import {firestoreConnect, WithFirestoreProps} from 'react-redux-firebase';
|
||||||
|
import {RootState} from '../../store';
|
||||||
|
import {selectProfile} from '../../store/firebase';
|
||||||
|
import Collections from '../../constants/collections';
|
||||||
|
// Typing
|
||||||
|
import Post from '../../models/Post';
|
||||||
|
import Comment from '../../types/Comment';
|
||||||
|
// Form
|
||||||
|
import useForm from '../../hooks';
|
||||||
|
|
||||||
|
import PostForm from './Form';
|
||||||
|
import PostComments from './Comments';
|
||||||
|
import PostDisplay from './Display';
|
||||||
|
|
||||||
|
interface IProps extends WithFirestoreProps {
|
||||||
|
post: Post;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a Post and the related comments. Shows a form to add a comment.
|
||||||
|
*/
|
||||||
|
const PostComponent: FC<IProps> = ({post, firestore}) => {
|
||||||
|
const {avatarUrl, displayName} = useSelector(selectProfile);
|
||||||
|
const newComment: Comment = {
|
||||||
|
name: displayName ?? 'error',
|
||||||
|
avatarUrl: avatarUrl ?? 'error',
|
||||||
|
text: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const {formData, handleChange, resetForm} = useForm(newComment);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
firestore
|
||||||
|
.update(`${Collections.POSTS}/${post.id}`, {
|
||||||
|
comments: [...post.comments, formData],
|
||||||
|
})
|
||||||
|
.then(() => resetForm())
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="container">
|
||||||
|
<Link to={Routes.POSTS} className="btn btn-light">
|
||||||
|
Back To Posts
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<PostDisplay post={post} />
|
||||||
|
|
||||||
|
<PostForm
|
||||||
|
text={formData.text}
|
||||||
|
handleChange={handleChange}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PostComments comments={post.comments} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container to fetch id params from thr URI and pass it to Profile page
|
||||||
|
*/
|
||||||
|
const PostPage: FC = () => {
|
||||||
|
const {id} = useParams();
|
||||||
|
const Component = compose<FC>(
|
||||||
|
firestoreConnect(() => [`${Collections.POSTS}/${id}`]),
|
||||||
|
connect(({firestore: {data}}: RootState) => ({
|
||||||
|
post: data.posts && {...data.posts[id], id},
|
||||||
|
})),
|
||||||
|
)(PostComponent);
|
||||||
|
|
||||||
|
return <Component />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PostPage;
|
||||||
Loading…
Reference in a new issue