chore: fix CSS + useMemo

This commit is contained in:
Danish Arora 2025-09-12 15:58:35 +05:30
parent 97d7926659
commit 2dc3cd1e63
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
14 changed files with 143 additions and 105 deletions

View File

@ -127,7 +127,7 @@ const CellItem: React.FC<{ cell: Cell }> = ({ cell }) => {
</span> </span>
</div> </div>
<ShareButton <ShareButton
size='sm' size="sm"
url={`${window.location.origin}/cell/${cell.id}`} url={`${window.location.origin}/cell/${cell.id}`}
title={cell.name} title={cell.name}
/> />

View File

@ -129,9 +129,12 @@ const CommentCard: React.FC<CommentCardProps> = ({
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ShareButton <ShareButton
size='sm' size="sm"
url={`${window.location.origin}/post/${postId}#comment-${comment.id}`} url={`${window.location.origin}/post/${postId}#comment-${comment.id}`}
title={comment.content.substring(0, 50) + (comment.content.length > 50 ? '...' : '')} title={
comment.content.substring(0, 50) +
(comment.content.length > 50 ? '...' : '')
}
/> />
<BookmarkButton <BookmarkButton
isBookmarked={isBookmarked} isBookmarked={isBookmarked}

View File

@ -118,7 +118,11 @@ export function CreateCellDialog({
<DialogTitle className="text-glow">Create New Cell</DialogTitle> <DialogTitle className="text-glow">Create New Cell</DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} onKeyDown={handleKeyDown} className="space-y-4"> <form
onSubmit={form.handleSubmit(onSubmit)}
onKeyDown={handleKeyDown}
className="space-y-4"
>
<FormField <FormField
control={form.control} control={form.control}
name="title" name="title"

View File

@ -293,7 +293,7 @@ const Header = () => {
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<DropdownMenuItem <DropdownMenuItem
onSelect={(e) => e.preventDefault()} onSelect={e => e.preventDefault()}
className="flex items-center space-x-2 text-orange-400 focus:text-orange-400" className="flex items-center space-x-2 text-orange-400 focus:text-orange-400"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
@ -306,7 +306,8 @@ const Header = () => {
Clear Local Database Clear Local Database
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription className="text-cyber-neutral"> <AlertDialogDescription className="text-cyber-neutral">
This will permanently delete all locally stored data including: This will permanently delete all locally stored
data including:
<br /> Posts and comments <br /> Posts and comments
<br /> User identities and preferences <br /> User identities and preferences
<br /> Bookmarks and votes <br /> Bookmarks and votes

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ArrowUp, ArrowDown, MessageSquare, Clipboard } from 'lucide-react'; import { ArrowUp, ArrowDown, MessageSquare } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { Post } from '@/types/forum'; import { Post } from '@/types/forum';
import { import {
@ -70,7 +70,6 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
await toggleBookmark(); await toggleBookmark();
}; };
return ( return (
<div className="thread-card mb-2"> <div className="thread-card mb-2">
<div className="flex"> <div className="flex">
@ -180,7 +179,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
</span> </span>
)} )}
<ShareButton <ShareButton
size='sm' size="sm"
url={`${window.location.origin}/post/${post.id}`} url={`${window.location.origin}/post/${post.id}`}
title={post.title} title={post.title}
/> />

View File

@ -255,7 +255,7 @@ const PostDetail = () => {
showText={true} showText={true}
/> />
<ShareButton <ShareButton
size='lg' size="lg"
url={`${window.location.origin}/post/${post.id}`} url={`${window.location.origin}/post/${post.id}`}
title={post.title} title={post.title}
/> />

View File

@ -7,7 +7,7 @@ import {
usePermissions, usePermissions,
useUserVotes, useUserVotes,
useAuth, useAuth,
usePostComments, useForumData,
} from '@/hooks'; } from '@/hooks';
import { EVerificationStatus } from '@/types/identity'; import { EVerificationStatus } from '@/types/identity';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -56,6 +56,7 @@ const PostList = () => {
const { canPost, canVote, canModerate } = usePermissions(); const { canPost, canVote, canModerate } = usePermissions();
const userVotes = useUserVotes(); const userVotes = useUserVotes();
const { currentUser, verificationStatus } = useAuth(); const { currentUser, verificationStatus } = useAuth();
const { commentsByPost } = useForumData();
const [newPostTitle, setNewPostTitle] = useState(''); const [newPostTitle, setNewPostTitle] = useState('');
const [newPostContent, setNewPostContent] = useState(''); const [newPostContent, setNewPostContent] = useState('');
@ -339,7 +340,7 @@ const PostList = () => {
<span></span> <span></span>
<span> <span>
<MessageSquare className="inline w-3 h-3 mr-1" /> <MessageSquare className="inline w-3 h-3 mr-1" />
{usePostComments(post.id).totalCount} comments {commentsByPost[post.id]?.length || 0} comments
</span> </span>
<ShareButton <ShareButton
url={`${window.location.origin}/post/${post.id}`} url={`${window.location.origin}/post/${post.id}`}

View File

@ -15,8 +15,6 @@ interface ShareButtonProps {
export function ShareButton({ export function ShareButton({
url, url,
title,
description = 'Check out this post',
size = 'sm', size = 'sm',
variant = 'ghost', variant = 'ghost',
className, className,
@ -73,9 +71,7 @@ export function ShareButton({
title="Copy link" title="Copy link"
> >
<Share2 size={iconSize[size]} /> <Share2 size={iconSize[size]} />
{showText && ( {showText && <span className="ml-2 text-xs">Share</span>}
<span className="ml-2 text-xs">Share</span>
)}
</Button> </Button>
); );
} }

View File

@ -55,21 +55,9 @@ export function WalletWizard({
onOpenChange(false); onOpenChange(false);
}; };
// Business logic: determine step status based on current wizard step // Consolidated step status logic
const getStepStatus = (step: WizardStep) => { const getStepStatus = (step: WizardStep) => {
if (step < currentStep) { // Check actual completion status first
return 'complete';
} else if (step === currentStep) {
return 'current';
} else {
return 'disabled';
}
};
const renderStepIcon = (step: WizardStep) => {
const status = getStepStatus(step);
// Check if step is actually completed based on auth state
const isActuallyComplete = (step: WizardStep): boolean => { const isActuallyComplete = (step: WizardStep): boolean => {
switch (step) { switch (step) {
case 1: case 1:
@ -83,7 +71,19 @@ export function WalletWizard({
} }
}; };
if (status === 'complete' || isActuallyComplete(step)) { if (isActuallyComplete(step)) {
return 'complete';
} else if (step === currentStep) {
return 'current';
} else {
return 'disabled';
}
};
const renderStepIcon = (step: WizardStep) => {
const status = getStepStatus(step);
if (status === 'complete') {
return <CheckCircle className="h-5 w-5 text-green-500" />; return <CheckCircle className="h-5 w-5 text-green-500" />;
} else if (status === 'current') { } else if (status === 'current') {
return <Loader2 className="h-5 w-5 text-blue-500 animate-spin" />; return <Loader2 className="h-5 w-5 text-blue-500 animate-spin" />;
@ -107,7 +107,7 @@ export function WalletWizard({
return ( return (
<Dialog open={open} onOpenChange={handleClose}> <Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-md border-neutral-800 bg-black text-white"> <DialogContent className="sm:max-w-lg border-neutral-800 bg-black text-white">
<DialogHeader> <DialogHeader>
<DialogTitle className="text-xl">Setup Your Account</DialogTitle> <DialogTitle className="text-xl">Setup Your Account</DialogTitle>
<DialogDescription className="text-neutral-400"> <DialogDescription className="text-neutral-400">
@ -116,21 +116,16 @@ export function WalletWizard({
</DialogHeader> </DialogHeader>
{/* Progress Indicator */} {/* Progress Indicator */}
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-center mb-8">
{[1, 2, 3].map(step => ( {[1, 2, 3].map((step, index) => (
<div key={step} className="flex items-center"> <div key={step} className="flex items-center">
<div className="flex items-center gap-2"> <div className="flex flex-col items-center gap-2">
{renderStepIcon(step as WizardStep)} {renderStepIcon(step as WizardStep)}
<span <span
className={`text-sm ${ className={`text-sm font-medium ${
getStepStatus(step as WizardStep) === 'current' getStepStatus(step as WizardStep) === 'current'
? 'text-blue-500 font-medium' ? 'text-blue-500'
: getStepStatus(step as WizardStep) === 'complete' || : getStepStatus(step as WizardStep) === 'complete'
(step === 1 && isAuthenticated) ||
(step === 2 &&
verificationStatus !==
EVerificationStatus.WALLET_UNCONNECTED) ||
(step === 3 && delegationStatus.isValid)
? 'text-green-500' ? 'text-green-500'
: 'text-gray-400' : 'text-gray-400'
}`} }`}
@ -138,14 +133,10 @@ export function WalletWizard({
{getStepTitle(step as WizardStep)} {getStepTitle(step as WizardStep)}
</span> </span>
</div> </div>
{step < 3 && ( {index < 2 && (
<div <div
className={`w-8 h-px mx-2 ${ className={`w-16 h-px mx-4 ${
getStepStatus(step as WizardStep) === 'complete' || getStepStatus(step as WizardStep) === 'complete'
(step === 1 && isAuthenticated) ||
(step === 2 &&
verificationStatus !==
EVerificationStatus.WALLET_UNCONNECTED)
? 'bg-green-500' ? 'bg-green-500'
: 'bg-gray-600' : 'bg-gray-600'
}`} }`}
@ -155,8 +146,8 @@ export function WalletWizard({
))} ))}
</div> </div>
{/* Step Content - Fixed height container */} {/* Step Content - Flexible height container */}
<div className="h-[400px] flex flex-col"> <div className="min-h-[400px] flex flex-col">
{currentStep === 1 && ( {currentStep === 1 && (
<WalletConnectionStep <WalletConnectionStep
onComplete={() => handleStepComplete(1)} onComplete={() => handleStepComplete(1)}
@ -185,7 +176,7 @@ export function WalletWizard({
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex justify-between items-center pt-4 border-t border-neutral-700"> <div className="flex justify-between items-center pt-6 mt-4 border-t border-neutral-700">
<p className="text-xs text-neutral-500">Step {currentStep} of 3</p> <p className="text-xs text-neutral-500">Step {currentStep} of 3</p>
{currentStep > 1 && ( {currentStep > 1 && (
<Button <Button
@ -193,7 +184,7 @@ export function WalletWizard({
size="sm" size="sm"
onClick={() => setCurrentStep((currentStep - 1) as WizardStep)} onClick={() => setCurrentStep((currentStep - 1) as WizardStep)}
disabled={isLoading} disabled={isLoading}
className="text-neutral-400 hover:text-white" className="text-neutral-400 hover:text-white hover:bg-neutral-800"
> >
Back Back
</Button> </Button>

View File

@ -118,13 +118,18 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
return { return {
...user, ...user,
ensDetails: identity.ensName ? { ensName: identity.ensName } : undefined, ensDetails: identity.ensName
? { ensName: identity.ensName }
: undefined,
ordinalDetails: identity.ordinalDetails, ordinalDetails: identity.ordinalDetails,
verificationStatus: identity.verificationStatus, verificationStatus: identity.verificationStatus,
lastChecked: Date.now(), lastChecked: Date.now(),
}; };
} catch (error) { } catch (error) {
console.error('Error verifying ownership via UserIdentityService:', error); console.error(
'Error verifying ownership via UserIdentityService:',
error
);
return { return {
...user, ...user,
ensDetails: undefined, ensDetails: undefined,
@ -210,12 +215,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
EVerificationStatus.ENS_ORDINAL_VERIFIED EVerificationStatus.ENS_ORDINAL_VERIFIED
); );
await saveUser(updatedUser); await saveUser(updatedUser);
await localDatabase.upsertUserIdentity(updatedUser.address, { await localDatabase.upsertUserIdentity(
ensName: walletInfo.ensName, updatedUser.address,
verificationStatus: {
EVerificationStatus.ENS_ORDINAL_VERIFIED, ensName: walletInfo.ensName,
lastUpdated: Date.now(), verificationStatus:
}); EVerificationStatus.ENS_ORDINAL_VERIFIED,
lastUpdated: Date.now(),
}
);
} else { } else {
setCurrentUser(newUser); setCurrentUser(newUser);
setVerificationStatus(EVerificationStatus.WALLET_CONNECTED); setVerificationStatus(EVerificationStatus.WALLET_CONNECTED);

View File

@ -121,7 +121,7 @@ export class LocalDatabase {
public async clearAll(): Promise<void> { public async clearAll(): Promise<void> {
// Clear in-memory cache // Clear in-memory cache
this.clear(); this.clear();
// Clear all IndexedDB stores // Clear all IndexedDB stores
if (!this.db) return; if (!this.db) return;
@ -140,7 +140,7 @@ export class LocalDatabase {
]; ];
const tx = this.db.transaction(storeNames, 'readwrite'); const tx = this.db.transaction(storeNames, 'readwrite');
await Promise.all( await Promise.all(
storeNames.map(storeName => { storeNames.map(storeName => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
@ -632,7 +632,8 @@ export class LocalDatabase {
record: Partial<UserIdentityCache[string]> & { lastUpdated?: number } record: Partial<UserIdentityCache[string]> & { lastUpdated?: number }
): Promise<void> { ): Promise<void> {
const existing: UserIdentityCache[string] = const existing: UserIdentityCache[string] =
this.cache.userIdentities[address] || { this.cache.userIdentities[address] ||
({
ensName: undefined, ensName: undefined,
ordinalDetails: undefined, ordinalDetails: undefined,
callSign: undefined, callSign: undefined,
@ -644,12 +645,15 @@ export class LocalDatabase {
// Casting below ensures the object satisfies the interface at compile time. // Casting below ensures the object satisfies the interface at compile time.
lastUpdated: 0, lastUpdated: 0,
verificationStatus: EVerificationStatus.WALLET_UNCONNECTED, verificationStatus: EVerificationStatus.WALLET_UNCONNECTED,
} as unknown as UserIdentityCache[string]; } as unknown as UserIdentityCache[string]);
const merged: UserIdentityCache[string] = { const merged: UserIdentityCache[string] = {
...existing, ...existing,
...record, ...record,
lastUpdated: Math.max(existing.lastUpdated ?? 0, record.lastUpdated ?? Date.now()), lastUpdated: Math.max(
existing.lastUpdated ?? 0,
record.lastUpdated ?? Date.now()
),
} as UserIdentityCache[string]; } as UserIdentityCache[string];
this.cache.userIdentities[address] = merged; this.cache.userIdentities[address] = merged;

View File

@ -1,36 +1,47 @@
import {Ordiscan, Inscription} from 'ordiscan' import { Ordiscan, Inscription } from 'ordiscan';
const API_KEY = import.meta.env.VITE_ORDISCAN_API; const API_KEY = import.meta.env.VITE_ORDISCAN_API;
class Ordinals { class Ordinals {
private static instance: Ordinals | null = null; private static instance: Ordinals | null = null;
private ordiscan: Ordiscan; private ordiscan: Ordiscan;
private readonly PARENT_INSCRIPTION_ID = "add60add0325f7c82e80d4852a8b8d5c46dbde4317e76fe4def2e718dd84b87ci0" private readonly PARENT_INSCRIPTION_ID =
'add60add0325f7c82e80d4852a8b8d5c46dbde4317e76fe4def2e718dd84b87ci0';
private constructor(ordiscan: Ordiscan) { private constructor(ordiscan: Ordiscan) {
this.ordiscan = ordiscan; this.ordiscan = ordiscan;
} }
static getInstance(): Ordinals { static getInstance(): Ordinals {
if (!Ordinals.instance) { if (!Ordinals.instance) {
Ordinals.instance = new Ordinals(new Ordiscan(API_KEY)); Ordinals.instance = new Ordinals(new Ordiscan(API_KEY));
}
return Ordinals.instance;
} }
return Ordinals.instance;
}
/** /**
* Get Ordinal details for a Bitcoin address * Get Ordinal details for a Bitcoin address
*/ */
async getOrdinalDetails(address: string): Promise<Inscription[] | null> { async getOrdinalDetails(address: string): Promise<Inscription[] | null> {
const inscriptions = await this.ordiscan.address.getInscriptions({address}) const inscriptions = await this.ordiscan.address.getInscriptions({
if (inscriptions.length > 0) { address,
if (inscriptions.some(inscription => inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID)) { });
return inscriptions.filter(inscription => inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID) if (inscriptions.length > 0) {
} else { if (
return null inscriptions.some(
} inscription =>
} inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID
return null )
) {
return inscriptions.filter(
inscription =>
inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID
);
} else {
return null;
}
} }
return null;
}
} }
export const ordinals = Ordinals.getInstance(); export const ordinals = Ordinals.getInstance();

View File

@ -100,7 +100,9 @@ export class WalletManager {
/** /**
* Resolve Ordinal details for a Bitcoin address * Resolve Ordinal details for a Bitcoin address
*/ */
static async resolveOperatorOrdinals(address: string): Promise<Inscription[] | null> { static async resolveOperatorOrdinals(
address: string
): Promise<Inscription[] | null> {
try { try {
return await ordinals.getOrdinalDetails(address); return await ordinals.getOrdinalDetails(address);
} catch (error) { } catch (error) {

View File

@ -15,7 +15,9 @@ export default function DebugPage() {
useEffect(() => { useEffect(() => {
// Subscribe to inbound messages from reliable channel // Subscribe to inbound messages from reliable channel
unsubscribeRef.current = messageManager.onMessageReceived(msg => { unsubscribeRef.current = messageManager.onMessageReceived(msg => {
setMessages(prev => [{ receivedAt: Date.now(), message: msg }, ...prev].slice(0, 500)); setMessages(prev =>
[{ receivedAt: Date.now(), message: msg }, ...prev].slice(0, 500)
);
}); });
return () => { return () => {
@ -47,7 +49,9 @@ export default function DebugPage() {
Total received: {messages.length} Total received: {messages.length}
</div> </div>
<div style={{ marginTop: 12, display: 'flex', gap: 12, flexWrap: 'wrap' }}> <div
style={{ marginTop: 12, display: 'flex', gap: 12, flexWrap: 'wrap' }}
>
{Object.values(MessageType).map(t => ( {Object.values(MessageType).map(t => (
<div <div
key={t} key={t}
@ -59,13 +63,22 @@ export default function DebugPage() {
background: 'rgba(51,65,85,0.2)', background: 'rgba(51,65,85,0.2)',
}} }}
> >
<strong style={{ textTransform: 'capitalize' }}>{t}</strong>: {typeCounts[t] || 0} <strong style={{ textTransform: 'capitalize' }}>{t}</strong>:{' '}
{typeCounts[t] || 0}
</div> </div>
))} ))}
</div> </div>
<div style={{ marginTop: 16, borderTop: '1px solid #334155', paddingTop: 12 }}> <div
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>Recent messages</div> style={{
marginTop: 16,
borderTop: '1px solid #334155',
paddingTop: 12,
}}
>
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>
Recent messages
</div>
<div <div
style={{ style={{
display: 'grid', display: 'grid',
@ -79,9 +92,11 @@ export default function DebugPage() {
<div style={{ fontWeight: 700, color: '#cbd5e1' }}>ID / Author</div> <div style={{ fontWeight: 700, color: '#cbd5e1' }}>ID / Author</div>
<div style={{ fontWeight: 700, color: '#cbd5e1' }}>Msg Timestamp</div> <div style={{ fontWeight: 700, color: '#cbd5e1' }}>Msg Timestamp</div>
{messages.map(m => ( {messages.map(m => (
<Fragment key={`${m.message.id}:${m.receivedAt}`}> <Fragment key={`${m.message.id}:${m.receivedAt}`}>
<div style={{ color: '#e5e7eb' }}>{formatTs(m.receivedAt)}</div> <div style={{ color: '#e5e7eb' }}>{formatTs(m.receivedAt)}</div>
<div style={{ textTransform: 'capitalize', color: '#e5e7eb' }}>{m.message.type}</div> <div style={{ textTransform: 'capitalize', color: '#e5e7eb' }}>
{m.message.type}
</div>
<div <div
style={{ style={{
overflow: 'hidden', overflow: 'hidden',
@ -91,9 +106,12 @@ export default function DebugPage() {
}} }}
title={`${m.message.id}${m.message.author}`} title={`${m.message.id}${m.message.author}`}
> >
{m.message.id} <span style={{ color: '#94a3b8' }}>{m.message.author}</span> {m.message.id} {' '}
<span style={{ color: '#94a3b8' }}>{m.message.author}</span>
</div>
<div style={{ color: '#e5e7eb' }}>
{formatTs(m.message.timestamp)}
</div> </div>
<div style={{ color: '#e5e7eb' }}>{formatTs(m.message.timestamp)}</div>
</Fragment> </Fragment>
))} ))}
</div> </div>