Skip to content

Commit

Permalink
Merge pull request reactplay#382 from reactplay/like-feature
Browse files Browse the repository at this point in the history
Like feature
  • Loading branch information
koustov committed Aug 5, 2022
2 parents c5162ac + e9f3e89 commit 713ea08
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 95 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"git-repo-api": "^0.0.17",
"graphql": "^16.5.0",
"html-to-image": "^1.9.0",
"json-graphql-parser": "^0.0.20",
"json-graphql-parser": "^0.1.7",
"lodash": "^4.17.21",
"node-sass": "^7.0.1",
"react": "^18.0.0",
Expand Down
25 changes: 25 additions & 0 deletions src/common/components/Like/Like.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import "./Like.scss";
import { AiOutlineLike, AiFillLike } from "react-icons/ai";

const Like = ({ onLikeClick, likeObj }) => {
const {liked, number} = likeObj

const likeClickHandler = () => {
if (onLikeClick) return onLikeClick();
};

return (
<button className="action counted -mr-0.5" onClick={likeClickHandler}>
<AiOutlineLike size='24px' className={liked ? "hidden" : "icon"}/>
<AiFillLike size='24px' className={liked ? "icon" : "hidden"}/>
{number > 0 ? (<div className="count-value">{number}</div>):(<div></div>)}

</button>
);
};

Like.defaultProps = {
onLikeClick: null,
};

export default Like;
Empty file.
10 changes: 6 additions & 4 deletions src/common/const/nhost.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const NHOST = {
AUTH_URL: `https://rgkjmwftqtbpayoyolwh.nhost.run/v1/auth/signin/provider/github?redirectTo=${encodeURI(
"http:https://localhost:3000/plays/create"
)}`,
const AUTH_URL = (redirectURL, provider = "github") => {
return `${process.env.REACT_APP_NHOST_BACKEND_URL}/v1/auth/signin/provider/${provider}?redirectTo=${encodeURI(
redirectURL
)}`;
};

export const NHOST = { AUTH_URL }
11 changes: 2 additions & 9 deletions src/common/createplay/CreatePlay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,9 @@ const CreatePlay = () => {
);
}

const refreshToken = searchParams.get("refreshToken");
// setValue(refreshToken);

if (!isAuthenticated) {
if (refreshToken) {
console.log(refreshToken);
} else {
window.location = NHOST.AUTH_URL;
return null;
}
window.location = NHOST.AUTH_URL(`http:https://localhost:${process.env.RAECT_APP_DEV_PORT ?? '3000'}/plays/create`);
return null;
} else {
initializeData();
}
Expand Down
29 changes: 29 additions & 0 deletions src/common/hooks/useLikePlays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { submit } from "common/services/request";
import {
likeIndividualPlay,
unlikeIndividualPlay,
} from "common/services/request/query/like-play";

const useLikePlays = () => {
const likePlay = async (obj) => {
try {
const likeExec = await submit(likeIndividualPlay(obj));
return Promise.resolve(likeExec);
} catch (err) {
return Promise.reject(err);
}
};

const unLikePlay = async (obj) => {
try {
const resp = await submit(unlikeIndividualPlay(obj));
return Promise.resolve(resp);
} catch (err) {
return Promise.reject(err);
}
};

return { likePlay, unLikePlay };
};

export default useLikePlays;
132 changes: 95 additions & 37 deletions src/common/playlists/PlayHeaderActions.jsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,130 @@
import React, { useState } from "react";
import React, { useCallback, useLayoutEffect, useState } from "react";
import { useAuthenticated, useUserId } from "@nhost/react";
import { BsGithub } from "react-icons/bs";
import { IoLogoYoutube } from "react-icons/io";
import { AiOutlineRead } from "react-icons/ai";
import { BiComment } from "react-icons/bi";
import { MdClose } from "react-icons/md";

import Like from "common/components/Like/Like";
import Comment from "common/components/Comment";
import useLikePlays from "common/hooks/useLikePlays";
import { NHOST } from "common/const";
import countByProp from "common/utils/countByProp";

const initialLikeObject = {
liked: false,
number: null,
interation: false,
};

