website refactor

This commit is contained in:
2026-01-14 23:46:04 +01:00
parent c1a86348d7
commit 4a2d7d15a5
294 changed files with 5637 additions and 3418 deletions

View File

@@ -1,36 +0,0 @@
'use client';
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
import { QueryClientProvider } from '@/lib/providers/QueryClientProvider';
import { AuthProvider } from '@/lib/auth/AuthContext';
import { FeatureFlagProvider } from '@/lib/feature/FeatureFlagProvider';
import NotificationProvider from '@/components/notifications/NotificationProvider';
import { NotificationIntegration } from '@/components/errors/NotificationIntegration';
import { EnhancedErrorBoundary } from '@/components/errors/EnhancedErrorBoundary';
import DevToolbar from '@/components/dev/DevToolbar';
import React from 'react';
interface AppWrapperProps {
children: React.ReactNode;
enabledFlags: string[];
}
export function AppWrapper({ children, enabledFlags }: AppWrapperProps) {
return (
<ContainerProvider>
<QueryClientProvider>
<AuthProvider>
<FeatureFlagProvider flags={enabledFlags}>
<NotificationProvider>
<NotificationIntegration />
<EnhancedErrorBoundary enableDevOverlay={process.env.NODE_ENV === 'development'}>
{children}
{process.env.NODE_ENV === 'development' && <DevToolbar />}
</EnhancedErrorBoundary>
</NotificationProvider>
</FeatureFlagProvider>
</AuthProvider>
</QueryClientProvider>
</ContainerProvider>
);
}

View File

@@ -45,9 +45,9 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
maxWidth,
...props
}: BoxProps<T> & ComponentPropsWithoutRef<T>,
ref: ForwardedRef<any>
ref: ForwardedRef<HTMLElement>
) => {
const Tag = (as as any) || 'div';
const Tag = (as as ElementType) || 'div';
const spacingMap: Record<string | number, string> = {
0: '0', 0.5: '0.5', 1: '1', 1.5: '1.5', 2: '2', 2.5: '2.5', 3: '3', 3.5: '3.5', 4: '4',
@@ -81,10 +81,10 @@ export const Box = forwardRef(<T extends ElementType = 'div'>(
className
].filter(Boolean).join(' ');
const style = maxWidth ? { maxWidth, ...((props as any).style || {}) } : (props as any).style;
const style = maxWidth ? { maxWidth, ...((props as Record<string, unknown>).style as object || {}) } : (props as Record<string, unknown>).style;
return (
<Tag ref={ref} className={classes} {...props} style={style}>
<Tag ref={ref as React.ForwardedRef<HTMLElement>} className={classes} {...props} style={style as React.CSSProperties}>
{children}
</Tag>
);

View File

@@ -1,6 +1,6 @@
'use client';
import React, { useState } from 'react';
import React from 'react';
// ISO 3166-1 alpha-2 country code to full country name mapping
const countryNames: Record<string, string> = {
@@ -78,8 +78,6 @@ export function CountryFlag({
className = '',
showTooltip = true
}: CountryFlagProps) {
const [showTooltipState, setShowTooltipState] = useState(false);
const sizeClasses = {
sm: 'text-xs',
md: 'text-sm',
@@ -92,17 +90,9 @@ export function CountryFlag({
return (
<span
className={`inline-flex items-center relative ${sizeClasses[size]} ${className}`}
onMouseEnter={() => setShowTooltipState(true)}
onMouseLeave={() => setShowTooltipState(false)}
title={showTooltip ? countryName : undefined}
>
<span className="select-none">{flag}</span>
{showTooltip && showTooltipState && (
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs font-medium text-white bg-deep-graphite border border-charcoal-outline rounded shadow-lg whitespace-nowrap z-50">
{countryName}
<span className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-charcoal-outline"></span>
</span>
)}
</span>
);
}

View File

@@ -1,8 +1,6 @@
'use client';
import React, {
useEffect,
useRef,
type ReactNode,
type KeyboardEvent as ReactKeyboardEvent,
} from 'react';
@@ -34,26 +32,6 @@ export function Modal({
onOpenChange,
isOpen,
}: ModalProps) {
const dialogRef = useRef<HTMLDivElement | null>(null);
const previouslyFocusedElementRef = useRef<Element | null>(null);
useEffect(() => {
if (isOpen) {
previouslyFocusedElementRef.current = document.activeElement;
const focusable = getFirstFocusable(dialogRef.current);
if (focusable) {
focusable.focus();
} else if (dialogRef.current) {
dialogRef.current.focus();
}
return;
}
if (!isOpen && previouslyFocusedElementRef.current instanceof HTMLElement) {
previouslyFocusedElementRef.current.focus();
}
}, [isOpen]);
const handleKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Escape') {
if (onOpenChange) {
@@ -61,26 +39,6 @@ export function Modal({
}
return;
}
if (event.key === 'Tab') {
const focusable = getFocusableElements(dialogRef.current);
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1] ?? first;
if (!first || !last) {
return;
}
if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
} else if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
}
}
};
const handleBackdropClick = (event: React.MouseEvent<HTMLDivElement>) => {
@@ -104,7 +62,6 @@ export function Modal({
onClick={handleBackdropClick}
>
<Box
ref={dialogRef}
style={{ width: '100%', maxWidth: '28rem', borderRadius: '1rem', backgroundColor: '#0f1115', border: '1px solid #262626', boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)', outline: 'none' }}
tabIndex={-1}
>
@@ -162,24 +119,3 @@ export function Modal({
</Box>
);
}
function getFocusableElements(root: HTMLElement | null): HTMLElement[] {
if (!root) return [];
const selectors = [
'a[href]',
'button:not([disabled])',
'textarea:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
];
const nodes = Array.from(
root.querySelectorAll<HTMLElement>(selectors.join(',')),
);
return nodes.filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
}
function getFirstFocusable(root: HTMLElement | null): HTMLElement | null {
const elements = getFocusableElements(root);
return elements[0] ?? null;
}

View File

@@ -1,11 +1,11 @@
import React, { ChangeEvent } from 'react';
import React, { ChangeEvent, SelectHTMLAttributes } from 'react';
interface SelectOption {
value: string;
label: string;
}
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
id?: string;
'aria-label'?: string;
value?: string;

View File

@@ -1,7 +1,4 @@
'use client';
import React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
import { motion } from 'framer-motion';
import { Box } from './Box';
import { Text } from './Text';
@@ -20,8 +17,6 @@ export function Toggle({
description,
disabled = false,
}: ToggleProps) {
const shouldReduceMotion = useReducedMotion();
return (
<label className={`flex items-start justify-between cursor-pointer py-3 border-b border-charcoal-outline/50 last:border-b-0 ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>
<Box style={{ flex: 1, paddingRight: '1rem' }}>
@@ -48,7 +43,7 @@ export function Toggle({
className="absolute inset-0 rounded-full bg-primary-blue"
initial={{ boxShadow: '0 0 0px rgba(25, 140, 255, 0)' }}
animate={{ boxShadow: '0 0 12px rgba(25, 140, 255, 0.4)' }}
transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
transition={{ duration: 0.2 }}
/>
)}
@@ -65,7 +60,6 @@ export function Toggle({
type: 'spring',
stiffness: 500,
damping: 30,
duration: shouldReduceMotion ? 0 : undefined,
}}
/>
</button>