-
Notifications
You must be signed in to change notification settings - Fork 290
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
base: v1
Are you sure you want to change the base?
Changes from all commits
9b4814f
d69d549
5dea9d3
aacb10c
c79e6b2
0d7a50d
55911c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 }) { | ||
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 = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove discard in favor of passing in |
||
makeEdit(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not abstract the logic too much, change to: |
||
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> | ||
</> | ||
); | ||
|
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change to |
||
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this logic should be inside <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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 && ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, what if both |
||
<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> | ||
</> | ||
); | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try avoid stylistic changes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
)} | ||
</div> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try avoid stylistic changes |
||
} |
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}`; |
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(); | ||
|
||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
There was a problem hiding this comment.
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 })