mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 21:03:09 +00:00
chore: fix CSS + useMemo
This commit is contained in:
parent
97d7926659
commit
2dc3cd1e63
@ -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}
|
||||
/>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -255,7 +255,7 @@ const PostDetail = () => {
|
||||
showText={true}
|
||||
/>
|
||||
<ShareButton
|
||||
size='lg'
|
||||
size="lg"
|
||||
url={`${window.location.origin}/post/${post.id}`}
|
||||
title={post.title}
|
||||
/>
|
||||
|
||||
@ -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}`}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user