100 lines
2.7 KiB
TypeScript
100 lines
2.7 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import type { ReactNode } from 'react';
|
|
import { tradingKillSwitchClient } from '../lib/runtime';
|
|
|
|
interface AccessibilityState {
|
|
status: 'loading' | 'available' | 'maintenance' | 'product_disabled';
|
|
message?: string;
|
|
}
|
|
|
|
const initialState: AccessibilityState = {
|
|
status: 'loading',
|
|
};
|
|
|
|
export function ProductAccessibilityGate({ children }: { children: ReactNode }) {
|
|
const [state, setState] = useState<AccessibilityState>(initialState);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
|
|
async function loadAvailability() {
|
|
try {
|
|
const result = await tradingKillSwitchClient.check();
|
|
if (!active) {
|
|
return;
|
|
}
|
|
|
|
if (result.disabled) {
|
|
setState({
|
|
status: 'product_disabled',
|
|
message: result.message ?? 'Trading access is temporarily disabled.',
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState({ status: 'available' });
|
|
} catch (error) {
|
|
// Fail open — kill switch service being down should not block users.
|
|
// The kill switch is a safety net, not an auth gate; degraded availability
|
|
// is preferable to a hard block when the check service is unreachable.
|
|
console.warn('[ProductAccessibilityGate] Kill switch check failed, defaulting to available.', error);
|
|
if (active) {
|
|
setState({ status: 'available' });
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadAvailability();
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
if (state.status === 'loading') {
|
|
return <CenteredMessage title="Loading trading workspace..." />;
|
|
}
|
|
|
|
if (state.status !== 'available') {
|
|
return (
|
|
<CenteredMessage
|
|
title={state.status === 'maintenance' ? 'Trading under maintenance' : 'Trading temporarily unavailable'}
|
|
body={state.message}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function CenteredMessage({ title, body }: { title: string; body?: string }) {
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
background: 'var(--background)',
|
|
color: 'var(--foreground)',
|
|
padding: '2rem',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
maxWidth: '32rem',
|
|
border: '1px solid var(--bl-border-subtle)',
|
|
borderRadius: '1rem',
|
|
padding: '2rem',
|
|
background: 'var(--bl-surface-overlay)',
|
|
boxShadow: 'var(--card-shadow)',
|
|
}}
|
|
>
|
|
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
|
|
{body ? (
|
|
<p style={{ margin: 0, color: 'var(--bl-text-quiet)', lineHeight: 1.6 }}>{body}</p>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|