Create a simple NFT minting website | BlockTrain

Create a simple NFT minting website | BlockTrain



Créer un site Web de frappe NFT simple

par BlockTrain10 minutes de lecture29 octobre 2022

Qu'allons-nous construire ?🤔

Nous allons créer un DAPP qui permet aux utilisateurs de connecter leur portefeuille et de créer des NFT à partir de notre contrat, cela semble incroyable, n'est-ce pas ?

Nous rédigerons un petit contrat ERC721 que nous déploierons sur un testnet et créerons des NFT à partir de celui-ci. Pour l'interface, nous utiliserons l'application React , et pour l'intégration du portefeuille, nous utiliserons RainbowKit , qui est un portefeuille, mais dispose également d'un SDK qui permet aux développeurs d'intégrer les connexions de portefeuille de manière transparente. Ensuite, nous utiliserons Wagmi pour utiliser notre contrat (récupérer notre contrat) et l'utiliser dans notre interface, wagmi est une bibliothèque qui est une collection de crochets de réaction contenant tout ce dont vous avez besoin pour commencer à travailler avec Ethereum.

Si cela ne semble pas excitant, je ne sais pas ce qui se passe :)


👀 Prérequis

  • Compréhension de base de ce que sont les NFT
  • Connaissances de base en HTML, CSS, JS
  • Petite curiosité :)

📝 Rédiger un contrat ERC721

Avant de créer notre DAPP de frappe, nous aurons besoin d'un contrat à partir duquel nous frapperons nos NFT, ne vous inquiétez pas si vous ne savez pas comment rédiger des contrats intelligents, nous avons déjà des projets détaillés sur les NFT comme Démarrer avec Développement NFT et comment créer un contrat NFT avec des métadonnées en chaîne, cela pourrait être un bon point de départ si vous cherchez à comprendre les contrats intelligents NFT, restez également à l'écoute car nous allons bientôt abandonner beaucoup de projets👀

Passons à Openzeppelin Wizard et obtenons un contrat passe-partout

https://i.imgur.com/oxJlIRd.png

  • Ici, nous ajoutons d'abord le nom et le symbole du contrat, cela sera utilisé comme identifiant sur les places de marché etherscan et NFT

  • Vient ensuite l'URI de base, ne vous inquiétez pas pour cela, nous allons bientôt déposer un projet sur les NFT musicaux où je vais couvrir cela en détail, pour l'instant vous pouvez utiliser mon lien ipfs hébergé : ipfs://bafybeidt5boy4ousc3jl7v6v5xvle6ybiteqduek7moil6gtzajazfxq2y/mais rappelez-vous simplement que cela est une référence à nos NFT, il contient nos métadonnées NFT et ressemble à ceci lorsqu'il est ouvert

    https://i.imgur.com/NurmKfr.png

    et le fichier individuel est un fichier json qui contient des métadonnées pour NFT, qui ressemble à ceci

    {
    	"image": "ipfs://bafybeihqsmqlnd7ifiwuaso6petbaxkux5s2bs7rzncpzepw2kvy5w4vfu/0.png",
    	"name": "DAPE #0",
    	"description": "Demo using a small collection of Degenerate Apes.",
    	"external_url": "https://twitter.com/Deveshb15",
    	"attributes": [
    		{
    			"trait_type": "Background",
    			"value": "Magic Mint"
    		},
    		{
    			"trait_type": "Skin",
    			"value": "Camo Coral"
    		},
    		{
    			"trait_type": "Expression",
    			"value": "Excited"
    		}
    	]
    }
    

    Il s'agit de la norme de métadonnées pour ERC721

  • Viennent ensuite les fonctionnalités et nous sélectionnerons Mintable qui permettra aux utilisateurs de créer, des identifiants d'auto-incrémentation qui seront utilisés pour incrémenter les tokenIds des NFT à chaque fois qu'un nouveau est créé, et le stockage d'URI pour s'assurer que notre baseURI est ajouté avec /${tokenId}.jsoncause c'est comme ça nos métadonnées NFT individuelles seront accessibles.