const PlayHeaderActions = ({ play }) => {
console.log(play);
const { play_like } = play;

const userId = useUserId();
const { likePlay, unLikePlay } = useLikePlays();
const [showComment, setShowComment] = useState(false);
const [likeObj, setLikeObj] = useState({ ...initialLikeObject });
const [loading, setLoading] = useState(false);
const isAuthenticated = useAuthenticated();

const constructLikeData = useCallback(
(userId) => {
if (!play_like?.length) return { liked: false, number: 0, interation: false };
const numberOfLikes = countByProp(play_like, "liked", true);
console.log("number of likes are", numberOfLikes);
const ifLiked = play_like.find((obj) => obj.user_id === userId);
return ifLiked
? ifLiked?.liked
? { liked: true, number: numberOfLikes, interation: true }
: { liked: false, number: numberOfLikes, interation: true }
: { liked: false, number: numberOfLikes, interation: false };
},
[play_like]
);

useLayoutEffect(() => {
setLikeObj(constructLikeData(userId));
}, [userId, constructLikeData]);

const handleLogin = (value) => {
return (window.location = NHOST.AUTH_URL(window.location.href, value));
};

const onLikeClick = async () => {
if (!isAuthenticated) return handleLogin("github");
try {
setLoading(true);
const mutationObj = { play_id: play.id, user_id: userId };

if (!likeObj.liked && !likeObj.interation) {
await likePlay(mutationObj);
setLikeObj((pre) => ({
liked: true,
number: pre.number + 1,
interation: true,
}));
} else {
await unLikePlay({ ...mutationObj, liked: !likeObj.liked });
setLikeObj((pre) => ({
liked: !pre.liked,
number: !pre.liked ? pre.number + 1 : pre.number - 1,
interation: true,
}));
}
} catch (err) {
console.log(err);
} finally {
return setLoading(false);
}
};

return (
<>
{
<button className="action badged" onClick={() => setShowComment(true)}>
<BiComment className="icon" size="24px" />
{/*<div className="badge-count">99</div>*/}
<span className="sr-only">Comments</span>
</button>
}
<Like onLikeClick={!loading ? onLikeClick : null} likeObj={likeObj} />
<button className='action badged' onClick={() => setShowComment(true)}>
<BiComment className='icon' size='24px' />
<span className='sr-only'>Comments</span>
</button>

{play.path && (
<a
target="_blank"
rel="noopener noreferrer"
className="action"
target='_blank'
rel='noopener noreferrer'
className='action'
href={`https://github.com/reactplay/react-play/tree/main/src${play.path}`}
>
<BsGithub className="icon" size="24px" />
<span className="sr-only">GitHub</span>
<BsGithub className='icon' size='24px' />
<span className='sr-only'>GitHub</span>
</a>
)}
<a
target="_blank"
rel="noopener noreferrer"
className="action"
target='_blank'
rel='noopener noreferrer'
className='action'
href={
play.blog
? play.blog
: `https://github.com/reactplay/react-play/tree/main/src${play.path}/Readme.md`
}
>
<AiOutlineRead className="icon" size="24px" />
<span className="sr-only">Blog</span>
<AiOutlineRead className='icon' size='24px' />
<span className='sr-only'>Blog</span>
</a>
{play.video && (
<a
target="_blank"
rel="noopener noreferrer"
className="action"
href={play.video}
>
<IoLogoYoutube className="icon" size="24px" />
<span className="sr-only">Video</span>
<a target='_blank' rel='noopener noreferrer' className='action' href={play.video}>
<IoLogoYoutube className='icon' size='24px' />
<span className='sr-only'>Video</span>
</a>
)}
{showComment && (
<div className="play-details-comments">
<div className="comments-header">
<h3 className="header-title">Comments</h3>
<button
className="header-action"
onClick={() => setShowComment(false)}
>
<MdClose size={24} className="icon" />
<div className='play-details-comments'>
<div className='comments-header'>
<h3 className='header-title'>Comments</h3>
<button className='header-action' onClick={() => setShowComment(false)}>
<MdClose size={24} className='icon' />
</button>
</div>
<div className="comments-body">
<div className='comments-body'>
<Comment />
</div>
</div>
Expand Down
44 changes: 26 additions & 18 deletions src/common/playlists/PlayHeaderInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import LevelBadge from "common/components/LevelBadge";

const Author = ({ user, githubUsername }) => {
return (
<div className='header-author flex items-center gap-2'>
<img className="rounded-full" src={user?.avatarUrl} width="25px" height="25px" alt="avatar" />
<div className="header-author flex items-center gap-2">
<img
className="rounded-full"
src={user?.avatarUrl}
width="25px"
height="25px"
alt="avatar"
/>
<a
href={`https://github.com/${githubUsername}`}
target='_blank'
className='play-anchor'
rel='noopener noreferrer'
target="_blank"
className="play-anchor"
rel="noopener noreferrer"
>
<strong>{user?.displayName}</strong>
</a>
Expand All @@ -20,10 +26,10 @@ const Author = ({ user, githubUsername }) => {

const Tags = ({ tags }) => {
return (
<ul className='list-tags'>
<ul className="list-tags">
{tags.map((item, index) => (
<li key={index}>
<span className='play-tag'>{item.tag.name}</span>
<span className="play-tag">{item.tag.name}</span>
</li>
))}
</ul>
Expand All @@ -32,23 +38,25 @@ const Tags = ({ tags }) => {

const PlayHeaderInfo = ({ play }) => {
return (
<div className='header-leftcol'>
<div className='header-leftcol-action'>
<Link to='/plays' className='action'>
<IoMdArrowBack className='icon' size='24px' />
<span className='sr-only'>Back</span>
<div className="header-leftcol overflow-hidden">
<div className="header-leftcol-action">
<Link to="/plays" className="action">
<IoMdArrowBack className="icon" size="24px" />
<span className="sr-only">Back</span>
</Link>
</div>
<div className='header-leftcol-contents'>
<div className='header-primary'>
<h3 className='header-title'>{play.name}</h3>
<div className='header-title-tags'>
<div className="header-leftcol-contents">
<div className="header-primary">
<h3 className="header-title">{play.name}</h3>
<div className="header-title-tags">
<LevelBadge level={play.level.name} />{" "}
{!!play.play_tags.length && <Tags tags={play.play_tags} />}
</div>
</div>
<div className='header-secondary'>
{play.user && <Author user={play.user} githubUsername={play.github} />}
<div className="header-secondary">
{play.user && (
<Author user={play.user} githubUsername={play.github} />
)}
</div>
</div>
</div>
Expand Down
27 changes: 25 additions & 2 deletions src/common/playlists/PlayThumbnail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { BsPlayCircleFill } from "react-icons/bs";
import thumbPlay from "images/thumb-play.png";
import Shimmer from "react-shimmer-effect";
import userImage from "images/user.png";
import Like from "common/components/Like/Like";
import { useUserId, useAuthenticated } from "@nhost/react";
import countByProp from "common/utils/countByProp";

const Author = ({ user }) => {
return (
Expand All @@ -28,7 +31,18 @@ const Author = ({ user }) => {

const PlayThumbnail = ({ play }) => {
const [cover, setCover] = useState(null);
const isAuthenticated = useAuthenticated();
const userId = useUserId();

const likeObject = () => {
const { play_like } = play;
const number = countByProp(play_like, 'liked', true);
if (isAuthenticated) {
const liked = play_like.find((i) => i.user_id === userId)?.liked;
return { liked, number };
}
return { liked: false, number };
};

useEffect(() => {
// Set the cover image
Expand All @@ -54,9 +68,13 @@ const PlayThumbnail = ({ play }) => {
}
}, [play]);


return (
<li key={play.id}>
<Link to={`/plays/${encodeURI(play.github)}/${encodeURI(play.name)}`} state={{ id: play.id }}>
<Link
to={`/plays/${encodeURI(play.github)}/${encodeURI(play.name)}`}
state={{ id: play.id }}
>
<div className='play-thumb'>
<Shimmer>
<img src={cover} alt='' className='play-thumb-img' />
Expand All @@ -65,7 +83,12 @@ const PlayThumbnail = ({ play }) => {
<div className='play-header'>
<div className='play-title'>{play.name}</div>
{play.user && <Author user={play.user} />}
<div className={`language language-${play.language || "js"}`}></div>
<div className='play-actions mt-4'>
<div className="flex flex-row justify-between items-end">
<Like onLikeClick={null} likeObj={likeObject()}/>
<div className={`language language-${play.language || "js"}`}></div>
</div>
</div>
</div>
<div className='play-status'>
<BsPlayCircleFill size='48px' />
Expand Down
Loading

0 comments on commit 713ea08

Please sign in to comment.