Acest site
Acest portofoliu, construit în React pe Firebase. Vizitatorii trimit o cerere structurată și îi pot urmări statusul; eu răspund dintr-o căsuță privată care se actualizează live.
- Tip
- Proiect propriu
- Rol
- Design, dezvoltare, lansare
- Când
- Septembrie 2026
- Construit cu
- React 19, React Router 8, Firebase Auth, Cloud Firestore, TypeScript, Vite, Vitest
- Status
- Online

Cerința
Aplic la posturi cu React și Firebase și iau proiecte mici pentru firme. Un portofoliu pentru amândouă trebuie să dovedească stack-ul despre care vorbește, așa că acest site e construit pe el: React pentru fiecare pagină, Firebase pentru tot ce trebuie să țină minte ceva.
Îl citesc două grupuri. Angajatorii vor să vadă cum lucrez: structura, testele, deciziile. Proprietarii de firme vor să știe dacă le pot construi site-ul și cum mă contactează. Site-ul e în engleză, germană și română și rulează pe GitHub Pages, care servește doar fișiere. Deci nu am un server al meu: regulile de securitate sunt backend-ul.
Ce am construit
Întâi a venit designul, în Claude Design: trei direcții, apoi fiecare pagină pentru desktop și telefon, înainte de orice linie de cod. Direcția aleasă, „Timetable”, folosește litere înguste, o listă de proiecte care se citește ca un mers al trenurilor și un singur marker galben. Codul a fost scris cu Claude Code. Eu verific ce ajunge online, iar verificările de mai jos rulează la fiecare schimbare.
- Fiecare pagină publică este prerandată în HTML la build cu React Router 8: 30 de pagini, zece în fiecare limbă, fiecare cu linkuri hreflang și propria Content Security Policy.
- Un formular de cerere care se schimbă după subiect (un proiect, un post, altceva), verifică fiecare câmp pe măsură ce scrieți și salvează cererea în Cloud Firestore.
- O pagină de status care se actualizează live când deschid cererea și când o marchez ca răspunsă.
- O căsuță privată la /admin: autentificare Google, doar pentru mine, o listă live cu filtre, statusuri, notițe private și ștergere.
- 29 de teste pentru regulile de securitate pe emulatorul Firestore, 65 de teste unitare și un test end-to-end în Chrome pe emulatoarele Auth și Firestore. CI-ul le rulează pe toate trei la fiecare push.
- Browserul dumneavoastrăFormularul verifică fiecare câmp pe măsură ce scrieți. Firebase se încarcă abia când apăsați Trimite.
- Autentificare anonimăÎi dă acestui browser un ID, ca să-și poată urmări cererea. Nu vi se cere nimic.
- O singură scriere în lotCererea și o notă despre când a trimis acest browser ultima cerere. Se salvează amândouă sau niciuna.
- Regulile de securitate FirestoreRefuză câmpurile necunoscute, tipurile greșite, textul prea lung și o a doua trimitere din același browser în mai puțin de un minut.
- Firestore, în UEFiecare cerere are o dată de ștergere peste un an. După ea, căsuța mea o șterge.
- Pagina de status Se schimbă singură când citesc cererea și când răspund.Căsuța mea Autentificare Google, doar pentru mine. Cererile noi apar fără reîncărcare.

