fix: address multiple PR review issues

- Fixed accessibility in audit logs with keyboard navigation and ARIA attributes
- Refactored ThreatAnalysisResults interface to module level for reusability
- Added BatchOperation enum validation and proper CSV escaping in batch monitoring
- Removed unused company state causing skeleton view in dashboard overview
- Enhanced error handling with user-facing messages for metrics loading
- Replaced hardcoded timeouts with condition-based waits in E2E tests
- Removed duplicate state management in security monitoring hooks
- Fixed CSRF documentation to show proper secret fallback pattern
- Updated CSP metrics docs with GDPR Article 6(1)(f) legal basis clarification
- Fixed React hooks order to prevent conditional execution after early returns
- Added explicit button type to prevent form submission behavior
This commit is contained in:
2025-07-14 00:24:10 +02:00
parent bba79d509b
commit ef1f0769c2
9 changed files with 221 additions and 77 deletions

View File

@ -184,7 +184,10 @@ export default function AuditLogsPage() {
}, [session?.user?.role, hasFetched, fetchAuditLogs]);
// Function to refresh audit logs (for filter changes)
const refreshAuditLogs = useCallback(() => {
const refreshAuditLogs = useCallback((newPage?: number) => {
if (newPage !== undefined) {
setPagination((prev) => ({ ...prev, page: newPage }));
}
setHasFetched(false);
}, []);
@ -445,8 +448,8 @@ export default function AuditLogsPage() {
size="sm"
disabled={!pagination.hasPrev}
onClick={() => {
setPagination((prev) => ({ ...prev, page: prev.page - 1 }));
refreshAuditLogs();
const newPage = pagination.page - 1;
refreshAuditLogs(newPage);
}}
>
Previous
@ -456,8 +459,8 @@ export default function AuditLogsPage() {
size="sm"
disabled={!pagination.hasNext}
onClick={() => {
setPagination((prev) => ({ ...prev, page: prev.page + 1 }));
refreshAuditLogs();
const newPage = pagination.page + 1;
refreshAuditLogs(newPage);
}}
>
Next

View File

@ -470,7 +470,7 @@ function DashboardContent() {
const { data: session, status } = useSession();
const router = useRouter();
const [metrics, setMetrics] = useState<MetricsResult | null>(null);
const [company] = useState<Company | null>(null);
// Remove unused company state that was causing skeleton view to always show
const [refreshing, setRefreshing] = useState<boolean>(false);
const [isInitialLoad, setIsInitialLoad] = useState<boolean>(true);
@ -501,12 +501,39 @@ function DashboardContent() {
// Map overview data to metrics format expected by the component
const mappedMetrics: Partial<MetricsResult> = {
totalSessions: overviewData.totalSessions,
avgSessionsPerDay: 0, // Will be computed properly later
avgSessionLength: null,
days: {},
languages: {},
countries: {},
belowThresholdCount: 0,
avgSessionsPerDay: overviewData.avgSessionsPerDay || 0,
avgSessionLength: overviewData.avgSessionLength || 0,
days:
overviewData.timeSeriesData?.reduce(
(acc, item) => {
if (item.date) {
acc[item.date] = item.sessionCount || 0;
}
return acc;
},
{} as Record<string, number>
) || {},
languages:
overviewData.languageDistribution?.reduce(
(acc, item) => {
if (item.language) {
acc[item.language] = item.count;
}
return acc;
},
{} as Record<string, number>
) || {},
countries:
overviewData.geographicDistribution?.reduce(
(acc, item) => {
if (item.country) {
acc[item.country] = item.count;
}
return acc;
},
{} as Record<string, number>
) || {},
belowThresholdCount: overviewData.belowThresholdCount || 0,
// Map sentiment data to individual counts
sentimentPositiveCount:
overviewData.sentimentDistribution?.find(
@ -541,12 +568,6 @@ function DashboardContent() {
}
}, [overviewData, isInitialLoad]);
useEffect(() => {
if (metricsError) {
console.error("Error fetching metrics:", metricsError);
}
}, [metricsError]);
// Admin refresh sessions mutation
const refreshSessionsMutation = trpc.admin.refreshSessions.useMutation({
onSuccess: () => {
@ -567,6 +588,30 @@ function DashboardContent() {
// tRPC queries handle data fetching automatically
}, [status, router]);
// Enhanced error handling with user feedback
if (metricsError) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center space-y-4">
<div className="text-red-600 text-lg font-semibold">
Failed to load dashboard data
</div>
<p className="text-gray-600">
There was an error loading your dashboard metrics. Please try
refreshing the page.
</p>
<button
type="button"
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Refresh Page
</button>
</div>
</div>
);
}
async function handleRefresh() {
if (isAuditor) return;
@ -594,14 +639,14 @@ function DashboardContent() {
);
}
if (!metrics || !company) {
if (!metrics) {
return <DashboardSkeleton />;
}
return (
<div className="space-y-8">
<DashboardHeader
company={company}
company={{ name: "Analytics Dashboard" } as Company}
metrics={metrics}
isAuditor={isAuditor}
refreshing={refreshing}