Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Feature Edit Post #118

Open
wants to merge 7 commits into
base: v1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 66 additions & 29 deletions components/post/editor.jsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,88 @@
import React, { useState } from 'react';
import { useCurrentUser } from '@/hooks/index';

export default function PostEditor() {
export default function PostEditor({ edit, makeEdit, text, Id }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add setEdit as prop: export default function PostEditor({ edit, makeEdit, setEdit, text, Id })

const [user] = useCurrentUser();

const [msg, setMsg] = useState(null);

if (!user) {
return (
<div style={{ color: '#555', textAlign: 'center' }}>
Please sign in to post
</div>
);
return <div style={{ color: '#555', textAlign: 'center' }}>Please sign in to post</div>;
}

const discard = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove discard in favor of passing in setEdit as prop

makeEdit();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is not clear what it does.

};

async function hanldeSubmit(e) {
e.preventDefault();
const body = {
content: e.currentTarget.content.value,
};
if (!e.currentTarget.content.value) return;
e.currentTarget.content.value = '';
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.ok) {
setMsg('Posted!');
setTimeout(() => setMsg(null), 5000);
if (edit === true) {
const body = {
content: e.currentTarget.content.value,
postId: Id,
};
// if (!e.currentTarget.content.value) return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code should not be pushed.

const res = await fetch('/api/posts', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.ok) {
makeEdit('Edited!', body.content);
} else {
makeEdit(res.text(), text);
}
} else {
const body = {
content: e.currentTarget.content.value,
};
if (!e.currentTarget.content.value) return;
e.currentTarget.content.value = '';
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.ok) {
setMsg('Posted!');
setTimeout(() => setMsg(null), 5000);
}
}
}

return (
<>
<p style={{ color: '#0070f3', textAlign: 'center' }}>
{msg}
</p>
<form onSubmit={hanldeSubmit} style={{ flexDirection: 'row' }} autoComplete="off">
<p style={{ color: '#0070f3', textAlign: 'center' }}>{msg}</p>
<form
onSubmit={hanldeSubmit}
onReset={discard}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not abstract the logic too much, change to: onReset={() => setEdit(false)}

style={{ flexDirection: 'row' }}
autoComplete="off">
<label htmlFor="name">
<input
name="content"
type="text"
placeholder="Say something, I'm giving up on you..."
/>
{edit === true ? (
<input name="content" type="text" placeholder={text} defaultValue={text} />
) : (
<input
name="content"
type="text"
placeholder="Say something, I'm giving up on you..."
/>
)}
</label>
<button type="submit" style={{ marginLeft: '0.5rem' }}>Post</button>
<button type="submit" style={{ marginLeft: '0.5rem' }}>
{edit === true ? 'Update' : 'Post'}
</button>
{edit === true ? (
<button
type="reset"
style={{
marginLeft: '0.5rem',
backgroundColor: 'white',
color: 'black',
}}>
Discard
</button>
) : null}
</form>
</>
);
Expand Down
140 changes: 94 additions & 46 deletions components/post/posts.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,94 @@
import React from 'react';
import React, { useState } from 'react';
import { useSWRInfinite } from 'swr';
import Link from 'next/link';
import { useUser } from '@/hooks/index';
import fetcher from '@/lib/fetch';
import { useCurrentUser } from '@/hooks/index';
import { defaultProfilePicture } from '@/lib/default';
import PostEditor from './editor';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to import PostEditor from '@/components/post/editor'; per jsconfig


