Bonjour,

Je suis entrain de faire une application en React avec un back en FastAPI, dans cette application j'ai des outils qui font appels à mon API, les outils sont défini par des cards sur ces cards il y a un bouton qui me permet de lancer l'action, à ce niveau tout fonctionne correctement, mais pour le moment mes paramètres sont definis dans le code, j'aimerais ajouter une modal qui me permet de paramétrer mes outils, donc sur chaque card je rajoute un bouton qui me permet d'ouvrir une modal pour le moment j'ai un composant modal générique, dans ce composant je rajoute mes champs avec le props children (je ne sais pas si c'est la meilleur manière), j'aimerais faire un sorte d'avoir une modal pour chaque outil avec des champs différent et ensuite sauvegarder les paramètres pour les utiliser par la suite lorsque je fais un appel à mon API

Voici un schema de ce que je voudrais (cela sera peut être plus compréhensible)

Schema

Ma question étant comment m'y prendre ? Je ne sais pas si il est mieux d'utiliser un context ? un portail ? ou autres ?
Je suis un peu bloqué sur ca :/

Si quelqu'un aurait une solution ou une Idée je suis preneur !

Merci à vous,
je suis disponible sur discord si des personnes veulent consulter mon code actuel :)

9 réponses


Smith john
Réponse acceptée

Si tu veux créer qu'un seul modal, je passerai probablement le contenu de la modal en props du coup. A l'interieur j'aurai un état inital que je passerai aussi en props et une fonction handleChange un truc dans ce style, t'ajustera le nom des props (je suis pas inspiré la).

       <ToolCard
          name="NMAP"
          description="Permet de scanner les hôtes, services, et ports ouverts sur un réseau."
          settingsClick={() => {
                setSelectedToolCard({
                    open: true,
                    toolName: "NMAP",
                    callback : handleSave,
                    modalContent : <> // idéalement créer des composants ça évite les trucs moche comme ça
                        <Input label="Target" placeholder="127.0.0.1/24" />
                        <Input label="Arguments" placeholder="-sn" />
                        <Input label="Toto" placeholder="-sn" />
                        <Input label="Tata" placeholder="-sn" />
                        <Input label="Titi" placeholder="-sn" />
                        <Input label="Tutu" placeholder="-sn" />
                        </>
                })
          }}
          progress={nmapScanProgress}
          onAction={handleNmap.bind(setNmapScanProgress)}
        />

        <ToolModal 
            initialData={selectedToolCard.datas} 
            modalContent={selectedToolCard.modalContent} 
            open={isOpen} 
            onClose={() => {
                    open: false,
                    toolName: null,
                    callback : null,
                    modalContent :null
            }}
            toolName={selectedToolCard.toolName}
            submitCallback={selectedToolCard.callback}
        />

// ToolModal.tsx

    type ToolModalProps = {
    ...les bon types
    }

    const ToolModal = ({...lespropsQuiVontBien}: ToolModalProps) => {
        const [datas, setDatas] = useState(initialData)

        const handleChange = (event: ChangEvent<HTMLElement>) => {
            // A AJUSTER EN FONCTION DES DATAS OU ALORS TU PASSES LE SETTER EN PROPS AUSSI
            setDatas(datas => ({...datas, [event.target.name]: event.target.value})) 
        }

        return (
            <Modal isOpen={open} onClose={onClose} toolName={toolName}>
            <div className="flex flex-col space-y-4">
              {modalContent}
              <div className="flex justify-end">
                <Button
                  onClick={submitCallback}
                  size="md"
                  icon={Save}
                  text="Valider"
                  className="bg-whiteColor"
                />
              </div>
            </div>
          </Modal>
        )
    }

Sinon tu créer un composant et une Modal pour chaque card et chacun se gere lui même

const Nmap = () => {
... les différents etats et fonctions

      return (
            <>
                le composant card de NMAP
                la modal dédié à NMAP
          </>
      )
}

Salut,

