diff --git a/public/index.html b/public/index.html index 5d168a8035335778f48dc80d07b630041aaf239a..88f032bb608e6b18ccb94ccb9f6d6b318f7aad27 100644 --- a/public/index.html +++ b/public/index.html @@ -2,14 +2,21 @@ <html lang="en"> <head> <meta charset="utf-8" /> - <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> + <!-- Favicons --> + <link rel="apple-touch-icon" href="%REACT_APP_STYLEGUIDE_URL%images/favicons/favicon-196x196.png"> + <link rel="icon" type="image/png" href="%REACT_APP_STYLEGUIDE_URL%images/favicons/favicon-196x196.png" sizes="196x196"> + <meta name="application-name" content="CF2021"> + <meta name="msapplication-TileColor" content="#000000"> + <meta name="msapplication-TileImage" content="%REACT_APP_STYLEGUIDE_URL%images/favicons/mstile-144x144.png"> + <meta name="msapplication-square70x70logo" content="%REACT_APP_STYLEGUIDE_URL%images/favicons/mstile-70x70.png"> + <meta name="msapplication-square150x150logo" content="%REACT_APP_STYLEGUIDE_URL%images/favicons/mstile-150x150.png"> + <meta name="msapplication-wide310x150logo" content="%REACT_APP_STYLEGUIDE_URL%images/favicons/mstile-310x150.png"> + <meta name="msapplication-square310x310logo" content="%REACT_APP_STYLEGUIDE_URL%images/favicons/mstile-310x310.png"> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> - <meta - name="description" - content="Web site created using create-react-app" - /> - <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> + <meta property="og:url" content="https://cf2021.pirati.cz/" /> + <meta property="og:type" content="website" /> + <meta property="og:title" content="CF 2021" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ @@ -24,8 +31,8 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - <link rel="stylesheet" href="%REACT_APP_STYLEGUIDE_URL%/css/styles.css" /> - <title>React App</title> + <link rel="stylesheet" href="%REACT_APP_STYLEGUIDE_URL%css/styles.css" /> + <title>CF 2021 | Pirátská strana</title> </head> <body class="h-screen"> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/src/App.jsx b/src/App.jsx index f608dce1985a689a28666a3b01f7fc6b5f7f76d4..d97211377bc650726b4fdbea9c41e464d26e224c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -41,7 +41,7 @@ const LoadingComponent = ( <div className="h-screen w-screen flex justify-center items-center"> <div className="text-center"> <img - src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-black.svg`} + src={`${process.env.REACT_APP_STYLEGUIDE_URL}images/logo-round-black.svg`} className="w-20 mb-2 inline-block" alt="Pirátská strana" /> diff --git a/src/actions/posts.js b/src/actions/posts.js index f196e0cf0af757b8c6307c49f2e94c7302144120..6d15bd7206688fa983cc14f2c46d633b3bb6c016 100644 --- a/src/actions/posts.js +++ b/src/actions/posts.js @@ -34,6 +34,37 @@ export const like = createAsyncAction( } ); +export const removeLike = createAsyncAction( + /** + * @param {CF2021.Post} post + */ + async (post) => { + return successResult(post); + }, + { + shortCircuitHook: ({ args }) => { + if (args.ranking.myVote !== "like") { + return errorResult(); + } + + return false; + }, + postActionHook: ({ result }) => { + if (!result.error) { + PostStore.update((state) => { + state.items[result.payload.id].ranking.likes -= 1; + state.items[result.payload.id].ranking.score -= 1; + state.items[result.payload.id].ranking.myVote = "none"; + + if (state.filters.sort === "byScore") { + updateWindowPosts(state); + } + }); + } + }, + } +); + export const dislike = createAsyncAction( /** * @param {CF2021.Post} post @@ -65,6 +96,37 @@ export const dislike = createAsyncAction( } ); +export const removeDislike = createAsyncAction( + /** + * @param {CF2021.Post} post + */ + async (post) => { + return successResult(post); + }, + { + shortCircuitHook: ({ args }) => { + if (args.ranking.myVote !== "dislike") { + return errorResult(); + } + + return false; + }, + postActionHook: ({ result }) => { + if (!result.error) { + PostStore.update((state) => { + state.items[result.payload.id].ranking.dislikes -= 1; + state.items[result.payload.id].ranking.score += 1; + state.items[result.payload.id].ranking.myVote = "none"; + + if (state.filters.sort === "byScore") { + updateWindowPosts(state); + } + }); + } + }, + } +); + /** * Add new discussion post. */ diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index 8034cf38c36def6b22a82ea3fbd7b4442f1cde82..f911e023d4cd1edc3d34140808afa190bd1136c5 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -6,7 +6,7 @@ const Footer = () => { <div className="footer__main py-4 lg:py-16 container container--default"> <section className="footer__brand"> <img - src="https://www.va-fighters.com/pirati/krajska-sablona/dist/assets/img/logo.svg" + src={`${process.env.REACT_APP_STYLEGUIDE_URL}images/logo-full-white.svg`} alt="" className="w-32 md:w-40 pb-6" /> diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 1a908b253eefb0329e987cf8ecd23ad73dbe431e..f7557e3a03273738aabf940fdfcd5de0758293f8 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -24,7 +24,7 @@ const Navbar = () => { <div className="navbar__brand my-4 flex items-center lg:pr-8 lg:my-0"> <NavLink to="/"> <img - src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-white.svg`} + src={`${process.env.REACT_APP_STYLEGUIDE_URL}images/logo-round-white.svg`} className="w-8" alt="Pirátská strana" /> diff --git a/src/components/Thumbs.jsx b/src/components/Thumbs.jsx index c267eeee91f4b075f3e59c0401d90bedf14480c7..33f81d81b8aab7825c99afb62266cca59ebbac5c 100644 --- a/src/components/Thumbs.jsx +++ b/src/components/Thumbs.jsx @@ -1,15 +1,16 @@ import React from "react"; import classNames from "classnames"; -const Thumbs = ({ likes, dislikes, onLike, onDislike, canThumb }) => { +const Thumbs = ({ likes, dislikes, onLike, onDislike, myVote }) => { return ( <div> <div className="space-x-2 text-sm flex items-center"> <button className={classNames("text-blue-300 flex items-center space-x-1", { - "cursor-not-allowed": !canThumb, + "cursor-pointer": myVote === "none" || myVote === "like", + "cursor-default": myVote === "dislike", })} - disabled={!canThumb} + disabled={myVote === "dislike"} onClick={onLike} > <span className="font-bold">{likes}</span> @@ -17,9 +18,10 @@ const Thumbs = ({ likes, dislikes, onLike, onDislike, canThumb }) => { </button> <button className={classNames("text-red-600 flex items-center space-x-1", { - "cursor-not-allowed": !canThumb, + "cursor-pointer": myVote === "none" || myVote === "dislike", + "cursor-default": myVote === "like", })} - disabled={!canThumb} + disabled={myVote === "like"} onClick={onDislike} > <i className="ico--thumbs-down transform -scale-x-1"></i> diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx index 3a76326f9465211bd77498b06a4226a241ac9f4a..88c6f5377137c794f5aff4b6b5cf980bc8405527 100644 --- a/src/components/posts/Post.jsx +++ b/src/components/posts/Post.jsx @@ -153,7 +153,7 @@ const Post = ({ dislikes={ranking.dislikes} onLike={onLike} onDislike={onDislike} - canThumb={ranking.myVote === "none"} + myVote={ranking.myVote} /> {displayActions && ( <DropdownMenu right> diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx index ca4187efee81448e8d569de63f8bd01cc6c00222..f1e7fea2170c5c1a2cee8e985581e92ad51596aa 100644 --- a/src/containers/PostsContainer.jsx +++ b/src/containers/PostsContainer.jsx @@ -1,7 +1,7 @@ import React from "react"; import pick from "lodash/pick"; -import { dislike, like } from "actions/posts"; +import { dislike, like, removeDislike, removeLike } from "actions/posts"; import PostList from "components/posts/PostList"; import { PostStore } from "stores"; @@ -16,6 +16,32 @@ const PostsContainer = ({ className }) => { // const onLike = (post) => like.run(); // const onDislike = (post) => console.log("dislike", post); + /** + * + * @param {CF2021.Post} post + */ + const onLike = (post) => { + if (post.ranking.myVote === "none") { + return like.run(post); + } + if (post.ranking.myVote === "like") { + return removeLike.run(post); + } + }; + + /** + * + * @param {CF2021.Post} post + */ + const onDislike = (post) => { + if (post.ranking.myVote === "none") { + return dislike.run(post); + } + if (post.ranking.myVote === "dislike") { + return removeDislike.run(post); + } + }; + const sliceStart = (window.page - 1) * window.perPage; const sliceEnd = window.page * window.perPage; @@ -40,8 +66,8 @@ const PostsContainer = ({ className }) => { items={window.items .slice(sliceStart, sliceEnd) .map((postId) => items[postId])} - onLike={like.run} - onDislike={dislike.run} + onLike={onLike} + onDislike={onDislike} className={className} dimArchived={!showingArchivedOnly} displayActions={true}