Ouvrons maintenant cela dans Remix , qui est une idée en ligne pour compiler et déployer des contrats intelligents.

Nous allons juste apporter quelques modifications au contrat, le contrat final devrait ressembler à ceci :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts@4.7.3/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.7.3/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.7.3/access/Ownable.sol";
import "@openzeppelin/contracts@4.7.3/utils/Counters.sol";

contract Blocktrain is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;
    uint256 public mintRate = 0.00069 ether;

    constructor() ERC721("Blocktrain", "BNT") {}

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://bafybeidt5boy4ousc3jl7v6v5xvle6ybiteqduek7moil6gtzajazfxq2y/";
    }

    function safeMint() public payable {
        require(msg.value >= mintRate, "Not enough ether");
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(msg.sender, tokenId);
    }

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        require(_exists(tokenId), "Token doesn't exists");
        return string(abi.encodePacked(_baseURI(), Strings.toString(tokenId), '.json'));
    }
}

Ici, nous avons changé safeMint()la fonction pour frapper NFT à la personne qui frappe (msg.sender) et avons supprimé le onlyOwnermodificateur afin que n'importe qui puisse frapper le NFT, j'ai également rendu la safeMint()fonction payante et ajouté un mintRatepour définir le taux de frappe NFT que l'utilisateur devra payer frapper le NFT c'est ainsi que le créateur gagne généralement en dehors de la redevance secondaire, ensuite j'ai changé la tokenURI()fonction pour ajouter le /${tokenId}.jsonà la fin du baseURI.

Compilons maintenant notre contrat et déployons-le sur Goerli Testnet , si vous ne savez pas comment obtenir gratuitement testnet ETH, je l'ai expliqué dans Créer un contrat NFT avec des métadonnées en chaîne

Une fois que vous obtenez du testnet ETH, laissez aller à Remix et appuyez sur Ctrl+Sceci pour compiler le contrat :

https://i.imgur.com/UTgkDX8.png

Une fois le contrat compilé, déployons-le sur Goerli Testnet , changeons l'environnement en Injected Metamask et assurons-nous que vous êtes sur Goerli Network dans Metamask et Deploy .

https://i.imgur.com/ihnwS5u.png

Il ouvrira une fenêtre contextuelle Metamask, confirmez simplement et votre contrat sera déployé, vous pouvez également consulter votre contrat sur goerli.etherscan.io , copiez simplement l' adresse du contrat à l'aide de ce bouton

https://i.imgur.com/7IWME2I.png

et collez-le sur goerli.etherscan.io

https://i.imgur.com/QmIAtzE.png

Maintenant que notre contrat est prêt, revenons à notre DAPP de frappe.


📖 Explication : Que signifie frapper un NFT ?

Frapper signifie littéralement créer quelque chose ou quelque chose qui est produit à ce moment-là à partir de zéro. Fondamentalement, cela signifie simplement que vous venez de donner naissance à un NFT hehe, c'est peut-être l'analogie la plus simple que vous comprendrez.

En termes de blockchain, cela signifie que NFT est né/produit à ce moment-là, c'est la raison pour laquelle il est transféré 0x0000000000000000000000000000000000000000(que vous pouvez considérer comme le trou noir de la blockchain, c'est une abréviation de l'adresse de genèse) à votre adresse, vous peut le voir magnifiquement sur Opensea

https://i.imgur.com/6ewEjnq.png

Une fois que l'actif est frappé, il s'agit officiellement d'un NFT et il peut être vendu sur n'importe quel marché NFT.


✍️ Créez une application React pour frapper nos NFT

Installons et configurons une application de réaction à l'aide du package cra(create-react-app).

Ouvrez votre terminal et cd dans le dossier où vous souhaitez créer le DAPP de frappe NFT .

Exécutez cette commande pour installer et configurer React :

npx create-react-app nft-minting-dapp

Cela peut prendre quelques minutes, selon votre machine, mais une fois qu'il est installé, il suffit de le cd dans le projet et nous installerons le CSS tailwind pour notre CSS. J'utilise tailwind mais n'hésitez pas à utiliser ce que vous voulez.