En faite je dirais que ta surtout plusieurs maniere de le faire et que ca va dépendre de la complexité de ton app.

Tu peux soit "préparer" à l'avance tes différents contenu et à l'ouverture de ta modal passé un parametre pour spécifier quel type de modal/contenu tu veux et dans ton composant Modal avoir un switch avec tes contenus.

Soit tu passes directement le contenu dans tes cards au clic et ton composant Modal à juste children en props.

Tu peux aussi mais c'est un peu plus avancé, utiliser le pattern render props + l'API context pour utiliser un contenu différent.

Feniix
Auteur

Salut,

Merci beaucoup pour ta réponse ! :)

Est ce que tu penses que je peux faire de cette manière : j'ai mon composant générique Modal, pour chaque outil je crée un fichier ToolModal (par ex) qui reprends ma modal générique, dans ma page Tools je mets un appel à cette modal en fonction de l'outil, dans mon ToolModal je fais une fonction qui me permet de remplir un tableau de paramètre, et ensuite au niveau de mon appel à l'API je transmets ce tableau ? Qu'en penses tu ? Le code ne va pas devenir un peu trop verbeux, si j'ai 10 outils par ex, je me retrouverais avec 10 tableaux de params etc ?

Merci !

Ben ta pas 40 choix je penses. C'est ce que tu as dit soit faire 10 modal différentes.

As-tu un exemple de code avec 2 card + modal différentes pour être sur d'avoir bien compris ta demande stp ?

Feniix
Auteur

Actuellement j'ai qu'un seul outil mais le code donne ceci, mon composant Card :

import {
  LucideBolt,
  LucideLoader2,
  LucidePlayCircle,
  LucideCheckCircle,
} from "lucide-react";
import Button from "./Button";
import { useState } from "react";

type ToolCardProps = {
  name: string;
  description: string;
  progress: number;
  settingsClick?: () => void;
  onAction: (
    setIsLoading: (isLoading: boolean) => void,
    setCurrentProgress: React.Dispatch<React.SetStateAction<number>>,
    setButtonText: (text: string) => void
  ) => void;
};

export default function ToolCard({
  name,
  description,
  progress = 0,
  settingsClick,
  onAction,
}: ToolCardProps) {
  const [isLoading, setIsLoading] = useState(false);
  const [buttonText, setButtonText] = useState("Lancer");
  const [currentProgress, setCurrentProgress] = useState(progress);

  const handleClick = () => {
    onAction(setIsLoading, setCurrentProgress, setButtonText);
  };

  return (
    <div className="relative flex flex-col w-56 p-2 bg-white rounded-lg">
      <div className="flex items-center justify-between">
        <h2 className="text-sm font-semibold">{name}</h2>
        <button
          className="text-textColor hover:text-activeColor"
          onClick={settingsClick}
        >
          <LucideBolt strokeWidth={1.0} size={16} />
        </button>
      </div>
      <p className="my-2 text-[10px] font-extralight">{description}</p>
      <div className="flex justify-end">
        {isLoading ? (
          <Button icon={LucideLoader2} className="bg-bgColor" animate />
        ) : (
          <Button
            icon={
              buttonText === "Succès" ? LucideCheckCircle : LucidePlayCircle
            }
            text={buttonText}
            className="bg-bgColor"
            onClick={handleClick}
          />
        )}
      </div>
      <div className="absolute bottom-0 left-0 w-full h-1 overflow-hidden rounded-b-lg">
        <div
          className="h-full transition-all duration-300 ease-in-out bg-activeColor"
          style={{ width: `${currentProgress}%` }}
        ></div>
      </div>
    </div>
  );
}

,ma page Tool :

import Header from "../shared/Header";
import ToolCard from "../components/ToolCard";
import Modal from "../components/Modal";
import { useState } from "react";
import Input from "../components/Input";
import Button from "../components/Button";
import { Save } from "lucide-react";
import { handleNmap } from "../utils/nmapActions";

