diff --git a/src/redux/comments/index.ts b/src/redux/comments/index.ts index 32605852..877f5957 100644 --- a/src/redux/comments/index.ts +++ b/src/redux/comments/index.ts @@ -1,6 +1,8 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { AnyPublication } from '@lens-protocol/api-bindings'; +type PendingComment = AnyPublication & { uri: string }; + export type CommentsReducerState = { refetchTriggerByPublication: { [publicationId: string]: number; @@ -8,7 +10,7 @@ export type CommentsReducerState = { hiddenComments: AnyPublication[]; counterLikes: { [publicationId: string]: number }; comments: { [publicationId: string]: AnyPublication[] }; - pendingComments: { [publicationId: string]: AnyPublication[] }; + pendingComments: { [publicationId: string]: PendingComment[] }; }; const initialState: CommentsReducerState = { @@ -49,14 +51,8 @@ const commentsSlice = createSlice({ state.counterLikes[publicationId] -= 1; } }, - addComment: (state, action: PayloadAction<{ publicationId: string; comment: AnyPublication }>) => { - const { publicationId, comment } = action.payload; - if (!state.comments[publicationId]) { - state.comments[publicationId] = []; - } - state.comments[publicationId].push(comment); - }, - addPendingComment: (state, action: PayloadAction<{ publicationId: string; comment: AnyPublication }>) => { + + addPendingComment: (state, action: PayloadAction<{ publicationId: string; comment: PendingComment }>) => { const { publicationId, comment } = action.payload; if (!state.pendingComments[publicationId]) { state.pendingComments[publicationId] = []; @@ -64,10 +60,19 @@ const commentsSlice = createSlice({ // Prepend new comment to the beginning of the list state.pendingComments[publicationId] = [comment, ...state.pendingComments[publicationId]]; }, - updateCommentStatus: (state, action: PayloadAction<{ publicationId: string; commentId: string; status: string }>) => { - const { publicationId, commentId, status } = action.payload; + removePendingComment: (state, action: PayloadAction<{ publicationId: string; commentId: string;}>) => { + console.log('publicationID', action.payload.publicationId); + console.log('commentID', action.payload.commentId); + + const { publicationId, commentId } = action.payload; const comment = state.comments[publicationId]?.find(comment => comment.id === commentId); - state.comments[publicationId] = state.comments[publicationId].filter(comment => comment.id !== commentId); + + console.log('comment', comment); + + // Delete the comment from the pending list + state.pendingComments[publicationId] = state.pendingComments[publicationId].filter(comment => comment.id !== commentId); + + // Search by uuid and delete from state }, }, @@ -79,9 +84,8 @@ export const { setCounterLikes, incrementCounterLikes, decrementCounterLikes, - addComment, addPendingComment, - updateCommentStatus, + removePendingComment, } = commentsSlice.actions; export default commentsSlice.reducer; diff --git a/src/sections/publication/NeonPaperContainer.tsx b/src/sections/publication/NeonPaperContainer.tsx new file mode 100644 index 00000000..9c4a304b --- /dev/null +++ b/src/sections/publication/NeonPaperContainer.tsx @@ -0,0 +1,103 @@ +import {FC, ReactNode} from "react"; + +import { Paper, styled } from '@mui/material'; +import {PaperProps} from "@mui/material/Paper"; +import {SxProps, Theme} from "@mui/material/styles"; +import {COLORS} from "@src/layouts/config-layout.ts"; + +interface NeonPaperProps { + children: ReactNode; + colors?: string[]; + animationSpeed?: string; + sx?: SxProps; +} + +const NeonPaperContainer = styled(Paper, { + shouldForwardProp: (prop) => prop !== 'colors' && prop !== 'animationSpeed', +})(({ colors, animationSpeed }) => ({ + '--gradient-pos-x': '50%', + '--gradient-pos-y': '50%', + '--border-radius': '10px', + '--border-gap': '3px', + width: '100%', + position: 'relative', + padding: 'var(--border-gap)', + borderRadius: 'var(--border-radius)', + cursor: 'pointer', + overflow: 'hidden', + '&:hover .gradient': { + animation: `rotate ${animationSpeed || '1s'} linear infinite`, + }, + '& .content': { + position: 'relative', + padding: '1rem', + color: '#ffffff', + borderRadius: 'var(--border-radius)', + backgroundColor: `${COLORS.GRAY_LIGHT}`, + zIndex: 1, + width: '100%', + }, + '& .border, & .neon': { + position: 'absolute', + width: '100%', + height: '100%', + top: 0, + left: 0, + borderRadius: 'var(--border-radius)', + overflow: 'hidden', + }, + '& .neon': { + filter: 'blur(10px)', + opacity: 0.5, + }, + '& .gradient': { + position: 'absolute', + inset: '-200px', + background: `conic-gradient( + from 0deg at var(--gradient-pos-x) var(--gradient-pos-y), +${colors?.join(', ') || '#1e87ff, #5c13c4, #ff0033, #ffda00, #64bc26, #1e87ff'} +)`, + borderRadius: 'var(--border-radius)', + animation: `rotate ${animationSpeed || '1s'} linear infinite`, + }, + '@keyframes rotate': { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, + }, +})); + +const NeonPaper: FC = ({ children, colors, animationSpeed }) => { + const handleMouseMove = (e) => { + const rect = e.target.getBoundingClientRect(); + const x = ((e.clientX - rect.left) / rect.width) * 100; + const y = ((e.clientY - rect.top) / rect.height) * 100; + + e.target.style.setProperty('--gradient-pos-x', `${x}%`); + e.target.style.setProperty('--gradient-pos-y', `${y}%`); + }; + + const handleMouseOut = (e) => { + e.target.style.setProperty('--gradient-pos-x', '50%'); + e.target.style.setProperty('--gradient-pos-y', '50%'); + }; + + return ( + +
+
+
+
+
+
+
{children}
+
+ ); +}; + +export default NeonPaper; diff --git a/src/sections/publication/publication-comment-item.tsx b/src/sections/publication/publication-comment-item.tsx index 6e2f3d3f..dfaa572c 100644 --- a/src/sections/publication/publication-comment-item.tsx +++ b/src/sections/publication/publication-comment-item.tsx @@ -42,6 +42,7 @@ import { decrementCounterLikes, setCounterLikes, } from '@redux/comments'; +import NeonPaperContainer from "@src/sections/publication/NeonPaperContainer.tsx"; // Components Lazy const LazyPopover = lazy(() => import('@mui/material/Popover')); @@ -60,6 +61,8 @@ type Props = { }; export default function PublicationCommentItem({ comment, hasReply, canReply }: Props) { + const isPendingComment = comment?.uri + const [openConfirmModal, setOpenConfirmModal] = useState(false); const [anchorEl, setAnchorEl] = useState(null); const openMenu = Boolean(anchorEl); @@ -161,7 +164,7 @@ export default function PublicationCommentItem({ comment, hasReply, canReply }: }} /> - {sessionData?.authenticated && comment?.by?.id === sessionData?.profile?.id && ( + {sessionData?.authenticated && comment?.by?.id === sessionData?.profile?.id && !isPendingComment && ( - {canReply && ( - - )} - + <> + {showComments ? ( + + ) : ( + + )} + + {comment?.stats?.comments} + + + + )} + + + {showComments && ( <> diff --git a/src/sections/publication/publication-details-comment-form.tsx b/src/sections/publication/publication-details-comment-form.tsx index ae47e5ce..bff6779f 100644 --- a/src/sections/publication/publication-details-comment-form.tsx +++ b/src/sections/publication/publication-details-comment-form.tsx @@ -22,7 +22,12 @@ import { ReadResult } from '@lens-protocol/react/dist/declarations/src/helpers/r import { uploadMetadataToIPFS, verifyIpfsData } from '@src/utils/ipfs'; import uuidv4 from '@src/utils/uuidv4.ts'; import { useDispatch } from 'react-redux'; -import {refetchCommentsByPublication, addPendingComment, updateCommentStatus} from '@redux/comments'; +import { + refetchCommentsByPublication, + addPendingComment, + updateCommentStatus, + removePendingComment +} from '@redux/comments'; import {useNotifications} from "@src/hooks/use-notifications.ts"; import { useNotificationPayload } from '@src/hooks/use-notification-payload.ts'; import {AnyPublication} from "@lens-protocol/api-bindings"; @@ -107,6 +112,7 @@ const MovieCommentForm = ({ commentOn, owner, root }: MovieCommentFormProps) => */ const onSubmit = handleSubmit(async (data) => { try { + // HAbilitar el efecto en el comentario const uuid = uuidv4(); const metadata = textOnly({ @@ -172,10 +178,6 @@ const MovieCommentForm = ({ commentOn, owner, root }: MovieCommentFormProps) => }, }; - reset(); // Reset the form - // Dispatch the addPendingComment action - dispatch(addPendingComment({ publicationId: commentOn, comment: pendingComment })); - // Validate metadata against the schema const validation = PublicationMetadataSchema.safeParse(metadata); if (!validation.success) { @@ -186,7 +188,15 @@ const MovieCommentForm = ({ commentOn, owner, root }: MovieCommentFormProps) => // Upload metadata to IPFS const uri = await uploadMetadataToIPFS(metadata); - // Verify availability of metadata on IPFS + // Send to redux the pending comment + // Dispatch the addPendingComment action + dispatch(addPendingComment({ publicationId: commentOn, comment: {...pendingComment, uri}})); + // Reset + reset(); // Reset the form + // Verify availability of metadata on IPFS / Retries + + // Eliminar el efecto + await verifyIpfsData(uri); // Create comment with retry logic @@ -195,7 +205,7 @@ const MovieCommentForm = ({ commentOn, owner, root }: MovieCommentFormProps) => metadata: uri, }).then(() => { // Update the comment status to confirmed - dispatch(updateCommentStatus({ publicationId: commentOn, commentId: pendingComment.id, status: 'confirmed' })); + dispatch(removePendingComment({ publicationId: commentOn, commentId: pendingComment.id})); // Send notifications to the author of the publication const notificationPayload = generatePayload('COMMENT', { id: owner?.id,