Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser

- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3)
- Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor
- Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS
- Configurare adresă platformă externalizată (build args / .env / config.js)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Top Clossers 2026-07-17 12:33:59 +03:00
commit cec967f953
321 changed files with 80506 additions and 0 deletions

View file

@ -0,0 +1,183 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import {
AlertOctagon, AlertTriangle, Info, ShieldCheck, AlertCircle, Cpu, Layers, Target, Plus,
} from 'lucide-react-native';
import type { LucideIcon } from 'lucide-react-native';
import {
HeadlineCard, FindingCard, SectionDivider, StatBlock, ChipsRow, Chip, CollapseButton,
} from '../editorial';
import type { FindingAccent } from '../../theme/colors';
import type { V3TechniquesResult, TechniqueDefinition } from '../../types/analysis';
import { formatTechniqueName } from '../../utils/formatTechniqueName';
import { localized, useTranslation, smoothEnums } from '../../i18n';
const DEFAULT_VISIBLE = 5;
const scoreAccent = (s: number) =>
s >= 70 ? '#ef4444' : s >= 40 ? '#f97316' : s >= 20 ? '#eab308' : '#22c55e';
const scoreIcon = (s: number): LucideIcon => {
if (s >= 70) return AlertOctagon;
if (s >= 40) return AlertTriangle;
if (s >= 20) return Info;
return ShieldCheck;
};
const scoreCategory = (s: number, isRo: boolean): string => {
if (s >= 70) return isRo ? 'Critic' : 'Critical';
if (s >= 40) return isRo ? 'Ridicat' : 'High';
if (s >= 20) return isRo ? 'Mediu' : 'Medium';
return isRo ? 'Scăzut' : 'Low';
};
const techIcon = (name: string): LucideIcon => {
const dim = (name?.split('.')[0] || '').toUpperCase();
if (dim === 'D3') return Cpu;
if (dim === 'D5') return Layers;
if (dim === 'D8') return Target;
return AlertCircle;
};
const techAccent = (severity: number): FindingAccent => {
if (severity >= 70) return 'critical';
if (severity >= 40) return 'warning';
return 'info';
};
const buildTldr = (r: V3TechniquesResult, isRo: boolean): string => {
const techCount = r.techniques_detected?.length ?? 0;
const dimCount = r.dimensions_affected?.length ?? 0;
const dims = (r.dimensions_affected ?? []).join(' · ');
if (techCount === 0) {
return isRo
? 'Nicio tehnică de manipulare detectată în acest conținut.'
: 'No manipulation techniques detected in this content.';
}
return isRo
? `${techCount} ${techCount === 1 ? 'tehnică' : 'tehnici'} de manipulare detectate în ${dimCount} ${dimCount === 1 ? 'dimensiune' : 'dimensiuni'}${dims ? ` (${dims})` : ''}.`
: `${techCount} manipulation ${techCount === 1 ? 'technique' : 'techniques'} detected across ${dimCount} ${dimCount === 1 ? 'dimension' : 'dimensions'}${dims ? ` (${dims})` : ''}.`;
};
interface Props {
result: V3TechniquesResult;
defMap?: Map<string, TechniqueDefinition>;
}
export default function TechniquesResults({ result, defMap }: Props) {
const { language } = useTranslation();
const isRo = language === 'ro';
const [expanded, setExpanded] = useState(false);
const score = result.manipulation_score ?? 0;
const accent = scoreAccent(score);
const Icon = scoreIcon(score);
const techCount = result.techniques_detected?.length ?? 0;
const dimCount = result.dimensions_affected?.length ?? 0;
const techs = result.techniques_detected ?? [];
const visible = expanded ? techs.length : Math.min(DEFAULT_VISIBLE, techs.length);
const shown = techs.slice(0, visible);
const remaining = techs.length - visible;
const warningFlags: string[] = (result.coupling_context as any)?.for_claims?.warning_flags ?? [];
return (
<View>
<HeadlineCard
accent={accent}
score={score}
scoreLabel={isRo ? '/ 100 SCOR' : '/ 100 SCORE'}
eyebrow={isRo ? 'Tehnici de manipulare' : 'Manipulation techniques'}
icon={Icon}
category={scoreCategory(score, isRo)}
descriptor={
dimCount > 0
? `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')} · ${dimCount} ${isRo ? (dimCount === 1 ? 'dimensiune' : 'dimensiuni') : (dimCount === 1 ? 'dimension' : 'dimensions')}`
: `${techCount} ${isRo ? (techCount === 1 ? 'tehnică' : 'tehnici') : (techCount === 1 ? 'technique' : 'techniques')}`
}
tldr={buildTldr(result, isRo)}
/>
{(score > 0 || dimCount > 0) && (
<StatBlock>
<ChipsRow>
{(result.dimensions_affected ?? []).map(dim => (
<Chip key={dim} variant="info">{dim}</Chip>
))}
{techCount > 0 && (
<Chip variant="violet">{techCount} {isRo ? 'tehnici detectate' : 'techniques detected'}</Chip>
)}
</ChipsRow>
</StatBlock>
)}
{techCount > 0 && (
<>
<SectionDivider
label={isRo ? 'Tehnici de manipulare' : 'Manipulation techniques'}
count={`${techCount} ${isRo ? 'detectate' : 'detected'}${dimCount > 0 ? ` · ${(result.dimensions_affected ?? []).join(' · ')}` : ''}`}
/>
{shown.map((tech, i) => {
const sev = tech.severity ?? (tech.intensity * 33);
const rawName = localized(tech, 'name') || `Technique #${tech.technique_id}`;
const def = defMap?.get(rawName);
const dimName = localized(tech, 'dimension_name', 'dimension');
const subdimName = localized(tech, 'subdimension_name', 'subdimension');
const eyebrowParts = [
(tech.name?.split('.')[0] || '').toUpperCase() || undefined,
dimName ? (subdimName ? `${dimName} · ${subdimName}` : dimName) : undefined,
tech.severity != null ? `${isRo ? 'severitate' : 'severity'} ${tech.severity}` : undefined,
].filter(Boolean) as string[];
return (
<FindingCard
key={i}
icon={techIcon(tech.name || '')}
accent={techAccent(sev)}
eyebrow={eyebrowParts.join(' · ')}
headline={formatTechniqueName(rawName)}
quote={smoothEnums(tech.evidence) || smoothEnums(def ? localized(def, 'description') : '') || undefined}
/>
);
})}
{remaining > 0 && (
<CollapseButton
onPress={() => setExpanded(true)}
expanded={false}
leading={<Plus size={16} color="rgba(255,255,255,0.6)" strokeWidth={2} />}
label={
isRo
? `Vezi celelalte ${remaining} ${remaining === 1 ? 'tehnică' : 'tehnici'}`
: `Show ${remaining} more ${remaining === 1 ? 'technique' : 'techniques'}`
}
/>
)}
{expanded && techs.length > DEFAULT_VISIBLE && (
<CollapseButton
onPress={() => setExpanded(false)}
expanded={true}
label={isRo ? 'Ascunde' : 'Hide'}
/>
)}
</>
)}
{techCount === 0 && (
<FindingCard
icon={ShieldCheck}
accent="success"
eyebrow={isRo ? 'Conținut curat' : 'Clean content'}
headline={isRo ? 'Nicio tehnică de manipulare detectată' : 'No manipulation techniques detected'}
/>
)}
{warningFlags.length > 0 && (
<FindingCard
icon={AlertTriangle}
accent="warning"
eyebrow={isRo ? 'Semnale adiționale' : 'Additional signals'}
headline={isRo ? 'Indicatori de manipulare la screening' : 'Manipulation signals at screening'}
quote={warningFlags.map(f => f.replace(/_/g, ' ')).join(' · ')}
/>
)}
</View>
);
}