403Webshell
Server IP : 54.37.205.81  /  Your IP : 216.73.216.76
Web Server : nginx/1.22.1
System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64
User : debian ( 1000)
PHP Version : 8.2.32
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tmp/cc-agency-update/copyrightchain_app/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tmp/cc-agency-update/copyrightchain_app/components/FileUploader.tsx
import React, { useState, useRef, useMemo, useEffect } from 'react';
import { Upload, File as FileIcon, Loader2, CheckCircle, ShieldAlert, X, Fingerprint, Mail, CreditCard, ExternalLink, Timer, AlertCircle, RefreshCcw, ShieldCheck, Sparkles, Stamp, Cpu, Zap, Star, Shield, Lock, Link as LinkIcon, Camera, Video, Coins, Cloud } from 'lucide-react';
import { Certificate, Language, ProtectionTier } from '../types';
import { translations } from '../translations';
import { getPlatformKeyPair, signCertificateData, exportPublicKeyAsJwk } from '../cryptoUtils';
import { useNotification } from '../App';

interface FileUploaderProps {
  onComplete: (cert: Certificate) => void;
  lang: Language;
}

const FileUploader: React.FC<FileUploaderProps> = ({ onComplete, lang }) => {
  const t = useMemo(() => translations[lang], [lang]);
  const { notify, deductCredits, user } = useNotification();
  const [file, setFile] = useState<File | null>(null);
  const [title, setTitle] = useState('');
  const [author, setAuthor] = useState('');
  const [isAuthorManuallySet, setIsAuthorManuallySet] = useState(false);
  const [selectedTier, setSelectedTier] = useState<ProtectionTier>('pro');
  const [isProcessing, setIsProcessing] = useState(false);
  const [isWitnessRecording, setIsWitnessRecording] = useState(false);
  const [witnessVideo, setWitnessVideo] = useState<Blob | null>(null);
  const [witnessVideoFileName, setWitnessVideoFileName] = useState('');
  const [progress, setProgress] = useState(0);
  const [statusMsg, setStatusMsg] = useState('');
  const [contentType, setContentType] = useState<'standard' | 'ai_assisted'>('standard');
  const [aiToolUsed, setAiToolUsed] = useState('');
  const [aiPrompt, setAiPrompt] = useState('');
  const [humanContribution, setHumanContribution] = useState('');
  const [aiProcessNotes, setAiProcessNotes] = useState('');
  const [aiDeclarationAccepted, setAiDeclarationAccepted] = useState(false);

  useEffect(() => {
    if (!isAuthorManuallySet) {
      setAuthor(lang === 'it' 
        ? 'ABO Bank ufficio legale Dipartimento di Proprietà Intellettuale e Diritto d’Autore' 
        : 'ABO Bank Legal Office - Intellectual Property and Copyright Department');
    }
  }, [lang, isAuthorManuallySet]);

  const fileInputRef = useRef<HTMLInputElement>(null);
  const videoProofInputRef = useRef<HTMLInputElement>(null);
  const videoRef = useRef<HTMLVideoElement>(null);
  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
  const witnessStreamRef = useRef<MediaStream | null>(null);

  const witnessDeclarationText = lang === 'it'
    ? 'Leggi ad alta voce: dichiaro di effettuare questo deposito su CopyrightChain, confermo che il file caricato è l’opera che intendo certificare e che conserverò il video originale scaricato al termine della registrazione.'
    : 'Read aloud: I declare that I am making this deposit on CopyrightChain. I confirm that the uploaded file is the work I intend to certify, and that I will keep the original video file downloaded at the end of this recording.';

  useEffect(() => {
    if (isWitnessRecording && witnessStreamRef.current && videoRef.current) {
      videoRef.current.srcObject = witnessStreamRef.current;
      videoRef.current.play().catch(() => {});
    }
    if (!isWitnessRecording && videoRef.current) {
      videoRef.current.srcObject = null;
    }
  }, [isWitnessRecording]);

  const tiers = [
    { id: 'basic', name: "Base", credits: 1, icon: <Zap size={20} />, color: 'slate', desc: "Hash + PDF + verifica pubblica" },
    { id: 'pro', name: "Bitcoin Proof", credits: 2, icon: <Shield size={20} />, color: 'blue', popular: true, desc: "Ancoraggio OpenTimestamps / Bitcoin" },
    { id: 'timestamp', name: "Marca Temporale InfoCert", credits: 3, icon: <Stamp size={20} />, color: 'indigo', desc: "Timbro InfoCert + PDF + verifica pubblica" },
  ];

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedFile = e.target.files?.[0];
    if (selectedFile) {
      setFile(selectedFile);
      if (!title) {
        const baseName = selectedFile.name.replace(/\.[^/.]+$/, "");
        setTitle(baseName || selectedFile.name);
      }
    }
  };

  const fileToBase64 = (file: File): Promise<string> => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = error => reject(error);
    });
  };

  const sha256Blob = async (blob: Blob): Promise<string> => {
    const buffer = await blob.arrayBuffer();
    const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
    return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
  };

  const downloadBlob = (blob: Blob, filename: string) => {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1500);
  };

  const downloadTextFile = (filename: string, content: string, mime = 'application/json') => {
    const blob = new Blob([content], { type: mime });
    downloadBlob(blob, filename);
  };

  const handleVideoProofFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedVideo = e.target.files?.[0];
    if (!selectedVideo) return;

    if (!selectedVideo.type.startsWith('video/')) {
      notify(lang === 'it' ? 'Seleziona un file video valido.' : 'Please select a valid video file.', 'error');
      return;
    }

    if (selectedVideo.size < 2048) {
      notify(lang === 'it' ? 'Il file video è troppo piccolo o vuoto.' : 'The video file is too small or empty.', 'error');
      return;
    }

    setWitnessVideo(selectedVideo);
    setWitnessVideoFileName(selectedVideo.name);
    notify(lang === 'it'
      ? 'Video Proof caricato. Il file resta sul tuo dispositivo: CopyrightChain salverà solo l’hash.'
      : 'Video Proof uploaded. The file stays on your device: CopyrightChain will store only the hash.',
      'success'
    );
  };

  const startWitnessRecording = async () => {
    try {
      setWitnessVideo(null);
      setWitnessVideoFileName('');

      const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
      witnessStreamRef.current = stream;
      setIsWitnessRecording(true);
      setTimeout(() => {
        if (videoRef.current && witnessStreamRef.current) {
          videoRef.current.srcObject = witnessStreamRef.current;
          videoRef.current.play().catch(() => {});
        }
      }, 150);

      const recorderOptions =
        typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported('video/webm;codecs=vp8,opus')
          ? { mimeType: 'video/webm;codecs=vp8,opus' }
          : undefined;

      const recorder = recorderOptions ? new MediaRecorder(stream, recorderOptions) : new MediaRecorder(stream);
      const chunks: Blob[] = [];

      recorder.ondataavailable = (e) => {
        if (e.data && e.data.size > 0) chunks.push(e.data);
      };

      recorder.onstop = () => {
        const blob = new Blob(chunks, { type: 'video/webm' });
        stream.getTracks().forEach(track => track.stop());
        witnessStreamRef.current = null;
        if (videoRef.current) videoRef.current.srcObject = null;
        setIsWitnessRecording(false);

        if (!blob || blob.size < 2048) {
          notify(lang === 'it'
            ? 'Video troppo breve o vuoto. Registra di nuovo leggendo la dichiarazione davanti alla camera.'
            : 'Video too short or empty. Please record again while reading the declaration in front of the camera.',
            'error'
          );
          return;
        }

        const safeTs = new Date().toISOString().replace(/[:.]/g, '-');
        const fileName = `copyrightchain-video-proof-${safeTs}.webm`;
        setWitnessVideo(blob);
        setWitnessVideoFileName(fileName);
        downloadBlob(blob, fileName);

        notify(lang === 'it'
          ? 'Video Proof registrato e scaricato. Conserva il file: CopyrightChain non lo archivia.'
          : 'Video Proof recorded and downloaded. Keep the file: CopyrightChain does not store it.',
          'success'
        );
      };

      mediaRecorderRef.current = recorder;
      recorder.start(1000);

      setTimeout(() => {
        if (recorder.state === 'recording') recorder.stop();
      }, 30000);
    } catch (err) {
      witnessStreamRef.current = null;
      if (videoRef.current) videoRef.current.srcObject = null;
      setIsWitnessRecording(false);
      notify(lang === 'it' ? 'Errore camera o microfono. Controlla i permessi del browser.' : 'Camera or microphone error. Check browser permissions.', 'error');
    }
  };

  const stopWitnessRecording = () => {
    const recorder = mediaRecorderRef.current;
    if (recorder && recorder.state === 'recording') {
      recorder.stop();
    }
  };

  const startCertificationFlow = async (e?: React.FormEvent) => {
    if (e) e.preventDefault();
    if (!file) return;

    const clientToken = sessionStorage.getItem('ccp_client_token');
    if (!clientToken) {
      notify('Sessione cliente non valida. Effettua di nuovo il login.', 'error');
      return;
    }

    if (contentType === 'ai_assisted') {
      if (!humanContribution.trim()) {
        notify(lang === 'it'
          ? 'Per AI Proof inserisci una breve descrizione del contributo umano.'
          : 'For AI Proof, enter a short description of the human contribution.',
          'error'
        );
        return;
      }
      if (!aiDeclarationAccepted) {
        notify(lang === 'it'
          ? 'Per AI Proof devi accettare la dichiarazione sul contributo umano.'
          : 'For AI Proof, you must accept the human contribution declaration.',
          'error'
        );
        return;
      }
    }

    setIsProcessing(true);
    setProgress(0);
    setStatusMsg('Preparazione file...');

    try {
      const base64Data = await fileToBase64(file);
      const buffer = await file.arrayBuffer();
      const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
      const timestamp = new Date().toISOString();
      const requestId = `REQ-${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
      const certType = selectedTier === 'basic'
        ? 'base'
        : selectedTier === 'timestamp'
          ? 'timestamp'
          : 'blockchain';

      let videoHash = '';
      let videoDeclarationText = '';
      let videoFileName = witnessVideoFileName;

      if (witnessVideo) {
        setStatusMsg('Calcolo hash Video Proof...');
        videoHash = await sha256Blob(witnessVideo);
        if (!videoFileName) {
          videoFileName = `copyrightchain-video-proof-${requestId}.webm`;
        }
        videoDeclarationText = lang === 'it'
          ? `Dichiarazione guidata Video Proof. ${author || 'Depositante'} dichiara nel video di effettuare questo deposito su CopyrightChain, conferma che il file caricato è l'opera da certificare, associata all'hash SHA-256 ${hash}, e dichiara che conserverà il video originale scaricato al termine della registrazione.`
          : `Guided Video Proof declaration. ${author || 'Depositor'} declares in the video that they are making this deposit on CopyrightChain, confirms that the uploaded file is the work to be certified, associated with SHA-256 hash ${hash}, and declares that they will keep the original video file downloaded at the end of the recording.`;
      }

      setProgress(25);
      setStatusMsg('Invio richiesta al gateway...');

      const res = await fetch('https://app.copyrightchain.it/api/ai/certify', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-CCP-Token': clientToken
        },
        body: JSON.stringify({
          cert_type: certType,
          request_id: requestId,
          title,
          file_hash: hash,
          original_filename: file.name,
          author,
          deposit_date: timestamp,
          content_type: contentType,
          ai_tool_used: contentType === 'ai_assisted' ? aiToolUsed.trim() : '',
          ai_prompt: contentType === 'ai_assisted' ? aiPrompt.trim() : '',
          human_contribution: contentType === 'ai_assisted' ? humanContribution.trim() : '',
          ai_process_notes: contentType === 'ai_assisted' ? aiProcessNotes.trim() : '',
          ai_declaration_accepted: contentType === 'ai_assisted' ? aiDeclarationAccepted : false,
          video_proof: Boolean(witnessVideo && videoHash),
          video_sha256: videoHash,
          video_file_name: videoFileName,
          video_retained_by_platform: false,
          video_declaration_text: videoDeclarationText
        })
      });

      const data = await res.json().catch(() => null);

      if (!res.ok || !data?.ok) {
        throw new Error(data?.error || data?.details || 'certificate_request_failed');
      }

      if (witnessVideo && videoHash) {
        const manifest = {
          certificate_id: String(data?.cert_id || requestId),
          request_id: requestId,
          title,
          original_filename: file.name,
          file_sha256: String(data?.file_hash || hash),
          video_proof: true,
          video_sha256: videoHash,
          video_file_name: videoFileName,
          video_retained_by_platform: false,
          declaration: videoDeclarationText,
          deposit_date: timestamp,
          verify_url: String(data?.verify_url || ''),
          content_type: contentType,
          ai_proof: contentType === 'ai_assisted' ? {
            ai_tool_used: aiToolUsed.trim(),
            ai_prompt: aiPrompt.trim(),
            human_contribution: humanContribution.trim(),
            ai_process_notes: aiProcessNotes.trim(),
            ai_declaration_accepted: aiDeclarationAccepted
          } : undefined,
          generated_at: new Date().toISOString(),
          note: 'CopyrightChain non conserva il file video. La verifica futura richiede il video originale scaricato dall’utente.'
        };
        downloadTextFile(`copyrightchain-video-proof-manifest-${String(data?.cert_id || requestId)}.json`, JSON.stringify(manifest, null, 2));
      }

      setProgress(70);
      setStatusMsg('Firma metadati locali...');

      const keys = await getPlatformKeyPair();
      const signature = await signCertificateData(`${hash}|${timestamp}`, keys.privateKey);
      const publicKeyJwk = await exportPublicKeyAsJwk(keys.publicKey);

      const finalAttestation = `Certificato emesso via CopyrightChain AI Gateway. ID: ${data?.cert_id || requestId}. Verifica: ${data?.verify_url || ''}`;

      const completedCert: Certificate = {
        id: String(data?.cert_id || requestId),
        title,
        fileName: file.name,
        fileHash: String(data?.file_hash || hash),
        timestamp,
        author,
        legalAttestation: finalAttestation,
        status: 'certified',
        paymentStatus: 'paid',
        bitcoinEvidence: certType === 'blockchain' ? { status: 'pending' } : undefined,
        timestampEvidence: certType === 'timestamp' ? {
          status: String(data?.timestamp_status || data?.timestamp?.status || 'issued'),
          provider: String(data?.timestamp?.provider || 'Openapi / InfoCert'),
          environment: String(data?.timestamp?.environment || ''),
          type: String(data?.timestamp?.type || 'infocert'),
          hash: String(data?.timestamp?.hash || data?.file_hash || hash),
          issuedAt: String(data?.timestamp?.timestamp_issued_at || ''),
          transaction: String(data?.timestamp?.transaction || ''),
          bodyUrl: String(data?.timestamp?.timestamp_body_url || ''),
          available: typeof data?.timestamp?.available === 'number' ? data.timestamp.available : undefined,
          used: typeof data?.timestamp?.used === 'number' ? data.timestamp.used : undefined
        } : undefined,
        mimeType: file.type,
        fileData: base64Data,
        language: lang,
        signature,
        publicKey: publicKeyJwk,
        tier: selectedTier,
        ...(contentType === 'ai_assisted' ? ({
          contentType,
          aiAssisted: true,
          aiToolUsed: aiToolUsed.trim(),
          aiPrompt: aiPrompt.trim(),
          humanContribution: humanContribution.trim(),
          aiProcessNotes: aiProcessNotes.trim(),
          aiDeclarationAccepted
        } as any) : {}),
        ...(String(data?.verify_url || '').trim() ? ({ verifyUrl: String(data.verify_url) } as any) : {}),
        ...((videoHash || data?.video_sha256) ? ({
          videoProof: true,
          videoSha256: String(videoHash || data?.video_sha256 || ''),
          videoFileName: String(videoFileName || data?.video_file_name || ''),
          videoRetainedByPlatform: false,
          videoDeclarationText
        } as any) : {})
      };

      setProgress(100);
      setStatusMsg(t.cloudSynced);
      setIsProcessing(false);
      notify(data?.billing_method === 'agency_cert_credits'
        ? `Certificazione completata con crediti Agenzia. Residui: ${data?.agency?.remaining_credits ?? '—'}`
        : (typeof data?.balance_abo === 'number'
          ? `Certificazione completata. Saldo aggiornato: ${data.balance_abo} ABO`
          : 'Certificazione completata con successo.'),
        'success'
      );
      onComplete(completedCert);
    } catch (err: any) {
      console.error(err);
      setIsProcessing(false);
      notify(`Errore certificazione: ${err?.message || t.errorCert}`, 'error');
    }
  };

  return (
    <div className="max-w-4xl mx-auto space-y-12">
      <div className="text-center space-y-4">
        <h2 className="text-5xl font-black text-slate-900 tracking-tight uppercase">NUOVO DEPOSITO LEGALE</h2>
        <div className="flex items-center justify-center gap-3">
            <div className="bg-blue-50 text-blue-700 px-6 py-2 rounded-full border border-blue-100 font-black text-xs uppercase tracking-widest flex items-center gap-2 shadow-sm">
               <Cloud size={14} /> Server: Sincronizzato (WordPress DB)
            </div>
            <div className="bg-yellow-50 text-yellow-700 px-6 py-2 rounded-full border border-yellow-100 font-black text-xs uppercase tracking-widest flex items-center gap-2 shadow-sm">
               <Coins size={14} /> Wallet: {user.credits.balance} ABO
            </div>
        </div>
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
        <div className="lg:col-span-2 space-y-8">
            <div className="bg-white p-12 rounded-[4rem] shadow-2xl border border-slate-100">
                <form onSubmit={startCertificationFlow} className="space-y-10">
                <div onClick={() => fileInputRef.current?.click()} className={`border-4 border-dashed rounded-[3rem] p-16 flex flex-col items-center justify-center cursor-pointer transition-all duration-500 ${file ? 'border-blue-400 bg-blue-50/30' : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50/50'}`}>
                    <input type="file" ref={fileInputRef} onChange={handleFileChange} className="hidden" />
                    {file ? (
                        <div className="text-center">
                            <div className="w-20 h-20 bg-white rounded-2xl flex items-center justify-center text-blue-600 mx-auto mb-4 shadow-xl"><FileIcon size={40} /></div>
                            <p className="font-black text-slate-900 text-lg">{file.name}</p>
                        </div>
                    ) : (
                        <div className="text-center">
                            <div className="w-20 h-20 bg-slate-50 rounded-2xl flex items-center justify-center text-slate-200 mx-auto mb-4"><Upload size={40} /></div>
                            <p className="font-black text-slate-700 text-xl">Scegli il file da proteggere</p>
                            <p className="text-[10px] font-black text-slate-300 uppercase mt-4 tracking-widest">Salvataggio automatico nel tuo archivio privato</p>
                        </div>
                    )}
                </div>

                <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
                    <div className="space-y-2">
                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Titolo opera</label>
                        <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10" placeholder="Esempio: Logo Aziendale 2025" required />
                    </div>
                    <div className="space-y-2">
                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Autore / Registrante</label>
                        <input type="text" value={author} onChange={(e) => { setAuthor(e.target.value); setIsAuthorManuallySet(true); }} className="w-full px-6 py-4 rounded-2xl bg-slate-50 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-blue-500/10" placeholder="Nome autore" required />
                    </div>
                </div>

                <div className="bg-slate-950 text-white p-8 rounded-[3rem] border border-slate-800 shadow-xl space-y-6">
                    <div className="flex items-start gap-4">
                        <div className="w-12 h-12 rounded-2xl bg-indigo-500/20 text-indigo-300 flex items-center justify-center shrink-0">
                            <Sparkles size={24} />
                        </div>
                        <div>
                            <h4 className="font-black uppercase tracking-tight text-xl leading-none">AI Proof / Contributo umano</h4>
                            <p className="text-xs text-slate-300 leading-relaxed mt-3 font-bold">
                                {lang === 'it'
                                  ? 'Se il contenuto è stato creato o modificato con AI, puoi dichiarare strumenti usati, prompt e intervento umano nel certificato.'
                                  : 'If the content was created or modified with AI, you can declare tools, prompt and human contribution in the certificate.'}
                            </p>
                        </div>
                    </div>

                    <div className="space-y-2">
                        <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">
                            {lang === 'it' ? 'Tipo contenuto' : 'Content type'}
                        </label>
                        <select
                            value={contentType}
                            onChange={(e) => {
                              const next = e.target.value as 'standard' | 'ai_assisted';
                              setContentType(next);
                              if (next !== 'ai_assisted') {
                                setAiDeclarationAccepted(false);
                              }
                            }}
                            className="w-full px-6 py-4 rounded-2xl bg-white text-slate-900 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-indigo-500/20"
                        >
                            <option value="standard">{lang === 'it' ? 'Contenuto standard' : 'Standard content'}</option>
                            <option value="ai_assisted">{lang === 'it' ? 'Contenuto creato o modificato con AI' : 'Content created or modified with AI'}</option>
                        </select>
                    </div>

                    {contentType === 'ai_assisted' && (
                        <div className="space-y-5 animate-in fade-in duration-300">
                            <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
                                <div className="space-y-2">
                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">
                                        {lang === 'it' ? 'Strumento AI usato' : 'AI tool used'}
                                    </label>
                                    <input
                                        type="text"
                                        value={aiToolUsed}
                                        onChange={(e) => setAiToolUsed(e.target.value)}
                                        className="w-full px-6 py-4 rounded-2xl bg-white text-slate-900 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-indigo-500/20"
                                        placeholder="ChatGPT, Midjourney, DALL·E, Sora, Canva AI..."
                                    />
                                </div>
                                <div className="space-y-2">
                                    <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">
                                        {lang === 'it' ? 'Prompt principale facoltativo' : 'Main prompt optional'}
                                    </label>
                                    <input
                                        type="text"
                                        value={aiPrompt}
                                        onChange={(e) => setAiPrompt(e.target.value)}
                                        className="w-full px-6 py-4 rounded-2xl bg-white text-slate-900 border border-slate-100 font-black text-xs outline-none focus:ring-4 focus:ring-indigo-500/20"
                                        placeholder={lang === 'it' ? 'Prompt o sintesi del prompt usato' : 'Prompt or summary of the prompt used'}
                                    />
                                </div>
                            </div>

                            <div className="space-y-2">
                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">
                                    {lang === 'it' ? 'Descrizione intervento umano' : 'Human contribution description'}
                                </label>
                                <textarea
                                    value={humanContribution}
                                    onChange={(e) => setHumanContribution(e.target.value)}
                                    rows={4}
                                    className="w-full px-6 py-4 rounded-2xl bg-white text-slate-900 border border-slate-100 font-bold text-xs outline-none focus:ring-4 focus:ring-indigo-500/20"
                                    placeholder={lang === 'it'
                                      ? 'Esempio: ho ideato il concept, scritto il prompt, selezionato le versioni generate, modificato colori e layout, corretto il testo e preparato il file finale.'
                                      : 'Example: I created the concept, wrote the prompt, selected generated versions, edited colors and layout, corrected text and prepared the final file.'}
                                />
                            </div>

                            <div className="space-y-2">
                                <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">
                                    {lang === 'it' ? 'Note sul processo creativo facoltative' : 'Optional creative process notes'}
                                </label>
                                <textarea
                                    value={aiProcessNotes}
                                    onChange={(e) => setAiProcessNotes(e.target.value)}
                                    rows={3}
                                    className="w-full px-6 py-4 rounded-2xl bg-white text-slate-900 border border-slate-100 font-bold text-xs outline-none focus:ring-4 focus:ring-indigo-500/20"
                                    placeholder={lang === 'it' ? 'Bozze, versioni, revisioni, file intermedi o passaggi rilevanti.' : 'Drafts, versions, revisions, intermediate files or relevant steps.'}
                                />
                            </div>

                            <label className="flex items-start gap-3 bg-white/10 border border-white/15 rounded-2xl p-4 cursor-pointer">
                                <input
                                    type="checkbox"
                                    checked={aiDeclarationAccepted}
                                    onChange={(e) => setAiDeclarationAccepted(e.target.checked)}
                                    className="mt-1"
                                />
                                <span className="text-[11px] leading-relaxed font-bold text-slate-200">
                                    {lang === 'it'
                                      ? 'Dichiaro che il contenuto certificato è stato creato o modificato con l’ausilio di strumenti di intelligenza artificiale e che il processo ha incluso un mio intervento umano di ideazione, selezione, modifica, revisione o finalizzazione.'
                                      : 'I declare that the certified content was created or modified with the support of artificial intelligence tools and that the process included my human contribution through ideation, selection, editing, review or finalization.'}
                                </span>
                            </label>
                        </div>
                    )}
                </div>

                <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
                    {tiers.map(tier => (
                    <button key={tier.id} type="button" onClick={() => setSelectedTier(tier.id as ProtectionTier)} className={`p-6 rounded-[2.5rem] border-2 transition-all flex flex-col items-center gap-2 ${selectedTier === tier.id ? 'border-blue-600 bg-blue-50/20 shadow-lg' : 'border-slate-100 bg-white hover:border-slate-200'}`}>
                        <div className={`${selectedTier === tier.id ? 'text-blue-600' : 'text-slate-300'}`}>{tier.icon}</div>
                        <span className="text-[10px] font-black uppercase tracking-widest">{tier.name}</span>
                        <span className="text-[9px] font-black text-slate-400">{tier.credits} credito{tier.credits > 1 ? 'i' : ''}</span>
                    </button>
                    ))}
                </div>

                <button type="submit" disabled={!file || isProcessing} className="w-full py-6 bg-slate-900 text-white rounded-[2rem] font-black uppercase tracking-[0.2em] shadow-2xl disabled:opacity-30 group flex items-center justify-center gap-3">
                    {isProcessing ? <Loader2 size={20} className="animate-spin" /> : <ShieldCheck size={20} />} 
                    {isProcessing ? statusMsg : `CERTIFICA E ARCHIVIA (${tiers.find(t => t.id === selectedTier)?.credits} credito${(tiers.find(t => t.id === selectedTier)?.credits || 1) > 1 ? 'i' : ''})`}
                </button>
                </form>
            </div>
        </div>

        <div className="lg:col-span-1 space-y-8">
            <div className="bg-blue-900 text-white p-10 rounded-[3rem] shadow-2xl relative overflow-hidden group">
                <div className="absolute top-0 right-0 p-10 opacity-5 group-hover:scale-110 transition-transform"><Video size={100} /></div>
                <h4 className="font-black uppercase tracking-tight text-xl leading-none mb-6">Video Proof</h4>
                <p className="text-xs text-blue-200 leading-relaxed mb-5 font-medium">
                  {lang === 'it'
                    ? 'Registra una dichiarazione video di deposito. Premi stop quando hai finito. Durata massima 30 secondi. Il video viene scaricato sul tuo dispositivo e non viene conservato da CopyrightChain.'
                    : 'Record a video declaration for this deposit. Press stop when finished. Maximum duration: 30 seconds. The video is downloaded to your device and is not stored by CopyrightChain.'}
                </p>
                <div className="bg-white/10 border border-white/15 rounded-2xl p-4 mb-6">
                  <p className="text-[9px] font-black uppercase tracking-widest text-blue-200 mb-2">
                    {lang === 'it' ? 'Testo da leggere nel video' : 'Text to read in the video'}
                  </p>
                  <p className="text-[11px] leading-relaxed font-bold text-white">
                    {witnessDeclarationText}
                  </p>
                </div>
                <input
                  type="file"
                  ref={videoProofInputRef}
                  accept="video/*"
                  onChange={handleVideoProofFileChange}
                  className="hidden"
                />

                {isWitnessRecording && (
                  <video ref={videoRef} autoPlay muted playsInline className="w-full rounded-2xl border border-white/10 mb-6 bg-black" />
                )}
                {witnessVideo ? (
                    <div className="bg-emerald-500/20 border border-emerald-500/30 p-4 rounded-2xl space-y-2">
                      <div className="flex items-center gap-3"><CheckCircle size={24} className="text-emerald-400" /><span className="text-xs font-black uppercase text-emerald-400">Video Proof pronto</span></div>
                      <p className="text-[10px] text-emerald-100 break-all font-bold">{witnessVideoFileName || 'video scaricato dall’utente'}</p>
                      <p className="text-[10px] text-blue-100 font-bold">Conserva questo file: servirà per una verifica futura dell’hash video.</p>
                    </div>
                ) : (
                    <div className="space-y-3">
                      <button type="button" onClick={isWitnessRecording ? stopWitnessRecording : startWitnessRecording} className={`w-full py-4 rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all ${isWitnessRecording ? 'bg-red-600 text-white animate-pulse' : 'bg-white text-slate-900'}`}>{isWitnessRecording ? (lang === 'it' ? 'Ferma registrazione' : 'Stop recording') : (lang === 'it' ? 'Registra Video Proof' : 'Record Video Proof')}</button>
                      {!isWitnessRecording && (
                        <button type="button" onClick={() => videoProofInputRef.current?.click()} className="w-full py-4 rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all bg-blue-500/20 text-white border border-white/20 hover:bg-blue-500/30">
                          {lang === 'it' ? 'Carica Video Proof già registrato' : 'Upload existing Video Proof'}
                        </button>
                      )}
                    </div>
                )}
            </div>
            
            <div className="bg-white p-8 rounded-[3rem] border border-slate-100 shadow-sm">
                <h5 className="font-black text-[10px] uppercase tracking-widest text-slate-400 mb-6">Infrastruttura di Archiviazione</h5>
                <div className="space-y-4">
                    <InfraStep label="WP DB Sync" status="Attivo" />
                    <InfraStep label="Bitcoin Node" status="Connesso" />
                    <InfraStep label="Encryption" status="AES-256" />
                </div>
            </div>
        </div>
      </div>
    </div>
  );
};

const InfraStep = ({ label, status }: { label: string, status: string }) => (
    <div className="flex justify-between items-center text-[10px] font-black uppercase">
        <span className="text-slate-400">{label}</span>
        <span className="text-blue-600">{status}</span>
    </div>
);

export default FileUploader;

Youez - 2016 - github.com/yon3zu
LinuXploit