Blog / Collecter des feedbacks via notification Slack dans un projet Laravel

Image used for article Collecter des feedbacks via notification Slack dans un projet Laravel

Collecter des feedbacks via notification Slack dans un projet Laravel




TL;DR: Comment créer un module de feedback dans un projet Laravel et recevoir une notification Slack dès qu’un message est soumis.




Vous trouverez le code source via ce Github Repository. Découvrez-en plus sur CapsulesX ou Bluesky.




Il est fréquent de rencontrer sur un site internet un formulaire de contact ou une adresse e-mail permettant de contacter l'administrateur du site. Ces formulaires requièrent généralement une adresse e-mail, un titre et un objet. Cet article propose une alternative plus ouverte à l'anonymat, en remplacement de ce format standard. En se servant de Slack.




Un bouton donne accès à un formulaire comprenant un champ de feedback et, en option, un champ pour une adresse e-mail si une réponse au message est souhaitée. Lors de l'envoi, une notification Slack est automatiquement générée pour informer l'administrateur. Aucun e-mail n'est produit, et aucune donnée n'est enregistrée dans une base de données.




Initialement, une seule route et une seule page sont configurées dans notre projet Laravel vierge.



routes/web.php


<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;


Route::get( '/', fn() => Inertia::render( 'Welcome' ) );



/resources/js/pages/Welcome.vue


<script setup>

import logotype from '/public/assets/capsules-logotype-background.svg';

</script>

<template>

    <div class="w-screen h-screen flex flex-col items-center justify-center text-center bg-primary-white">

        <img class="w-24 h-24" v-bind:src="logotype">

        <h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />

    </div>

</template>







Le composant de feedback peut être entièrement contenu dans un fichier Vue. La structure HTML comprend un bouton et un formulaire. Voici le contenu du module.



resources/js/components/Feedback.vue


<script setup>

import { ref } from 'vue';
import { router } from '@inertiajs/vue3';
import logotype from '/public/assets/capsules-logotype.svg';


const isOpen = ref( false );
const isSent = ref( false );
const errors = ref( {} );

const message = ref( '' );
const email = ref( '' );

function toggle()
{
    if( ! isOpen.value )
    {
        message.value = '';
        email.value = '';

        isSent.value = false;
        errors.value = {};
    }

    isOpen.value = ! isOpen.value;
}

function submit()
{
    errors.value = {};

    const data = email.value ? { email : email.value, message : message.value } : { message : message.value };

    router.post( '/feedbacks', data, { onError : error => { errors.value = error; }, onSuccess : () => { isSent.value = true; } } );
}

</script>

<template>

    <div class="m-8 flex flex-col-reverse items-end space-y-reverse space-y-4">

        <button class="w-12 h-12 flex items-center justify-center" v-on:click="toggle()">

            <div v-show="! isOpen" class="w-full h-full rounded-xl bg-white flex items-center justify-center drop-shadow-2xl hover:bg-primary-blue hover:bg-opacity-5"><img class="h-8 w-8" v-bind:src="logotype"></div>

            <div v-show="! isOpen" class="absolute top-0 left-0 w-full h-full rounded-xl bg-white flex items-center justify-center animate-ping opacity-50"><img class="h-8 w-8" v-bind:src="logotype"></div>

            <svg v-show="isOpen" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-primary-blue"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>

        </button>

        <div v-if="isOpen">

            <div v-if="! isSent" class="font-mono rounded-xl bg-white drop-shadow-xl ">

                <div class="p-2">

                    <form class="flex flex-col" v-on:submit.prevent="submit()">

                        <label for="message" hidden />

                        <textarea
                            id="message"
                            class="mb-2 p-2 outline-none rounded-md resize-none text-xs bg-slate-100"
                            v-bind:class="{ 'border border-solid border-red-500 text-red-500' : errors && errors[ 'message' ] } "
                            type="text"
                            cols="30"
                            rows="10"
                            v-bind:placeholder="'Your message'"
                            v-model="message"
                        />

                        <div class="flex">

                            <label for="email" hidden />

                            <input
                                id="email"
                                class="px-2 grow outline-none rounded-md text-xs bg-slate-100"
                                v-bind:class=" { 'border border-solid border-red-500 text-red-500' : errors && errors[ 'mail' ] } "
                                type="text"
                                v-bind:placeholder="'Your email - Optional'"
                                v-model="email"
                            >

                            <button
                                class="ml-2 px-4 py-2 inline-flex items-center rounded-md text-sm font-medium text-primary-blue bg-primary-blue bg-opacity-50 hover:bg-opacity-60"
                                type="submit"
                            >

                                <p v-text="'Send'" />

                            </button>

                        </div>

                    </form>

                    <div>

                        <p v-for=" ( error, key ) in errors " v-bind:key="key" class="first:mt-4 ml-1 text-[10px] text-red-500" v-text="error" />

                    </div>

                </div>

            </div>

            <div v-else class="font-mono p-4 flex items-center justify-center space-x-4 bg-white rounded-xl drop-shadow-xl">

                <p class="w-full text-center text-xs text-primary-black" v-text="'Thank you for your feedback !'" />

                <p v-text="'🎉'" />

                <img class="h-8 w-8" v-bind:src="logotype">

            </div>

        </div>

    </div>

