diff --git a/.vscode/settings.json b/.vscode/settings.json
index bc5e22b43..8cd4f4119 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,6 @@
"editor.tabSize": 4,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
- }
+ },
+ "cSpell.words": ["uidotdev"]
}
diff --git a/packages/frontend/src/assets/img/share.png b/packages/frontend/src/assets/img/share.png
new file mode 100644
index 000000000..24dc46da3
Binary files /dev/null and b/packages/frontend/src/assets/img/share.png differ
diff --git a/packages/frontend/src/features/post/components/Post/Post.tsx b/packages/frontend/src/features/post/components/Post/Post.tsx
index a5500e9c2..b7f9964f7 100644
--- a/packages/frontend/src/features/post/components/Post/Post.tsx
+++ b/packages/frontend/src/features/post/components/Post/Post.tsx
@@ -14,6 +14,8 @@ import { PostActionMenu } from './PostActionMenu'
import { PostBlockedMask } from './PostBlockedMask'
import PostFooter from './PostFooter'
import { PostReportedMask } from './PostReportedMask'
+import ShareLinkTransition from '../ShareLinkTransition/ShareLinkTransition'
+import { useCopy } from '@/features/shared/hooks/useCopy'
export default function Post({
id = '',
@@ -87,6 +89,8 @@ export default function Post({
votedEpoch,
])
+ const { hasCopied, copyToClipboard } = useCopy()
+
const [localUpCount, setLocalUpCount] = useState(upCount)
const [localDownCount, setLocalDownCount] = useState(downCount)
@@ -98,6 +102,13 @@ export default function Post({
const [isMineState, setIsMineState] = useState(isMine)
const [isError, setIsError] = useState(false)
+ const handleShareClick = () => {
+ if (id) {
+ const postLink = `${window.location.origin}/posts/${id}`
+ copyToClipboard(postLink)
+ }
+ }
+
// set isAction when finalAction is changed
useEffect(() => {
setIsMineState(isMine)
@@ -170,6 +181,7 @@ export default function Post({
{isReported && }
{isBlocked && }
{}
+ {}
{compact && status === PostStatus.Success ? (
{postInfo}
@@ -190,6 +202,7 @@ export default function Post({
voteAction={isAction}
handleVote={handleVote}
handleComment={onComment}
+ handleShare={handleShareClick}
/>
{compact && imageUrl && (
diff --git a/packages/frontend/src/features/post/components/Post/PostFooter.tsx b/packages/frontend/src/features/post/components/Post/PostFooter.tsx
index 32364b4f5..7373dcfc6 100644
--- a/packages/frontend/src/features/post/components/Post/PostFooter.tsx
+++ b/packages/frontend/src/features/post/components/Post/PostFooter.tsx
@@ -1,5 +1,6 @@
import CommentImg from '@/assets/img/comment.png'
import DownVoteImg from '@/assets/img/downvote.png'
+import ShareImg from '@/assets/img/share.png'
import UpVoteImg from '@/assets/img/upvote.png'
import { VoteAction } from '@/types/Vote'
@@ -12,6 +13,7 @@ interface PostFooterProps {
voteAction: VoteAction | null
handleVote: (voteType: VoteAction) => void
handleComment: () => void
+ handleShare: () => void
}
function PostFooter({
@@ -23,6 +25,7 @@ function PostFooter({
voteAction,
handleVote,
handleComment,
+ handleShare,
}: PostFooterProps) {
return (
)
}
@@ -173,4 +177,28 @@ function UpVoteBtn({
)
}
+interface ShareBtnProps {
+ onClick: () => void
+ isLoggedIn: boolean
+}
+function ShareBtn({ onClick, isLoggedIn }: ShareBtnProps) {
+ const cursor = isLoggedIn ? 'pointer' : 'not-allowed'
+
+ return (
+ <>
+
+ >
+ )
+}
+
export default PostFooter
diff --git a/packages/frontend/src/features/post/components/ShareLinkTransition/ShareLinkTransition.tsx b/packages/frontend/src/features/post/components/ShareLinkTransition/ShareLinkTransition.tsx
new file mode 100644
index 000000000..92de2a1ad
--- /dev/null
+++ b/packages/frontend/src/features/post/components/ShareLinkTransition/ShareLinkTransition.tsx
@@ -0,0 +1,50 @@
+import ShareImg from '@/assets/img/share.png'
+import { motion, useAnimation } from 'framer-motion'
+import { useEffect } from 'react'
+
+interface ShareLinkTransitionProps {
+ isOpen: boolean
+}
+
+export default function ShareLinkTransition({
+ isOpen,
+}: ShareLinkTransitionProps) {
+ const controls = useAnimation()
+
+ useEffect(() => {
+ if (!isOpen) return
+ controls.start({
+ y: [100, 0],
+ opacity: [0, 1, 0],
+ transition: {
+ y: {
+ type: 'spring',
+ stiffness: 50,
+ damping: 10,
+ duration: 1,
+ },
+ opacity: {
+ duration: 2, // Complete fade-in-out cycle
+ ease: 'easeInOut',
+ },
+ },
+ })
+ }, [controls, isOpen])
+
+ if (!isOpen) return null
+
+ return (
+
+
+
+ )
+}
diff --git a/packages/frontend/src/features/shared/components/Backdrop/Backdrop.tsx b/packages/frontend/src/features/shared/components/Backdrop/Backdrop.tsx
index 7b189445e..6a6eeb96c 100644
--- a/packages/frontend/src/features/shared/components/Backdrop/Backdrop.tsx
+++ b/packages/frontend/src/features/shared/components/Backdrop/Backdrop.tsx
@@ -25,7 +25,7 @@ export default function Backdrop({
},
}
- const chidrenVarients = {
+ const childrenVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
@@ -63,7 +63,7 @@ export default function Backdrop({
h-full
mt-0
`}
- variants={chidrenVarients}
+ variants={childrenVariants}
initial="hidden"
animate="visible"
>
diff --git a/packages/frontend/src/features/shared/hooks/useCopy.ts b/packages/frontend/src/features/shared/hooks/useCopy.ts
new file mode 100644
index 000000000..818628bf3
--- /dev/null
+++ b/packages/frontend/src/features/shared/hooks/useCopy.ts
@@ -0,0 +1,24 @@
+import { useEffect, useState } from 'react'
+import { useCopyToClipboard } from 'react-use'
+
+export function useCopy(autoClearTimer: number = 2000) {
+ const [copiedText, copyToClipboard] = useCopyToClipboard()
+ const [hasCopied, setHasCopied] = useState(false)
+
+ const handleCopy = (text: string) => {
+ copyToClipboard(text)
+ setHasCopied(true)
+ }
+
+ useEffect(() => {
+ if (!hasCopied || autoClearTimer === 0) return
+
+ const timer = setTimeout(() => {
+ setHasCopied(false)
+ }, autoClearTimer)
+
+ return () => clearTimeout(timer)
+ }, [hasCopied, autoClearTimer])
+
+ return { copiedText, hasCopied, copyToClipboard: handleCopy }
+}