export default function Tools() {
  const [isOpen, setIsOpen] = useState(false);
  const [nmapScanProgress, setNmapScanProgress] = useState<number>(0);

  return (
    <>
      <Header title="Outils" />
      <div className="flex flex-wrap gap-2 mx-6 my-4">
        <ToolCard
          name="NMAP"
          description="Permet de scanner les hôtes, services, et ports ouverts sur un réseau."
          settingsClick={() => setIsOpen(true)}
          progress={nmapScanProgress}
          onAction={handleNmap.bind(setNmapScanProgress)}
        />
        <ToolCard
          name="NMAP"
          description="Permet de scanner les hôtes, services, et ports ouverts sur un réseau."
          settingsClick={() => setIsOpen(true)}
          progress={nmapScanProgress}
          onAction={handleNmap.bind(setNmapScanProgress)}
        />
      </div>
      <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} toolName="NMAP">
        <div className="flex flex-col space-y-4">
          <Input label="Target" placeholder="127.0.0.1/24" />
          <Input label="Arguments" placeholder="-sn" />
          <div className="flex justify-end">
            <Button
              size="md"
              icon={Save}
              text="Valider"
              className="bg-whiteColor"
            />
          </div>
        </div>
      </Modal>
    </>
  );
}

qui fait appel à mon composant Modal :

import { CircleX } from "lucide-react";

export default function Modal({
  isOpen,
  onClose,
  children,
  toolName,
}: {
  isOpen: boolean;
  onClose?: () => void;
  children: React.ReactNode;
  toolName: string;
}) {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 flex items-center justify-center transition-opacity duration-300 ease-out bg-opacity-50 bg-textColor">
      <div className="w-full max-w-[250px] p-4 rounded-lg bg-bgColor transform transition-transform duration-300 ease-out scale-95 opacity-0 animate-modal-enter">
        <div className="flex items-center justify-between mb-4">
          <h2 className="text-sm font-semibold">
            Paramètrage -<span className="text-xs"> {toolName}</span>
          </h2>
          <CircleX size={16} className="text-red-600" onClick={onClose} />
        </div>
        <div>{children}</div>
      </div>
    </div>
  );
}

, pour le moment je n'ai pas encore associé la récupération des inputs mais l’idée est là

Merci pour ton aide :)

Pour être sur d'avoir bien compris.

    <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} toolName="NMAP">
        <div className="flex flex-col space-y-4">
          <Input label="Target" placeholder="127.0.0.1/24" />
          <Input label="Arguments" placeholder="-sn" />
          <div className="flex justify-end">
            <Button
              size="md"
              icon={Save}
              text="Valider"
              className="bg-whiteColor"
            />
          </div>
        </div>
      </Modal>

      Peut devenir ceci et donc la data que tu envoies a ton API change c'est bien ça ?

    <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} toolName="NMAP">
        <div className="flex flex-col space-y-4">
          <Input label="Target" placeholder="127.0.0.1/24" />
          <Input label="Arguments" placeholder="-sn" />
          <Input label="Toto" placeholder="-sn" />
          <Input label="Tata" placeholder="-sn" />
          <Input label="Titi" placeholder="-sn" />
          <Input label="Tutu" placeholder="-sn" />
          <div className="flex justify-end">
            <Button
              size="md"
              icon={Save}
              text="Valider"
              className="bg-whiteColor"
            />
          </div>
        </div>
      </Modal>
Feniix
Auteur

Oui voila c'est ça, en gros les éléments dans ma Modal change en fonction de l'outil, pour le moment je penses avoir seulement des inputs

Feniix
Auteur

Je pense que je vais partir sur ça, merci beaucoup pour ton aide ! :)

Pour le moment je n'ai pas prevu beaucoup d'outil donc ca devrait le faire au niveau des données

En tout cas, merci beaucoup !

Je pense que je vais opter pour ça, un grand merci pour ton aide ! :)
Merci encore infiniment !

Tutuapp 9Apps