Părțile grele
Fără server, iar formularul tot trebuie să fie sigur
Oricine poate apela Firestore direct cu configurația publică a site-ului, ocolind formularul. Așa că verificările din formular sunt doar pentru oameni; ce contează sunt regulile.
O cerere nouă trebuie să aibă exact câmpurile așteptate, fiecare cu tipul și lungimea corecte, o adresă de e-mail validă, statusul „new”, ID-ul propriu al expeditorului și o oră de creare egală cu ceasul serverului. Eu pot schimba ulterior trei câmpuri și nimic altceva. Formularul și regulile împart aceleași limite: testele regulilor importă același obiect LIMITS ca formularul și verifică fiecare limită și un caracter peste ea, ca cele două să nu se poată despărți.
// firestore.rules
function isValidNewInquiry(data) {
return data.keys().hasAll(inquiryFields())
&& data.keys().hasOnly(inquiryFields())
&& data.kind in ["project", "job", "other"]
&& isText(data.name, 1, 100)
&& isText(data.message, 20, 2000)
// …
&& data.uid == request.auth.uid
&& data.createdAt == request.time
&& expiresInAYear(data.expireAt);
}
O cerere pe minut, fără server
Un formular fără limită de trimitere invită un script care trimite o mie de mesaje. Fără server nu ai unde să le numeri. Firestore însă poate.
Când apăsați Trimite, Firebase autentifică browserul anonim, ca să aibă un ID. Cererea se scrie într-un singur lot cu un al doilea document, senders/ID-ul-dumneavoastră, care păstrează ora ultimei cereri. Regula pentru cerere verifică cu getAfter() că același lot pune ora pe acum. Regula pentru documentul senders refuză schimbarea dacă ora anterioară e mai recentă de 60 de secunde. Se salvează amândouă documentele sau niciunul.
// firestore.rules
match /senders/{uid} {
allow update: if request.auth != null
&& request.auth.uid == uid
&& request.resource.data.keys().hasOnly(["lastSentAt"])
&& request.resource.data.lastSentAt == request.time
&& request.time > resource.data.lastSentAt + duration.value(60, "s");
}
Asta oprește o buclă scăpată din greșeală, nu un atacator hotărât, care poate crea utilizatori anonimi noi. App Check ar fi următorul strat.

Firebase doar când e nevoie
SDK-ul Firebase e cea mai mare dependență a site-ului, iar majoritatea vizitatorilor nu trimit niciodată o cerere. Așa că nicio pagină nu îl importă la încărcare. Formularul încarcă codul pentru Firestore și Auth cu un import() dinamic când apăsați Trimite, iar pagina de status când se deschide.
Pagina principală livrează circa 130 KB de JavaScript, comprimat, și niciun cod Firebase. Trimiterea mai adaugă circa 155 KB. Căsuța, care are nevoie de autentificarea Google, e o rută separată pe care vizitatorii nu o încarcă niciodată. Fonturile sunt găzduite pe site, doar subseturile latine, care acoperă engleza, germana și româna.
Date cu termen de expirare
Politica de confidențialitate promite că o cerere se șterge după 12 luni, iar o asemenea promisiune se uită ușor. Așa că fiecare cerere are o dată expireAt peste un an, iar regulile refuză orice altă dată. Browserul o pune după propriul ceas, deci regula acceptă o zi abatere în orice sens.
Regula time-to-live din Firestore ar șterge singură asemenea documente, dar are nevoie de planul plătit Blaze, iar site-ul rulează pe cel gratuit. Așa că se ocupă căsuța: de fiecare dată când o deschid, caută toate cererile al căror termen a trecut și le șterge împreună cu notița mea, într-un singur lot. Notițele mele preiau expireAt al cererii, iar regulile verifică să coincidă, ca nicio notiță să nu trăiască mai mult decât cererea.
Rezultate
- 29 de teste pentru regulile de securitate trec pe emulatorul Firestore, plus 65 de teste unitare (27 septembrie 2026).
- Testul end-to-end trece toate cele 17 verificări: studiile de caz care se deschid din lista de proiecte în trei limbi, trimiterea, validarea, limita de trimitere, un browser străin refuzat, autentificarea proprietarului și pagina de status care se schimbă live (27 septembrie 2026).
- 30 de pagini prerandate în trei limbi, fără derulare orizontală la 375 px și fără erori în consolă.
- Pagina principală: circa 130 KB de JavaScript, comprimat, fără cod Firebase.
Ce aș face mai departe
- App Check cu reCAPTCHA Enterprise, ca doar acest site să poată scrie în Firestore.
- Pe planul Blaze: ștergerea cererilor vechi prin regula time-to-live din Firestore și un e-mail către mine pentru fiecare cerere nouă, dintr-o Cloud Function.
- Trimiterea cererilor cu Firestore Lite, mult mai mic, și SDK-ul complet doar pe paginile care se actualizează live.