Cours Vibe coding avec l'IA · Module 13: Clone de Twitch
Pipeline CI/CD - GitHub Actions
Déploiement automatisé.
Workflow GitHub Actions
1name: Deploy
2on:
3 push:
4 branches: [main]
5
6jobs:
7 deploy:
8 runs-on: ubuntu-latest
9 steps:
10 - uses: actions/checkout@v3
11 - uses: actions/setup-node@v3
12 with:
13 node-version: '20'
14 - run: npm ci
15 - run: npm test
16 - run: npm run build
17 - uses: amondnet/vercel-action@v25
18 with:
19 vercel-token: ${{ secrets.VERCEL_TOKEN }}À bientôt !
Code de cette leçon : App.tsx
1'use client';
2
3import { useState } from 'react';
4
5interface Category {
6 id: string;
7 name: string;
8 boxArtUrl: string;
9 viewerCount: number;
10 tags: string[];
11}
12
13const mockCategories: Category[] = [
14 {
15 id: '1',
16 name: 'Software & Game Development',
17 boxArtUrl: 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=300&h=400&fit=crop',
18 viewerCount: 12547,
19 tags: ['Programming', 'Tutorial']
20 },
21 {
22 id: '2',
23 name: 'Science & Technology',
24 boxArtUrl: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=300&h=400&fit=crop',
25 viewerCount: 8932,
26 tags: ['Educational', 'Tech']
27 },
28 {
29 id: '3',
30 name: 'Just Chatting',
31 boxArtUrl: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=300&h=400&fit=crop',
32 viewerCount: 45123,
33 tags: ['IRL', 'Talk Show']
34 },
35 {
36 id: '4',
37 name: 'Music & Performing Arts',
38 boxArtUrl: 'https://images.unsplash.com/photo-1511379938547-c1f69419868d?w=300&h=400&fit=crop',
39 viewerCount: 6789,
40 tags: ['Music', 'Performance']
41 },
42 {
43 id: '5',
44 name: 'Art',
45 boxArtUrl: 'https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?w=300&h=400&fit=crop',
46 viewerCount: 4321,
47 tags: ['Creative', 'Drawing']
48 },
49 {
50 id: '6',
51 name: 'Makers & Crafting',
52 boxArtUrl: 'https://images.unsplash.com/photo-1452860606245-08befc0ff44b?w=300&h=400&fit=crop',
53 viewerCount: 2156,
54 tags: ['DIY', 'Crafts']
55 }
56];
57
58export default function BrowsePage() {
59 const [activeTab, setActiveTab] = useState<'categories' | 'live'>('categories');
60
61 return (
62 <div className="min-h-screen bg-gray-950 text-white p-8">
63 <h1 className="text-3xl font-bold mb-6">Browse</h1>
64
65 {/* Tabs */}
66 <div className="flex gap-6 mb-8 border-b border-gray-800">
67 {[
68 { key: 'categories', label: 'Categories' },
69 { key: 'live', label: 'Live Channels' }
70 ].map(tab => (
71 <button
72 key={tab.key}
73 onClick={() => setActiveTab(tab.key as any)}
74 className={`pb-3 font-semibold ${
75 activeTab === tab.key
76 ? 'border-b-2 border-purple-500'
77 : 'text-gray-400 hover:text-white'
78 }`}
79 >
80 {tab.label}
81 </button>
82 ))}
83 </div>
84
85 {/* Categories Grid */}
86 {activeTab === 'categories' && (
87 <div className="grid grid-cols-6 gap-4">
88 {mockCategories.map(category => (
89 <div key={category.id} className="cursor-pointer group">
90 <div className="relative mb-2 overflow-hidden rounded">
91 <img
92 src={category.boxArtUrl}
93 alt={category.name}
94 className="w-full aspect-[3/4] object-cover group-hover:scale-110 transition"
95 />
96 <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-2">
97 <div className="text-xs text-gray-300">
98 {category.viewerCount.toLocaleString()} viewers
99 </div>
100 </div>
101 </div>
102 <h3 className="font-semibold text-sm mb-1">{category.name}</h3>
103 <div className="flex flex-wrap gap-1">
104 {category.tags.map(tag => (
105 <span key={tag} className="text-xs bg-gray-800 px-2 py-0.5 rounded">
106 {tag}
107 </span>
108 ))}
109 </div>
110 </div>
111 ))}
112 </div>
113 )}
114 </div>
115 );
116}Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Que fait le pipeline CI/CD avec GitHub Actions décrit dans la leçon ?
2. Quel évènement déclenche le workflow Deploy (la section on:) ?
Tâches pratiques dans le jeu
- Tri vertical
Ordonne les étapes du job deploy dans le workflow :
- Tri horizontal
Ordonne la référence au token Vercel depuis secrets (une ligne) : ${{ secrets.VERCEL_TOKEN }}