mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
show like button to guests
This commit is contained in:
parent
8e40cf9885
commit
7e20975cb1
7 changed files with 149 additions and 112 deletions
38
src/components/node/NodeLikeButton/index.tsx
Normal file
38
src/components/node/NodeLikeButton/index.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { Icon } from '~/components/input/Icon';
|
||||||
|
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
interface NodeLikeButtonProps {
|
||||||
|
active: boolean;
|
||||||
|
count?: number;
|
||||||
|
className?: string;
|
||||||
|
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NodeLikeButton: FC<NodeLikeButtonProps> = ({
|
||||||
|
className,
|
||||||
|
active,
|
||||||
|
count,
|
||||||
|
onClick,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.like, className, {
|
||||||
|
[styles.is_liked]: active,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{active ? (
|
||||||
|
<Icon icon="heart_full" size={24} onClick={onClick} />
|
||||||
|
) : (
|
||||||
|
<Icon icon="heart" size={24} onClick={onClick} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!count && count > 0 && <div className={styles.count}>{count}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { NodeLikeButton };
|
72
src/components/node/NodeLikeButton/styles.module.scss
Normal file
72
src/components/node/NodeLikeButton/styles.module.scss
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
@import 'src/styles/variables';
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: scale(1.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
transform: scale(1.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.like {
|
||||||
|
transition: fill, stroke 0.25s;
|
||||||
|
will-change: transform;
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 32px;
|
||||||
|
fill: currentColor;
|
||||||
|
|
||||||
|
&.is_liked {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: $color_like;
|
||||||
|
}
|
||||||
|
|
||||||
|
.like_count {
|
||||||
|
color: $color_like;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: $color_like;
|
||||||
|
animation: pulse 0.75s infinite;
|
||||||
|
|
||||||
|
.count {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
position: absolute;
|
||||||
|
font: $font_12_bold;
|
||||||
|
left: 16px;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.25s, color 0.25s;
|
||||||
|
background: $content_bg_dark;
|
||||||
|
padding: 0 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
color: $gray_50;
|
||||||
|
pointer-events: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
import React, { memo, VFC } from 'react';
|
import { memo, VFC } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Authorized } from '~/components/containers/Authorized';
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
|
||||||
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
|
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
|
||||||
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
|
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
|
||||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
import { getPrettyDate } from '~/utils/dom';
|
import { getPrettyDate } from '~/utils/dom';
|
||||||
|
|
||||||
|
import { NodeLikeButton } from '../NodeLikeButton';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -77,39 +77,28 @@ const NodeTitle: VFC<IProps> = memo(
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Authorized>
|
<SeparatedMenu className={styles.buttons}>
|
||||||
<SeparatedMenu className={styles.buttons}>
|
{canEdit && (
|
||||||
{canEdit && (
|
<NodeEditMenu
|
||||||
<NodeEditMenu
|
className={styles.button}
|
||||||
className={styles.button}
|
canStar={canStar}
|
||||||
canStar={canStar}
|
isHeroic={isHeroic}
|
||||||
isHeroic={isHeroic}
|
isLocked={isLocked}
|
||||||
isLocked={isLocked}
|
onStar={onStar}
|
||||||
onStar={onStar}
|
onLock={onLock}
|
||||||
onLock={onLock}
|
onEdit={onEdit}
|
||||||
onEdit={onEdit}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{canLike && (
|
{canLike && (
|
||||||
<div
|
<NodeLikeButton
|
||||||
className={classNames(styles.button, styles.like, {
|
active={isLiked}
|
||||||
[styles.is_liked]: isLiked,
|
count={likeCount}
|
||||||
})}
|
onClick={onLike}
|
||||||
>
|
className={styles.button}
|
||||||
{isLiked ? (
|
/>
|
||||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
)}
|
||||||
) : (
|
</SeparatedMenu>
|
||||||
<Icon icon="heart" size={24} onClick={onLike} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!likeCount && likeCount > 0 && (
|
|
||||||
<div className={styles.like_count}>{likeCount}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</SeparatedMenu>
|
|
||||||
</Authorized>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,77 +59,6 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
45% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
60% {
|
|
||||||
transform: scale(1.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
75% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
transform: scale(1.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.like {
|
|
||||||
transition: fill, stroke 0.25s;
|
|
||||||
will-change: transform;
|
|
||||||
position: relative;
|
|
||||||
flex: 0 0 32px;
|
|
||||||
fill: currentColor;
|
|
||||||
|
|
||||||
&.is_liked {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: $color_like;
|
|
||||||
}
|
|
||||||
|
|
||||||
.like_count {
|
|
||||||
color: $color_like;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
fill: $color_like;
|
|
||||||
animation: pulse 0.75s infinite;
|
|
||||||
|
|
||||||
.like_count {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.like_count {
|
|
||||||
position: absolute;
|
|
||||||
font: $font_12_bold;
|
|
||||||
left: 16px;
|
|
||||||
bottom: 0;
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 0.25s, color 0.25s;
|
|
||||||
background: $content_bg_dark;
|
|
||||||
padding: 0 3px;
|
|
||||||
border-radius: 10px;
|
|
||||||
z-index: 3;
|
|
||||||
color: $gray_50;
|
|
||||||
pointer-events: none;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-right: $gap;
|
margin-right: $gap;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { TestDialog } from '~/containers/dialogs/TestDialog';
|
||||||
|
|
||||||
export enum Dialog {
|
export enum Dialog {
|
||||||
Login = 'Login',
|
Login = 'Login',
|
||||||
|
Register = 'Register',
|
||||||
LoginSocialRegister = 'LoginSocialRegister',
|
LoginSocialRegister = 'LoginSocialRegister',
|
||||||
Loading = 'Loading',
|
Loading = 'Loading',
|
||||||
RestoreRequest = 'RestoreRequest',
|
RestoreRequest = 'RestoreRequest',
|
||||||
|
@ -24,6 +25,7 @@ export enum Dialog {
|
||||||
|
|
||||||
export const DIALOG_CONTENT = {
|
export const DIALOG_CONTENT = {
|
||||||
[Dialog.Login]: LoginDialog,
|
[Dialog.Login]: LoginDialog,
|
||||||
|
[Dialog.Register]: LoginDialog, // TODO: make inviting dialog
|
||||||
[Dialog.LoginSocialRegister]: LoginSocialRegisterDialog,
|
[Dialog.LoginSocialRegister]: LoginSocialRegisterDialog,
|
||||||
[Dialog.Loading]: LoadingDialog,
|
[Dialog.Loading]: LoadingDialog,
|
||||||
[Dialog.Test]: TestDialog,
|
[Dialog.Test]: TestDialog,
|
||||||
|
|
|
@ -20,19 +20,19 @@ export interface Props {
|
||||||
|
|
||||||
const NodeCommentForm: FC<Props> = observer(({ saveComment }) => {
|
const NodeCommentForm: FC<Props> = observer(({ saveComment }) => {
|
||||||
const { user, isUser } = useAuth();
|
const { user, isUser } = useAuth();
|
||||||
const showLoginDialog = useShowModal(Dialog.Login);
|
const showRegisterDialog = useShowModal(Dialog.Register);
|
||||||
|
|
||||||
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments);
|
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments);
|
||||||
const onCommentSave = useCallback(
|
const onCommentSave = useCallback(
|
||||||
async (comment: IComment) => {
|
async (comment: IComment) => {
|
||||||
if (!isUser) {
|
if (!isUser) {
|
||||||
showLoginDialog({});
|
showRegisterDialog({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveComment(comment);
|
return saveComment(comment);
|
||||||
},
|
},
|
||||||
[isUser, showLoginDialog, saveComment],
|
[isUser, showRegisterDialog, saveComment],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
|
@ -7,9 +7,12 @@ import { ScrollHelperBottom } from '~/components/common/ScrollHelperBottom';
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from '~/components/containers/Card';
|
||||||
import { Footer } from '~/components/main/Footer';
|
import { Footer } from '~/components/main/Footer';
|
||||||
import { NodeTitle } from '~/components/node/NodeTitle';
|
import { NodeTitle } from '~/components/node/NodeTitle';
|
||||||
|
import { Dialog } from '~/constants/modal';
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from '~/containers/main/Container';
|
||||||
import { SubmitBarRouter } from '~/containers/main/SubmitBarRouter';
|
import { SubmitBarRouter } from '~/containers/main/SubmitBarRouter';
|
||||||
import { NodeBottomBlock } from '~/containers/node/NodeBottomBlock';
|
import { NodeBottomBlock } from '~/containers/node/NodeBottomBlock';
|
||||||
|
import { useAuth } from '~/hooks/auth/useAuth';
|
||||||
|
import { useShowModal } from '~/hooks/modal/useShowModal';
|
||||||
import { useNodeActions } from '~/hooks/node/useNodeActions';
|
import { useNodeActions } from '~/hooks/node/useNodeActions';
|
||||||
import { useNodeBlocks } from '~/hooks/node/useNodeBlocks';
|
import { useNodeBlocks } from '~/hooks/node/useNodeBlocks';
|
||||||
import { useNodeCoverImage } from '~/hooks/node/useNodeCoverImage';
|
import { useNodeCoverImage } from '~/hooks/node/useNodeCoverImage';
|
||||||
|
@ -19,6 +22,8 @@ import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
const NodeLayout = observer(() => {
|
const NodeLayout = observer(() => {
|
||||||
|
const { isUser } = useAuth();
|
||||||
|
const showRegisterDialog = useShowModal(Dialog.Register);
|
||||||
const { node, isLoading, update } = useNodeContext();
|
const { node, isLoading, update } = useNodeContext();
|
||||||
const { head, block } = useNodeBlocks(node, isLoading);
|
const { head, block } = useNodeBlocks(node, isLoading);
|
||||||
const [canEdit, canLike, canStar] = useNodePermissions(node);
|
const [canEdit, canLike, canStar] = useNodePermissions(node);
|
||||||
|
@ -26,6 +31,8 @@ const NodeLayout = observer(() => {
|
||||||
|
|
||||||
useNodeCoverImage(node);
|
useNodeCoverImage(node);
|
||||||
|
|
||||||
|
const onUnauthorizedLike = useCallback(() => showRegisterDialog({}), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
{head}
|
{head}
|
||||||
|
@ -46,9 +53,9 @@ const NodeLayout = observer(() => {
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
createdAt={node.created_at || ''}
|
createdAt={node.created_at || ''}
|
||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
canLike={canLike}
|
canLike={canLike || !isUser}
|
||||||
canStar={canStar}
|
canStar={canStar}
|
||||||
onLike={onLike}
|
onLike={isUser ? onLike : onUnauthorizedLike}
|
||||||
onStar={onStar}
|
onStar={onStar}
|
||||||
onLock={onLock}
|
onLock={onLock}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue