mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-04 05:43:10 +00:00
chore: linting + improve CSS
This commit is contained in:
parent
b6e78ac71c
commit
f9863121ba
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useForumData } from '@/hooks';
|
import { useForum } from '@/hooks';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
@ -35,54 +35,48 @@ interface CommentFeedItem extends FeedItemBase {
|
|||||||
type FeedItem = PostFeedItem | CommentFeedItem;
|
type FeedItem = PostFeedItem | CommentFeedItem;
|
||||||
|
|
||||||
const ActivityFeed: React.FC = () => {
|
const ActivityFeed: React.FC = () => {
|
||||||
// ✅ Use reactive hooks for data
|
const { content, network } = useForum();
|
||||||
const forumData = useForumData();
|
|
||||||
|
|
||||||
const {
|
const { posts, comments, cells, commentsByPost } = content;
|
||||||
postsWithVoteStatus,
|
const { isConnected } = network;
|
||||||
commentsWithVoteStatus,
|
|
||||||
cellsWithStats,
|
|
||||||
isInitialLoading,
|
|
||||||
} = forumData;
|
|
||||||
|
|
||||||
// ✅ Use pre-computed data with vote scores
|
|
||||||
const combinedFeed: FeedItem[] = [
|
const combinedFeed: FeedItem[] = useMemo(() => {
|
||||||
...postsWithVoteStatus.map(
|
return [
|
||||||
(post): PostFeedItem => ({
|
...posts.map(
|
||||||
id: post.id,
|
(post): PostFeedItem => ({
|
||||||
type: 'post',
|
...post,
|
||||||
timestamp: post.timestamp,
|
type: 'post',
|
||||||
ownerAddress: post.author,
|
ownerAddress: post.authorAddress,
|
||||||
title: post.title,
|
cellId: post.cellId,
|
||||||
cellId: post.cellId,
|
postId: post.id,
|
||||||
postId: post.id,
|
title: post.title,
|
||||||
commentCount: forumData.commentsByPost[post.id]?.length || 0,
|
commentCount: commentsByPost[post.id]?.length || 0,
|
||||||
voteCount: post.voteScore,
|
voteCount: post.upvotes.length - post.downvotes.length,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
...commentsWithVoteStatus
|
...comments
|
||||||
.map((comment): CommentFeedItem | null => {
|
.map((comment): CommentFeedItem | null => {
|
||||||
const parentPost = postsWithVoteStatus.find(
|
const parentPost = posts.find(p => p.id === comment.postId);
|
||||||
p => p.id === comment.postId
|
if (!parentPost) return null;
|
||||||
);
|
return {
|
||||||
if (!parentPost) return null;
|
id: comment.id,
|
||||||
return {
|
type: 'comment',
|
||||||
id: comment.id,
|
timestamp: comment.timestamp,
|
||||||
type: 'comment',
|
ownerAddress: comment.author,
|
||||||
timestamp: comment.timestamp,
|
content: comment.content,
|
||||||
ownerAddress: comment.author,
|
postId: comment.postId,
|
||||||
content: comment.content,
|
cellId: parentPost.cellId,
|
||||||
postId: comment.postId,
|
voteCount: comment.upvotes.length - comment.downvotes.length,
|
||||||
cellId: parentPost.cellId,
|
};
|
||||||
voteCount: comment.voteScore,
|
})
|
||||||
};
|
.filter((item): item is CommentFeedItem => item !== null),
|
||||||
})
|
].sort((a, b) => b.timestamp - a.timestamp);
|
||||||
.filter((item): item is CommentFeedItem => item !== null),
|
}, [posts, comments, commentsByPost]);
|
||||||
].sort((a, b) => b.timestamp - a.timestamp);
|
|
||||||
|
|
||||||
const renderFeedItem = (item: FeedItem) => {
|
const renderFeedItem = (item: FeedItem) => {
|
||||||
const cell = item.cellId
|
const cell = item.cellId
|
||||||
? cellsWithStats.find(c => c.id === item.cellId)
|
? cells.find(c => c.id === item.cellId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const timeAgo = formatDistanceToNow(new Date(item.timestamp), {
|
const timeAgo = formatDistanceToNow(new Date(item.timestamp), {
|
||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
@ -150,7 +144,7 @@ const ActivityFeed: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isInitialLoading) {
|
if (!isConnected) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{[...Array(5)].map((_, i) => (
|
{[...Array(5)].map((_, i) => (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useContent, usePermissions } from '@/hooks';
|
import { useContent, usePermissions } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
Layout,
|
Layout,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
@ -226,9 +226,7 @@ const CellList = () => {
|
|||||||
title="Refresh data"
|
title="Refresh data"
|
||||||
className="px-3 border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
|
className="px-3 border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
|
||||||
>
|
>
|
||||||
<RefreshCw
|
<RefreshCw className="w-4 h-4" />
|
||||||
className="w-4 h-4"
|
|
||||||
/>
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{canCreateCell && <CreateCellDialog />}
|
{canCreateCell && <CreateCellDialog />}
|
||||||
|
|||||||
@ -61,10 +61,14 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
|||||||
// Use library pending API
|
// Use library pending API
|
||||||
const commentVotePending = content.pending.isPending(comment.id);
|
const commentVotePending = content.pending.isPending(comment.id);
|
||||||
|
|
||||||
const score = comment.voteScore ?? 0;
|
const score = comment.upvotes.length - comment.downvotes.length;
|
||||||
const isModerated = Boolean(comment.moderated);
|
const isModerated = Boolean(comment.moderated);
|
||||||
const userDownvoted = Boolean(comment.downvotes.some(v => v.author === currentUser?.address));
|
const userDownvoted = Boolean(
|
||||||
const userUpvoted = Boolean(comment.upvotes.some(v => v.author === currentUser?.address));
|
comment.downvotes.some(v => v.author === currentUser?.address)
|
||||||
|
);
|
||||||
|
const userUpvoted = Boolean(
|
||||||
|
comment.upvotes.some(v => v.author === currentUser?.address)
|
||||||
|
);
|
||||||
const isOwnComment = currentUser?.address === comment.author;
|
const isOwnComment = currentUser?.address === comment.author;
|
||||||
|
|
||||||
const handleVoteComment = async (isUpvote: boolean) => {
|
const handleVoteComment = async (isUpvote: boolean) => {
|
||||||
@ -86,7 +90,9 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
|||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${
|
||||||
userUpvoted ? 'text-cyber-accent' : 'text-cyber-neutral hover:text-cyber-accent'
|
userUpvoted
|
||||||
|
? 'text-cyber-accent'
|
||||||
|
: 'text-cyber-neutral hover:text-cyber-accent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleVoteComment(true)}
|
onClick={() => handleVoteComment(true)}
|
||||||
disabled={!permissions.canVote}
|
disabled={!permissions.canVote}
|
||||||
@ -99,7 +105,9 @@ const CommentCard: React.FC<CommentCardProps> = ({
|
|||||||
<span className="text-sm font-bold">{score}</span>
|
<span className="text-sm font-bold">{score}</span>
|
||||||
<button
|
<button
|
||||||
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${
|
||||||
userDownvoted ? 'text-cyber-accent' : 'text-cyber-neutral hover:text-cyber-accent'
|
userDownvoted
|
||||||
|
? 'text-cyber-accent'
|
||||||
|
: 'text-cyber-neutral hover:text-cyber-accent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleVoteComment(false)}
|
onClick={() => handleVoteComment(false)}
|
||||||
disabled={!permissions.canVote}
|
disabled={!permissions.canVote}
|
||||||
|
|||||||
@ -79,7 +79,8 @@ export function CreateCellDialog({
|
|||||||
if (!canCreateCell) {
|
if (!canCreateCell) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Permission Denied',
|
title: 'Permission Denied',
|
||||||
description: 'Only verified ENS or Logos Ordinal owners can create cells.',
|
description:
|
||||||
|
'Only verified ENS or Logos Ordinal owners can create cells.',
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -4,16 +4,15 @@ import { TrendingUp, Users, Eye, CheckCircle } from 'lucide-react';
|
|||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { useAuth, useContent } from '@/hooks';
|
import { useAuth, useContent } from '@/hooks';
|
||||||
import { EVerificationStatus } from '@opchan/core';
|
import { EVerificationStatus } from '@opchan/core';
|
||||||
import { CypherImage } from '@/components/ui/CypherImage';
|
import { CypherImage } from '@/components/ui/CypherImage';
|
||||||
|
|
||||||
const FeedSidebar: React.FC = () => {
|
const FeedSidebar: React.FC = () => {
|
||||||
const {cells, posts, comments, cellsWithStats, userVerificationStatus} = useContent();
|
const { cells, posts, comments, cellsWithStats, userVerificationStatus } =
|
||||||
|
useContent();
|
||||||
const { currentUser, verificationStatus } = useAuth();
|
const { currentUser, verificationStatus } = useAuth();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
totalCells: cells.length,
|
totalCells: cells.length,
|
||||||
totalPosts: posts.length,
|
totalPosts: posts.length,
|
||||||
@ -61,7 +60,9 @@ const FeedSidebar: React.FC = () => {
|
|||||||
<Users className="w-5 h-5 text-cyber-accent" />
|
<Users className="w-5 h-5 text-cyber-accent" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium text-sm">{currentUser?.displayName}</div>
|
<div className="font-medium text-sm">
|
||||||
|
{currentUser?.displayName}
|
||||||
|
</div>
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={`${verificationBadge.color} text-white text-xs`}
|
className={`${verificationBadge.color} text-white text-xs`}
|
||||||
|
|||||||
@ -49,9 +49,9 @@ import { WakuHealthDot } from '@/components/ui/waku-health-indicator';
|
|||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { currentUser, delegationInfo } = useAuth();
|
const { currentUser, delegationInfo } = useAuth();
|
||||||
const {statusMessage} = useNetwork();
|
const { statusMessage } = useNetwork();
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { content } = useForum();
|
const { content } = useForum();
|
||||||
|
|
||||||
@ -65,7 +65,10 @@ const Header = () => {
|
|||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
// Use centralized UI state instead of direct LocalDatabase access
|
// Use centralized UI state instead of direct LocalDatabase access
|
||||||
const [hasShownWizard, setHasShownWizard] = useUIState('hasShownWalletWizard', false);
|
const [hasShownWizard, setHasShownWizard] = useUIState(
|
||||||
|
'hasShownWalletWizard',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// Auto-open wizard when wallet connects for the first time
|
// Auto-open wizard when wallet connects for the first time
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -109,23 +112,26 @@ const Header = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('currentUser', currentUser)
|
console.log('currentUser', currentUser);
|
||||||
}, [currentUser])
|
}, [currentUser]);
|
||||||
|
|
||||||
const getStatusIcon = () => {
|
const getStatusIcon = () => {
|
||||||
if (!isConnected) return <CircleSlash className="w-4 h-4" />;
|
if (!isConnected) return <CircleSlash className="w-4 h-4" />;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
currentUser?.verificationStatus ===
|
||||||
|
EVerificationStatus.ENS_ORDINAL_VERIFIED &&
|
||||||
delegationInfo?.isValid
|
delegationInfo?.isValid
|
||||||
) {
|
) {
|
||||||
return <CheckCircle className="w-4 h-4" />;
|
return <CheckCircle className="w-4 h-4" />;
|
||||||
} else if (currentUser?.verificationStatus === EVerificationStatus.WALLET_CONNECTED) {
|
} else if (
|
||||||
|
currentUser?.verificationStatus === EVerificationStatus.WALLET_CONNECTED
|
||||||
|
) {
|
||||||
return <AlertTriangle className="w-4 h-4" />;
|
return <AlertTriangle className="w-4 h-4" />;
|
||||||
} else if (
|
} else if (
|
||||||
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
|
currentUser?.verificationStatus ===
|
||||||
|
EVerificationStatus.ENS_ORDINAL_VERIFIED
|
||||||
) {
|
) {
|
||||||
return <Key className="w-4 h-4" />;
|
return <Key className="w-4 h-4" />;
|
||||||
} else {
|
} else {
|
||||||
@ -197,11 +203,13 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
{getStatusIcon()}
|
{getStatusIcon()}
|
||||||
<span className="ml-1">
|
<span className="ml-1">
|
||||||
{currentUser?.verificationStatus === EVerificationStatus.WALLET_UNCONNECTED
|
{currentUser?.verificationStatus ===
|
||||||
|
EVerificationStatus.WALLET_UNCONNECTED
|
||||||
? 'CONNECT'
|
? 'CONNECT'
|
||||||
: delegationInfo?.isValid
|
: delegationInfo?.isValid
|
||||||
? 'READY'
|
? 'READY'
|
||||||
: currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
|
: currentUser?.verificationStatus ===
|
||||||
|
EVerificationStatus.ENS_ORDINAL_VERIFIED
|
||||||
? 'EXPIRED'
|
? 'EXPIRED'
|
||||||
: 'DELEGATE'}
|
: 'DELEGATE'}
|
||||||
</span>
|
</span>
|
||||||
@ -215,7 +223,9 @@ const Header = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center space-x-2 text-white hover:bg-cyber-muted/30"
|
className="flex items-center space-x-2 text-white hover:bg-cyber-muted/30"
|
||||||
>
|
>
|
||||||
<div className="text-sm font-mono">{currentUser?.displayName}</div>
|
<div className="text-sm font-mono">
|
||||||
|
{currentUser?.displayName}
|
||||||
|
</div>
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@ -2,59 +2,52 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ArrowUp, ArrowDown, MessageSquare } from 'lucide-react';
|
import { ArrowUp, ArrowDown, MessageSquare } from 'lucide-react';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import type { Post, PostMessage } from '@opchan/core';
|
import type { Post } from '@opchan/core';
|
||||||
import { RelevanceIndicator } from '@/components/ui/relevance-indicator';
|
import { RelevanceIndicator } from '@/components/ui/relevance-indicator';
|
||||||
import { AuthorDisplay } from '@/components/ui/author-display';
|
import { AuthorDisplay } from '@/components/ui/author-display';
|
||||||
import { BookmarkButton } from '@/components/ui/bookmark-button';
|
import { BookmarkButton } from '@/components/ui/bookmark-button';
|
||||||
import { LinkRenderer } from '@/components/ui/link-renderer';
|
import { LinkRenderer } from '@/components/ui/link-renderer';
|
||||||
import { useContent, usePermissions } from '@/hooks';
|
import { useAuth, useContent, usePermissions } from '@/hooks';
|
||||||
import { ShareButton } from '@/components/ui/ShareButton';
|
import { ShareButton } from '@/components/ui/ShareButton';
|
||||||
|
|
||||||
interface PostCardProps {
|
interface PostCardProps {
|
||||||
post: Post | PostMessage;
|
post: Post;
|
||||||
commentCount?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
const PostCard: React.FC<PostCardProps> = ({ post }) => {
|
||||||
const content = useContent();
|
const {
|
||||||
|
bookmarks,
|
||||||
|
pending,
|
||||||
|
vote,
|
||||||
|
togglePostBookmark,
|
||||||
|
cells,
|
||||||
|
commentsByPost,
|
||||||
|
} = useContent();
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
|
const { currentUser } = useAuth();
|
||||||
|
|
||||||
// Get cell data from content
|
const cellName = cells.find(c => c.id === post.cellId)?.name || 'unknown';
|
||||||
const cell = content.cells.find((c) => c.id === post.cellId);
|
const commentCount = commentsByPost[post.id]?.length || 0;
|
||||||
const cellName = cell?.name || 'unknown';
|
|
||||||
|
|
||||||
// Use pre-computed vote data or safely compute from arrays when available
|
const isPending = pending.isPending(post.id);
|
||||||
const computedVoteScore =
|
|
||||||
'voteScore' in post && typeof (post as Post).voteScore === 'number'
|
|
||||||
? (post as Post).voteScore
|
|
||||||
: undefined;
|
|
||||||
const upvoteCount =
|
|
||||||
'upvotes' in post && Array.isArray((post as Post).upvotes)
|
|
||||||
? (post as Post).upvotes.length
|
|
||||||
: 0;
|
|
||||||
const downvoteCount =
|
|
||||||
'downvotes' in post && Array.isArray((post as Post).downvotes)
|
|
||||||
? (post as Post).downvotes.length
|
|
||||||
: 0;
|
|
||||||
const score = computedVoteScore ?? upvoteCount - downvoteCount;
|
|
||||||
|
|
||||||
// Use library pending API
|
const isBookmarked = bookmarks.some(
|
||||||
const isPending = content.pending.isPending(post.id);
|
b => b.targetId === post.id && b.type === 'post'
|
||||||
|
);
|
||||||
// Get user vote status from post data
|
|
||||||
const userUpvoted =
|
|
||||||
(post as unknown as { userUpvoted?: boolean }).userUpvoted || false;
|
|
||||||
const userDownvoted =
|
|
||||||
(post as unknown as { userDownvoted?: boolean }).userDownvoted || false;
|
|
||||||
|
|
||||||
// Check if bookmarked
|
|
||||||
const isBookmarked = content.bookmarks.some((b) => b.targetId === post.id && b.type === 'post');
|
|
||||||
const [bookmarkLoading, setBookmarkLoading] = React.useState(false);
|
const [bookmarkLoading, setBookmarkLoading] = React.useState(false);
|
||||||
|
|
||||||
// Remove duplicate vote status logic
|
const score = post.upvotes.length - post.downvotes.length;
|
||||||
|
const userUpvoted = Boolean(
|
||||||
|
post.upvotes.some(v => v.author === currentUser?.address)
|
||||||
|
);
|
||||||
|
const userDownvoted = Boolean(
|
||||||
|
post.downvotes.some(v => v.author === currentUser?.address)
|
||||||
|
);
|
||||||
|
|
||||||
// ✅ Content truncation (simple presentation logic is OK)
|
const contentText =
|
||||||
const contentText = typeof post.content === 'string' ? post.content : String(post.content ?? '');
|
typeof post.content === 'string'
|
||||||
|
? post.content
|
||||||
|
: String(post.content ?? '');
|
||||||
const contentPreview =
|
const contentPreview =
|
||||||
contentText.length > 200
|
contentText.length > 200
|
||||||
? contentText.substring(0, 200) + '...'
|
? contentText.substring(0, 200) + '...'
|
||||||
@ -62,7 +55,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
|||||||
|
|
||||||
const handleVote = async (e: React.MouseEvent, isUpvote: boolean) => {
|
const handleVote = async (e: React.MouseEvent, isUpvote: boolean) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await content.vote({ targetId: post.id, isUpvote });
|
await vote({ targetId: post.id, isUpvote });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBookmark = async (e?: React.MouseEvent) => {
|
const handleBookmark = async (e?: React.MouseEvent) => {
|
||||||
@ -72,7 +65,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
|||||||
}
|
}
|
||||||
setBookmarkLoading(true);
|
setBookmarkLoading(true);
|
||||||
try {
|
try {
|
||||||
await content.togglePostBookmark(post, post.cellId);
|
await togglePostBookmark(post, post.cellId);
|
||||||
} finally {
|
} finally {
|
||||||
setBookmarkLoading(false);
|
setBookmarkLoading(false);
|
||||||
}
|
}
|
||||||
@ -97,13 +90,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium px-1 ${
|
className={`text-sm font-medium px-1`}
|
||||||
score > 0
|
|
||||||
? 'text-cyber-accent'
|
|
||||||
: score < 0
|
|
||||||
? 'text-blue-400'
|
|
||||||
: 'text-cyber-neutral'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{score}
|
{score}
|
||||||
</span>
|
</span>
|
||||||
@ -130,9 +117,17 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
|||||||
<div className="block hover:opacity-80">
|
<div className="block hover:opacity-80">
|
||||||
{/* Post metadata */}
|
{/* Post metadata */}
|
||||||
<div className="flex items-center text-xs text-cyber-neutral mb-2 space-x-2">
|
<div className="flex items-center text-xs text-cyber-neutral mb-2 space-x-2">
|
||||||
<span className="font-medium text-cyber-accent">
|
<Link
|
||||||
r/{cellName}
|
to={cellName ? `/cell/${post.cellId}` : "#"}
|
||||||
</span>
|
className="font-medium text-cyber-accent hover:underline focus:underline"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={e => {
|
||||||
|
if (!cellName) e.preventDefault();
|
||||||
|
}}
|
||||||
|
title={cellName ? `Go to /${cellName}` : undefined}
|
||||||
|
>
|
||||||
|
r/{cellName || 'unknown'}
|
||||||
|
</Link>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>Posted by u/</span>
|
<span>Posted by u/</span>
|
||||||
<AuthorDisplay
|
<AuthorDisplay
|
||||||
@ -146,18 +141,23 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
|
|||||||
addSuffix: true,
|
addSuffix: true,
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{('relevanceScore' in post) && typeof (post as Post).relevanceScore === 'number' && (
|
{'relevanceScore' in post &&
|
||||||
<>
|
typeof (post as Post).relevanceScore === 'number' && (
|
||||||
<span>•</span>
|
<>
|
||||||
<RelevanceIndicator
|
<span>•</span>
|
||||||
score={(post as Post).relevanceScore as number}
|
<RelevanceIndicator
|
||||||
details={('relevanceDetails' in post ? (post as Post).relevanceDetails : undefined)}
|
score={(post as Post).relevanceScore as number}
|
||||||
type="post"
|
details={
|
||||||
className="text-xs"
|
'relevanceDetails' in post
|
||||||
showTooltip={true}
|
? (post as Post).relevanceDetails
|
||||||
/>
|
: undefined
|
||||||
</>
|
}
|
||||||
)}
|
type="post"
|
||||||
|
className="text-xs"
|
||||||
|
showTooltip={true}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Post title and content - clickable to navigate to post */}
|
{/* Post title and content - clickable to navigate to post */}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { AuthorDisplay } from './ui/author-display';
|
|||||||
import { BookmarkButton } from './ui/bookmark-button';
|
import { BookmarkButton } from './ui/bookmark-button';
|
||||||
import { MarkdownRenderer } from './ui/markdown-renderer';
|
import { MarkdownRenderer } from './ui/markdown-renderer';
|
||||||
import CommentCard from './CommentCard';
|
import CommentCard from './CommentCard';
|
||||||
import { useContent, usePermissions } from '@/hooks';
|
import { useAuth, useContent, usePermissions } from '@/hooks';
|
||||||
import type { Cell as ForumCell } from '@opchan/core';
|
import type { Cell as ForumCell } from '@opchan/core';
|
||||||
import { ShareButton } from './ui/ShareButton';
|
import { ShareButton } from './ui/ShareButton';
|
||||||
|
|
||||||
@ -28,17 +28,19 @@ const PostDetail = () => {
|
|||||||
// Use aggregated forum API
|
// Use aggregated forum API
|
||||||
const content = useContent();
|
const content = useContent();
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
|
const { currentUser } = useAuth();
|
||||||
// Get post and comments using focused hooks
|
// Get post and comments using focused hooks
|
||||||
const post = content.posts.find((p) => p.id === postId);
|
const post = content.posts.find(p => p.id === postId);
|
||||||
const visibleComments = postId ? content.commentsByPost[postId] ?? [] : [];
|
const visibleComments = postId ? (content.commentsByPost[postId] ?? []) : [];
|
||||||
|
|
||||||
// Use library pending API
|
// Use library pending API
|
||||||
const postPending = content.pending.isPending(post?.id);
|
const postPending = content.pending.isPending(post?.id);
|
||||||
const postVotePending = content.pending.isPending(post?.id);
|
const postVotePending = content.pending.isPending(post?.id);
|
||||||
|
|
||||||
// Check if bookmarked
|
// Check if bookmarked
|
||||||
const isBookmarked = content.bookmarks.some((b) => b.targetId === post?.id && b.type === 'post');
|
const isBookmarked = content.bookmarks.some(
|
||||||
|
b => b.targetId === post?.id && b.type === 'post'
|
||||||
|
);
|
||||||
const [bookmarkLoading, setBookmarkLoading] = React.useState(false);
|
const [bookmarkLoading, setBookmarkLoading] = React.useState(false);
|
||||||
|
|
||||||
const [newComment, setNewComment] = useState('');
|
const [newComment, setNewComment] = useState('');
|
||||||
@ -115,11 +117,13 @@ const PostDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get vote status from post data (enhanced posts only)
|
const score = post.upvotes.length - post.downvotes.length;
|
||||||
const enhanced = post as unknown as { userUpvoted?: boolean; userDownvoted?: boolean; voteScore?: number };
|
const isPostUpvoted = Boolean(
|
||||||
const isPostUpvoted = Boolean(enhanced.userUpvoted);
|
post.upvotes.some(v => v.author === currentUser?.address)
|
||||||
const isPostDownvoted = Boolean(enhanced.userDownvoted);
|
);
|
||||||
const score = typeof enhanced.voteScore === 'number' ? enhanced.voteScore : 0;
|
const isPostDownvoted = Boolean(
|
||||||
|
post.downvotes.some(v => v.author === currentUser?.address)
|
||||||
|
);
|
||||||
|
|
||||||
const handleModerateComment = async (commentId: string) => {
|
const handleModerateComment = async (commentId: string) => {
|
||||||
const reason =
|
const reason =
|
||||||
@ -160,7 +164,9 @@ const PostDetail = () => {
|
|||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
className={`p-1 rounded-sm hover:bg-muted/50 ${
|
className={`p-1 rounded-sm hover:bg-muted/50 ${
|
||||||
isPostUpvoted ? 'text-primary' : ''
|
isPostUpvoted
|
||||||
|
? 'text-cyber-accent'
|
||||||
|
: 'text-cyber-neutral hover:text-cyber-accent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleVotePost(true)}
|
onClick={() => handleVotePost(true)}
|
||||||
disabled={!permissions.canVote}
|
disabled={!permissions.canVote}
|
||||||
@ -173,7 +179,9 @@ const PostDetail = () => {
|
|||||||
<span className="text-sm font-bold">{score}</span>
|
<span className="text-sm font-bold">{score}</span>
|
||||||
<button
|
<button
|
||||||
className={`p-1 rounded-sm hover:bg-muted/50 ${
|
className={`p-1 rounded-sm hover:bg-muted/50 ${
|
||||||
isPostDownvoted ? 'text-primary' : ''
|
isPostDownvoted
|
||||||
|
? 'text-cyber-accent'
|
||||||
|
: 'text-cyber-neutral hover:text-cyber-accent'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleVotePost(false)}
|
onClick={() => handleVotePost(false)}
|
||||||
disabled={!permissions.canVote}
|
disabled={!permissions.canVote}
|
||||||
@ -194,9 +202,17 @@ const PostDetail = () => {
|
|||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
||||||
<span className="font-medium text-primary">
|
<Link
|
||||||
|
to={cell?.id ? `/cell/${cell.id}` : "#"}
|
||||||
|
className="font-medium text-primary hover:underline focus:underline"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={e => {
|
||||||
|
if (!cell?.id) e.preventDefault();
|
||||||
|
}}
|
||||||
|
title={cell?.name ? `Go to /${cell.name}` : undefined}
|
||||||
|
>
|
||||||
r/{cell?.name || 'unknown'}
|
r/{cell?.name || 'unknown'}
|
||||||
</span>
|
</Link>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>Posted by u/</span>
|
<span>Posted by u/</span>
|
||||||
<AuthorDisplay
|
<AuthorDisplay
|
||||||
@ -279,9 +295,7 @@ const PostDetail = () => {
|
|||||||
|
|
||||||
{!permissions.canComment && (
|
{!permissions.canComment && (
|
||||||
<div className="mb-6 p-4 border border-cyber-muted rounded-sm bg-cyber-muted/20 text-center">
|
<div className="mb-6 p-4 border border-cyber-muted rounded-sm bg-cyber-muted/20 text-center">
|
||||||
<p className="text-sm mb-3">
|
<p className="text-sm mb-3">Connect your wallet to comment</p>
|
||||||
Connect your wallet to comment
|
|
||||||
</p>
|
|
||||||
<Button asChild size="sm">
|
<Button asChild size="sm">
|
||||||
<Link to="/">Connect Wallet</Link>
|
<Link to="/">Connect Wallet</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@ -306,7 +320,7 @@ const PostDetail = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
visibleComments.map((comment) => (
|
visibleComments.map(comment => (
|
||||||
<CommentCard
|
<CommentCard
|
||||||
key={comment.id}
|
key={comment.id}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { usePermissions, useAuth, useContent } from '@/hooks';
|
import { usePermissions, useAuth, useContent } from '@/hooks';
|
||||||
import type { Post as ForumPost, Cell as ForumCell, VoteMessage } from '@opchan/core';
|
import type {
|
||||||
|
Post as ForumPost,
|
||||||
|
Cell as ForumCell,
|
||||||
|
VoteMessage,
|
||||||
|
} from '@opchan/core';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
@ -31,8 +35,8 @@ import {
|
|||||||
const PostList = () => {
|
const PostList = () => {
|
||||||
const { cellId } = useParams<{ cellId: string }>();
|
const { cellId } = useParams<{ cellId: string }>();
|
||||||
|
|
||||||
// ✅ Use reactive hooks for data and actions
|
const { createPost, vote, moderate, refresh, commentsByPost, cells, posts } =
|
||||||
const { createPost, vote, moderate, refresh, commentsByPost, cells, posts } = useContent();
|
useContent();
|
||||||
const cell = cells.find((c: ForumCell) => c.id === cellId);
|
const cell = cells.find((c: ForumCell) => c.id === cellId);
|
||||||
const isCreatingPost = false;
|
const isCreatingPost = false;
|
||||||
const isVoting = false;
|
const isVoting = false;
|
||||||
@ -101,7 +105,11 @@ const PostList = () => {
|
|||||||
if (!newPostContent.trim()) return;
|
if (!newPostContent.trim()) return;
|
||||||
|
|
||||||
// ✅ All validation handled in hook
|
// ✅ All validation handled in hook
|
||||||
const post = await createPost({ cellId, title: newPostTitle, content: newPostContent });
|
const post = await createPost({
|
||||||
|
cellId,
|
||||||
|
title: newPostTitle,
|
||||||
|
content: newPostContent,
|
||||||
|
});
|
||||||
if (post) {
|
if (post) {
|
||||||
setNewPostTitle('');
|
setNewPostTitle('');
|
||||||
setNewPostContent('');
|
setNewPostContent('');
|
||||||
@ -129,8 +137,12 @@ const PostList = () => {
|
|||||||
if (!currentUser) return null;
|
if (!currentUser) return null;
|
||||||
const p = posts.find((p: ForumPost) => p.id === postId);
|
const p = posts.find((p: ForumPost) => p.id === postId);
|
||||||
if (!p) return null;
|
if (!p) return null;
|
||||||
const up = p.upvotes.some((v: VoteMessage) => v.author === currentUser.address);
|
const up = p.upvotes.some(
|
||||||
const down = p.downvotes.some((v: VoteMessage) => v.author === currentUser.address);
|
(v: VoteMessage) => v.author === currentUser.address
|
||||||
|
);
|
||||||
|
const down = p.downvotes.some(
|
||||||
|
(v: VoteMessage) => v.author === currentUser.address
|
||||||
|
);
|
||||||
return up ? 'upvote' : down ? 'downvote' : null;
|
return up ? 'upvote' : down ? 'downvote' : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -248,9 +260,7 @@ const PostList = () => {
|
|||||||
|
|
||||||
{!canPost && !currentUser && (
|
{!canPost && !currentUser && (
|
||||||
<div className="section-spacing content-card-sm text-center">
|
<div className="section-spacing content-card-sm text-center">
|
||||||
<p className="text-sm mb-3">
|
<p className="text-sm mb-3">Connect your wallet to post</p>
|
||||||
Connect your wallet to post
|
|
||||||
</p>
|
|
||||||
<Button asChild size="sm">
|
<Button asChild size="sm">
|
||||||
<Link to="/">Connect Wallet</Link>
|
<Link to="/">Connect Wallet</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@ -277,9 +287,7 @@ const PostList = () => {
|
|||||||
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'upvote' ? 'text-cyber-accent' : ''}`}
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'upvote' ? 'text-cyber-accent' : ''}`}
|
||||||
onClick={() => handleVotePost(post.id, true)}
|
onClick={() => handleVotePost(post.id, true)}
|
||||||
disabled={!canVote || isVoting}
|
disabled={!canVote || isVoting}
|
||||||
title={
|
title={canVote ? 'Upvote' : 'Connect your wallet to vote'}
|
||||||
canVote ? 'Upvote' : 'Connect your wallet to vote'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ArrowUp className="w-4 h-4" />
|
<ArrowUp className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
@ -290,9 +298,7 @@ const PostList = () => {
|
|||||||
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'downvote' ? 'text-cyber-accent' : ''}`}
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'downvote' ? 'text-cyber-accent' : ''}`}
|
||||||
onClick={() => handleVotePost(post.id, false)}
|
onClick={() => handleVotePost(post.id, false)}
|
||||||
disabled={!canVote || isVoting}
|
disabled={!canVote || isVoting}
|
||||||
title={
|
title={canVote ? 'Downvote' : 'Connect your wallet to vote'}
|
||||||
canVote ? 'Downvote' : 'Connect your wallet to vote'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ArrowDown className="w-4 h-4" />
|
<ArrowDown className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
type CypherImageProps = {
|
type CypherImageProps = {
|
||||||
src?: string;
|
src?: string;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Share2 } from 'lucide-react';
|
import { Share2 } from 'lucide-react';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { useToast } from '../ui/use-toast';
|
import { useToast } from '../ui/use-toast';
|
||||||
|
|
||||||
interface ShareButtonProps {
|
interface ShareButtonProps {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Accordion = AccordionPrimitive.Root;
|
const Accordion = AccordionPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { buttonVariants } from '@/components/ui/button-variants';
|
import { buttonVariants } from '@/components/ui/button-variants';
|
||||||
|
|
||||||
const AlertDialog = AlertDialogPrimitive.Root;
|
const AlertDialog = AlertDialogPrimitive.Root;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
||||||
|
|||||||
@ -14,11 +14,12 @@ export function AuthorDisplay({
|
|||||||
className = '',
|
className = '',
|
||||||
showBadge = true,
|
showBadge = true,
|
||||||
}: AuthorDisplayProps) {
|
}: AuthorDisplayProps) {
|
||||||
const { ensName, ordinalDetails, callSign, displayName } = useUserDisplay(address);
|
const { ensName, ordinalDetails, callSign, displayName } =
|
||||||
|
useUserDisplay(address);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
console.log({ensName, ordinalDetails, callSign, displayName, address})
|
console.log({ ensName, ordinalDetails, callSign, displayName, address });
|
||||||
}, [address, ensName, ordinalDetails, callSign, displayName])
|
}, [address, ensName, ordinalDetails, callSign, displayName]);
|
||||||
|
|
||||||
// Only show a badge if the author has ENS, Ordinal, or Call Sign
|
// Only show a badge if the author has ENS, Ordinal, or Call Sign
|
||||||
const shouldShowBadge = showBadge && (ensName || ordinalDetails || callSign);
|
const shouldShowBadge = showBadge && (ensName || ordinalDetails || callSign);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Avatar = React.forwardRef<
|
const Avatar = React.forwardRef<
|
||||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Bookmark, BookmarkCheck } from 'lucide-react';
|
import { Bookmark, BookmarkCheck } from 'lucide-react';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
interface BookmarkButtonProps {
|
interface BookmarkButtonProps {
|
||||||
isBookmarked: boolean;
|
isBookmarked: boolean;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Bookmark, BookmarkType } from '@opchan/core';
|
import { Bookmark, BookmarkType } from '@opchan/core';
|
||||||
import { useUserDisplay } from '@opchan/react';
|
import { useUserDisplay } from '@opchan/react';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { Slot } from '@radix-ui/react-slot';
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
import { ChevronRight, MoreHorizontal } from 'lucide-react';
|
import { ChevronRight, MoreHorizontal } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Breadcrumb = React.forwardRef<
|
const Breadcrumb = React.forwardRef<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { Slot } from '@radix-ui/react-slot';
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
import { type VariantProps } from 'class-variance-authority';
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { buttonVariants } from './button-variants';
|
import { buttonVariants } from './button-variants';
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { DayPicker } from 'react-day-picker';
|
import { DayPicker } from 'react-day-picker';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { buttonVariants } from '@/components/ui/button-variants';
|
import { buttonVariants } from '@/components/ui/button-variants';
|
||||||
|
|
||||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
const Card = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import useEmblaCarousel, {
|
|||||||
} from 'embla-carousel-react';
|
} from 'embla-carousel-react';
|
||||||
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1];
|
type CarouselApi = UseEmblaCarouselType[1];
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as RechartsPrimitive from 'recharts';
|
import * as RechartsPrimitive from 'recharts';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
const THEMES = { light: '', dark: '.dark' } as const;
|
const THEMES = { light: '', dark: '.dark' } as const;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||||
import { Check } from 'lucide-react';
|
import { Check } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
const Checkbox = React.forwardRef<
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { type DialogProps } from '@radix-ui/react-dialog';
|
|||||||
import { Command as CommandPrimitive } from 'cmdk';
|
import { Command as CommandPrimitive } from 'cmdk';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
const Command = React.forwardRef<
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
||||||
import { Check, ChevronRight, Circle } from 'lucide-react';
|
import { Check, ChevronRight, Circle } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const ContextMenu = ContextMenuPrimitive.Root;
|
const ContextMenu = ContextMenuPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -194,7 +194,9 @@ export function DelegationStep({
|
|||||||
{/* User Address */}
|
{/* User Address */}
|
||||||
{currentUser && (
|
{currentUser && (
|
||||||
<div className="text-xs text-neutral-400">
|
<div className="text-xs text-neutral-400">
|
||||||
<div className="font-mono break-all">{currentUser.displayName}</div>
|
<div className="font-mono break-all">
|
||||||
|
{currentUser.displayName}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Drawer as DrawerPrimitive } from 'vaul';
|
import { Drawer as DrawerPrimitive } from 'vaul';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Drawer = ({
|
const Drawer = ({
|
||||||
shouldScaleBackground = true,
|
shouldScaleBackground = true,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||||
import { Check, ChevronRight, Circle } from 'lucide-react';
|
import { Check, ChevronRight, Circle } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
useFormContext,
|
useFormContext,
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
const Form = FormProvider;
|
const Form = FormProvider;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
|
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const HoverCard = HoverCardPrimitive.Root;
|
const HoverCard = HoverCardPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { OTPInput, OTPInputContext } from 'input-otp';
|
import { OTPInput, OTPInputContext } from 'input-otp';
|
||||||
import { Dot } from 'lucide-react';
|
import { Dot } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const InputOTP = React.forwardRef<
|
const InputOTP = React.forwardRef<
|
||||||
React.ElementRef<typeof OTPInput>,
|
React.ElementRef<typeof OTPInput>,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as LabelPrimitive from '@radix-ui/react-label';
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as MenubarPrimitive from '@radix-ui/react-menubar';
|
import * as MenubarPrimitive from '@radix-ui/react-menubar';
|
||||||
import { Check, ChevronRight, Circle } from 'lucide-react';
|
import { Check, ChevronRight, Circle } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const MenubarMenu = MenubarPrimitive.Menu;
|
const MenubarMenu = MenubarPrimitive.Menu;
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,14 @@ export function ModerationToggle() {
|
|||||||
const { canModerate } = usePermissions();
|
const { canModerate } = usePermissions();
|
||||||
const { cellsWithStats } = useContent();
|
const { cellsWithStats } = useContent();
|
||||||
|
|
||||||
const [showModerated, setShowModerated] = useUIState<boolean>('showModerated', false);
|
const [showModerated, setShowModerated] = useUIState<boolean>(
|
||||||
const toggleShowModerated = React.useCallback((value: boolean) => setShowModerated(value), [setShowModerated]);
|
'showModerated',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const toggleShowModerated = React.useCallback(
|
||||||
|
(value: boolean) => setShowModerated(value),
|
||||||
|
[setShowModerated]
|
||||||
|
);
|
||||||
|
|
||||||
// Check if user is admin of any cell
|
// Check if user is admin of any cell
|
||||||
const isAdminOfAnyCell = cellsWithStats.some(cell => canModerate(cell.id));
|
const isAdminOfAnyCell = cellsWithStats.some(cell => canModerate(cell.id));
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
|
|||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const NavigationMenu = React.forwardRef<
|
const NavigationMenu = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { ButtonProps } from '@/components/ui/button';
|
import { ButtonProps } from '@/components/ui/button';
|
||||||
import { buttonVariants } from '@/components/ui/button-variants';
|
import { buttonVariants } from '@/components/ui/button-variants';
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root;
|
const Popover = PopoverPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Progress = React.forwardRef<
|
const Progress = React.forwardRef<
|
||||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
||||||
import { Circle } from 'lucide-react';
|
import { Circle } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const RadioGroup = React.forwardRef<
|
const RadioGroup = React.forwardRef<
|
||||||
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
|
||||||
type ResizableTextareaProps =
|
type ResizableTextareaProps =
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { GripVertical } from 'lucide-react';
|
import { GripVertical } from 'lucide-react';
|
||||||
import * as ResizablePrimitive from 'react-resizable-panels';
|
import * as ResizablePrimitive from 'react-resizable-panels';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const ResizablePanelGroup = ({
|
const ResizablePanelGroup = ({
|
||||||
className,
|
className,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef<
|
const ScrollArea = React.forwardRef<
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root;
|
const Select = SelectPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
const Separator = React.forwardRef<
|
||||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
|||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Sheet = SheetPrimitive.Root;
|
const Sheet = SheetPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { VariantProps, cva } from 'class-variance-authority';
|
|||||||
import { PanelLeft } from 'lucide-react';
|
import { PanelLeft } from 'lucide-react';
|
||||||
|
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Switch = React.forwardRef<
|
const Switch = React.forwardRef<
|
||||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
const Table = React.forwardRef<
|
||||||
HTMLTableElement,
|
HTMLTableElement,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root;
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const Textarea = React.forwardRef<
|
const Textarea = React.forwardRef<
|
||||||
HTMLTextAreaElement,
|
HTMLTextAreaElement,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import * as ToastPrimitives from '@radix-ui/react-toast';
|
|||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const ToastProvider = ToastPrimitives.Provider;
|
const ToastProvider = ToastPrimitives.Provider;
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||||
import { type VariantProps } from 'class-variance-authority';
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { toggleVariants } from '@/components/ui/toggle-variants';
|
import { toggleVariants } from '@/components/ui/toggle-variants';
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
||||||
import { type VariantProps } from 'class-variance-authority';
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
import { toggleVariants } from './toggle-variants';
|
import { toggleVariants } from './toggle-variants';
|
||||||
|
|
||||||
const Toggle = React.forwardRef<
|
const Toggle = React.forwardRef<
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||||
|
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider;
|
const TooltipProvider = TooltipPrimitive.Provider;
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,9 @@ export function VerificationStep({
|
|||||||
verificationResult?.success &&
|
verificationResult?.success &&
|
||||||
verificationResult.message.includes('Checking ownership')
|
verificationResult.message.includes('Checking ownership')
|
||||||
) {
|
) {
|
||||||
const hasOwnership = currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED;
|
const hasOwnership =
|
||||||
|
currentUser?.verificationStatus ===
|
||||||
|
EVerificationStatus.ENS_ORDINAL_VERIFIED;
|
||||||
|
|
||||||
if (hasOwnership) {
|
if (hasOwnership) {
|
||||||
setVerificationResult({
|
setVerificationResult({
|
||||||
@ -115,7 +117,9 @@ export function VerificationStep({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getVerificationType = () => {
|
const getVerificationType = () => {
|
||||||
return currentUser?.walletType === 'bitcoin' ? 'Bitcoin Ordinal' : 'Ethereum ENS';
|
return currentUser?.walletType === 'bitcoin'
|
||||||
|
? 'Bitcoin Ordinal'
|
||||||
|
: 'Ethereum ENS';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVerificationIcon = () => {
|
const getVerificationIcon = () => {
|
||||||
@ -123,7 +127,9 @@ export function VerificationStep({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getVerificationColor = () => {
|
const getVerificationColor = () => {
|
||||||
return currentUser?.walletType === 'bitcoin' ? 'text-orange-500' : 'text-blue-500';
|
return currentUser?.walletType === 'bitcoin'
|
||||||
|
? 'text-orange-500'
|
||||||
|
: 'text-blue-500';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVerificationDescription = () => {
|
const getVerificationDescription = () => {
|
||||||
@ -206,7 +212,9 @@ export function VerificationStep({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show verification status
|
// Show verification status
|
||||||
if (currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED) {
|
if (
|
||||||
|
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
@ -222,8 +230,12 @@ export function VerificationStep({
|
|||||||
</p>
|
</p>
|
||||||
{currentUser && (
|
{currentUser && (
|
||||||
<div className="text-xs text-neutral-400">
|
<div className="text-xs text-neutral-400">
|
||||||
{currentUser?.walletType === 'bitcoin' && <p>Ordinal ID: Verified</p>}
|
{currentUser?.walletType === 'bitcoin' && (
|
||||||
{currentUser?.walletType === 'ethereum' && <p>ENS Name: Verified</p>}
|
<p>Ordinal ID: Verified</p>
|
||||||
|
)}
|
||||||
|
{currentUser?.walletType === 'ethereum' && (
|
||||||
|
<p>ENS Name: Verified</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Wifi, WifiOff, CheckCircle } from 'lucide-react';
|
import { Wifi, WifiOff, CheckCircle } from 'lucide-react';
|
||||||
import { useNetwork } from '@opchan/react';
|
import { useNetwork } from '@opchan/react';
|
||||||
import { cn } from '../../utils'
|
import { cn } from '../../utils';
|
||||||
|
|
||||||
interface WakuHealthIndicatorProps {
|
interface WakuHealthIndicatorProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -13,7 +13,7 @@ export function WakuHealthIndicator({
|
|||||||
showText = true,
|
showText = true,
|
||||||
size = 'md',
|
size = 'md',
|
||||||
}: WakuHealthIndicatorProps) {
|
}: WakuHealthIndicatorProps) {
|
||||||
const {isConnected, statusMessage} = useNetwork();
|
const { isConnected, statusMessage } = useNetwork();
|
||||||
|
|
||||||
const getIcon = () => {
|
const getIcon = () => {
|
||||||
if (isConnected === true) {
|
if (isConnected === true) {
|
||||||
@ -61,7 +61,8 @@ export function WakuHealthIndicator({
|
|||||||
*/
|
*/
|
||||||
export function WakuHealthDot({ className }: { className?: string }) {
|
export function WakuHealthDot({ className }: { className?: string }) {
|
||||||
const { isConnected } = useNetwork();
|
const { isConnected } = useNetwork();
|
||||||
const statusColor = isConnected === true ? 'green' : isConnected === false ? 'red' : 'gray';
|
const statusColor =
|
||||||
|
isConnected === true ? 'green' : isConnected === false ? 'red' : 'gray';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -194,7 +194,9 @@ export function WalletConnectionDialog({
|
|||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-neutral-300 mb-2">Address:</p>
|
<p className="text-sm text-neutral-300 mb-2">Address:</p>
|
||||||
<p className="text-xs font-mono text-neutral-400 break-all">
|
<p className="text-xs font-mono text-neutral-400 break-all">
|
||||||
{activeAddress ? `${activeAddress.slice(0, 6)}...${activeAddress.slice(-4)}` : ''}
|
{activeAddress
|
||||||
|
? `${activeAddress.slice(0, 6)}...${activeAddress.slice(-4)}`
|
||||||
|
: ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -29,8 +29,13 @@ export function WalletWizard({
|
|||||||
}: WalletWizardProps) {
|
}: WalletWizardProps) {
|
||||||
const [currentStep, setCurrentStep] = React.useState<WizardStep>(1);
|
const [currentStep, setCurrentStep] = React.useState<WizardStep>(1);
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
const [delegationStatus, setDelegationStatus] = React.useState<boolean>(false);
|
const [delegationStatus, setDelegationStatus] =
|
||||||
const { isAuthenticated, verificationStatus, delegationStatus: getDelegationStatus } = useAuth();
|
React.useState<boolean>(false);
|
||||||
|
const {
|
||||||
|
isAuthenticated,
|
||||||
|
verificationStatus,
|
||||||
|
delegationStatus: getDelegationStatus,
|
||||||
|
} = useAuth();
|
||||||
|
|
||||||
// Reset wizard when opened - always start at step 1 for simplicity
|
// Reset wizard when opened - always start at step 1 for simplicity
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -43,9 +48,11 @@ export function WalletWizard({
|
|||||||
// Load delegation status when component mounts or when user changes
|
// Load delegation status when component mounts or when user changes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
getDelegationStatus().then(status => {
|
getDelegationStatus()
|
||||||
setDelegationStatus(status.isValid);
|
.then(status => {
|
||||||
}).catch(console.error);
|
setDelegationStatus(status.isValid);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
} else {
|
} else {
|
||||||
setDelegationStatus(false);
|
setDelegationStatus(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
useAuth ,
|
useAuth,
|
||||||
useForum ,
|
useForum,
|
||||||
useNetwork,
|
useNetwork,
|
||||||
usePermissions,
|
usePermissions,
|
||||||
useContent,
|
useContent,
|
||||||
useUIState,
|
useUIState,
|
||||||
useUserDisplay,
|
useUserDisplay,
|
||||||
} from '@opchan/react';
|
} from '@opchan/react';
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,9 @@ if (!(window as Window & typeof globalThis).Buffer) {
|
|||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<WagmiProvider config={config}>
|
<WagmiProvider config={config}>
|
||||||
<AppKitProvider {...appkitConfig}>
|
<AppKitProvider {...appkitConfig}>
|
||||||
<OpchanWithAppKit config={{ ordiscanApiKey: '6bb07766-d98c-4ddd-93fb-6a0e94d629dd' }}>
|
<OpchanWithAppKit
|
||||||
|
config={{ ordiscanApiKey: '6bb07766-d98c-4ddd-93fb-6a0e94d629dd' }}
|
||||||
|
>
|
||||||
<App />
|
<App />
|
||||||
</OpchanWithAppKit>
|
</OpchanWithAppKit>
|
||||||
</AppKitProvider>
|
</AppKitProvider>
|
||||||
|
|||||||
@ -53,8 +53,12 @@ const BookmarksPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const postBookmarks = bookmarks.filter(bookmark => bookmark.type === BookmarkType.POST);
|
const postBookmarks = bookmarks.filter(
|
||||||
const commentBookmarks = bookmarks.filter(bookmark => bookmark.type === BookmarkType.COMMENT);
|
bookmark => bookmark.type === BookmarkType.POST
|
||||||
|
);
|
||||||
|
const commentBookmarks = bookmarks.filter(
|
||||||
|
bookmark => bookmark.type === BookmarkType.COMMENT
|
||||||
|
);
|
||||||
|
|
||||||
const getFilteredBookmarks = () => {
|
const getFilteredBookmarks = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
@ -79,7 +83,6 @@ const BookmarksPage = () => {
|
|||||||
await clearAllBookmarks();
|
await clearAllBookmarks();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@ -20,15 +20,17 @@ const FeedPage: React.FC = () => {
|
|||||||
const { verificationStatus } = useAuth();
|
const { verificationStatus } = useAuth();
|
||||||
const [sortOption, setSortOption] = useState<SortOption>('relevance');
|
const [sortOption, setSortOption] = useState<SortOption>('relevance');
|
||||||
|
|
||||||
|
const allPosts = useMemo(
|
||||||
// Build sorted posts from content slices
|
() => sortPosts([...content.posts], sortOption),
|
||||||
const allPosts = useMemo(() => sortPosts([...content.posts], sortOption), [content.posts, sortOption]);
|
[content.posts, sortOption]
|
||||||
|
);
|
||||||
// ✅ Get comment count from filtered organized data
|
|
||||||
const getCommentCount = (postId: string) => (content.commentsByPost[postId] || []).length;
|
|
||||||
|
|
||||||
// Loading skeleton
|
// Loading skeleton
|
||||||
if (!content.posts.length && !content.comments.length && !content.cells.length) {
|
if (
|
||||||
|
!content.posts.length &&
|
||||||
|
!content.comments.length &&
|
||||||
|
!content.cells.length
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-cyber-dark">
|
<div className="min-h-screen bg-cyber-dark">
|
||||||
<div className="container mx-auto px-4 py-6 max-w-6xl">
|
<div className="container mx-auto px-4 py-6 max-w-6xl">
|
||||||
@ -151,20 +153,13 @@ const FeedPage: React.FC = () => {
|
|||||||
{verificationStatus !==
|
{verificationStatus !==
|
||||||
EVerificationStatus.ENS_ORDINAL_VERIFIED && (
|
EVerificationStatus.ENS_ORDINAL_VERIFIED && (
|
||||||
<p className="text-sm text-cyber-neutral/80">
|
<p className="text-sm text-cyber-neutral/80">
|
||||||
Connect your wallet to
|
Connect your wallet to start posting
|
||||||
start posting
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
allPosts.map(post => (
|
allPosts.map(post => <PostCard key={post.id} post={post} />)
|
||||||
<PostCard
|
|
||||||
key={post.id}
|
|
||||||
post={post}
|
|
||||||
commentCount={getCommentCount(post.id)}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState} from 'react';
|
import { useState } from 'react';
|
||||||
import { useForum } from '@opchan/react';
|
import { useForum } from '@opchan/react';
|
||||||
import { useAuth } from '@opchan/react';
|
import { useAuth } from '@opchan/react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -27,8 +27,7 @@ import {
|
|||||||
Globe,
|
Globe,
|
||||||
Edit3,
|
Edit3,
|
||||||
Save,
|
Save,
|
||||||
X,
|
X,
|
||||||
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { EDisplayPreference, EVerificationStatus } from '@opchan/core';
|
import { EDisplayPreference, EVerificationStatus } from '@opchan/core';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
@ -42,7 +41,6 @@ export default function ProfilePage() {
|
|||||||
// Get current user from auth context for the address
|
// Get current user from auth context for the address
|
||||||
const { currentUser, delegationInfo } = useAuth();
|
const { currentUser, delegationInfo } = useAuth();
|
||||||
|
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [callSign, setCallSign] = useState('');
|
const [callSign, setCallSign] = useState('');
|
||||||
@ -253,36 +251,31 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-cyber-neutral">
|
<div className="text-sm text-cyber-neutral">
|
||||||
{/* Show ENS name if available */}
|
{/* Show ENS name if available */}
|
||||||
{(currentUser.ensDetails?.ensName ) && (
|
{currentUser.ensDetails?.ensName && (
|
||||||
<div>
|
<div>ENS: {currentUser.ensDetails?.ensName}</div>
|
||||||
ENS:{' '}
|
|
||||||
{currentUser.ensDetails?.ensName}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{/* Show Ordinal details if available */}
|
{/* Show Ordinal details if available */}
|
||||||
{(currentUser.ordinalDetails ) && (
|
{currentUser.ordinalDetails && (
|
||||||
<div>
|
<div>
|
||||||
Ordinal:{' '}
|
Ordinal:{' '}
|
||||||
{currentUser.ordinalDetails.ordinalDetails}
|
{currentUser.ordinalDetails.ordinalDetails}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Show fallback if neither ENS nor Ordinal */}
|
{/* Show fallback if neither ENS nor Ordinal */}
|
||||||
{!(
|
{!currentUser.ensDetails?.ensName &&
|
||||||
currentUser.ensDetails?.ensName
|
!currentUser.ordinalDetails?.ordinalDetails && (
|
||||||
) &&
|
<div>No ENS or Ordinal verification</div>
|
||||||
!(
|
)}
|
||||||
currentUser.ordinalDetails?.ordinalDetails
|
<div className="flex items-center gap-2 mt-2">
|
||||||
) && <div>No ENS or Ordinal verification</div>}
|
{getVerificationIcon()}
|
||||||
<div className="flex items-center gap-2 mt-2">
|
<Badge className={getVerificationColor()}>
|
||||||
{getVerificationIcon()}
|
{getVerificationText()}
|
||||||
<Badge className={getVerificationColor()}>
|
</Badge>
|
||||||
{getVerificationText()}
|
</div>
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Wallet Section */}
|
{/* Wallet Section */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@ -469,7 +462,9 @@ export default function ProfilePage() {
|
|||||||
Delegation
|
Delegation
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
variant={delegationInfo.isValid ? 'default' : 'secondary'}
|
variant={
|
||||||
|
delegationInfo.isValid ? 'default' : 'secondary'
|
||||||
|
}
|
||||||
className={
|
className={
|
||||||
delegationInfo.isValid
|
delegationInfo.isValid
|
||||||
? 'bg-green-500/20 text-green-400 border-green-500/30'
|
? 'bg-green-500/20 text-green-400 border-green-500/30'
|
||||||
@ -540,17 +535,17 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Warning for expired delegation */}
|
{/* Warning for expired delegation */}
|
||||||
{(!delegationInfo.isValid && delegationInfo.hasDelegation) && (
|
{!delegationInfo.isValid && delegationInfo.hasDelegation && (
|
||||||
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md">
|
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-md">
|
||||||
<div className="flex items-center gap-2 text-orange-400">
|
<div className="flex items-center gap-2 text-orange-400">
|
||||||
<AlertTriangle className="w-4 h-4" />
|
<AlertTriangle className="w-4 h-4" />
|
||||||
<span className="text-xs font-medium">
|
<span className="text-xs font-medium">
|
||||||
Delegation expired. Renew to continue using your
|
Delegation expired. Renew to continue using your
|
||||||
browser key.
|
browser key.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,37 +1,57 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { OpChanProvider, type WalletAdapter, type WalletAdapterAccount } from '@opchan/react';
|
import {
|
||||||
|
OpChanProvider,
|
||||||
|
type WalletAdapter,
|
||||||
|
type WalletAdapterAccount,
|
||||||
|
} from '@opchan/react';
|
||||||
import { useAppKitAccount, modal } from '@reown/appkit/react';
|
import { useAppKitAccount, modal } from '@reown/appkit/react';
|
||||||
import { AppKit } from '@reown/appkit';
|
import { AppKit } from '@reown/appkit';
|
||||||
import type { OpChanClientConfig } from '@opchan/core';
|
import type { OpChanClientConfig } from '@opchan/core';
|
||||||
import { walletManager } from '@opchan/core';
|
import { walletManager } from '@opchan/core';
|
||||||
|
|
||||||
interface Props { config: OpChanClientConfig; children: React.ReactNode }
|
interface Props {
|
||||||
|
config: OpChanClientConfig;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export const OpchanWithAppKit: React.FC<Props> = ({ config, children }) => {
|
export const OpchanWithAppKit: React.FC<Props> = ({ config, children }) => {
|
||||||
const btc = useAppKitAccount({ namespace: 'bip122' });
|
const btc = useAppKitAccount({ namespace: 'bip122' });
|
||||||
const eth = useAppKitAccount({ namespace: 'eip155' });
|
const eth = useAppKitAccount({ namespace: 'eip155' });
|
||||||
|
|
||||||
const listenersRef = React.useRef(new Set<(a: WalletAdapterAccount | null) => void>());
|
const listenersRef = React.useRef(
|
||||||
|
new Set<(a: WalletAdapterAccount | null) => void>()
|
||||||
|
);
|
||||||
|
|
||||||
const getCurrent = React.useCallback((): WalletAdapterAccount | null => {
|
const getCurrent = React.useCallback((): WalletAdapterAccount | null => {
|
||||||
if (btc.isConnected && btc.address) return { address: btc.address, walletType: 'bitcoin' };
|
if (btc.isConnected && btc.address)
|
||||||
if (eth.isConnected && eth.address) return { address: eth.address, walletType: 'ethereum' };
|
return { address: btc.address, walletType: 'bitcoin' };
|
||||||
|
if (eth.isConnected && eth.address)
|
||||||
|
return { address: eth.address, walletType: 'ethereum' };
|
||||||
return null;
|
return null;
|
||||||
}, [btc.isConnected, btc.address, eth.isConnected, eth.address]);
|
}, [btc.isConnected, btc.address, eth.isConnected, eth.address]);
|
||||||
|
|
||||||
const adapter = React.useMemo<WalletAdapter>(() => ({
|
const adapter = React.useMemo<WalletAdapter>(
|
||||||
getAccount: () => getCurrent(),
|
() => ({
|
||||||
onChange: (cb) => {
|
getAccount: () => getCurrent(),
|
||||||
listenersRef.current.add(cb);
|
onChange: cb => {
|
||||||
return () => { listenersRef.current.delete(cb); };
|
listenersRef.current.add(cb);
|
||||||
},
|
return () => {
|
||||||
}), [getCurrent]);
|
listenersRef.current.delete(cb);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[getCurrent]
|
||||||
|
);
|
||||||
|
|
||||||
// Notify listeners when AppKit account changes
|
// Notify listeners when AppKit account changes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const account = getCurrent();
|
const account = getCurrent();
|
||||||
listenersRef.current.forEach(cb => {
|
listenersRef.current.forEach(cb => {
|
||||||
try { cb(account); } catch { /* ignore */ }
|
try {
|
||||||
|
cb(account);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [getCurrent]);
|
}, [getCurrent]);
|
||||||
|
|
||||||
@ -56,5 +76,3 @@ export const OpchanWithAppKit: React.FC<Props> = ({ config, children }) => {
|
|||||||
</OpChanProvider>
|
</OpChanProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { clsx, type ClassValue } from 'clsx';
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
export { urlLoads } from './urlLoads';
|
export { urlLoads } from './urlLoads';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user