cd nft-minting-dapp
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Une fois que vous avez exécuté ces commandes, ouvrons le dossier dans un éditeur de code, j'utilise personnellement VS Code.

Ouvrez-le tailwind.config.jset remplacez-le par :

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Ajoutons maintenant les @tailwinddirectives ** ** pour chacune des couches de Tailwind à votre ./src/index.cssfichier.

@tailwind base;
@tailwind components;
@tailwind utilities;

Nous avons maintenant terminé l'installation de react-app et tailwindCSS

Exécutez cette commande et vous devriez avoir votre application en cours d'exécution

npm start

Vous devriez voir quelque chose comme ceci :

https://i.imgur.com/eL0PcHr.png

Maintenant, nettoyons les choses et créons simplement un bouton de menthe de base

Supprimez les fichiers marqués en rouge dans la barre latérale gauche et supprimez les éléments marqués deApp.js

https://i.imgur.com/NcrQ4h9.png

Maintenant, App.jscela devrait ressembler à ceci :

function App() {
	return (
		<div className="flex items-center justify-center min-h-screen">
			<div className="flex flex-col items-center justify-center gap-4">
        <h1 className="text-4xl font-extrabold">Mint an NFT</h1>
        <button className="bg-black text-white py-2 px-4 rounded-xl transform hover:scale-105">
          Mint
        </button>
      </div>
		</div>
	);
}

export default App;

