Aller au contenu

← Retour au projet sur l'accueil

ZUNELI

Bêta privée · Accès privé

ZUNELI

Une plateforme de mise en relation entre chauffeurs VTC et propriétaires de véhicules à Dakar. Elle met les gens en contact, rien de plus : aucune commission, aucune garantie de transaction.

Essayer la démo

Rôle
Conception et développement, seul
ZUNELI

Le moteur de score entre vos mains

ZUNELIZUNELIBêta privéeSimulation · données fictives
Qu'est-ce qui est réel ?

Simulation construite sur le code de ZUNELI, données fictives.

Code réel
Le moteur de score (quatre critères, poids fixes), l'ordre de la liste (profils abonnés d'abord, puis le score) et le masquage des numéros viennent du code de ZUNELI, comportement inchangé. La jauge de score est copiée telle quelle.
Données fictives
Les huit profils de chauffeurs, les zones et l'annonce du propriétaire sont inventés. Aucune personne réelle, aucun numéro de téléphone.
Illustration ajoutée
L'écran du laboratoire de score et son panneau « comment c'est calculé » sont écrits pour ce site. ZUNELI n'a pas d'écran de ce genre.
ZUNELI

ZUNELI

La démo ne se charge que si vous la demandez.

  1. 01

    Le problème

    À Dakar, un propriétaire veut confier une voiture et un chauffeur veut en vivre. Chacun doit faire confiance à un inconnu, et rien ne lui dit si l'arrangement lui convient. ZUNELI met les deux côtés en relation et dit pourquoi un profil convient. Elle ne prend aucune commission et ne garantit aucune transaction.

  2. 02

    La chaîne de recherche

    Quatre étapes, toujours dans cet ordre. La requête : l'annonce du propriétaire et les filtres. Les candidats : les profils de chauffeurs qui passent les filtres. Le score : quatre critères fixes, chacun un oui ou un non. Le classement : les profils abonnés d'abord, puis le score ordonne chaque groupe. Changez la requête dans la démo et regardez la liste bouger.

  3. 03

    Un score avec sa raison

    Le score vient de quatre critères fixes : le versement, la zone, la disponibilité, et le fait que l'autre partie soit vérifiée. Ils ne comptent pas tous autant : le versement et la vérification pèsent le plus, la zone vient ensuite, la disponibilité en dernier. Sous « Pourquoi ce match ? », chaque carte liste les critères qui correspondent. Les pondérations exactes ne sont pas publiées. Les champs comparés sont des listes fermées, jamais du texte libre.

  4. 04

    Un numéro reste masqué

    Un numéro n'est jamais affiché en clair, quel que soit l'abonnement. La règle tient dans une seule fonction que tous les écrans appellent. Saisissez un numéro inventé dans la démo et regardez ce qu'afficherait une carte. Rien de ce que vous tapez ne quitte votre navigateur.

    Sous le capot

    La règle de masquage : quatre chiffres restent visibles, le reste devient des étoiles.

    Fichier : ZUNELI · src/lib/format.ts · lignes 17 à 30

    export function maskPhone(phone: string): string {  const country = AVAILABLE_COUNTRIES.find((c) => phone.startsWith(c.dialCode));   if (!country) {    const visible = phone.slice(0, 4);    const masked = "*".repeat(Math.max(phone.length - 4, 0));    return `${visible}${masked}`;  }   const local = phone.slice(country.dialCode.length);  const visible = local.slice(0, 4);  const masked = "*".repeat(Math.max(local.length - 4, 0));  return `${country.dialCode} ${visible}${masked}`;}

    Code réel du projet, copié et anonymisé.

  5. 05

    Essayez vous-même

    Changez la zone et le versement du propriétaire, filtrez sur les chauffeurs vérifiés, ouvrez « Comment c'est calculé » sur n'importe quel profil. Tout s'exécute dans votre navigateur sur des profils inventés. Ce qui se passe après une mise en relation n'est pas simulé : l'acceptation d'une proposition est une seule étape dans le vrai code, montrée ci-dessous.

    Sous le capot

    L'acceptation d'une proposition se fait une seule fois : le statut ne bouge que s'il est encore en attente, donc deux acceptations simultanées ne peuvent pas passer toutes les deux.

    Fichier : ZUNELI · src/app/actions/proposal-actions.ts · lignes 142 à 170

      await prisma.$transaction(async (tx) => {    const updated = await tx.proposal.updateMany({      where: { id: proposalId, status: "PENDING" },      data: { status: "ACCEPTED" },    });    if (updated.count === 0) {      throw new Error("Cette proposition a deja ete traitee.");    }     const existingCollaboration = await tx.collaboration.findUnique({ where: { proposalId } });    if (existingCollaboration) {      throw new Error("Une collaboration existe deja pour cette proposition.");    }     // Anti-survente FleetOffer (roadmap Phase 5, 2026-09-18) : impossible    // pour un Vehicle individuel (un seul exemplaire par definition), mais    // une FleetOffer peut avoir plusieurs Proposal PENDING simultanees sur    // des Match differents — verifie qu'il reste au moins une unite libre    // au moment precis de l'acceptation (dans la transaction, pour eviter    // une course entre deux acceptations concurrentes).    if (proposal.fleetOfferId) {      const activeCount = await tx.collaboration.count({        where: { isActive: true, proposal: { fleetOfferId: proposal.fleetOfferId } },      });      const fleetOffer = await tx.fleetOffer.findUniqueOrThrow({ where: { id: proposal.fleetOfferId } });      if (activeCount >= fleetOffer.totalUnits) {        throw new Error("Aucune unite disponible sur cette offre groupee : toutes les unites sont deja affectees a une collaboration active.");      }    }

    Code réel du projet, copié et anonymisé.

Ce que vous pouvez y faire

  • Changer la zone et le versement journalier du propriétaire et regarder huit chauffeurs fictifs se reclasser
  • Lire la raison de chaque rang, en mots
  • Filtrer sur les profils vérifiés ou abonnés
  • Ouvrir une conversation et essayer le numéro de téléphone masqué
  • Passer du mode clair au mode sombre

Stack

Next.js 16 · React 19 · TypeScript · Tailwind CSS v4 · Prisma 7 · Neon Postgres · NextAuth v5 · Cloudflare R2 · Vercel

Contexte

À Dakar, un propriétaire qui veut confier son véhicule et un chauffeur qui cherche une voiture se trouvent surtout par le bouche-à-oreille et les messageries. ZUNELI met les deux côtés en relation. La V1 est livrée et fonctionne en bêta privée. Elle ne prend aucune commission et ne garantit aucune transaction : son rôle s'arrête là où commence la conversation entre deux personnes.

Le problème

Chacun doit faire confiance à un inconnu. Le propriétaire remet un véhicule, le chauffeur engage son activité dessus. Rien ne permet de vérifier l'autre, ni de savoir si l'accord proposé lui convient. ZUNELI est conçue autour de profils vérifiés, un score de compatibilité qui se lit d'un coup d'œil, et des numéros de téléphone qui ne circulent jamais en clair.

Contraintes

  • Le marché de Dakar, avec un usage surtout mobile
  • La confiance entre inconnus : un véhicule est confié, une activité est engagée
  • Les numéros de téléphone sont des données sensibles
  • Connexion par numéro de téléphone et code à usage unique, l'envoi des codes par SMS n'étant pas encore branché

Ce que j'ai construit

  • Inscription et profils pour les chauffeurs comme pour les propriétaires, en bêta privée
  • Découverte en liste, avec un score de compatibilité sur chaque profil. Dans la liste des chauffeurs montrée aux propriétaires, les profils abonnés passent en premier et le score les ordonne au sein de chaque groupe
  • Messagerie intégrée entre profils mis en relation
  • Propositions, puis collaborations, puis avis à la fin d'une collaboration
  • Vérification de documents, examinée depuis une page d'administration
  • Pays et métiers stockés dans des tables, pour en ajouter sans toucher au code

Décisions structurantes

  1. 01

    Un score que chacun peut lire

    Le score de compatibilité repose sur quatre critères fixes : le versement, la zone, la disponibilité et le fait que l'autre partie soit vérifiée. Ils n'ont pas tous la même importance, et les pondérations exactes ne sont pas publiées. Ce sont des règles simples et fixes. Les champs comparés sont des listes fermées, jamais du texte libre, si bien que deux profils se comparent toujours de la même façon. La complétude du profil et la qualité du véhicule restent hors du score et ne servent qu'à départager, pour que le chiffre reste quelque chose qu'on peut expliquer à un chauffeur ou à un propriétaire. Le score n'est pas tout l'ordre : dans la liste des chauffeurs montrée aux propriétaires, les profils abonnés sont listés en premier et le score les ordonne au sein de chaque groupe.

    Écarté

    Un score qui intègre la complétude du profil et la qualité du véhicule. Il aurait rendu le chiffre plus difficile à expliquer.

  2. 02

    Un numéro de téléphone n'est jamais affiché en clair

    Un numéro de téléphone est une donnée sensible : ZUNELI n'en affiche aucun, pas même à un abonné. La mise en relation entre les deux côtés passe par la plateforme.

    Écarté

    Débloquer les numéros pour les abonnés premium. Même pour eux, la mise en relation reste sur la plateforme.

  3. 03

    La confiance vient de la vérification, et les badges restent distincts

    Les documents de vérification partent vers un bucket Cloudflare R2 privé et ne s'ouvrent que par un lien signé qui expire au bout de cinq minutes. Ils sont validés depuis une page d'administration. Les photos publiques vivent dans un second bucket, séparé, et la limite de 5 Mo est revérifiée côté serveur. Le badge « Vérifié » et le badge « Profil complet » sont toujours affichés séparément.

    Écarté

    Un badge unique pour les deux. Un profil complet n'est pas un profil vérifié, et les mélanger aurait dit aux utilisateurs quelque chose de faux.