</template>




Ce composant représente un bouton qui, lorsqu'il est cliqué, fait apparaître un formulaire grâce à la variable isOpen. Lorsqu'on clique sur le bouton 'Send', la méthode submit() est appelée, envoyant une requête POST à la route /feedbacks. Si tout est en ordre, la variable isSent devient vraie, et un message de remerciement remplace le formulaire. Sinon, les champs incorrects sont mis en rouge.




Il faut maintenant ajouter ce composant à la page Welcome.



resources/js/pages/Welcome.vue


<script setup>

import Feedback from '/resources/js/components/Feedback.vue';
import logotype from '/public/assets/capsules-logotype-background.svg';

</script>

<template>

    <Feedback class="fixed z-10 bottom-0 right-0" />

    <div class="w-screen h-screen flex flex-col items-center justify-center text-center bg-primary-white">

        <img class="w-24 h-24" v-bind:src="logotype">

        <h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />

    </div>

</template>







Le composant Feedback est importé et fixé en bas à droite de l'écran. Maintenant que le module fonctionne côté client, il est temps de créer la route, d'implémenter la validation et d'envoyer les données vers Slack. Pour cet article, il n'est pas nécessaire de créer un contrôleur spécifique.



app/Http/FeedbackRequest.php


<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;


class FeedbackRequest extends FormRequest
{
    public function rules() : array
    {
        return [
            'message' => [ 'required', 'min:1', 'max:499' ],
            'email' => [ 'sometimes', 'email' ],
        ];
    }
}




Le FeedbackRequest permet de renvoyer des erreurs si une donnée n'a pas été correctement envoyée.







routes/web.php


<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Requests\FeedbackRequest;


Route::get( '/', fn() => Inertia::render( 'Welcome' ) );

Route::post( 'feedbacks', function( FeedbackRequest $request ){} );







La prochaine étape consiste à connecter le projet Laravel à l'espace de travail Slack. À cette fin, un package Laravel est disponible : laravel/slack-notification-channel.


composer require laravel/slack-notification-channel




Conjointement, il faut créer une App Slack via ce lien . Cliquer sur Create New App > From scratch. Insérer ensuite un nom d’app, et choisir son espace de travail Slack. Une page Basic information apparait. Choisir la fonctionnalité Incoming Webhooks et l’activer. Cliquer sur Add New Webhook to Workspace et choisir le canal de l’espace de travail sur lequel recevoir les notifications.




Un webhook au format suivant est alors disponible :https://hooks.slack.com/services/{your-webhook-key}.




Ce webhook est à ajouter dans la variable d’environnement LOG_SLACK_WEBHOOK_URL, celle-ci est accessible dans le fichier de configration config/logging.php.



config/logging.php


'slack' => [
    'driver' => 'slack',
    'url' => env('LOG_SLACK_WEBHOOK_URL'),
    'username' => 'Laravel Log',
    'emoji' => ':boom:',
    'level' => env('LOG_LEVEL', 'critical'),
    'replace_placeholders' => true,
],



.env


LOG_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/{your-webhook-key}




La notification peut maintenant être envoyée depuis la route /feedbacks.


routes/web.php


<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Requests\FeedbackRequest;
use Illuminate\Support\Facades\Notification;
use App\Notifications\FeedbackReceived;


Route::get( '/', fn() => Inertia::render( 'Welcome' ) );

Route::post( 'feedbacks', fn( FeedbackRequest $request ) => Notification::route( 'slack', config( 'logging.channels.slack.url' ) )->notify( new FeedbackReceived( $request ) ) );




Il ne manque plus qu’à construire la notification FeedbackReceived.



app/Notifications/FeedbackReceived.php


<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use App\Http\Requests\FeedbackRequest;
use Illuminate\Notifications\Messages\SlackMessage;


class FeedbackReceived extends Notification
{
    private FeedbackRequest $request;

    public function __construct( FeedbackRequest $request )
    {
        $this->request = $request;
    }

    public function via() : array
    {
        return [ 'slack' ];
    }

    public function toSlack() : SlackMessage
    {
        $email = $this->request->email ?? 'Anonymous';

        return ( new SlackMessage )->content( "New Capsules Codes Feedback : \"{$this->request->message}\" by {$email}" );
    }
}







Une notification sauvage apparaît !




Ravi d’avoir pu aider !

v1.4.0

Icône XIcône Github