Et la sortie [localhost:3000](http://localhost:3000)devrait être :

https://i.imgur.com/bJxL4m6.png

Ceci n'est qu'un aperçu de base de l'application, nous le remplirons progressivement avec les données.

Passons à l'ajout d'un adaptateur de portefeuille.


⚙️ Configurer les adaptateurs de portefeuille

Nous allons utiliser RainbowKit pour cela, vous pouvez lire les docs ici → RainbowKit

Pour installer RainbowKit, nous aurons besoin de quelques packages

npm install @rainbow-me/rainbowkit wagmi ethers

wagmi et ethers sont des dépendances homologues pour rainbowkit.

Vous pouvez accéder à la documentation d'installation de RainbowKit ou suivre les étapes ci-dessous.

Importons ensuite RainbowKit, wagmi et ethers , danssrc/index.js

import '@rainbow-me/rainbowkit/styles.css';

import {
  getDefaultWallets,
  RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import {
  chain,
  configureChains,
  createClient,
  WagmiConfig,
} from 'wagmi';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { publicProvider } from 'wagmi/providers/public'; 

Configurons maintenant les chaînes souhaitées et générons les connecteurs requis. Vous devrez également configurer un wagmiclient.

...

const { chains, provider } = configureChains(
  [chain.mainnet, chain.polygon, chain.goerli, chain.polygonMumbai],
  [
    alchemyProvider({ apiKey: process.env.ALCHEMY_ID }),
    publicProvider()
  ]
);

const { connectors } = getDefaultWallets({
  appName: 'My NFT Minting DAPP',
  chains
});

const wagmiClient = createClient({
  autoConnect: true,
  connectors,
  provider
})

Pour obtenir un ALCHEMY_ID, vous devrez créer un compte sur Alchemy et créer une nouvelle application sur Goerli pour obtenir un ALCHEMY_ID, si vous n'avez pas compris cela, allez-y et lisez la section Déployons le contrat de ce projet  je 'ai expliqué cela en détail

https://i.imgur.com/EI4h2Rl.png

https://i.imgur.com/pn2BOwC.png

Copiez la clé API et collez-la comme variable ALCHEMY_ID

Vous vous demandez probablement pourquoi je montre ma clé API, eh bien j'ai créé cette application uniquement dans le cadre de ce projet et je la supprimerai probablement une fois utilisée.

Enveloppez ensuite votre application avec RainbowKitProvider et [WagmiConfig](https://wagmi.sh/docs/provider) .

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <WagmiConfig client={wagmiClient}>
      <RainbowKitProvider chains={chains}>
        <App />
      </RainbowKitProvider>
    </WagmiConfig>
  </React.StrictMode>
);

Une fois cela fait, nous devrions pouvoir utiliser ConnectWalletle crochet de rainbowkit pour permettre aux utilisateurs de se connecter au portefeuille et d'utiliser notre application

L'application ressemblera à ceci :

import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useAccount } from 'wagmi'

function App() {
  const { address } = useAccount()

	return (
		<div>
      <div className='flex items-center justify-end p-2'>
        <ConnectButton />
      </div>
			<div className="flex flex-col items-center justify-center min-h-[90vh] gap-4">
        <h1 className="text-4xl font-extrabold">Mint an NFT</h1>
        {
          address ? (
            <button className="bg-black text-white py-2 px-4 rounded-xl transform hover:scale-105">
              Mint
            </button>
          ) : (
            <ConnectButton />
          )
        }
      </div>
		</div>
	);
}

export default App;

Ici, nous vérifions si le addressest connecté à l'aide de wagmi, si l'adresse est connectée, affichez le bouton Mint ou affichez le bouton de connexion , il devrait ressembler à ceci :

https://i.imgur.com/clxDYCX.png

Allez-y et stylisez-le comme vous le souhaitez, j'irai avec un design simpliste.

https://i.imgur.com/2cayCVK.png

Maintenant que nous avons configuré nos portefeuilles, allons-y et récupérons le contrat dans notre interface


🥨 Obtenons notre adresse de contrat et notre contrat ABI

Pour obtenir l'adresse du contrat que nous avons déployé précédemment sur remix, cliquez sur cette icône

https://i.imgur.com/7IWME2I.png

Stockez maintenant CONTRACT_ADDRESSen tant que variable dansApp.js

const CONTRACT_ADDRESS = '0x0c7EB3349E3B13fc0E6D0Fb405eEf42Cd5FC7bf4'

function App() {
  const { address } = useAccount()

...

Passons maintenant à notre CONTRACT_ABI, allez dans remix et cliquez sur l'onglet du compilateur de solidité dans la barre latérale gauche et copiez l'ABI en cliquant sur cette icône

https://i.imgur.com/nU9rOhY.png

Créez maintenant un dossier dans src/ABI.jset exportez abila variable avec l'ABI copié à partir du remix, quelque chose comme ceci :

https://i.imgur.com/APc2kvB.png

Et utilisez-le dans App.jsquelque chose comme ceci:

import { abi as CONTRACT_ABI } from './ABI';
const CONTRACT_ADDRESS = '0x0c7EB3349E3B13fc0E6D0Fb405eEf42Cd5FC7bf4'

function App() {
  const { address } = useAccount()

...

🤺 Récupérer notre contrat avec Wagmi

Maintenant que nous avons les CONTRACT_ADDRESS et CONTRACT_ABI , récupérons notre contrat en utilisant useContract fourni par wagmi

Importons d'abord le crochet useContract :

import { useAccount, useContract, useSigner } from 'wagmi'

Maintenant que nous avons importé useContract , utilisons dans notreApp

function App() {
  const { address } = useAccount()
	const { data: signer } = useSigner()

  const contract = useContract({
    addressOrName: CONTRACT_ADDRESS,
    contractInterface: CONTRACT_ABI,
    signerOrProvider: signer
  })

  console.log(contract)

...

Nous avons également besoin useSignerd'un crochet qui sera nécessaire pour effectuer une transaction comme la frappe d'un NFT.

Vous devriez voir les fonctions de contrat dans l'onglet de la console, quelque chose comme ça

https://i.imgur.com/uRrpApB.png

C'est ainsi que nous allons interagir avec notre contrat, vous pouvez voir notre safeMint()fonction dans la console, nous l'utiliserons pour frapper notre NFT

Essayons d'obtenir le mintRate de notre NFT une fois, pour cela nous devrons créer une fonction car mintRate()la fonction renverra une promesse et nous utiliserons async/wait, la fonction devrait ressembler à ceci :

	// console.log(contract)
  const getMint = async() => {
    const price = await contract.mintRate()
    console.log(price.toString())
  }

  useEffect(() => {
    if(contract?.signer) {
      getMint()
    }
  }, [contract])

...

Vous devriez voir quelque chose comme ceci dans votre console :

https://i.imgur.com/b3NdpKM.png

N'oubliez pas que le mintRate ici est dans WEI , vous pouvez en savoir plus sur wei ici

N'hésitez pas à explorer toutes les fonctions du contrat, pas nécessaire mais bien si vous voulez comprendre et pratiquer.


👾 Création de notre fonction Mint

Maintenant que nous avons notre contrat et que nous sommes capables d'interagir avec notre contrat intelligent, commençons à travailler sur le contrat intelligent.

function safeMint() public payable {
    require(msg.value >= mintRate, "Not enough ether");
    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();
    _safeMint(msg.sender, tokenId);
}

Si vous vous souvenez bien, nous avions notre safeMint()dû et nous facturons un petit montant à l'utilisateur pour frapper le NFT, nous devrons donc l'envoyer comme argument à la fonction

Voyons à quoi ressemble la fonction mint :

const mintNft = async() => {
    try {
      const mint = await contract.safeMint({ value: mintRate })
      console.log(mint)
      
      contract.on("Transfer", (to, from, token) => {
        console.log(to, from, token)
      })
    } catch(err) {
      console.log(err)
    }
  }

Ici, nous essayons d'abord de créer la fonction en utilisant la safeMint()fonction du contrat intelligent, et d'envoyer la valeur en tant qu'argument avec le mintRate que nous avons récupéré auparavant, rappelez-vous que nous ajoutons un bloc try-catch pour détecter si une erreur se produit et montrer cela à l'utilisateur.

Vous vous demandez peut-être qu'est-ce contract.onqu'il surveille la prochaine transaction et vérifie si l'événement portant le nom Transferse produit et si console ses arguments, nous l'utiliserons pour afficher les données de celui-ci.

Il s'agit d'un aperçu à vol d'oiseau de toutes les fonctions et de tous les états du code, je partagerai également un repl (code entier) à la fin, mais c'est l'intégralité du flux :

const [mintRate, setMintRate] = useState("0")
const [error, setError] = useState(false)
const [tokenId, setTokenId] = useState(null)
const [loading, setLoading] = useState(false)

const getMint = async() => {
  const price = await contract.mintRate()
  setMintRate(price.toString())
}

useEffect(() => {
  if(contract?.signer) {
    getMint()
  }
}, [contract])

const mintNft = async() => {
  setLoading(true)
  try {
    const mint = await contract.safeMint({ value: mintRate })
    console.log(mint)
    
    contract.on("Transfer", (to, from, token) => {
      console.log(to, from, token)
      setTokenId(token.toString())
      setLoading(false)
    })
  } catch(err) {
    console.log(err)
    setError(err)
    setLoading(false)
  }
}

console.log("MINT ", mintRate)
console.log("TOKEN ", tokenId)
console.log("ERROR ", error)
console.log("LOADING ", loading)

Nous définissons 4 états, mintRatetokenIderror, et loading, nous utiliserons mintRate pour frapper le NFT et envoyer la valeur comme argument, nous utiliserons error pour attraper l'erreur et l'afficher dans l'interface utilisateur, nous utiliserons loading pour créer un chargement lors de la création du NFT, et tokenId est essentiellement le jeton NFT qui a été créé en ce moment, nous l'utiliserons pour diriger les utilisateurs vers la page opensea de leur jeton.

Maintenant, lions-le à notre fonction mint :

<button onClick={mintNft} className="bg-black text-white py-2 px-4 rounded-xl transform hover:scale-105">
  Mint
</button>

et essayez-le dans le navigateur :

Cela devrait déclencher une transaction comme ceci :

https://i.imgur.com/xlWpUnm.png

Cliquez sur confirmer et surveillez les logs de votre console

https://i.imgur.com/e7HW7hH.png

C'est tout le flux du processus de frappe.

Viola🎉, nous avons maintenant notre NFT, utilisons maintenant les états donnés dans l'interface utilisateur pour créer une bonne expérience utilisateur


🤌 Créez un flux pour permettre aux utilisateurs de le visualiser sur une place de marché

Nous allons maintenant utiliser nos états actuels pour créer une bonne expérience utilisateur :

....

<div className="flex flex-col items-center justify-center min-h-[90vh] gap-4">
	<h1 className="text-4xl font-extrabold">Mint an NFT</h1>
	{address ? (
		loading ? (
			<h3 className="text-xl">Minting your NFT...</h3>
		) : (
			<button
				onClick={mintNft}
				className="bg-black text-white py-2 px-4 rounded-xl transform hover:scale-105"
			>
				Mint
			</button>
		)
	) : (
		<ConnectButton />
	)}
	{error && (
		<div>
			<p>Something went wrong!</p>
		</div>
	)}
	{tokenId && (
		<div className="flex flex-col">
			<p className="font-bold">Token with id {tokenId} minted</p>
			<p>
				Check it over here:{" "}
				<a
					href={`https://testnets.opensea.io/assets/goerli/${CONTRACT_ADDRESS}/${tokenId}`}
          target="_blank"
          rel="noreferrer"
				>
					Opensea
				</a>
			</p>
		</div>
	)}
</div>

....

Ici, nous vérifions si le jeton est en train d'être frappé et s'il est en train d'être frappé, nous montrons un écran de chargement, soyez sincère ici et créez une animation de chargement incroyable, j'adorerais voir vos tags sur Twitter ;)

Ensuite, nous vérifions si l'erreur est vraie et si c'est vrai que nous leur montrons un message d'erreur, rappelez-vous que nous consoleons l'erreur consignée dans le bloc try-catch, vous pouvez même enregistrer le message d'erreur en tant qu'état et le montrer ici, comme je l'ai dit être curieux et essayer différentes choses.

Enfin, nous utiliserons le tokenId créé pour afficher un lien vers la page Opensea.

Essayons maintenant :

https://i.imgur.com/l1toVko.png

Une fois qu'il est frappé, nous pouvons le voir sur l'écran

Frappé

Cliquons sur Opensea :

https://i.imgur.com/MAYW6Wc.png

Et voilà, il y a votre NFT🤑

Vous vous demandez peut-être pourquoi j'ai un singe dégénéré comme mon NFT, c'est parce que j'ai utilisé le baseURI de la collection Degenrate Ape parce que c'est juste pour les tests et que je l'ai déployé sur le testnet.


🍾 Tout est fait !

Génial maintenant que nous avons créé un DAPP qui permet à l'utilisateur de créer NFT, déployons-le sur Vercel ou Netlify , ce qui vous permet de déployer des sites Web gratuitement, j'adorerais aussi créer vos NFT, n'oubliez pas de nous taguer sur Twitter alors que nous connaissons et monnayons vos NFT.

Soyez curieux ici et allez au contenu de votre cœur et concevez magnifiquement la page de menthe j'aimerais voir votre créativité ici.

Lien Replit du code source : https://replit.com/@deveshB1/NFTMintDapp

C'est un enveloppement, tapotez-vous dans le dos et construisez de la merde cool !

Commentaires

Posts les plus consultés de ce blog

Comment faire du business sur Amazon ? [GUIDE COMPLET 2022]

100 outils pour lancer sa startup sans argent (mais pas sans talent)

Nomad '✨Maker - TOP✨ Ai GENERATOR

T🌴'artistiK -📗 Books and Publications Spotlight | Lulu

Diagnostiquer et récupérer une messagerie piratée | AVG

115 idées de revenus passifs pour obtenir de l'argent pour travailler pour vous en 2021 - Blog Ippei

6 tunnels de vente indispensables pour vendre sur Internet

ai-collection/README.fr.md at main · ai-collection/ai-collection · GitHub

Meilleurs outils de référencement 🛠 organisée par Saijo George

Stratégie dropshipping : Les 10 leviers marketing pour votre boutique