From f4d32d7ec317e4a1aae8abdd29cf434c7875c6c0 Mon Sep 17 00:00:00 2001 From: Brijeshthummar02 Date: Fri, 12 Dec 2025 13:34:16 +0530 Subject: [PATCH 1/2] Feature: Enable Social Sharing of Earned ROXN Rewards Signed-off-by: Brijeshthummar02 --- .../components/share-achievement-modal.tsx | 242 ++++++++++++++++++ client/src/hooks/use-social-share.ts | 142 ++++++++++ client/src/pages/dashboard-page.tsx | 56 +++- 3 files changed, 435 insertions(+), 5 deletions(-) create mode 100644 client/src/components/share-achievement-modal.tsx create mode 100644 client/src/hooks/use-social-share.ts diff --git a/client/src/components/share-achievement-modal.tsx b/client/src/components/share-achievement-modal.tsx new file mode 100644 index 0000000..a01129b --- /dev/null +++ b/client/src/components/share-achievement-modal.tsx @@ -0,0 +1,242 @@ +import { useState, useRef } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from './ui/dialog'; +import { Button } from './ui/button'; +import { Badge } from './ui/badge'; +import { Card } from './ui/card'; +import { useSocialShare } from '../hooks/use-social-share'; +import { + Twitter, + Linkedin, + Copy, + Download, + Share2, + Coins, + Award, + ExternalLink, + Check, + Sparkles +} from 'lucide-react'; +import { motion } from 'framer-motion'; + +interface ShareAchievementModalProps { + isOpen: boolean; + onClose: () => void; + achievementData: { + amount: string; + currency: 'XDC' | 'ROXN' | 'USDC'; + projectName?: string; + issueTitle?: string; + issueNumber?: number; + transactionHash?: string; + }; +} + +export function ShareAchievementModal({ + isOpen, + onClose, + achievementData +}: ShareAchievementModalProps) { + const [copied, setCopied] = useState(false); + const cardRef = useRef(null); + const { shareOnTwitter, shareOnLinkedIn, copyToClipboard, viewOnExplorer } = useSocialShare(); + + const { amount, currency, projectName, issueTitle, issueNumber, transactionHash } = achievementData; + + const handleShareTwitter = () => { + shareOnTwitter(achievementData); + }; + + const handleShareLinkedIn = () => { + shareOnLinkedIn(achievementData); + }; + + const handleCopy = async () => { + const success = await copyToClipboard(achievementData); + if (success) { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + const handleViewExplorer = () => { + viewOnExplorer(transactionHash); + }; + + // Currency-specific styling + const getCurrencyStyle = () => { + switch (currency) { + case 'ROXN': + return { + gradient: 'from-violet-500 via-purple-500 to-fuchsia-500', + bgGradient: 'from-violet-500/20 via-purple-500/10 to-fuchsia-500/20', + text: 'text-violet-400', + border: 'border-violet-500/30' + }; + case 'USDC': + return { + gradient: 'from-blue-500 via-cyan-500 to-teal-500', + bgGradient: 'from-blue-500/20 via-cyan-500/10 to-teal-500/20', + text: 'text-cyan-400', + border: 'border-cyan-500/30' + }; + case 'XDC': + default: + return { + gradient: 'from-cyan-500 via-emerald-500 to-green-500', + bgGradient: 'from-cyan-500/20 via-emerald-500/10 to-green-500/20', + text: 'text-emerald-400', + border: 'border-emerald-500/30' + }; + } + }; + + const style = getCurrencyStyle(); + + return ( + + + + + + Share Your Achievement! + + + Celebrate your contribution and inspire others to join Roxonn. + + + + {/* Achievement Card Preview */} +
+ {/* Decorative background elements */} +
+
+
+ +
+ {/* Header with Roxonn branding */} +
+
+
+ +
+ ROXONN +
+ + Achievement Unlocked + +
+ + {/* Main content */} +
+ +

I just earned

+
+ {amount} {currency} +
+

for contributing

+
+
+ + {/* Project info */} + {(projectName || issueTitle) && ( +
+ {projectName && ( +

{projectName}

+ )} + {issueTitle && ( +

+ {issueNumber && `#${issueNumber} - `}{issueTitle} +

+ )} +
+ )} + + {/* Footer */} +
+ app.roxonn.com +
+ + Blockchain Verified +
+
+
+
+ + {/* Share Buttons */} +
+ + +
+ + + + {transactionHash && ( + + )} + + +
+ ); +} + +// Export a hook for easy usage +export function useShareAchievement() { + const [isOpen, setIsOpen] = useState(false); + const [achievementData, setAchievementData] = useState({ + amount: '0', + currency: 'ROXN' + }); + + const openShareModal = (data: ShareAchievementModalProps['achievementData']) => { + setAchievementData(data); + setIsOpen(true); + }; + + const closeShareModal = () => { + setIsOpen(false); + }; + + return { + isOpen, + achievementData, + openShareModal, + closeShareModal + }; +} diff --git a/client/src/hooks/use-social-share.ts b/client/src/hooks/use-social-share.ts new file mode 100644 index 0000000..ca2073f --- /dev/null +++ b/client/src/hooks/use-social-share.ts @@ -0,0 +1,142 @@ +import { useToast } from './use-toast'; + +interface ShareOptions { + amount: string; + currency: 'XDC' | 'ROXN' | 'USDC'; + projectName?: string; + issueTitle?: string; + issueNumber?: number; + transactionHash?: string; + customMessage?: string; +} + +/** + * Hook for sharing ROXN achievements on social media + * Provides methods for Twitter, LinkedIn, and clipboard sharing + */ +export function useSocialShare() { + const { toast } = useToast(); + + /** + * Generate a share message based on platform and achievement data + */ + const generateShareMessage = ( + options: ShareOptions, + platform: 'twitter' | 'linkedin' | 'generic' + ): string => { + if (options.customMessage) { + return options.customMessage; + } + + const { amount, currency, projectName, issueNumber } = options; + const projectText = projectName ? ` on ${projectName}` : ''; + const issueText = issueNumber ? ` (#${issueNumber})` : ''; + + const baseMessage = `šŸŽ‰ I just earned ${amount} ${currency} for contributing${projectText}${issueText}!`; + + const hashtags = '#Roxonn #XDC #OpenSource #Web3 #Bounty'; + const link = 'https://app.roxonn.com'; + + if (platform === 'twitter') { + return `${baseMessage}\n\n${hashtags}\n\nšŸš€ Start earning with @RoxonnPlatform:\n${link}`; + } else if (platform === 'linkedin') { + return `${baseMessage}\n\nRoxonn is revolutionizing open-source contributions with blockchain-powered rewards. Join the future of collaborative development!\n\n${link}\n\n${hashtags}`; + } + return `${baseMessage}\n\n${hashtags}\n\n${link}`; + }; + + /** + * Share on Twitter/X + */ + const shareOnTwitter = (options: ShareOptions) => { + const text = encodeURIComponent(generateShareMessage(options, 'twitter')); + window.open( + `https://twitter.com/intent/tweet?text=${text}`, + '_blank', + 'width=550,height=420' + ); + toast({ + title: 'Opening Twitter', + description: 'Share your achievement with the world!', + }); + }; + + /** + * Share on LinkedIn + */ + const shareOnLinkedIn = (options: ShareOptions) => { + const url = encodeURIComponent('https://app.roxonn.com'); + const summary = encodeURIComponent(generateShareMessage(options, 'linkedin')); + window.open( + `https://www.linkedin.com/sharing/share-offsite/?url=${url}&summary=${summary}`, + '_blank', + 'width=550,height=520' + ); + toast({ + title: 'Opening LinkedIn', + description: 'Share your professional achievement!', + }); + }; + + /** + * Copy share text to clipboard + */ + const copyToClipboard = async (options: ShareOptions): Promise => { + try { + const message = generateShareMessage(options, 'generic'); + await navigator.clipboard.writeText(message); + toast({ + title: 'Copied to clipboard!', + description: 'Share message copied successfully.', + }); + return true; + } catch (error) { + toast({ + title: 'Failed to copy', + description: 'Could not copy to clipboard.', + variant: 'destructive', + }); + return false; + } + }; + + /** + * View transaction on XDC explorer + */ + const viewOnExplorer = (transactionHash?: string) => { + if (transactionHash) { + const explorerUrl = `https://xdcscan.com/tx/${transactionHash}`; + window.open(explorerUrl, '_blank'); + } + }; + + /** + * Use native share API if available (mobile friendly) + */ + const nativeShare = async (options: ShareOptions): Promise => { + if (!navigator.share) { + return false; + } + + try { + await navigator.share({ + title: `I earned ${options.amount} ${options.currency} on Roxonn!`, + text: generateShareMessage(options, 'generic'), + url: 'https://app.roxonn.com', + }); + return true; + } catch (error) { + // User cancelled or share failed + return false; + } + }; + + return { + shareOnTwitter, + shareOnLinkedIn, + copyToClipboard, + viewOnExplorer, + nativeShare, + generateShareMessage, + }; +} diff --git a/client/src/pages/dashboard-page.tsx b/client/src/pages/dashboard-page.tsx index bd36585..8d4be14 100644 --- a/client/src/pages/dashboard-page.tsx +++ b/client/src/pages/dashboard-page.tsx @@ -8,6 +8,7 @@ import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { STAGING_API_URL } from "@/config"; import { ethers } from "ethers"; +import { ShareAchievementModal, useShareAchievement } from "@/components/share-achievement-modal"; import { Wallet, TrendingUp, @@ -24,6 +25,7 @@ import { Crown, Gift, ChevronRight, + Share2, } from "lucide-react"; // Animation variants @@ -119,6 +121,8 @@ function ActivityItem({ currency, txHash, repoName, + issueId, + onShare, }: { type: "reward" | "contribution" | "subscription" | "referral"; title: string; @@ -128,6 +132,8 @@ function ActivityItem({ currency?: string; txHash?: string; repoName?: string; + issueId?: number; + onShare?: () => void; }) { const icons = { reward: , @@ -167,10 +173,27 @@ function ActivityItem({ )}

{description}

-

- - {time} -

+
+

+ + {time} +

+ {/* Share button for reward type activities */} + {type === "reward" && amount && onShare && ( + + )} +
{link ? ( @@ -220,6 +243,8 @@ function QuickAction({ export default function DashboardPage() { const { user } = useAuth(); const { data: walletInfo, isLoading: walletLoading, refetch: refetchWallet } = useWallet(); + + const { isOpen: isShareModalOpen, achievementData, openShareModal, closeShareModal } = useShareAchievement(); // Fetch subscription status const { data: subscriptionStatus } = useQuery({ @@ -297,10 +322,27 @@ export default function DashboardPage() { currency: activity.metadata?.currency, txHash: activity.metadata?.txHash, repoName: activity.metadata?.repoName, + issueId: activity.metadata?.issueId, })) || []; + const handleShareActivity = (activity: any) => { + openShareModal({ + amount: activity.amount || '0', + currency: (activity.currency || 'ROXN') as 'XDC' | 'ROXN' | 'USDC', + projectName: activity.repoName, + issueNumber: activity.issueId, + transactionHash: activity.txHash, + }); + }; + return (
+ {/* Share Achievement Modal */} +
{/* Header */} ) : recentActivity.length > 0 ? ( recentActivity.map((activity: any, index: number) => ( - + handleShareActivity(activity)} + /> )) ) : (
From 0334cd473e72f7d1457df1c0a3615318acbab3e7 Mon Sep 17 00:00:00 2001 From: Brijeshthummar02 Date: Mon, 22 Dec 2025 20:53:05 +0530 Subject: [PATCH 2/2] issues fixed Signed-off-by: Brijeshthummar02 --- client/src/components/share-achievement-modal.tsx | 3 --- client/src/hooks/use-social-share.ts | 15 +++++++++------ client/src/pages/dashboard-page.tsx | 10 +++++++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/client/src/components/share-achievement-modal.tsx b/client/src/components/share-achievement-modal.tsx index a01129b..43da4dc 100644 --- a/client/src/components/share-achievement-modal.tsx +++ b/client/src/components/share-achievement-modal.tsx @@ -2,14 +2,11 @@ import { useState, useRef } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from './ui/dialog'; import { Button } from './ui/button'; import { Badge } from './ui/badge'; -import { Card } from './ui/card'; import { useSocialShare } from '../hooks/use-social-share'; import { Twitter, Linkedin, Copy, - Download, - Share2, Coins, Award, ExternalLink, diff --git a/client/src/hooks/use-social-share.ts b/client/src/hooks/use-social-share.ts index ca2073f..c7ea1ea 100644 --- a/client/src/hooks/use-social-share.ts +++ b/client/src/hooks/use-social-share.ts @@ -53,7 +53,7 @@ export function useSocialShare() { window.open( `https://twitter.com/intent/tweet?text=${text}`, '_blank', - 'width=550,height=420' + 'noopener,noreferrer,width=550,height=420' ); toast({ title: 'Opening Twitter', @@ -66,11 +66,10 @@ export function useSocialShare() { */ const shareOnLinkedIn = (options: ShareOptions) => { const url = encodeURIComponent('https://app.roxonn.com'); - const summary = encodeURIComponent(generateShareMessage(options, 'linkedin')); window.open( - `https://www.linkedin.com/sharing/share-offsite/?url=${url}&summary=${summary}`, + `https://www.linkedin.com/sharing/share-offsite/?url=${url}`, '_blank', - 'width=550,height=520' + 'noopener,noreferrer,width=550,height=520' ); toast({ title: 'Opening LinkedIn', @@ -105,8 +104,11 @@ export function useSocialShare() { */ const viewOnExplorer = (transactionHash?: string) => { if (transactionHash) { - const explorerUrl = `https://xdcscan.com/tx/${transactionHash}`; - window.open(explorerUrl, '_blank'); + const normalizedHash = transactionHash.startsWith('xdc') + ? '0x' + transactionHash.slice(3) + : transactionHash; + const explorerUrl = `https://xdcscan.io/tx/${normalizedHash}`; + window.open(explorerUrl, '_blank', 'noopener,noreferrer'); } }; @@ -127,6 +129,7 @@ export function useSocialShare() { return true; } catch (error) { // User cancelled or share failed + console.debug('Native share failed:', error); return false; } }; diff --git a/client/src/pages/dashboard-page.tsx b/client/src/pages/dashboard-page.tsx index 8d4be14..5e3bb36 100644 --- a/client/src/pages/dashboard-page.tsx +++ b/client/src/pages/dashboard-page.tsx @@ -325,7 +325,15 @@ export default function DashboardPage() { issueId: activity.metadata?.issueId, })) || []; - const handleShareActivity = (activity: any) => { + interface ActivityData { + amount?: string; + currency?: 'XDC' | 'ROXN' | 'USDC'; + repoName?: string; + issueId?: number; + txHash?: string; + } + + const handleShareActivity = (activity: ActivityData) => { openShareModal({ amount: activity.amount || '0', currency: (activity.currency || 'ROXN') as 'XDC' | 'ROXN' | 'USDC',