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>
</div>
<ShareButton
size='sm'
size="sm"
url={`${window.location.origin}/cell/${cell.id}`}
title={cell.name}
/>

View File

@ -129,9 +129,12 @@ const CommentCard: React.FC<CommentCardProps> = ({
</div>
<div className="flex items-center gap-2">
<ShareButton
size='sm'
size="sm"
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
isBookmarked={isBookmarked}

View File

@ -118,7 +118,11 @@ export function CreateCellDialog({
<DialogTitle className="text-glow">Create New Cell</DialogTitle>
</DialogHeader>
<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
control={form.control}
name="title"

View File

@ -293,7 +293,7 @@ const Header = () => {
<AlertDialog>
<AlertDialogTrigger asChild>
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
onSelect={e => e.preventDefault()}
className="flex items-center space-x-2 text-orange-400 focus:text-orange-400"
>
<Trash2 className="w-4 h-4" />
@ -306,7 +306,8 @@ const Header = () => {
Clear Local Database
</AlertDialogTitle>
<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 /> User identities and preferences
<br /> Bookmarks and votes

View File

@ -1,6 +1,6 @@
import React from 'react';
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 { Post } from '@/types/forum';
import {
@ -70,7 +70,6 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
await toggleBookmark();
};
return (
<div className="thread-card mb-2">
<div className="flex">
@ -180,7 +179,7 @@ const PostCard: React.FC<PostCardProps> = ({ post, commentCount = 0 }) => {
</span>
)}
<ShareButton
size='sm'
size="sm"
url={`${window.location.origin}/post/${post.id}`}
title={post.title}
/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -121,7 +121,7 @@ export class LocalDatabase {
public async clearAll(): Promise<void> {
// Clear in-memory cache
this.clear();
// Clear all IndexedDB stores
if (!this.db) return;
@ -140,7 +140,7 @@ export class LocalDatabase {
];
const tx = this.db.transaction(storeNames, 'readwrite');
await Promise.all(
storeNames.map(storeName => {
return new Promise<void>((resolve, reject) => {
@ -632,7 +632,8 @@ export class LocalDatabase {
record: Partial<UserIdentityCache[string]> & { lastUpdated?: number }
): Promise<void> {
const existing: UserIdentityCache[string] =
this.cache.userIdentities[address] || {
this.cache.userIdentities[address] ||
({
ensName: undefined,
ordinalDetails: undefined,
callSign: undefined,
@ -644,12 +645,15 @@ export class LocalDatabase {
// Casting below ensures the object satisfies the interface at compile time.
lastUpdated: 0,
verificationStatus: EVerificationStatus.WALLET_UNCONNECTED,
} as unknown as UserIdentityCache[string];
} as unknown as UserIdentityCache[string]);
const merged: UserIdentityCache[string] = {
...existing,
...record,
lastUpdated: Math.max(existing.lastUpdated ?? 0, record.lastUpdated ?? Date.now()),
lastUpdated: Math.max(
existing.lastUpdated ?? 0,
record.lastUpdated ?? Date.now()
),
} as UserIdentityCache[string];
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;
class Ordinals {
private static instance: Ordinals | null = null;
private ordiscan: Ordiscan;
private readonly PARENT_INSCRIPTION_ID = "add60add0325f7c82e80d4852a8b8d5c46dbde4317e76fe4def2e718dd84b87ci0"
private static instance: Ordinals | null = null;
private ordiscan: Ordiscan;
private readonly PARENT_INSCRIPTION_ID =
'add60add0325f7c82e80d4852a8b8d5c46dbde4317e76fe4def2e718dd84b87ci0';
private constructor(ordiscan: Ordiscan) {
this.ordiscan = ordiscan;
}
private constructor(ordiscan: Ordiscan) {
this.ordiscan = ordiscan;
}
static getInstance(): Ordinals {
if (!Ordinals.instance) {
Ordinals.instance = new Ordinals(new Ordiscan(API_KEY));
}
return Ordinals.instance;
static getInstance(): Ordinals {
if (!Ordinals.instance) {
Ordinals.instance = new Ordinals(new Ordiscan(API_KEY));
}
return Ordinals.instance;
}
/**
* Get Ordinal details for a Bitcoin address
*/
async getOrdinalDetails(address: string): Promise<Inscription[] | null> {
const inscriptions = await this.ordiscan.address.getInscriptions({address})
if (inscriptions.length > 0) {
if (inscriptions.some(inscription => inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID)) {
return inscriptions.filter(inscription => inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID)
} else {
return null
}
}
return null
/**
* Get Ordinal details for a Bitcoin address
*/
async getOrdinalDetails(address: string): Promise<Inscription[] | null> {
const inscriptions = await this.ordiscan.address.getInscriptions({
address,
});
if (inscriptions.length > 0) {
if (
inscriptions.some(
inscription =>
inscription.parent_inscription_id === this.PARENT_INSCRIPTION_ID
)
) {
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
*/
static async resolveOperatorOrdinals(address: string): Promise<Inscription[] | null> {
static async resolveOperatorOrdinals(
address: string
): Promise<Inscription[] | null> {
try {
return await ordinals.getOrdinalDetails(address);
} catch (error) {

View File

@ -15,7 +15,9 @@ export default function DebugPage() {
useEffect(() => {
// Subscribe to inbound messages from reliable channel
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 () => {
@ -47,7 +49,9 @@ export default function DebugPage() {
Total received: {messages.length}
</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 => (
<div
key={t}
@ -59,13 +63,22 @@ export default function DebugPage() {
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 style={{ marginTop: 16, borderTop: '1px solid #334155', paddingTop: 12 }}>
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>Recent messages</div>
<div
style={{
marginTop: 16,
borderTop: '1px solid #334155',
paddingTop: 12,
}}
>
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>
Recent messages
</div>
<div
style={{
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' }}>Msg Timestamp</div>
{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={{ textTransform: 'capitalize', color: '#e5e7eb' }}>{m.message.type}</div>
<div style={{ textTransform: 'capitalize', color: '#e5e7eb' }}>
{m.message.type}
</div>
<div
style={{
overflow: 'hidden',
@ -91,9 +106,12 @@ export default function DebugPage() {
}}
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 style={{ color: '#e5e7eb' }}>{formatTs(m.message.timestamp)}</div>
</Fragment>
))}
</div>