
BookTruck – site de prezentare, donații și campanii de strângere de fonduri pentru biblioteca mobilă
booktruck.cartilepefata.ro ↗Context
Pe Victor Miron l-am cunoscut în decembrie 2016, la un eveniment din Timișoara despre cărți. Atunci am aflat de Asociația culturală Cărțile pe Față și de misiunea ei de a-i face pe români să citească mai mult. Am început ca voluntar la cluburile de lectură „Vocea cititorului”, am co-organizat două ediții, iar după mutarea în Cluj-Napoca am preluat, treptat, infrastructura IT a asociației.
BookTruck este biblioteca pe roți a asociației, care duce cărți copiilor din satele fără bibliotecă funcțională. Site-ul a pornit în martie 2022, pe Next.js 12, ca un site de prezentare cu formular de donații. De atunci a devenit locul prin care asociația strânge bani, primește cărți și voluntari și își vinde propriile cărți, iar în toamna lui 2026 a primit un panou de administrare, campanii de strângere de fonduri și conturi pentru donatori.
Problema
Un site de strângere de fonduri trebuie să fie, în primul rând, de încredere: o donație lunară trebuie să apară în evidențe chiar dacă procesatorul de plăți nu anunță site-ul, iar o rambursare făcută din contul de comerciant trebuie să se vadă și în panou. Asociația voia și campanii de tipul „dăruiește-ți ziua de naștere”, create de echipă sau chiar de susținători, dar fără ca orice pagină să ajungă publică nevăzută de nimeni. Pe lângă asta, vinde cărțile BookTruck, organizează în fiecare an o campanie de Crăciun și are nevoie să-și actualizeze singură partenerii, galeria, presa, punctele de donare și harta școlilor adoptate – fără să-mi scrie mie pentru fiecare logo.
Ce am construit
- Un site public în română și engleză, cu adrese traduse pentru fiecare pagină (de exemplu
/doneaza-cartiși/en/donate-books). Prima pagină are formularul de donații cu un contor al sumei strânse, ultima campanie activă, licitația care se încheie cel mai curând și harta școlilor adoptate; varianta în română adaugă redirecționarea a 3,5% din impozit (scoasă în față în sezonul declarațiilor), sponsorizările, susținerea prin SMS și abonarea la newsletterul de pe Substack. - Donații unice sau lunare prin EuPlătesc, cu card, sau prin ordin de plată. Sumele propuse, moneda și textele formularului se editează din panou; un text lăsat gol folosește traducerea implicită.
- Campanii de strângere de fonduri („Dăruiește-ți ziua de naștere”): fiecare campanie are pagina ei, cu poze și video, descriere, sumă țintă, bară de progres, timp rămas, butoane de distribuire și lista donațiilor. Donatorii pot dona o dată sau lunar, pot rămâne anonimi și pot lăsa un mesaj. Avatarul vine din Gravatar, iar dacă nu există, site-ul desenează o carte generată din donație. O donație anonimă nu folosește niciodată Gravatar, ca poza să nu-l dea de gol pe donator. Campaniile încheiate rămân online, cu rezultatul final, dar nu mai primesc donații.
- Un flux de aprobare pentru campanii: ciornă, previzualizare (vizibilă doar autorului și administratorilor), trimitere spre aprobare, aprobare sau respingere cu motiv. Administratorii primesc e-mail la fiecare trimitere, iar autorul află rezultatul tot prin e-mail.
- Licitații caritabile: actori, scriitori, muzicieni și alți susținători donează obiecte, iar publicul licitează online. Fiecare licitație e un singur obiect, cu pagina, pozele și ora ei de închidere, iar pagina
/licitatiile grupează pe colecții. Oferta se face din cont, cu bifa de angajament, și e un angajament, nu o plată: nu se reține niciun ban cât timp licitația rulează. Fiecare ofertă trebuie să urce peste cea curentă cu cel puțin un pas și cel mult un plafon, ambele stabilite pentru fiecare obiect, iar același cont nu poate face două oferte la rând. Cine nu vrea să-i apară numele licitează ca „Licitator #47”, cu un număr care rămâne același pe toată licitația. O ofertă în ultimele minute prelungește licitația, ca nimeni să nu câștige doar pentru că a apăsat ultimul. Vizitatorii pot urmări o licitație și primesc e-mail cu o zi și cu o oră înainte de final, iar cine intră în cont de pe o licitație se întoarce direct la ea. În „Contul meu”, la „Ofertele mele”, fiecare își vede ofertele și licitațiile câștigate. - Conturi pentru susținători, cu înregistrare protejată de Cloudflare Turnstile și autentificare prin magic link, fără parolă. În „Contul meu” fiecare își vede donațiile, ofertele (inclusiv cele anonime și cele lunare), își editează datele și își poate porni propriile campanii, care trec prin același flux de aprobare, cu limite pentru numărul de campanii deschise și durata lor.
- Campania de Crăciun: o pagină cu progresul strângerii de fonduri, numărătoare inversă până la Crăciun, galerie și un mesaj de mulțumire după donație. Când ținta e atinsă, pagina trece singură la mulțumiri și parteneri. Din septembrie până după Crăciun, o bară din meniu numără zilele rămase.
- Donații de cărți: o hartă interactivă a județelor cu punctele unde se pot lăsa cărți, cu telefon și link de hartă pentru fiecare.
- Harta școlilor adoptate: județul Cluj desenat pe unități administrativ-teritoriale, cu școlile adoptate și cele care așteaptă un adoptator ca puncte pe satele lor.
- Comenzi pentru cărțile BookTruck, printre ele „Dana și verișoarele”, plătite cu Stripe și facturate automat prin Oblio, pe persoană fizică sau pe firmă, cu livrare prin curier sau ridicare personală. Formularul se ascunde când nu mai e nimic în stoc, iar o comandă trimisă de două ori reia plata deja începută în loc să creeze alta.
- Voluntariat: un formular cu disponibilitatea fiecăruia, trimis prin Brevo direct coordonatorilor.
- Galerie, apariții în presă, parteneri pe trei niveluri și pagina „Biblioteca vie cu Raluca Anton”, cu bilete prin Oveit. Pozele din galerie au adrese proprii, ca să poată fi distribuite.
- Detalii de sezon: între 20 octombrie și 2 noiembrie, pe site apar personaje de Halloween.
- Consimțământ pentru cookie-uri pe categorii: statisticile (Google Analytics, Smartlook, Vercel Analytics) și conținutul extern (YouTube, Substack) se încarcă doar după acord. La refuz, cookie-urile de statistică se șterg, iar alegerea se cere din nou după șase luni.
- Un panou de administrare: un tablou de bord cu donațiile lunii, donatorii lunari activi și comenzile de procesat; donatori, cu istoricul plăților, rambursare pe plată și anularea donațiilor lunare; statistici cu grafice care compară perioade; editorul formularului de donații; campanii, cu aprobare și moderarea mesajelor; licitații, oferte și adjudecări, cu cifrele lor pe tabloul de bord și export CSV; comenzi (Primită → În pregătire → Expediată) și produse; parteneri pe niveluri, cu ordine stabilită din panou; galerie, cu ordonare prin drag-and-drop; presă; punctele de donare și școlile de pe hartă; utilizatori (echipă și conturi publice), invitații, roluri, jurnal de activitate și setări. Rolurile sunt Editor, Coordonator și Administrator, iar fiecare acțiune e verificată pe server, nu doar ascunsă în interfață.
- Import de galerie din Google Drive: lipești linkul unui dosar public și alegi pozele. Celelalte imagini – încărcări de pe calculator, sigle, produse, avatare și pozele campaniilor – stau în Supabase Storage.
Sub capotă
Piesa de care sunt cel mai mulțumit este sincronizarea donațiilor cu EuPlătesc. Rulează în fiecare noapte prin Vercel Cron (sau manual, din panou) și prinde exact cazurile în care un site obișnuit pierde date: plățile lunare pe care EuPlătesc le face fără să cheme site-ul, plățile al căror callback nu a ajuns, rambursările și anulările făcute direct din contul EuPlătesc. La prima rulare a adus tot istoricul, din 2018 încoace. Poate rula de oricâte ori fără duplicate; o rulare incrementală acoperă ultimele 35 de zile și durează câteva secunde. Pentru că același cont de comerciant e folosit și de alte site-uri, importă doar tranzacțiile BookTruck, iar descrierea plății păstrează campania, deci și plățile lunare ale unei campanii ajung înapoi la ea. Integrarea folosește biblioteca EuPlătesc pentru Node.js pe care o întrețin.
Campaniile refolosesc aceleași donații și aceeași sincronizare, nu un sistem paralel. Suma strânsă nu se salvează, ci se calculează din plățile încasate până la încheierea campaniei, minus rambursările: o donație lunară contribuie cu fiecare plată cât timp campania e activă, iar plățile de după nu mai mișcă progresul afișat. Pozele și videoclipurile se încarcă direct din browser în Supabase Storage, cu URL-uri semnate, deci fișierele mari nu trec prin server, iar un job săptămânal șterge fișierele rămase fără campanie. Descrierea se scrie cu Tiptap și se curăță pe server, cu o listă scurtă de taguri permise.
Pentru licitații, plasarea unei oferte se face într-o singură tranzacție care blochează rândul licitației în Postgres. Minimul, plafonul, timpul rămas și liderul se recitesc acolo, nu se iau de la browser, pentru că două oferte egale la câteva milisecunde distanță sunt un caz real, iar un index unic pe sumă e ultima barieră. Tot acolo se alocă numărul de licitator. Un job Vercel Cron închide în fiecare minut licitațiile expirate și altul trimite reminderele, iar orice vizită pe o pagină publică închide și ea o licitație al cărei termen a trecut, deci nimeni nu poate licita după final chiar dacă o rulare e ratată. Închiderea poate rula de mai multe ori fără efecte duble. La final, câștigătorul primește e-mail, ceilalți află rezultatul, iar echipa preia adjudecarea din panou: notițe de discuție, link de plată sau plată offline, predarea obiectului și, dacă un câștigător renunță, reatribuirea către următorul clasat, la prețul ofertei lui. Linkul de plată creează o donație obișnuită prin EuPlătesc, deci banii apar în aceleași evidențe și statistici ca restul donațiilor. Membrii echipei nu pot licita, iar o ofertă aberantă se poate anula din panou, cu motiv, fără să fie ștearsă. Regulile licitațiilor sunt funcții pure, testate, folosite și de server, și de pagini, ca interfața să arate exact limitele pe care le aplică serverul.
Site-ul a trecut de la Pages Router la App Router în 2024, are o bază de date Prisma pe Supabase Postgres, react-hook-form și zod în loc de Formik și Yup, iar i18n-ul se face cu next-intl. Prima pagină e generată static și se reîmprospătează singură, ca o campanie care se încheie să dispară fără nicio editare. Autentificarea folosește Better Auth cu magic link și limite în Redis, formularele publice sunt protejate cu Cloudflare Turnstile, iar mediile de test nu sunt indexate. Paginile au adrese canonice, variante de limbă, sitemap și imagini pentru distribuire. Regulile de donații, campanii și licitații, sincronizarea, statisticile, permisiunile, consimțământul pentru cookie-uri, rutele de plată și joburile licitațiilor au teste Vitest.
Rezultat
Asociația își administrează singură conținutul, campaniile, licitațiile și comenzile și vede, într-un singur loc, donațiile, recurențele active și progresul fiecărei campanii, cu evidențe care se reconciliază singure în fiecare noapte. Susținătorii își pot porni propria strângere de fonduri, pot licita pentru obiecte donate și își pot urmări donațiile și ofertele fără să scrie cuiva. Site-ul își face treaba zilnic: oamenii donează, comandă cărți și se înscriu ca voluntari, iar BookTruck-urile pleacă spre școli.
Tehnologii
Next.js 16 (App Router), React 19, TypeScript, next-intl, Prisma 7 pe Supabase Postgres, Supabase Storage, Better Auth, Redis, Tailwind CSS 4, shadcn/ui, Headless UI, framer-motion, Tiptap, dnd-kit, Recharts, react-hook-form + zod, EuPlătesc, Stripe, Oblio, Brevo, Cloudflare Turnstile, Google Drive API, Vitest, Vercel.
Tehnologii:
- Next.js
- next-intl
- Prisma
- Supabase
- Better Auth
- EuPlătesc
- Stripe
- Oblio
- Vitest
Partajează pe: