Skip to content

Commit a3714ff

Browse files
fix(frontend): simplify block state tracker to single chain UI
1 parent 3b54c92 commit a3714ff

File tree

4 files changed

+186
-219
lines changed

4 files changed

+186
-219
lines changed

frontend/components/block-state-tracker/block-card.tsx

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,60 +7,53 @@ import type { Block } from '@/types/block'
77

88
interface BlockCardProps {
99
block: Block
10-
showLabel?: boolean
11-
compact?: boolean
10+
className?: string
1211
}
1312

1413
/**
15-
* Individual block card with animated state transitions.
16-
*
17-
* Uses framer-motion's layoutId for smooth position animations between containers.
18-
* Color transitions are handled via CSS transitions for smoother performance.
19-
* Label appearance and text changes are animated for visual smoothness.
14+
* Individual block card in the blockchain visualization.
15+
* Color and label transition smoothly as the block progresses through states.
2016
*/
21-
export function BlockCard({
22-
block,
23-
showLabel = false,
24-
compact = false,
25-
}: BlockCardProps) {
17+
export function BlockCard({ block, className }: BlockCardProps) {
2618
const config = BLOCK_STATE_CONFIG[block.state]
2719

2820
return (
2921
<motion.div
30-
layoutId={`block-${block.id}`}
31-
initial={false}
32-
transition={{
33-
layout: { type: 'spring', stiffness: 300, damping: 30 },
22+
layout
23+
initial={{ opacity: 0, scale: 0.8 }}
24+
animate={{ opacity: 1, scale: 1 }}
25+
exit={{
26+
opacity: 0,
27+
scale: 0,
28+
transition: { duration: 0.3, ease: 'easeIn' },
3429
}}
30+
transition={{ duration: 0.3, ease: 'easeOut' }}
3531
style={{
3632
background: config.gradient,
3733
boxShadow: config.shadow,
3834
}}
3935
className={cn(
40-
'flex flex-col items-center justify-center rounded-lg font-semibold',
36+
'flex flex-col items-center justify-center rounded-xl font-semibold',
4137
'select-none cursor-default text-white',
42-
'transition-[background,box-shadow] duration-400 ease-in-out',
43-
compact
44-
? 'w-14 h-14 text-xs sm:w-16 sm:h-16 sm:text-sm'
45-
: 'w-16 h-16 text-sm sm:w-20 sm:h-20 sm:text-base',
38+
'transition-[background,box-shadow] duration-500 ease-in-out',
39+
'w-20 h-20 text-base sm:w-24 sm:h-24 sm:text-lg',
40+
className,
4641
)}
4742
>
48-
<span className="font-bold drop-shadow-[0_1px_1px_rgba(0,0,0,0.3)]">
43+
<span className="font-bold drop-shadow-[0_1px_2px_rgba(0,0,0,0.3)]">
4944
#{block.id}
5045
</span>
5146
<AnimatePresence mode="wait">
52-
{showLabel && (
53-
<motion.span
54-
key={config.label}
55-
initial={{ opacity: 0, y: 4 }}
56-
animate={{ opacity: 0.9, y: 0 }}
57-
exit={{ opacity: 0, y: -4 }}
58-
transition={{ duration: 0.2, ease: 'easeOut' }}
59-
className="text-[10px] mt-0.5 sm:text-xs drop-shadow-[0_1px_1px_rgba(0,0,0,0.2)]"
60-
>
61-
{config.label}
62-
</motion.span>
63-
)}
47+
<motion.span
48+
key={config.label}
49+
initial={{ opacity: 0, y: 4 }}
50+
animate={{ opacity: 0.9, y: 0 }}
51+
exit={{ opacity: 0, y: -4 }}
52+
transition={{ duration: 0.15, ease: 'easeOut' }}
53+
className="text-xs mt-1 sm:text-sm drop-shadow-[0_1px_1px_rgba(0,0,0,0.2)]"
54+
>
55+
{config.label}
56+
</motion.span>
6457
</AnimatePresence>
6558
</motion.div>
6659
)

frontend/components/block-state-tracker/blockchain.tsx

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,100 @@
11
'use client'
22

33
import { AnimatePresence, motion } from 'framer-motion'
4+
import { Loader2 } from 'lucide-react'
45
import { useLayoutEffect, useRef } from 'react'
5-
import { BLOCK_STATE_CONFIG } from '@/constants/block-state'
66
import type { Block } from '@/types/block'
77
import { BlockCard } from './block-card'
88

99
interface BlockchainProps {
10-
finalizedBlocks: Block[]
11-
verifiedBlocks: Block[]
10+
blocks: Block[]
1211
}
1312

1413
/**
15-
* Horizontal blockchain visualization showing finalized and verified blocks.
16-
* Auto-scrolls to show the newest blocks on the right.
14+
* Horizontal blockchain visualization.
15+
* Blocks are added from the right and stay in place as their state changes.
16+
* Auto-scrolls to show the newest blocks.
1717
*/
18-
export function Blockchain({
19-
finalizedBlocks,
20-
verifiedBlocks,
21-
}: BlockchainProps) {
18+
export function Blockchain({ blocks }: BlockchainProps) {
2219
const scrollContainerRef = useRef<HTMLDivElement>(null)
2320
const prevBlockCountRef = useRef(0)
2421

25-
// Combine and sort blocks: lower IDs on left, higher IDs on right
26-
const chainBlocks = [...verifiedBlocks, ...finalizedBlocks].sort(
27-
(a, b) => a.id - b.id,
28-
)
22+
// Sort blocks by ID (oldest on left, newest on right)
23+
const sortedBlocks = [...blocks].sort((a, b) => a.id - b.id)
2924

30-
// Scroll BEFORE paint so layout animation target is visible
25+
// Auto-scroll to the right when new blocks are added
3126
useLayoutEffect(() => {
3227
const container = scrollContainerRef.current
3328
if (!container) return
3429

35-
const currentCount = chainBlocks.length
30+
const currentCount = blocks.length
3631
const prevCount = prevBlockCountRef.current
3732

3833
if (currentCount > prevCount && currentCount > 0) {
39-
container.scrollLeft = container.scrollWidth
34+
// Small delay to let the new block render first
35+
requestAnimationFrame(() => {
36+
container.scrollTo({
37+
left: container.scrollWidth,
38+
behavior: 'smooth',
39+
})
40+
})
4041
}
4142

4243
prevBlockCountRef.current = currentCount
43-
}, [chainBlocks.length])
44+
}, [blocks.length])
4445

4546
return (
4647
<div className="flex flex-col bg-[#16162a]/80 rounded-xl border border-[#2a2a4a]/50">
47-
<div className="px-4 py-3 border-b border-[#2a2a4a]/50 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
48+
<div className="px-4 py-3 border-b border-[#2a2a4a]/50">
4849
<h3 className="text-sm font-semibold uppercase tracking-wider text-[#8888a0]">
4950
Blockchain
5051
</h3>
51-
{/* Legend */}
52-
<div className="flex items-center gap-4 text-xs text-[#6a6a7a]">
53-
<div className="flex items-center gap-1.5">
54-
<div
55-
className="w-3 h-3 rounded"
56-
style={{ background: BLOCK_STATE_CONFIG.finalized.gradient }}
57-
/>
58-
<span>Finalized</span>
59-
</div>
60-
<div className="flex items-center gap-1.5">
61-
<div
62-
className="w-3 h-3 rounded"
63-
style={{ background: BLOCK_STATE_CONFIG.verified.gradient }}
64-
/>
65-
<span>Verified</span>
66-
</div>
67-
</div>
6852
</div>
6953
<div
7054
ref={scrollContainerRef}
7155
className="flex-1 p-4 overflow-x-auto overflow-y-hidden scrollbar-none"
7256
>
73-
<div className="flex items-center min-h-[100px] sm:min-h-[120px] w-fit">
74-
<AnimatePresence mode="popLayout">
75-
{chainBlocks.map((block, index) => (
76-
<motion.div
77-
key={block.id}
78-
className="flex items-center shrink-0"
79-
layout
80-
transition={{
81-
layout: { type: 'spring', stiffness: 300, damping: 30 },
82-
}}
83-
>
84-
{/* Chain connector */}
85-
{index > 0 && (
86-
<div className="w-4 h-0.5 bg-[#3a3a5a] sm:w-6 shrink-0" />
87-
)}
88-
<BlockCard block={block} showLabel compact />
89-
</motion.div>
90-
))}
91-
</AnimatePresence>
92-
</div>
57+
{sortedBlocks.length === 0 ? (
58+
<div className="flex flex-col items-center justify-center gap-3 w-full py-8">
59+
<Loader2 className="text-[#6a6a7a] animate-spin size-12" />
60+
<p className="text-[#6a6a7a] text-sm">Waiting for blocks...</p>
61+
</div>
62+
) : (
63+
<motion.div
64+
className="flex items-center gap-2 min-h-[120px] sm:min-h-[140px] w-fit"
65+
layout
66+
transition={{ layout: { duration: 0.3, ease: 'easeInOut' } }}
67+
>
68+
<AnimatePresence mode="sync">
69+
{sortedBlocks.map((block, index) => (
70+
<motion.div
71+
key={block.id}
72+
className="flex items-center shrink-0"
73+
layout
74+
initial={{ opacity: 0, x: 20 }}
75+
animate={{ opacity: 1, x: 0 }}
76+
transition={{ duration: 0.3, ease: 'easeOut' }}
77+
>
78+
{/* Chain connector */}
79+
{index > 0 && (
80+
<motion.div
81+
className="w-3 h-1 bg-[#3a3a5a] rounded-full mr-2 sm:w-4 shrink-0"
82+
initial={{ scaleX: 0 }}
83+
animate={{ scaleX: 1 }}
84+
exit={{
85+
scaleX: 0,
86+
opacity: 0,
87+
transition: { duration: 0.2 },
88+
}}
89+
transition={{ duration: 0.2, delay: 0.1 }}
90+
/>
91+
)}
92+
<BlockCard block={block} />
93+
</motion.div>
94+
))}
95+
</AnimatePresence>
96+
</motion.div>
97+
)}
9398
</div>
9499
</div>
95100
)

0 commit comments

Comments
 (0)