chore: linting + improve CSS

This commit is contained in:
Danish Arora 2025-10-03 19:00:01 +05:30
parent b6e78ac71c
commit f9863121ba
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
70 changed files with 386 additions and 313 deletions

View File

@ -1,5 +1,5 @@
import React from 'react';
import { useForumData } from '@/hooks';
import React, { useMemo } from 'react';
import { useForum } from '@/hooks';
import { Link } from 'react-router-dom';
import { formatDistanceToNow } from 'date-fns';
import { Skeleton } from '@/components/ui/skeleton';
@ -35,54 +35,48 @@ interface CommentFeedItem extends FeedItemBase {
type FeedItem = PostFeedItem | CommentFeedItem;
const ActivityFeed: React.FC = () => {
// ✅ Use reactive hooks for data
const forumData = useForumData();
const { content, network } = useForum();
const {
postsWithVoteStatus,
commentsWithVoteStatus,
cellsWithStats,
isInitialLoading,
} = forumData;
const { posts, comments, cells, commentsByPost } = content;
const { isConnected } = network;
// ✅ Use pre-computed data with vote scores
const combinedFeed: FeedItem[] = [
...postsWithVoteStatus.map(
(post): PostFeedItem => ({
id: post.id,
type: 'post',
timestamp: post.timestamp,
ownerAddress: post.author,
title: post.title,
cellId: post.cellId,
postId: post.id,
commentCount: forumData.commentsByPost[post.id]?.length || 0,
voteCount: post.voteScore,
})
),
...commentsWithVoteStatus
.map((comment): CommentFeedItem | null => {
const parentPost = postsWithVoteStatus.find(
p => p.id === comment.postId
);
if (!parentPost) return null;
return {
id: comment.id,
type: 'comment',
timestamp: comment.timestamp,
ownerAddress: comment.author,
content: comment.content,
postId: comment.postId,
cellId: parentPost.cellId,
voteCount: comment.voteScore,
};
})
.filter((item): item is CommentFeedItem => item !== null),
].sort((a, b) => b.timestamp - a.timestamp);
const combinedFeed: FeedItem[] = useMemo(() => {
return [
...posts.map(
(post): PostFeedItem => ({
...post,
type: 'post',
ownerAddress: post.authorAddress,
cellId: post.cellId,
postId: post.id,
title: post.title,
commentCount: commentsByPost[post.id]?.length || 0,
voteCount: post.upvotes.length - post.downvotes.length,
})
),
...comments
.map((comment): CommentFeedItem | null => {
const parentPost = posts.find(p => p.id === comment.postId);
if (!parentPost) return null;
return {
id: comment.id,
type: 'comment',
timestamp: comment.timestamp,
ownerAddress: comment.author,
content: comment.content,
postId: comment.postId,
cellId: parentPost.cellId,
voteCount: comment.upvotes.length - comment.downvotes.length,
};
})
.filter((item): item is CommentFeedItem => item !== null),
].sort((a, b) => b.timestamp - a.timestamp);
}, [posts, comments, commentsByPost]);
const renderFeedItem = (item: FeedItem) => {
const cell = item.cellId
? cellsWithStats.find(c => c.id === item.cellId)
? cells.find(c => c.id === item.cellId)
: undefined;
const timeAgo = formatDistanceToNow(new Date(item.timestamp), {
addSuffix: true,
@ -150,7 +144,7 @@ const ActivityFeed: React.FC = () => {
);
};
if (isInitialLoading) {
if (!isConnected) {
return (
<div className="space-y-3">
{[...Array(5)].map((_, i) => (

View File

@ -1,6 +1,6 @@
import { useState, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { useContent, usePermissions } from '@/hooks';
import { useContent, usePermissions } from '@/hooks';
import {
Layout,
MessageSquare,
@ -226,9 +226,7 @@ const CellList = () => {
title="Refresh data"
className="px-3 border-cyber-muted/30 text-cyber-neutral hover:bg-cyber-muted/30"
>
<RefreshCw
className="w-4 h-4"
/>
<RefreshCw className="w-4 h-4" />
</Button>
{canCreateCell && <CreateCellDialog />}

View File

@ -61,10 +61,14 @@ const CommentCard: React.FC<CommentCardProps> = ({
// Use library pending API
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 userDownvoted = Boolean(comment.downvotes.some(v => v.author === currentUser?.address));
const userUpvoted = Boolean(comment.upvotes.some(v => v.author === currentUser?.address));
const userDownvoted = Boolean(
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 handleVoteComment = async (isUpvote: boolean) => {
@ -86,7 +90,9 @@ const CommentCard: React.FC<CommentCardProps> = ({
<div className="flex flex-col items-center">
<button
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)}
disabled={!permissions.canVote}
@ -99,7 +105,9 @@ const CommentCard: React.FC<CommentCardProps> = ({
<span className="text-sm font-bold">{score}</span>
<button
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)}
disabled={!permissions.canVote}

View File

@ -79,7 +79,8 @@ export function CreateCellDialog({
if (!canCreateCell) {
toast({
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',
});
return;

View File

@ -4,16 +4,15 @@ import { TrendingUp, Users, Eye, CheckCircle } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { useAuth, useContent } from '@/hooks';
import { useAuth, useContent } from '@/hooks';
import { EVerificationStatus } from '@opchan/core';
import { CypherImage } from '@/components/ui/CypherImage';
const FeedSidebar: React.FC = () => {
const {cells, posts, comments, cellsWithStats, userVerificationStatus} = useContent();
const { cells, posts, comments, cellsWithStats, userVerificationStatus } =
useContent();
const { currentUser, verificationStatus } = useAuth();
const stats = {
totalCells: cells.length,
totalPosts: posts.length,
@ -61,7 +60,9 @@ const FeedSidebar: React.FC = () => {
<Users className="w-5 h-5 text-cyber-accent" />
</div>
<div className="flex-1">
<div className="font-medium text-sm">{currentUser?.displayName}</div>
<div className="font-medium text-sm">
{currentUser?.displayName}
</div>
<Badge
variant="secondary"
className={`${verificationBadge.color} text-white text-xs`}

View File

@ -49,9 +49,9 @@ import { WakuHealthDot } from '@/components/ui/waku-health-indicator';
const Header = () => {
const { currentUser, delegationInfo } = useAuth();
const {statusMessage} = useNetwork();
const { statusMessage } = useNetwork();
const location = useLocation()
const location = useLocation();
const { toast } = useToast();
const { content } = useForum();
@ -65,7 +65,10 @@ const Header = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
// 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
React.useEffect(() => {
@ -109,23 +112,26 @@ const Header = () => {
}
};
useEffect(() => {
console.log('currentUser', currentUser)
}, [currentUser])
console.log('currentUser', currentUser);
}, [currentUser]);
const getStatusIcon = () => {
if (!isConnected) return <CircleSlash className="w-4 h-4" />;
if (
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED &&
currentUser?.verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED &&
delegationInfo?.isValid
) {
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" />;
} else if (
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
currentUser?.verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED
) {
return <Key className="w-4 h-4" />;
} else {
@ -197,11 +203,13 @@ const Header = () => {
>
{getStatusIcon()}
<span className="ml-1">
{currentUser?.verificationStatus === EVerificationStatus.WALLET_UNCONNECTED
{currentUser?.verificationStatus ===
EVerificationStatus.WALLET_UNCONNECTED
? 'CONNECT'
: delegationInfo?.isValid
? 'READY'
: currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
: currentUser?.verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED
? 'EXPIRED'
: 'DELEGATE'}
</span>
@ -215,7 +223,9 @@ const Header = () => {
size="sm"
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" />
</Button>
</DropdownMenuTrigger>

View File

@ -2,59 +2,52 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { ArrowUp, ArrowDown, MessageSquare } from 'lucide-react';
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 { AuthorDisplay } from '@/components/ui/author-display';
import { BookmarkButton } from '@/components/ui/bookmark-button';
import { LinkRenderer } from '@/components/ui/link-renderer';
import { useContent, usePermissions } from '@/hooks';
import { useAuth, useContent, usePermissions } from '@/hooks';
import { ShareButton } from '@/components/ui/ShareButton';
interface PostCardProps {
post: Post | PostMessage;
commentCount?: number;
post: Post;
}
const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
const content = useContent();
const PostCard: React.FC<PostCardProps> = ({ post }) => {
const {
bookmarks,
pending,
vote,
togglePostBookmark,
cells,
commentsByPost,
} = useContent();
const permissions = usePermissions();
const { currentUser } = useAuth();
// Get cell data from content
const cell = content.cells.find((c) => c.id === post.cellId);
const cellName = cell?.name || 'unknown';
const cellName = cells.find(c => c.id === post.cellId)?.name || 'unknown';
const commentCount = commentsByPost[post.id]?.length || 0;
// Use pre-computed vote data or safely compute from arrays when available
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;
const isPending = pending.isPending(post.id);
// Use library pending API
const isPending = content.pending.isPending(post.id);
// 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 isBookmarked = bookmarks.some(
b => b.targetId === post.id && b.type === 'post'
);
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 = typeof post.content === 'string' ? post.content : String(post.content ?? '');
const contentText =
typeof post.content === 'string'
? post.content
: String(post.content ?? '');
const contentPreview =
contentText.length > 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) => {
e.preventDefault();
await content.vote({ targetId: post.id, isUpvote });
await vote({ targetId: post.id, isUpvote });
};
const handleBookmark = async (e?: React.MouseEvent) => {
@ -72,7 +65,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
}
setBookmarkLoading(true);
try {
await content.togglePostBookmark(post, post.cellId);
await togglePostBookmark(post, post.cellId);
} finally {
setBookmarkLoading(false);
}
@ -97,13 +90,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
</button>
<span
className={`text-sm font-medium px-1 ${
score > 0
? 'text-cyber-accent'
: score < 0
? 'text-blue-400'
: 'text-cyber-neutral'
}`}
className={`text-sm font-medium px-1`}
>
{score}
</span>
@ -130,9 +117,17 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
<div className="block hover:opacity-80">
{/* Post metadata */}
<div className="flex items-center text-xs text-cyber-neutral mb-2 space-x-2">
<span className="font-medium text-cyber-accent">
r/{cellName}
</span>
<Link
to={cellName ? `/cell/${post.cellId}` : "#"}
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>Posted by u/</span>
<AuthorDisplay
@ -146,18 +141,23 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
addSuffix: true,
})}
</span>
{('relevanceScore' in post) && typeof (post as Post).relevanceScore === 'number' && (
<>
<span></span>
<RelevanceIndicator
score={(post as Post).relevanceScore as number}
details={('relevanceDetails' in post ? (post as Post).relevanceDetails : undefined)}
type="post"
className="text-xs"
showTooltip={true}
/>
</>
)}
{'relevanceScore' in post &&
typeof (post as Post).relevanceScore === 'number' && (
<>
<span></span>
<RelevanceIndicator
score={(post as Post).relevanceScore as number}
details={
'relevanceDetails' in post
? (post as Post).relevanceDetails
: undefined
}
type="post"
className="text-xs"
showTooltip={true}
/>
</>
)}
</div>
{/* Post title and content - clickable to navigate to post */}

View File

@ -17,7 +17,7 @@ import { AuthorDisplay } from './ui/author-display';
import { BookmarkButton } from './ui/bookmark-button';
import { MarkdownRenderer } from './ui/markdown-renderer';
import CommentCard from './CommentCard';
import { useContent, usePermissions } from '@/hooks';
import { useAuth, useContent, usePermissions } from '@/hooks';
import type { Cell as ForumCell } from '@opchan/core';
import { ShareButton } from './ui/ShareButton';
@ -28,17 +28,19 @@ const PostDetail = () => {
// Use aggregated forum API
const content = useContent();
const permissions = usePermissions();
const { currentUser } = useAuth();
// Get post and comments using focused hooks
const post = content.posts.find((p) => p.id === postId);
const visibleComments = postId ? content.commentsByPost[postId] ?? [] : [];
const post = content.posts.find(p => p.id === postId);
const visibleComments = postId ? (content.commentsByPost[postId] ?? []) : [];
// Use library pending API
const postPending = content.pending.isPending(post?.id);
const postVotePending = content.pending.isPending(post?.id);
// 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 [newComment, setNewComment] = useState('');
@ -115,11 +117,13 @@ const PostDetail = () => {
}
};
// Get vote status from post data (enhanced posts only)
const enhanced = post as unknown as { userUpvoted?: boolean; userDownvoted?: boolean; voteScore?: number };
const isPostUpvoted = Boolean(enhanced.userUpvoted);
const isPostDownvoted = Boolean(enhanced.userDownvoted);
const score = typeof enhanced.voteScore === 'number' ? enhanced.voteScore : 0;
const score = post.upvotes.length - post.downvotes.length;
const isPostUpvoted = Boolean(
post.upvotes.some(v => v.author === currentUser?.address)
);
const isPostDownvoted = Boolean(
post.downvotes.some(v => v.author === currentUser?.address)
);
const handleModerateComment = async (commentId: string) => {
const reason =
@ -160,7 +164,9 @@ const PostDetail = () => {
<div className="flex flex-col items-center">
<button
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)}
disabled={!permissions.canVote}
@ -173,7 +179,9 @@ const PostDetail = () => {
<span className="text-sm font-bold">{score}</span>
<button
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)}
disabled={!permissions.canVote}
@ -194,9 +202,17 @@ const PostDetail = () => {
<div className="flex-1">
<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'}
</span>
</Link>
<span></span>
<span>Posted by u/</span>
<AuthorDisplay
@ -279,9 +295,7 @@ const PostDetail = () => {
{!permissions.canComment && (
<div className="mb-6 p-4 border border-cyber-muted rounded-sm bg-cyber-muted/20 text-center">
<p className="text-sm mb-3">
Connect your wallet to comment
</p>
<p className="text-sm mb-3">Connect your wallet to comment</p>
<Button asChild size="sm">
<Link to="/">Connect Wallet</Link>
</Button>
@ -306,7 +320,7 @@ const PostDetail = () => {
</p>
</div>
) : (
visibleComments.map((comment) => (
visibleComments.map(comment => (
<CommentCard
key={comment.id}
comment={comment}

View File

@ -1,7 +1,11 @@
import React, { useState } from 'react';
import { Link, useParams } from 'react-router-dom';
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 { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
@ -31,8 +35,8 @@ import {
const PostList = () => {
const { cellId } = useParams<{ cellId: string }>();
// ✅ Use reactive hooks for data and actions
const { createPost, vote, moderate, refresh, commentsByPost, cells, posts } = useContent();
const { createPost, vote, moderate, refresh, commentsByPost, cells, posts } =
useContent();
const cell = cells.find((c: ForumCell) => c.id === cellId);
const isCreatingPost = false;
const isVoting = false;
@ -101,7 +105,11 @@ const PostList = () => {
if (!newPostContent.trim()) return;
// ✅ 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) {
setNewPostTitle('');
setNewPostContent('');
@ -129,8 +137,12 @@ const PostList = () => {
if (!currentUser) return null;
const p = posts.find((p: ForumPost) => p.id === postId);
if (!p) return null;
const up = p.upvotes.some((v: VoteMessage) => v.author === currentUser.address);
const down = p.downvotes.some((v: VoteMessage) => v.author === currentUser.address);
const up = p.upvotes.some(
(v: VoteMessage) => v.author === currentUser.address
);
const down = p.downvotes.some(
(v: VoteMessage) => v.author === currentUser.address
);
return up ? 'upvote' : down ? 'downvote' : null;
};
@ -248,9 +260,7 @@ const PostList = () => {
{!canPost && !currentUser && (
<div className="section-spacing content-card-sm text-center">
<p className="text-sm mb-3">
Connect your wallet to post
</p>
<p className="text-sm mb-3">Connect your wallet to post</p>
<Button asChild size="sm">
<Link to="/">Connect Wallet</Link>
</Button>
@ -277,9 +287,7 @@ const PostList = () => {
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'upvote' ? 'text-cyber-accent' : ''}`}
onClick={() => handleVotePost(post.id, true)}
disabled={!canVote || isVoting}
title={
canVote ? 'Upvote' : 'Connect your wallet to vote'
}
title={canVote ? 'Upvote' : 'Connect your wallet to vote'}
>
<ArrowUp className="w-4 h-4" />
</button>
@ -290,9 +298,7 @@ const PostList = () => {
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'downvote' ? 'text-cyber-accent' : ''}`}
onClick={() => handleVotePost(post.id, false)}
disabled={!canVote || isVoting}
title={
canVote ? 'Downvote' : 'Connect your wallet to vote'
}
title={canVote ? 'Downvote' : 'Connect your wallet to vote'}
>
<ArrowDown className="w-4 h-4" />
</button>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
type CypherImageProps = {
src?: string;

View File

@ -1,6 +1,6 @@
import { Button } from '@/components/ui/button';
import { Share2 } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { useToast } from '../ui/use-toast';
interface ShareButtonProps {

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDown } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Accordion = AccordionPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { buttonVariants } from '@/components/ui/button-variants';
const AlertDialog = AlertDialogPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
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',

View File

@ -14,11 +14,12 @@ export function AuthorDisplay({
className = '',
showBadge = true,
}: AuthorDisplayProps) {
const { ensName, ordinalDetails, callSign, displayName } = useUserDisplay(address);
const { ensName, ordinalDetails, callSign, displayName } =
useUserDisplay(address);
useEffect(()=> {
console.log({ensName, ordinalDetails, callSign, displayName, address})
}, [address, ensName, ordinalDetails, callSign, displayName])
useEffect(() => {
console.log({ ensName, ordinalDetails, callSign, displayName, address });
}, [address, ensName, ordinalDetails, callSign, displayName]);
// Only show a badge if the author has ENS, Ordinal, or Call Sign
const shouldShowBadge = showBadge && (ensName || ordinalDetails || callSign);

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
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',

View File

@ -1,6 +1,6 @@
import { Button } from '@/components/ui/button';
import { Bookmark, BookmarkCheck } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
interface BookmarkButtonProps {
isBookmarked: boolean;

View File

@ -10,7 +10,7 @@ import {
} from 'lucide-react';
import { Bookmark, BookmarkType } from '@opchan/core';
import { useUserDisplay } from '@opchan/react';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { formatDistanceToNow } from 'date-fns';
import { useNavigate } from 'react-router-dom';

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { ChevronRight, MoreHorizontal } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Breadcrumb = React.forwardRef<
HTMLElement,

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { buttonVariants } from './button-variants';
export interface ButtonProps

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { buttonVariants } from '@/components/ui/button-variants';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Card = React.forwardRef<
HTMLDivElement,

View File

@ -4,7 +4,7 @@ import useEmblaCarousel, {
} from 'embla-carousel-react';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { Button } from '@/components/ui/button';
type CarouselApi = UseEmblaCarouselType[1];

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as RechartsPrimitive from 'recharts';
import { cn } from '../../utils'
import { cn } from '../../utils';
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: '', dark: '.dark' } as const;

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,

View File

@ -3,7 +3,7 @@ import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { Dialog, DialogContent } from '@/components/ui/dialog';
const Command = React.forwardRef<

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const ContextMenu = ContextMenuPrimitive.Root;

View File

@ -194,7 +194,9 @@ export function DelegationStep({
{/* User Address */}
{currentUser && (
<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>
)}

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Dialog = DialogPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Drawer = ({
shouldScaleBackground = true,

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const DropdownMenu = DropdownMenuPrimitive.Root;

View File

@ -10,7 +10,7 @@ import {
useFormContext,
} from 'react-hook-form';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { Label } from '@/components/ui/label';
const Form = FormProvider;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import { cn } from '../../utils'
import { cn } from '../../utils';
const HoverCard = HoverCardPrimitive.Root;

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Dot } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({ className, type, ...props }, ref) => {

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as MenubarPrimitive from '@radix-ui/react-menubar';
import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const MenubarMenu = MenubarPrimitive.Menu;

View File

@ -8,8 +8,14 @@ export function ModerationToggle() {
const { canModerate } = usePermissions();
const { cellsWithStats } = useContent();
const [showModerated, setShowModerated] = useUIState<boolean>('showModerated', false);
const toggleShowModerated = React.useCallback((value: boolean) => setShowModerated(value), [setShowModerated]);
const [showModerated, setShowModerated] = useUIState<boolean>(
'showModerated',
false
);
const toggleShowModerated = React.useCallback(
(value: boolean) => setShowModerated(value),
[setShowModerated]
);
// Check if user is admin of any cell
const isAdminOfAnyCell = cellsWithStats.some(cell => canModerate(cell.id));

View File

@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { ButtonProps } from '@/components/ui/button';
import { buttonVariants } from '@/components/ui/button-variants';

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Popover = PopoverPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { Resizable } from 're-resizable';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { Textarea } from '@/components/ui/textarea';
type ResizableTextareaProps =

View File

@ -1,7 +1,7 @@
import { GripVertical } from 'lucide-react';
import * as ResizablePrimitive from 'react-resizable-panels';
import { cn } from '../../utils'
import { cn } from '../../utils';
const ResizablePanelGroup = ({
className,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '../../utils'
import { cn } from '../../utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Select = SelectPrimitive.Root;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,

View File

@ -3,7 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Sheet = SheetPrimitive.Root;

View File

@ -4,7 +4,7 @@ import { VariantProps, cva } from 'class-variance-authority';
import { PanelLeft } from 'lucide-react';
import { useIsMobile } from '@/hooks/use-mobile';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Table = React.forwardRef<
HTMLTableElement,

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Tabs = TabsPrimitive.Root;

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const Textarea = React.forwardRef<
HTMLTextAreaElement,

View File

@ -3,7 +3,7 @@ import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';
import { cn } from '../../utils'
import { cn } from '../../utils';
const ToastProvider = ToastPrimitives.Provider;

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { toggleVariants } from '@/components/ui/toggle-variants';
const ToggleGroupContext = React.createContext<

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '../../utils'
import { cn } from '../../utils';
import { toggleVariants } from './toggle-variants';
const Toggle = React.forwardRef<

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '../../utils'
import { cn } from '../../utils';
const TooltipProvider = TooltipPrimitive.Provider;

View File

@ -39,7 +39,9 @@ export function VerificationStep({
verificationResult?.success &&
verificationResult.message.includes('Checking ownership')
) {
const hasOwnership = currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED;
const hasOwnership =
currentUser?.verificationStatus ===
EVerificationStatus.ENS_ORDINAL_VERIFIED;
if (hasOwnership) {
setVerificationResult({
@ -115,7 +117,9 @@ export function VerificationStep({
};
const getVerificationType = () => {
return currentUser?.walletType === 'bitcoin' ? 'Bitcoin Ordinal' : 'Ethereum ENS';
return currentUser?.walletType === 'bitcoin'
? 'Bitcoin Ordinal'
: 'Ethereum ENS';
};
const getVerificationIcon = () => {
@ -123,7 +127,9 @@ export function VerificationStep({
};
const getVerificationColor = () => {
return currentUser?.walletType === 'bitcoin' ? 'text-orange-500' : 'text-blue-500';
return currentUser?.walletType === 'bitcoin'
? 'text-orange-500'
: 'text-blue-500';
};
const getVerificationDescription = () => {
@ -206,7 +212,9 @@ export function VerificationStep({
}
// Show verification status
if (currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED) {
if (
currentUser?.verificationStatus === EVerificationStatus.ENS_ORDINAL_VERIFIED
) {
return (
<div className="flex flex-col h-full">
<div className="flex-1 space-y-4">
@ -222,8 +230,12 @@ export function VerificationStep({
</p>
{currentUser && (
<div className="text-xs text-neutral-400">
{currentUser?.walletType === 'bitcoin' && <p>Ordinal ID: Verified</p>}
{currentUser?.walletType === 'ethereum' && <p>ENS Name: Verified</p>}
{currentUser?.walletType === 'bitcoin' && (
<p>Ordinal ID: Verified</p>
)}
{currentUser?.walletType === 'ethereum' && (
<p>ENS Name: Verified</p>
)}
</div>
)}
</div>

View File

@ -1,6 +1,6 @@
import { Wifi, WifiOff, CheckCircle } from 'lucide-react';
import { useNetwork } from '@opchan/react';
import { cn } from '../../utils'
import { cn } from '../../utils';
interface WakuHealthIndicatorProps {
className?: string;
@ -13,7 +13,7 @@ export function WakuHealthIndicator({
showText = true,
size = 'md',
}: WakuHealthIndicatorProps) {
const {isConnected, statusMessage} = useNetwork();
const { isConnected, statusMessage } = useNetwork();
const getIcon = () => {
if (isConnected === true) {
@ -61,7 +61,8 @@ export function WakuHealthIndicator({
*/
export function WakuHealthDot({ className }: { className?: string }) {
const { isConnected } = useNetwork();
const statusColor = isConnected === true ? 'green' : isConnected === false ? 'red' : 'gray';
const statusColor =
isConnected === true ? 'green' : isConnected === false ? 'red' : 'gray';
return (
<div

View File

@ -194,7 +194,9 @@ export function WalletConnectionDialog({
</p>
<p className="text-sm text-neutral-300 mb-2">Address:</p>
<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>
</div>

View File

@ -29,8 +29,13 @@ export function WalletWizard({
}: WalletWizardProps) {
const [currentStep, setCurrentStep] = React.useState<WizardStep>(1);
const [isLoading, setIsLoading] = React.useState(false);
const [delegationStatus, setDelegationStatus] = React.useState<boolean>(false);
const { isAuthenticated, verificationStatus, delegationStatus: getDelegationStatus } = useAuth();
const [delegationStatus, setDelegationStatus] =
React.useState<boolean>(false);
const {
isAuthenticated,
verificationStatus,
delegationStatus: getDelegationStatus,
} = useAuth();
// Reset wizard when opened - always start at step 1 for simplicity
React.useEffect(() => {
@ -43,9 +48,11 @@ export function WalletWizard({
// Load delegation status when component mounts or when user changes
React.useEffect(() => {
if (isAuthenticated) {
getDelegationStatus().then(status => {
setDelegationStatus(status.isValid);
}).catch(console.error);
getDelegationStatus()
.then(status => {
setDelegationStatus(status.isValid);
})
.catch(console.error);
} else {
setDelegationStatus(false);
}

View File

@ -1,11 +1,9 @@
export {
useAuth ,
useForum ,
useAuth,
useForum,
useNetwork,
usePermissions,
useContent,
useUIState,
useUserDisplay,
} from '@opchan/react';

View File

@ -14,7 +14,9 @@ if (!(window as Window & typeof globalThis).Buffer) {
createRoot(document.getElementById('root')!).render(
<WagmiProvider config={config}>
<AppKitProvider {...appkitConfig}>
<OpchanWithAppKit config={{ ordiscanApiKey: '6bb07766-d98c-4ddd-93fb-6a0e94d629dd' }}>
<OpchanWithAppKit
config={{ ordiscanApiKey: '6bb07766-d98c-4ddd-93fb-6a0e94d629dd' }}
>
<App />
</OpchanWithAppKit>
</AppKitProvider>

View File

@ -53,8 +53,12 @@ const BookmarksPage = () => {
);
}
const postBookmarks = bookmarks.filter(bookmark => bookmark.type === BookmarkType.POST);
const commentBookmarks = bookmarks.filter(bookmark => bookmark.type === BookmarkType.COMMENT);
const postBookmarks = bookmarks.filter(
bookmark => bookmark.type === BookmarkType.POST
);
const commentBookmarks = bookmarks.filter(
bookmark => bookmark.type === BookmarkType.COMMENT
);
const getFilteredBookmarks = () => {
switch (activeTab) {
@ -79,7 +83,6 @@ const BookmarksPage = () => {
await clearAllBookmarks();
};
return (
<div className="page-container">
<Header />

View File

@ -20,15 +20,17 @@ const FeedPage: React.FC = () => {
const { verificationStatus } = useAuth();
const [sortOption, setSortOption] = useState<SortOption>('relevance');
// Build sorted posts from content slices
const allPosts = useMemo(() => sortPosts([...content.posts], sortOption), [content.posts, sortOption]);
// ✅ Get comment count from filtered organized data
const getCommentCount = (postId: string) => (content.commentsByPost[postId] || []).length;
const allPosts = useMemo(
() => sortPosts([...content.posts], sortOption),
[content.posts, sortOption]
);
// Loading skeleton
if (!content.posts.length && !content.comments.length && !content.cells.length) {
if (
!content.posts.length &&
!content.comments.length &&
!content.cells.length
) {
return (
<div className="min-h-screen bg-cyber-dark">
<div className="container mx-auto px-4 py-6 max-w-6xl">
@ -151,20 +153,13 @@ const FeedPage: React.FC = () => {
{verificationStatus !==
EVerificationStatus.ENS_ORDINAL_VERIFIED && (
<p className="text-sm text-cyber-neutral/80">
Connect your wallet to
start posting
Connect your wallet to start posting
</p>
)}
</div>
</div>
) : (
allPosts.map(post => (
<PostCard
key={post.id}
post={post}
commentCount={getCommentCount(post.id)}
/>
))
allPosts.map(post => <PostCard key={post.id} post={post} />)
)}
</div>
</div>

View File

@ -1,4 +1,4 @@
import { useState} from 'react';
import { useState } from 'react';
import { useForum } from '@opchan/react';
import { useAuth } from '@opchan/react';
import { Button } from '@/components/ui/button';
@ -27,8 +27,7 @@ import {
Globe,
Edit3,
Save,
X,
X,
} from 'lucide-react';
import { EDisplayPreference, EVerificationStatus } from '@opchan/core';
import { useToast } from '@/hooks/use-toast';
@ -42,7 +41,6 @@ export default function ProfilePage() {
// Get current user from auth context for the address
const { currentUser, delegationInfo } = useAuth();
const [isEditing, setIsEditing] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [callSign, setCallSign] = useState('');
@ -253,36 +251,31 @@ export default function ProfilePage() {
</div>
<div className="text-sm text-cyber-neutral">
{/* Show ENS name if available */}
{(currentUser.ensDetails?.ensName ) && (
<div>
ENS:{' '}
{currentUser.ensDetails?.ensName}
</div>
{currentUser.ensDetails?.ensName && (
<div>ENS: {currentUser.ensDetails?.ensName}</div>
)}
{/* Show Ordinal details if available */}
{(currentUser.ordinalDetails ) && (
{currentUser.ordinalDetails && (
<div>
Ordinal:{' '}
{currentUser.ordinalDetails.ordinalDetails}
</div>
)}
{/* Show fallback if neither ENS nor Ordinal */}
{!(
currentUser.ensDetails?.ensName
) &&
!(
currentUser.ordinalDetails?.ordinalDetails
) && <div>No ENS or Ordinal verification</div>}
<div className="flex items-center gap-2 mt-2">
{getVerificationIcon()}
<Badge className={getVerificationColor()}>
{getVerificationText()}
</Badge>
{!currentUser.ensDetails?.ensName &&
!currentUser.ordinalDetails?.ordinalDetails && (
<div>No ENS or Ordinal verification</div>
)}
<div className="flex items-center gap-2 mt-2">
{getVerificationIcon()}
<Badge className={getVerificationColor()}>
{getVerificationText()}
</Badge>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Wallet Section */}
<div className="space-y-3">
@ -469,7 +462,9 @@ export default function ProfilePage() {
Delegation
</span>
<Badge
variant={delegationInfo.isValid ? 'default' : 'secondary'}
variant={
delegationInfo.isValid ? 'default' : 'secondary'
}
className={
delegationInfo.isValid
? 'bg-green-500/20 text-green-400 border-green-500/30'
@ -540,17 +535,17 @@ export default function ProfilePage() {
</div>
{/* Warning for expired delegation */}
{(!delegationInfo.isValid && delegationInfo.hasDelegation) && (
<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">
<AlertTriangle className="w-4 h-4" />
<span className="text-xs font-medium">
Delegation expired. Renew to continue using your
browser key.
</span>
</div>
</div>
)}
{!delegationInfo.isValid && delegationInfo.hasDelegation && (
<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">
<AlertTriangle className="w-4 h-4" />
<span className="text-xs font-medium">
Delegation expired. Renew to continue using your
browser key.
</span>
</div>
</div>
)}
</CardContent>
</Card>
</div>

View File

@ -1,37 +1,57 @@
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 { AppKit } from '@reown/appkit';
import type { OpChanClientConfig } 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 }) => {
const btc = useAppKitAccount({ namespace: 'bip122' });
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 => {
if (btc.isConnected && btc.address) return { address: btc.address, walletType: 'bitcoin' };
if (eth.isConnected && eth.address) return { address: eth.address, walletType: 'ethereum' };
if (btc.isConnected && btc.address)
return { address: btc.address, walletType: 'bitcoin' };
if (eth.isConnected && eth.address)
return { address: eth.address, walletType: 'ethereum' };
return null;
}, [btc.isConnected, btc.address, eth.isConnected, eth.address]);
const adapter = React.useMemo<WalletAdapter>(() => ({
getAccount: () => getCurrent(),
onChange: (cb) => {
listenersRef.current.add(cb);
return () => { listenersRef.current.delete(cb); };
},
}), [getCurrent]);
const adapter = React.useMemo<WalletAdapter>(
() => ({
getAccount: () => getCurrent(),
onChange: cb => {
listenersRef.current.add(cb);
return () => {
listenersRef.current.delete(cb);
};
},
}),
[getCurrent]
);
// Notify listeners when AppKit account changes
React.useEffect(() => {
const account = getCurrent();
listenersRef.current.forEach(cb => {
try { cb(account); } catch { /* ignore */ }
try {
cb(account);
} catch {
/* ignore */
}
});
}, [getCurrent]);
@ -56,5 +76,3 @@ export const OpchanWithAppKit: React.FC<Props> = ({ config, children }) => {
</OpChanProvider>
);
};

View File

@ -1,9 +1,8 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
return twMerge(clsx(inputs));
}
export { urlLoads } from './urlLoads';
export { urlLoads } from './urlLoads';