function Post({ post }) {
const [edit, setEdit] = useState(false);
const [content, setContent] = useState(post.content);
const [msg, setMsg] = useState('');
const user = useUser(post.creatorId);
const [currUser] = useCurrentUser();
const makeEdit = (msg, text) => {
setEdit(false);
setMsg(msg);
setTimeout(() => setMsg(null), 1500);
if (text) setContent(text);
};
Comment on lines +11 to +21
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic should be inside PostEditor. Howabout passing an id into it. something like:

<PostEditor editingId="foo" />

return (
<>
<style jsx>
{`
div {
box-shadow: 0 5px 10px rgba(0,0,0,0.12);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
padding: 1.5rem;
margin-bottom: 0.5rem;
transition: box-shadow 0.2s ease 0s;
}
div:hover {
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
}
small {
color: #777;
}
`}
</style>
<div>
<div style={{ position: 'relative' }}>
<p style={{ color: '#0070f3', textAlign: 'center' }}>{msg}</p>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be inside PostEditor similar to the other comment

{user && (
<Link href={`/user/${user._id}`}>
<a style={{ display: 'inline-flex', alignItems: 'center' }}>
<img width="27" height="27" style={{ borderRadius: '50%', objectFit: 'cover', marginRight: '0.3rem' }} src={user.profilePicture || defaultProfilePicture(user._id)} alt={user.name} />
<img
width="27"
height="27"
style={{
borderRadius: '50%',
objectFit: 'cover',
marginRight: '0.3rem',
}}
src={user.profilePicture || defaultProfilePicture(user._id)}
alt={user.name}
/>
<b>{user.name}</b>
</a>
</Link>
)}
<p>
{post.content}
</p>
{edit === true ? (
<PostEditor edit={edit} makeEdit={makeEdit} text={content} Id={post._id} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add setEdit={setEdit} to props

) : (
<p>{content}</p>
)}
<small>{new Date(post.createdAt).toLocaleString()}</small>
{user?._id === currUser?._id && (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, what if both user and currUser is undefined?

<button
className="edit"
style={{
backgroundColor: 'white',
boxShadow: 'none',
position: 'absolute',
top: '10px',
right: '7px',
}}
onClick={() => {
setEdit(!edit);
}}>
<svg
aria-hidden="true"
viewBox="0 0 16 16"
version="1.1"
height="16"
width="16"
className="octicon octicon-pencil">
<path
fillRule="evenodd"
d="M11.013 1.427a1.75 1.75 0 012.474 0l1.086 1.086a1.75 1.75 0 010 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 01-.927-.928l.929-3.25a1.75 1.75 0 01.445-.758l8.61-8.61zm1.414 1.06a.25.25 0 00-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 000-.354l-1.086-1.086zM11.189 6.25L9.75 4.81l-6.286 6.287a.25.25 0 00-.064.108l-.558 1.953 1.953-.558a.249.249 0 00.108-.064l6.286-6.286z"></path>
</svg>
</button>
)}
</div>
</>
);
Expand All @@ -46,38 +97,34 @@ function Post({ post }) {
const PAGE_SIZE = 10;

export function usePostPages({ creatorId } = {}) {
return useSWRInfinite((index, previousPageData) => {
// reached the end
if (previousPageData && previousPageData.posts.length === 0) return null;
return useSWRInfinite(
(index, previousPageData) => {
// reached the end
if (previousPageData && previousPageData.posts.length === 0) return null;

// first page, previousPageData is null
if (index === 0) {
return `/api/posts?limit=${PAGE_SIZE}${
creatorId ? `&by=${creatorId}` : ''
}`;
}
// first page, previousPageData is null
if (index === 0) {
return `/api/posts?limit=${PAGE_SIZE}${creatorId ? `&by=${creatorId}` : ''}`;
}

// using oldest posts createdAt date as cursor
// We want to fetch posts which has a datethat is
// before (hence the .getTime() - 1) the last post's createdAt
const from = new Date(
new Date(
previousPageData.posts[previousPageData.posts.length - 1].createdAt,
).getTime() - 1,
).toJSON();
// using oldest posts createdAt date as cursor
// We want to fetch posts which has a datethat is
// before (hence the .getTime() - 1) the last post's createdAt
const from = new Date(
new Date(previousPageData.posts[previousPageData.posts.length - 1].createdAt).getTime() - 1
).toJSON();

return `/api/posts?from=${from}&limit=${PAGE_SIZE}${
creatorId ? `&by=${creatorId}` : ''
}`;
}, fetcher, {
refreshInterval: 10000, // Refresh every 10 seconds
});
return `/api/posts?from=${from}&limit=${PAGE_SIZE}${creatorId ? `&by=${creatorId}` : ''}`;
},
fetcher,
{
refreshInterval: 10000, // Refresh every 10 seconds
}
);
}
Comment on lines -49 to 124
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of these changes are stylistic changes (maybe because of some formatting tool on your local), which make it difficult to review.

Is it possible to undo these?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will take care of it.


export default function Posts({ creatorId }) {
const {
data, error, size, setSize,
} = usePostPages({ creatorId });
const { data, error, size, setSize } = usePostPages({ creatorId });

const posts = data ? data.reduce((acc, val) => [...acc, ...val.posts], []) : [];
const isLoadingInitialData = !data && !error;
Expand All @@ -87,19 +134,20 @@ export default function Posts({ creatorId }) {

return (
<div>
{posts.map((post) => <Post key={post._id} post={post} />)}
{posts.map((post) => (
<Post key={post._id} post={post} />
))}
Comment on lines +137 to +139
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try avoid stylistic changes

{!isReachingEnd && (
<button
type="button"
style={{
background: 'transparent',
color: '#000',
}}
onClick={() => setSize(size + 1)}
disabled={isReachingEnd || isLoadingMore}
>
{isLoadingMore ? '. . .' : 'load more'}
</button>
<button
type="button"
style={{
background: 'transparent',
color: '#000',
}}
onClick={() => setSize(size + 1)}
disabled={isReachingEnd || isLoadingMore}>
{isLoadingMore ? '. . .' : 'load more'}
</button>
Comment on lines +141 to +150
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try avoid stylistic changes

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

)}
</div>
);
Expand Down
30 changes: 24 additions & 6 deletions db/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,29 @@ export async function getPosts(db, from = new Date(), by, limit) {
.toArray();
}

export async function editPost(db, { content, postId }) {
return db
.collection('posts')
.findOneAndUpdate(
{ _id: postId },
{
$set: {
content,
},
},
{ returnOriginal: false },
)
.then(({ value }) => value);
}

export async function insertPost(db, { content, creatorId }) {
return db.collection('posts').insertOne({
_id: nanoid(12),
content,
creatorId,
createdAt: new Date(),
}).then(({ ops }) => ops[0]);
return db
.collection('posts')
.insertOne({
_id: nanoid(12),
content,
creatorId,
createdAt: new Date(),
})
.then(({ ops }) => ops[0]);
Comment on lines -21 to +44
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try avoid stylistic changes

}
2 changes: 1 addition & 1 deletion lib/default.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// https://github.com/tobiaslins/avatar
export const defaultProfilePicture = (id) => `https://avatar.tobi.sh/${id}`
export const defaultProfilePicture = (id) => `https://avatar.tobi.sh/${id}`;
4 changes: 2 additions & 2 deletions lib/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sgMail.setApiKey(process.env.SENDGRID_API_KEY);
export async function sendMail(msg) {
try {
await sgMail.send(msg);
} catch(e) {
throw new Error(`Could not send email: ${e.message}`)
} catch (e) {
throw new Error(`Could not send email: ${e.message}`);
}
}
19 changes: 18 additions & 1 deletion pages/api/posts/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import nc from 'next-connect';
import { all } from '@/middlewares/index';
import { getPosts, insertPost } from '@/db/index';
import { editPost, getPosts, insertPost } from '@/db/index';

const handler = nc();

Expand All @@ -20,9 +20,26 @@ handler.get(async (req, res) => {
// This is safe to cache because from defines
// a concrete range of posts
res.setHeader('cache-control', `public, max-age=${maxAge}`);
return null;
}

res.send({ posts });
return posts;
});

handler.patch(async (req, res) => {
if (!req.user) {
return res.status(401).send('unauthenticated');
}

if (!req.body.content) return res.status(400).send('You must write something');

const post = await editPost(req.db, {
content: req.body.content,
postId: req.body.postId,
});
Comment on lines +37 to +40
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to authenticate this. Cannot let a user edit other people's posts


return res.json({ post });
});

handler.post(async (req, res) => {
Expand Down
Loading