· 12 min read
Utiliser PinesUI dans un projet Symfony
PInes UI est une collection de composants Tailwind CSS et Alpine.js. Voyons comment l'utiliser dans un projet Symfony.

Utiliser PinesUI avec Symfony : Un guide complet 🚀
Vous cherchez à améliorer l’interface utilisateur de votre application Symfony sans vous prendre la tête ? PinesUI pourrait bien être la solution qu’il vous faut ! Dans cet article, nous allons explorer comment intégrer cette bibliothèque élégante dans votre projet Symfony.
🔍 C’est quoi PinesUI ?
PinesUI est une bibliothèque d’interface utilisateur (UI) moderne et flexible conçue pour fonctionner parfaitement avec AlpineJS et TailwindCSS. Elle propose une collection riche d’éléments d’interface prêts à l’emploi qui peuvent transformer radicalement l’expérience utilisateur de votre application.
✨ Principales caractéristiques de PinesUI :
- Simplicité d’intégration : Il suffit d’inclure AlpineJS et TailwindCSS dans votre projet, puis de copier-coller les composants souhaités.
- Compatibilité étendue : PinesUI s’intègre harmonieusement avec d’autres bibliothèques ou frameworks, y compris Symfony.
- Composants variés et élégants : La bibliothèque propose une large gamme d’éléments :
- Menus déroulants et contextuels
- Modales interactives
- Accordéons dynamiques
- Info-bulles personnalisables
- Galeries d’images
- Animations fluides
- Et bien plus encore !
🛠️ Un projet concret pour illustrer l’utilisation de PinesUI
Pour démontrer la puissance et la flexibilité de PinesUI, nous allons créer ensemble un projet Symfony spécialement conçu pour les Youtubeurs et Youtubeuses.
📋 Fonctionnalités de notre application :
Notre application permettra de créer une page de présentation complète avec :
- Un titre accrocheur et personnalisable
- Une description détaillée du contenu de la chaîne
- Des liens vers les différents réseaux sociaux avec des icônes attrayantes
- Une galerie des dernières vidéos mises en ligne
- Une configuration simple via le fichier
.env
(sans nécessiter de base de données)
🚀 Création du projet Symfony
Commençons par mettre en place notre environnement de développement :
# Création d'un nouveau projet Symfony
symfony new youtube-profile --webapp
# Accès au répertoire du projet
cd youtube-profile
# Installation de TailwindCSS (via AssetMapper)
composer require symfonycasts/tailwind-bundle
symfony console tailwind:init
# Installation de AlpineJS (via AssetMapper)
symfony console importmap:require alpinejs
symfony console importmap:require @alpinejs/collapse
Finalisons la configuration de AlpineJS, dans le fichier assets/app.js
:
import './bootstrap.js';
/*
* Welcome to your app's main JavaScript file!
*
* This file will be included onto the page via the importmap() Twig function,
* which should already be in your base.html.twig.
*/
import './styles/app.css';
import Alpine from 'alpinejs'
import collapse from '@alpinejs/collapse'
window.Alpine = Alpine
Alpine.plugin(collapse)
Alpine.start()
Et modifions le fichier assets/styles/app.css
pour supprimer les styles par défaut de Symfony.
@tailwind base;
@tailwind components;
@tailwind utilities;
🛣️ Créons notre route home
Nous allons créer une route home
qui affichera notre page de présentation.
symfony console make:controller Home
Et changeons la route pour qu’elle réponde à l’URL /
.
#[Route('/', name: 'home')]
public function index(): Response
{
return $this->render('home/index.html.twig');
}
Lançons Tailwind en mode watch
Note : Nous allons lancer Tailwind en mode
watch
pour que les modifications soient appliquées automatiquement sans avoir besoin de builder à chaque fois.
symfony console tailwind:build --watch
⚙️ Le fichier de configuration .env
Nous allons ajouter des variables d’environnement pour configurer notre application.
YOUTUBE_TITLE="Mon titre de chaîne"
YOUTUBE_DESCRIPTION="Ma description de chaîne"
YOUTUBE_CHANNEL_ID="xxxx"
SOCIAL_LINKS_X="https://x.com/mon_compte"
SOCIAL_LINKS_YOUTUBE="https://www.youtube.com/@mon_canal"
SOCIAL_LINKS_BLUESKY=https://bsky.app/profile/mon_compte
SOCIAL_LINKS_LINKEDIN=https://www.linkedin.com/in/mon_compte
SOCIAL_LINKS_INSTAGRAM=https://www.instagram.com/mon_compte
SOCIAL_LINKS_TIKTOK=https://www.tiktok.com/@mon_compte
CUSTOM_LINKS_WEBSITE=https://www.mon-site-we
🎨 Créons notre service qui récupèrera les données de notre chaîne YouTube
Nous allons créer un service qui récupérera les données de notre chaîne YouTube, créons le fichier src/Service/YoutubeService.php
.
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class YoutubeService
{
const YOUTUBE_RSS_URL = 'https://www.youtube.com/feeds/videos.xml?channel_id=';
public function __construct(
#[Autowire('%env(YOUTUBE_CHANNEL_ID)%')] private readonly string $youtubeChannelId,
private readonly HttpClientInterface $httpClient,
) {
}
public function getLastVideos(): array
{
try {
// Récupération du flux RSS
$response = $this->httpClient->request('GET', self::YOUTUBE_RSS_URL . $this->youtubeChannelId);
$xmlContent = $response->getContent();
// Chargement du XML
$xml = new \SimpleXMLElement($xmlContent);
// Préparation du tableau de vidéos
$videos = [];
// Parcours des entrées
foreach ($xml->entry as $entry) {
// Accès aux données dans l'espace de noms media
$mediaGroup = $entry->children('http://search.yahoo.com/mrss/')->group;
// Vérification des données obligatoires
if (empty($entry->title) || !isset($entry->link[0])) {
continue; // Ignorer cette entrée si des données essentielles sont manquantes
}
// Ajout de chaque vidéo au tableau avec gestion des valeurs manquantes
$videos[] = [
'id' => (string)($entry->children('http://www.youtube.com/xml/schemas/2015')->videoId ?? ''),
'title' => (string)$entry->title,
'link' => (string)($entry->link[0]->attributes()->href ?? ''),
'published' => (string)($entry->published ?? ''),
'thumbnail' => isset($mediaGroup->thumbnail) ? (string)$mediaGroup->thumbnail->attributes()->url : '',
'description' => isset($mediaGroup->description) ? (string)$mediaGroup->description : '',
'views' => isset($mediaGroup->community->statistics) ?
(string)$mediaGroup->community->statistics->attributes()->views : '0',
];
}
return $videos;
} catch (\Throwable $e) {
// Gestion de toutes les erreurs possibles (HTTP, XML, etc.)
return [];
}
}
}
🎨 Créons notre template Twig
Les portions de code Twig utilisent des composants de PinesUI.
Nous allons créer un template Twig qui affichera notre page de présentation.
Pour cela, nous allons créer 4 sous templates :
home/_navigation.html.twig
home/_profile.html.twig
home/_social_links.html.twig
home/_videos.html.twig
home/_navigation.html.twig
<nav class="flex items-center w-full h-24 select-none" x-data="{ showMenu: false }">
<div class="relative flex flex-wrap items-start justify-between w-full mx-auto font-medium md:items-center md:h-24 md:justify-between">
<a href="#_" class="flex items-center py-4 pl-6 pr-4 font-extrabold text-white md:py-0">
<span>{{ youtubeTitle }}</span>
</a>
<div :class="{'flex': showMenu, 'hidden md:flex': !showMenu }" class="absolute z-50 flex-col items-center justify-center w-full h-auto px-2 text-center text-gray-400 -translate-x-1/2 border-0 border-gray-700 rounded-full md:border md:w-auto md:h-10 left-1/2 md:flex-row md:items-center">
<a href="#profile" class="relative inline-block w-full h-full px-4 py-5 mx-2 font-medium leading-tight text-center text-white md:py-2 group md:w-auto md:px-2 lg:mx-3 md:text-center">
<span>Profil</span>
<span class="absolute bottom-0 left-0 w-full h-px duration-300 ease-out translate-y-px bg-gradient-to-r md:from-gray-700 md:via-gray-400 md:to-gray-700 from-gray-900 via-gray-600 to-gray-900"></span>
</a>
<a href="#social" class="relative inline-block w-full h-full px-4 py-5 mx-2 font-medium leading-tight text-center duration-300 ease-out md:py-2 group hover:text-white md:w-auto md:px-2 lg:mx-3 md:text-center">
<span>Réseaux sociaux</span>
<span class="absolute bottom-0 w-0 h-px duration-300 ease-out translate-y-px group-hover:left-0 left-1/2 group-hover:w-full bg-gradient-to-r md:from-gray-700 md:via-gray-400 md:to-gray-700 from-gray-900 via-gray-600 to-gray-900"></span>
</a>
<a href="#videos" class="relative inline-block w-full h-full px-4 py-5 mx-2 font-medium leading-tight text-center duration-300 ease-out md:py-2 group hover:text-white md:w-auto md:px-2 lg:mx-3 md:text-center">
<span>Vidéos</span>
<span class="absolute bottom-0 w-0 h-px duration-300 ease-out translate-y-px group-hover:left-0 left-1/2 group-hover:w-full bg-gradient-to-r md:from-gray-700 md:via-gray-400 md:to-gray-700 from-gray-900 via-gray-600 to-gray-900"></span>
</a>
</div>
<div @click="showMenu = !showMenu" class="absolute right-0 z-50 flex flex-col items-end translate-y-1.5 w-10 h-10 p-2 mr-4 rounded-full cursor-pointer md:hidden hover:bg-gray-200/10 hover:bg-opacity-10" :class="{ 'text-gray-400': showMenu, 'text-gray-100': !showMenu }">
<svg class="w-6 h-6" x-show="!showMenu" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" x-cloak>
<path d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<svg class="w-6 h-6" x-show="showMenu" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" x-cloak>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
</div>
</nav>
home/_profile.html.twig
<div id="profile" class="container px-6 py-16 mx-auto md:px-4">
<div class="text-center mb-12">
<h1 class="text-4xl font-extrabold leading-none tracking-tight text-white sm:text-5xl md:text-6xl">{{ youtubeTitle }}</h1>
<p class="mx-auto mt-6 text-sm text-gray-200 md:mt-8 sm:text-base md:max-w-xl md:text-lg xl:text-xl">{{ youtubeDescription }}</p>
</div>
</div>
home/_social_links.html.twig
<div id="social" class="max-w-6xl mx-auto mb-16">
<div class="flex flex-wrap gap-8 justify-center">
{% if customLinksWebsite %}
<a href="{{ customLinksWebsite }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-indigo-600/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">Site Web</p>
</a>
{% endif %}
{% if socialLinksYoutube %}
<a href="{{ socialLinksYoutube }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-red-600/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">YouTube</p>
</a>
{% endif %}
{% if socialLinksX %}
<a href="{{ socialLinksX }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-black/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">Twitter / X</p>
</a>
{% endif %}
{% if socialLinksLinkedin %}
<a href="{{ socialLinksLinkedin }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-blue-600/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">LinkedIn</p>
</a>
{% endif %}
{% if socialLinksInstagram %}
<a href="{{ socialLinksInstagram }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-pink-600/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">Instagram</p>
</a>
{% endif %}
{% if socialLinksBluesky %}
<a href="{{ socialLinksBluesky }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-blue-400/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z"/>
<path d="M13 7h-2v6h2V7zm0 8h-2v2h2v-2z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">Bluesky</p>
</a>
{% endif %}
{% if socialLinksTiktok %}
<a href="{{ socialLinksTiktok }}" target="_blank" class="group">
<div class="w-16 h-16 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center shadow-lg border border-white/5 group-hover:bg-black/80 group-hover:scale-110 transition-all duration-300">
<svg class="w-7 h-7 text-white/80 group-hover:text-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-5.2 1.74 2.89 2.89 0 0 1 2.31-4.64 2.93 2.93 0 0 1 .88.13V9.4a6.84 6.84 0 0 0-1-.05A6.33 6.33 0 0 0 5 20.1a6.34 6.34 0 0 0 10.86-4.43v-7a8.16 8.16 0 0 0 4.77 1.52v-3.4a4.85 4.85 0 0 1-1-.1z"/>
</svg>
</div>
<p class="text-sm text-gray-300 text-center mt-2 group-hover:text-white transition-colors">TikTok</p>
</a>
{% endif %}
</div>
</div>
home/_videos.html.twig
<div id="videos" class="container px-6 py-16 mx-auto md:px-4">
<h2 class="text-3xl font-bold text-white mb-8 text-center">Dernières vidéos</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for video in videos %}
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" class="bg-white/10 backdrop-blur-sm rounded-lg overflow-hidden hover:bg-black/30 transition-all duration-300 cursor-pointer group" @click="modalOpen = true">
<div class="relative">
<img src="{{ video.thumbnail }}" alt="{{ video.title }}" class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute inset-0 flex items-end">
<div class="w-full bg-black/80 p-4">
<h3 class="text-white font-semibold text-lg line-clamp-2">{{ video.title }}</h3>
<p class="text-xs text-gray-400 group-hover:text-white transition-colors mt-1">
Publié le {{ video.published|date('d/m/Y') }}
{% if video.views != '0' %}
· {{ video.views }} vues
{% endif %}
</p>
</div>
</div>
</div>
<!-- Modale pour les détails de la vidéo -->
<template x-teleport="body">
<div x-show="modalOpen" class="fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen" x-cloak>
<div x-show="modalOpen"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="modalOpen=false" class="absolute inset-0 w-full h-full bg-black bg-opacity-75"></div>
<div x-show="modalOpen"
x-trap.inert.noscroll="modalOpen"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="relative w-full max-w-4xl py-4 sm:py-6 bg-gradient-to-br from-gray-900 via-black to-gray-800 px-4 sm:px-7 sm:rounded-lg overflow-hidden mx-2 sm:mx-0" @click.stop>
<div class="flex items-center justify-between pb-3 sm:pb-4 border-b border-white/10">
<h3 class="text-lg sm:text-xl font-semibold text-white pr-8 line-clamp-1">{{ video.title }}</h3>
<button @click="modalOpen=false" class="absolute right-3 top-3 flex items-center justify-center w-8 h-8 text-gray-400 rounded-full hover:text-white hover:bg-gray-700/50 transition-colors">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mt-4 sm:mt-6">
<div class="aspect-video w-full bg-black">
<iframe
class="w-full h-full"
src="https://www.youtube.com/embed/{{ video.link|replace({'https://www.youtube.com/watch?v=': ''}) }}"
title="{{ video.title }}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</div>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4 gap-3">
<div class="flex flex-wrap items-center text-sm text-gray-400 gap-y-2">
<div class="flex items-center mr-4">
<svg class="w-4 h-4 mr-2 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
</svg>
Publié le {{ video.published|date('d/m/Y') }}
</div>
{% if video.views != '0' %}
<div class="flex items-center">
<svg class="w-4 h-4 mr-2 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
{{ video.views }} vues
</div>
{% endif %}
</div>
<a href="{{ video.link }}" target="_blank" class="w-full sm:w-auto inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700 transition-colors">
<svg class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"/>
</svg>
Voir sur YouTube
</a>
</div>
</div>
</div>
</div>
</template>
</div>
{% endfor %}
</div>
</div>
home/index.html.twig
{% extends 'base.html.twig' %}
{% block title %}{{ youtubeTitle }} - Profil YouTube{% endblock %}
{% block body %}
<section class="w-full px-3 antialiased bg-gradient-to-br from-gray-900 via-black to-gray-800 lg:px-6">
<div class="mx-auto max-w-7xl">
{% include 'home/_navigation.html.twig' %}
<!-- Section Profil -->
{% include 'home/_profile.html.twig' %}
<!-- Réseaux sociaux et liens -->
{% include 'home/_social_links.html.twig' %}
<!-- Section Vidéos -->
{% include 'home/_videos.html.twig' %}
</div>
</section>
{% endblock %}
base.html.twig
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
And voilĂ !
Nous avons maintenant une page de profil YouTube qui affiche les informations de notre chaîne YouTube, les liens vers nos réseaux sociaux et les dernières vidéos en utilisant PinesUI dans un projet Symfony.
Simple, non ?
Loading comments...