diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 17f2dbc..11e2202 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,6 +47,6 @@ jobs: - name: Deploy to Firebase uses: w9jds/firebase-action@master with: - args: deploy + args: deploy --only hosting env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} diff --git a/src/constants/collections.ts b/src/constants/collections.ts new file mode 100644 index 0000000..59bc389 --- /dev/null +++ b/src/constants/collections.ts @@ -0,0 +1,9 @@ +/** + * Register all firestore collections + */ +enum Collections { + USERS = 'users', + POSTS = 'posts', +} + +export default Collections; diff --git a/src/models/Dev.ts b/src/models/Dev.ts index 792b170..7d3567d 100644 --- a/src/models/Dev.ts +++ b/src/models/Dev.ts @@ -6,8 +6,8 @@ import Repo from '../types/Repo'; /** Shorter dev interface */ export interface DevSummary { id?: string; - displayName: string; - avatarUrl: string; + displayName?: string; + avatarUrl?: string; description: string; status: string; company: string; @@ -40,18 +40,15 @@ export const getDescription = (status?: string, company?: string): string => { * new Dev() returns a placeholder used when initializing a new profile. * id is not specified to not overwrite document uid. */ -export class Dev implements IDev { - id?: string; - isActive = true; - displayName = ''; - status = 'Developer'; - company = ''; - avatarUrl = ''; - description = ''; - location = ''; - skills: string[] = []; - github: string = ''; - links: Links = { +export const blankDev: IDev = { + isActive: true, + status: 'Developer', + company: '', + description: '', + location: '', + skills: [], + github: '', + links: { website: '', instagram: '', facebook: '', @@ -59,12 +56,12 @@ export class Dev implements IDev { twitter: '', github: '', youtube: '', - }; - bio = ''; - experiences: Experience[] = []; - educations: Education[] = []; - repos: Repo[] = []; -} + }, + bio: '', + experiences: [], + educations: [], + repos: [], +}; /** * sample Dev for development and tests diff --git a/src/models/Post.ts b/src/models/Post.ts index ffa7d71..5b64ba8 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -5,10 +5,10 @@ import Comment from '../types/Comment'; */ interface Post { id: string; - userID: string; - name: string; + userID?: string; + name?: string; text: string; - picture: string; + avatarUrl?: string; likes: string[]; comments: Comment[]; // date: Date; @@ -20,21 +20,21 @@ interface Post { export const dummyPost: Post = { id: '12', userID: '42', - picture: + avatarUrl: 'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200', name: 'John Doe', text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint possimus corporis sunt necessitatibus! Minus nesciunt soluta suscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?', comments: [ { - picture: + avatarUrl: 'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200', name: 'John Doe', text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sintpossimus corporis sunt necessitatibus! Minus nesciunt solutasuscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?', }, { - picture: + avatarUrl: 'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200', name: 'Ruidy Nemo', text: diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx index 8ddf23b..58ec6e4 100644 --- a/src/pages/Post.tsx +++ b/src/pages/Post.tsx @@ -1,59 +1,113 @@ import React, {FC} from 'react'; -import Post, {dummyPost as post} from '../models/Post'; +// Routing +import {useParams, Link} from 'react-router-dom'; +import Routes from '../constants/routes'; +// Redux +import {compose} from '@reduxjs/toolkit'; +import {connect} from 'react-redux'; +import {firestoreConnect, WithFirestoreProps} from 'react-redux-firebase'; +import {RootState} from '../store'; +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 = () => ( -
- - Back To Posts - +const PostPage: FC = ({post, firestore}) => { + const newComment: Comment = { + name: post.name ?? 'error', + avatarUrl: post.avatarUrl ?? 'error', + text: '', + }; -
-
- - {post.name} -

{post.name}

-
-
-
-

{post.text}

-
-
+ const {formData, handleChange, resetForm} = useForm(newComment); -
-
-

Leave A Comment

-
-
- - -
-
+ const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); -
- {post.comments.map((c: Comment, i: number) => ( -
- -
-

{c.text}

-
+ firestore + .update(`${Collections.POSTS}/${post.id}`, { + comments: [...post.comments, formData], + }) + .then(() => resetForm()) + .catch(err => console.error(err)); + }; + + return ( +
+ + Back To Posts + + +
+
+ + {post.name} +

{post.name}

+
- ))} -
-
-); +
+

{post.text}

+
+
-export default PostPage; +
+
+

Leave A Comment

+
+
+ + +
+
+ +
+ {post.comments?.map((c: Comment, i: number) => ( +
+ +
+

{c.text}

+
+
+ ))} +
+
+ ); +}; + +/** + * Container to fetch id params from thr URI and pass it to Profile page + */ +const PostPageContainer: FC = () => { + const {id} = useParams(); + const Component = compose( + firestoreConnect(() => [`${Collections.POSTS}/${id}`]), + connect(({firestore: {data}}: RootState) => ({ + post: data.posts && {...data.posts[id], id}, + })), + )(PostPage); + + return ; +}; + +export default PostPageContainer; diff --git a/src/pages/Posts.tsx b/src/pages/Posts.tsx index e26d47f..4b4557e 100644 --- a/src/pages/Posts.tsx +++ b/src/pages/Posts.tsx @@ -1,14 +1,65 @@ -import React, {FC} from 'react'; -import Post, {dummyPost as post} from '../models/Post'; -import Header from '../components/Header'; +import React, {FC, useState} from 'react'; +// 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'; +// Routing +import {Link} from 'react-router-dom'; +import Routes from '../constants/routes'; +// Style import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faThumbsUp, faThumbsDown} from '@fortawesome/free-solid-svg-icons'; +import {faThumbsUp} from '@fortawesome/free-solid-svg-icons'; +import Header from '../components/Header'; +// Typing +import Post, {dummyPost as post} from '../models/Post'; +import Collections from '../constants/collections'; +interface IProps extends WithFirestoreProps { + posts: Post[]; +} /** * A Dev's Posts list */ -const Posts: FC = () => { - const posts: Post[] = [post, post]; +const Posts: FC = ({posts, firestore, firebase}) => { + const [text, setText] = useState(''); + const {avatarUrl, displayName} = useSelector(selectProfile); + const id = firebase.auth().currentUser?.uid; + + const newPost = { + userID: id, + name: displayName, + text, + avatarUrl, + likes: [] as string[], + comments: [], + }; + + const handleChange = (e: React.ChangeEvent) => + setText(e.target.value); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + firestore + .add(Collections.POSTS, newPost) + .then(() => setText('')) + .catch(err => console.error(err)); + }; + // const removeLike = (e: React.MouseEvent) => + // new Error('Not implemented yet.'); + const addLike = (postID: string) => ( + e: React.MouseEvent, + ) => { + e.preventDefault(); + const post = posts.find(p => p.id === postID); + + if (post) { + firestore + .update(`${Collections.POSTS}/${post.id}`, {likes: [...post.likes, id]}) + .catch(err => console.error(err)); + } + }; return (
@@ -18,41 +69,55 @@ const Posts: FC = () => {

Say Something

-
- + +