Skip to content

Commit 9fd279c

Browse files
authored
Merge pull request #211 from fix/coderabbit-review-comments
fix: address all CodeRabbit review comments from PR #210
2 parents c27f8fd + 2750219 commit 9fd279c

19 files changed

Lines changed: 244 additions & 52 deletions

src/components/ArrayVisualizer.jsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,26 @@ function ArrayVisualizer({
183183
<motion.div
184184
initial={{ opacity: 0 }}
185185
animate={{ opacity: 1 }}
186-
className="absolute inset-0 bg-black/30 z-10"
187-
aria-hidden="true"
188-
/>
186+
className="absolute inset-0 z-10 flex items-center justify-center bg-black/80"
187+
role="button"
188+
tabIndex={0}
189+
onClick={() => onGatedFeatureClick?.('complexity_limit')}
190+
onKeyDown={e => {
191+
if (e.key === 'Enter' || e.key === ' ') {
192+
e.preventDefault();
193+
onGatedFeatureClick?.('complexity_limit');
194+
}
195+
}}
196+
>
197+
<div className="text-center text-white p-6 max-w-xs">
198+
<p className="text-lg font-semibold mb-2">
199+
{t('featureGate.complexity_limit')}
200+
</p>
201+
<p className="text-sm opacity-80">
202+
{t('featureGate.complexity_limit_description')}
203+
</p>
204+
</div>
205+
</motion.div>
189206
)}
190207
</div>
191208
) : (

src/components/ErrorBoundary.jsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,8 @@
44
* See LICENSE for details.
55
*/
66

7-
import { Component } from 'react';
7+
import { Component, createElement } from 'react';
88

9-
/**
10-
* React error boundary that catches render errors in its subtree
11-
* and displays a non-destructive fallback instead of crashing the
12-
* entire application.
13-
*/
149
class ErrorBoundary extends Component {
1510
constructor(props) {
1611
super(props);
@@ -25,9 +20,39 @@ class ErrorBoundary extends Component {
2520
console.error('[ErrorBoundary]', error, info?.componentStack);
2621
}
2722

23+
componentDidUpdate(prevProps) {
24+
if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
25+
this.setState({ hasError: false });
26+
}
27+
}
28+
2829
render() {
2930
if (this.state.hasError) {
30-
return this.props.fallback ?? null;
31+
return (
32+
this.props.fallback ??
33+
createElement(
34+
'div',
35+
{
36+
className:
37+
'flex flex-col items-center justify-center h-full p-8 text-center',
38+
},
39+
createElement(
40+
'p',
41+
{ className: 'text-lg font-semibold mb-2' },
42+
'Something went wrong.'
43+
),
44+
createElement(
45+
'button',
46+
{
47+
type: 'button',
48+
className:
49+
'mt-2 px-4 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700',
50+
onClick: () => this.setState({ hasError: false }),
51+
},
52+
'Try again'
53+
)
54+
)
55+
);
3156
}
3257
return this.props.children;
3358
}

src/components/ErrorBoundary.test.jsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,35 @@ describe('ErrorBoundary', () => {
3333
spy.mockRestore();
3434
});
3535

36-
it('renders nothing when a child throws and no fallback provided', () => {
36+
it('renders built-in fallback when a child throws and no fallback provided', () => {
3737
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
38-
const { container } = render(
38+
render(
3939
<ErrorBoundary>
4040
<Boom />
4141
</ErrorBoundary>
4242
);
43-
expect(container.innerHTML).toBe('');
43+
expect(screen.getByText('Something went wrong.')).toBeInTheDocument();
44+
expect(
45+
screen.getByRole('button', { name: 'Try again' })
46+
).toBeInTheDocument();
47+
spy.mockRestore();
48+
});
49+
50+
it('recovers when resetKey changes', () => {
51+
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
52+
const { rerender } = render(
53+
<ErrorBoundary resetKey={1}>
54+
<Boom />
55+
</ErrorBoundary>
56+
);
57+
expect(screen.getByText('Something went wrong.')).toBeInTheDocument();
58+
59+
rerender(
60+
<ErrorBoundary resetKey={2}>
61+
<span>recovered content</span>
62+
</ErrorBoundary>
63+
);
64+
expect(screen.getByText('recovered content')).toBeInTheDocument();
4465
spy.mockRestore();
4566
});
4667
});

src/components/Footer.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ function Footer() {
181181
</p>
182182
<div className="flex items-center gap-2">
183183
<a
184-
href="https://github.com/ayoub3bidi/bayan-flow/blob/develop/LICENSE"
184+
href={`${GITHUB_REPO_URL}/blob/main/LICENSE`}
185185
target="_blank"
186186
rel="noopener noreferrer"
187187
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 border border-orange-200 hover:bg-orange-200 transition-colors"

src/components/GraphAlgorithmMatrixVisualizer.jsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,26 @@ function GraphAlgorithmMatrixVisualizer({
103103
<motion.div
104104
initial={{ opacity: 0 }}
105105
animate={{ opacity: 1 }}
106-
className="absolute inset-0 bg-black/30 z-10"
107-
aria-hidden="true"
108-
/>
106+
className="absolute inset-0 z-10 flex items-center justify-center bg-black/80"
107+
role="button"
108+
tabIndex={0}
109+
onClick={() => onGatedFeatureClick?.('complexity_limit')}
110+
onKeyDown={e => {
111+
if (e.key === 'Enter' || e.key === ' ') {
112+
e.preventDefault();
113+
onGatedFeatureClick?.('complexity_limit');
114+
}
115+
}}
116+
>
117+
<div className="text-center text-white p-6 max-w-xs">
118+
<p className="text-lg font-semibold mb-2">
119+
{t('featureGate.complexity_limit')}
120+
</p>
121+
<p className="text-sm opacity-80">
122+
{t('featureGate.complexity_limit_description')}
123+
</p>
124+
</div>
125+
</motion.div>
109126
)}
110127
</div>
111128
);

src/components/GraphAlgorithmMatrixVisualizer.test.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,11 @@ describe('GraphAlgorithmMatrixVisualizer', () => {
120120
vi.advanceTimersByTime(1000);
121121
});
122122

123-
const blurOverlay = document.querySelector('.bg-black\\/30');
123+
const blurOverlay = document.querySelector('.bg-black\\/80');
124124
expect(blurOverlay).toBeInTheDocument();
125-
expect(screen.getByText('Complexity Analysis')).toBeInTheDocument();
125+
expect(
126+
screen.getAllByText('Complexity Analysis').length
127+
).toBeGreaterThanOrEqual(1);
126128
} finally {
127129
vi.useRealTimers();
128130
}

src/components/GraphVisualizer.jsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,26 @@ function GraphVisualizer({
321321
<motion.div
322322
initial={{ opacity: 0 }}
323323
animate={{ opacity: 1 }}
324-
className="absolute inset-0 bg-black/30 z-10"
325-
aria-hidden="true"
326-
/>
324+
className="absolute inset-0 z-10 flex items-center justify-center bg-black/80"
325+
role="button"
326+
tabIndex={0}
327+
onClick={() => onGatedFeatureClick?.('complexity_limit')}
328+
onKeyDown={e => {
329+
if (e.key === 'Enter' || e.key === ' ') {
330+
e.preventDefault();
331+
onGatedFeatureClick?.('complexity_limit');
332+
}
333+
}}
334+
>
335+
<div className="text-center text-white p-6 max-w-xs">
336+
<p className="text-lg font-semibold mb-2">
337+
{t('featureGate.complexity_limit')}
338+
</p>
339+
<p className="text-sm opacity-80">
340+
{t('featureGate.complexity_limit_description')}
341+
</p>
342+
</div>
343+
</motion.div>
327344
)}
328345
</div>
329346
) : (

src/components/GraphVisualizer.test.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,11 @@ describe('GraphVisualizer', () => {
195195
vi.advanceTimersByTime(1000);
196196
});
197197

198-
const blurOverlay = document.querySelector('.bg-black\\/30');
198+
const blurOverlay = document.querySelector('.bg-black\\/80');
199199
expect(blurOverlay).toBeInTheDocument();
200-
expect(screen.getByText('Complexity Analysis')).toBeInTheDocument();
200+
expect(
201+
screen.getAllByText('Complexity Analysis').length
202+
).toBeGreaterThanOrEqual(1);
201203
} finally {
202204
vi.useRealTimers();
203205
}

src/components/GridVisualizer.jsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,26 @@ function GridVisualizer({
182182
<motion.div
183183
initial={{ opacity: 0 }}
184184
animate={{ opacity: 1 }}
185-
className="absolute inset-0 bg-black/30 z-10"
186-
aria-hidden="true"
187-
/>
185+
className="absolute inset-0 z-10 flex items-center justify-center bg-black/80"
186+
role="button"
187+
tabIndex={0}
188+
onClick={() => onGatedFeatureClick?.('complexity_limit')}
189+
onKeyDown={e => {
190+
if (e.key === 'Enter' || e.key === ' ') {
191+
e.preventDefault();
192+
onGatedFeatureClick?.('complexity_limit');
193+
}
194+
}}
195+
>
196+
<div className="text-center text-white p-6 max-w-xs">
197+
<p className="text-lg font-semibold mb-2">
198+
{t('featureGate.complexity_limit')}
199+
</p>
200+
<p className="text-sm opacity-80">
201+
{t('featureGate.complexity_limit_description')}
202+
</p>
203+
</div>
204+
</motion.div>
188205
)}
189206
</div>
190207
) : (

src/components/PythonCodePanel.jsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '../algorithms/python';
1616
import { getPseudocodeForLocale } from '../algorithms/pseudocode';
1717
import { highlightPseudocodeToHtml } from '../utils/pseudocodeHighlight';
18+
import { getFocusableElements } from '../utils/focusableElements';
1819
import Editor from '@monaco-editor/react';
1920
import { useTheme } from '../hooks/useTheme';
2021
import { usePythonExecution } from '../hooks/usePythonExecution';
@@ -78,20 +79,17 @@ function PythonCodePanel({ isOpen, onClose, algorithm }) {
7879
document.addEventListener('keydown', handleEscape);
7980
return () => document.removeEventListener('keydown', handleEscape);
8081
}, [isOpen, onClose]);
81-
8282
// Focus trap
8383
useEffect(() => {
8484
if (isOpen && panelRef.current) {
85-
const focusableElements = panelRef.current.querySelectorAll(
86-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
87-
);
88-
const firstElement = focusableElements[0];
89-
const lastElement = focusableElements[focusableElements.length - 1];
90-
9185
const handleKeyDown = event => {
9286
if (event.key === 'Escape') {
9387
onClose();
9488
} else if (event.key === 'Tab') {
89+
const focusableElements = getFocusableElements(panelRef.current);
90+
const firstElement = focusableElements[0];
91+
const lastElement = focusableElements[focusableElements.length - 1];
92+
9593
if (event.shiftKey) {
9694
if (document.activeElement === firstElement) {
9795
event.preventDefault();
@@ -107,12 +105,11 @@ function PythonCodePanel({ isOpen, onClose, algorithm }) {
107105
};
108106

109107
document.addEventListener('keydown', handleKeyDown);
110-
firstElement?.focus();
108+
getFocusableElements(panelRef.current)[0]?.focus();
111109

112110
return () => document.removeEventListener('keydown', handleKeyDown);
113111
}
114112
}, [isOpen, onClose]);
115-
116113
const pythonCode = getPythonCode(algorithm);
117114
const displayName = getAlgorithmDisplayName(algorithm);
118115
const pseudocodeText = useMemo(

0 commit comments

Comments
 (0)