Recommandations cliquables avec avatars LinkedIn

- Ajout de liens vers les profils des auteurs de recommandations (prop url)
- Ajout d'avatars pour 7 recommandeurs (Maxime Boudier, Matthieu Diouron, Benoit Sarda, Pascal Gentil, Benoit Talbot, Anne Marchadier, Laurent Perez)
- Simplification du champ avatar : juste le nom de fichier au lieu du chemin complet, résolution automatique via import.meta.glob
- Ajout des URLs de profil LinkedIn dans les 14 fichiers de recommandation
This commit is contained in:
Jalil Arfaoui 2026-02-22 14:07:09 +01:00
parent a3732887f5
commit bf26caded3
30 changed files with 74 additions and 12 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

@ -2,15 +2,25 @@
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import type { ImageMetadata } from "astro"; import type { ImageMetadata } from "astro";
const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
'/src/assets/images/recommendations/*.{jpg,jpeg,png,webp}',
{ eager: true }
);
interface Props { interface Props {
author: string; author: string;
authorRole: string; authorRole: string;
company: string; company: string;
text: string; text: string;
avatar?: ImageMetadata; avatar?: string;
url?: string;
} }
const { author, authorRole, company, text, avatar } = Astro.props; const { author, authorRole, company, text, avatar, url } = Astro.props;
const avatarImage = avatar
? avatarImages[`/src/assets/images/recommendations/${avatar}`]?.default
: undefined;
const truncated = text.length > 200 ? text.slice(0, 200).replace(/\s+\S*$/, '') + '...' : text; const truncated = text.length > 200 ? text.slice(0, 200).replace(/\s+\S*$/, '') + '...' : text;
@ -27,9 +37,9 @@ const initials = author
"{truncated}" "{truncated}"
</p> </p>
<div class="mt-3 flex items-center gap-2.5"> <div class="mt-3 flex items-center gap-2.5">
{avatar ? ( {avatarImage ? (
<Image <Image
src={avatar} src={avatarImage}
alt={author} alt={author}
width={28} width={28}
height={28} height={28}
@ -41,7 +51,14 @@ const initials = author
</div> </div>
)} )}
<cite class="not-italic text-xs"> <cite class="not-italic text-xs">
<span class="font-semibold text-white/90">{author}</span> {url ? (
<a href={url} target="_blank" rel="noopener noreferrer" class="font-semibold text-white/90 hover:text-purple-200 transition-colors">
{author}
<svg class="inline-block w-2.5 h-2.5 ml-0.5 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
</a>
) : (
<span class="font-semibold text-white/90">{author}</span>
)}
<span class="text-white/40"> · {authorRole}, {company}</span> <span class="text-white/40"> · {authorRole}, {company}</span>
</cite> </cite>
</div> </div>

View file

@ -2,17 +2,27 @@
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import type { ImageMetadata } from "astro"; import type { ImageMetadata } from "astro";
const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
'/src/assets/images/recommendations/*.{jpg,jpeg,png,webp}',
{ eager: true }
);
interface Props { interface Props {
author: string; author: string;
authorRole: string; authorRole: string;
company: string; company: string;
text: string; text: string;
date: Date; date: Date;
avatar?: ImageMetadata; avatar?: string;
url?: string;
lang?: string; lang?: string;
} }
const { author, authorRole, company, text, date, avatar, lang = 'fr' } = Astro.props; const { author, authorRole, company, text, date, avatar, url, lang = 'fr' } = Astro.props;
const avatarImage = avatar
? avatarImages[`/src/assets/images/recommendations/${avatar}`]?.default
: undefined;
const dateLocales: Record<string, string> = { fr: 'fr-FR', en: 'en-US', ar: 'ar-SA' }; const dateLocales: Record<string, string> = { fr: 'fr-FR', en: 'en-US', ar: 'ar-SA' };
const formattedDate = date.toLocaleDateString(dateLocales[lang] || 'fr-FR', { const formattedDate = date.toLocaleDateString(dateLocales[lang] || 'fr-FR', {
@ -44,9 +54,9 @@ const avatarGradient = colors[colorIndex];
<p class="text-white/80 leading-relaxed flex-1 text-[0.9rem]" set:html={text} /> <p class="text-white/80 leading-relaxed flex-1 text-[0.9rem]" set:html={text} />
<div class="mt-5 pt-4 border-t border-white/[0.08] flex items-center gap-3"> <div class="mt-5 pt-4 border-t border-white/[0.08] flex items-center gap-3">
{avatar ? ( {avatarImage ? (
<Image <Image
src={avatar} src={avatarImage}
alt={author} alt={author}
width={40} width={40}
height={40} height={40}
@ -58,7 +68,14 @@ const avatarGradient = colors[colorIndex];
</div> </div>
)} )}
<cite class="not-italic min-w-0"> <cite class="not-italic min-w-0">
<span class="block font-semibold text-white text-sm truncate">{author}</span> {url ? (
<a href={url} target="_blank" rel="noopener noreferrer" class="block font-semibold text-white text-sm truncate hover:text-purple-200 transition-colors">
{author}
<svg class="inline-block w-3 h-3 ml-1 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /></svg>
</a>
) : (
<span class="block font-semibold text-white text-sm truncate">{author}</span>
)}
<span class="block text-xs text-white/50 truncate">{authorRole} · {company}</span> <span class="block text-xs text-white/50 truncate">{authorRole} · {company}</span>
<span class="block text-xs text-white/30 mt-0.5">{formattedDate}</span> <span class="block text-xs text-white/30 mt-0.5">{formattedDate}</span>
</cite> </cite>

View file

@ -72,11 +72,12 @@ const experiencesCollection = defineCollection({
const recommendationsCollection = defineCollection({ const recommendationsCollection = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/recommendations" }), loader: glob({ pattern: "**/*.md", base: "./src/content/recommendations" }),
schema: ({ image }) => z.object({ schema: z.object({
author: z.string(), author: z.string(),
authorRole: z.string(), authorRole: z.string(),
company: z.string(), company: z.string(),
avatar: image().optional(), avatar: z.string().optional(),
url: z.string().url().optional(),
date: z.date(), date: z.date(),
relationship: z.string().optional(), relationship: z.string().optional(),
lang: z.enum(['fr', 'en']).default('fr'), lang: z.enum(['fr', 'en']).default('fr'),

View file

@ -2,6 +2,8 @@
author: "Anne Marchadier Valmont" author: "Anne Marchadier Valmont"
authorRole: "Responsable administratif" authorRole: "Responsable administratif"
company: "4CAD Group" company: "4CAD Group"
avatar: anne-marchadier.jpg
url: https://www.linkedin.com/in/anne-marchadier-valmont-50421a12
date: 2009-08-19 date: 2009-08-19
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Antoine Wolff" author: "Antoine Wolff"
authorRole: "Développeur, graphiste et chef de projet" authorRole: "Développeur, graphiste et chef de projet"
company: "LeCollectif" company: "LeCollectif"
url: https://www.linkedin.com/in/wolffantoine
date: 2020-12-07 date: 2020-12-07
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,8 @@
author: "Benoit Sarda" author: "Benoit Sarda"
authorRole: "Sr Solution Architect, Manuf" authorRole: "Sr Solution Architect, Manuf"
company: "Amazon Web Services (AWS)" company: "Amazon Web Services (AWS)"
avatar: benoit-sarda.jpg
url: https://www.linkedin.com/in/benoitsarda
date: 2011-12-07 date: 2011-12-07
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,8 @@
author: "Benoit Talbot" author: "Benoit Talbot"
authorRole: "Consultant technico-fonctionel Sage ERP X3" authorRole: "Consultant technico-fonctionel Sage ERP X3"
company: "Concept ERP" company: "Concept ERP"
avatar: benoit-talbot.jpg
url: https://www.linkedin.com/in/benoit-talbot-65610a81
date: 2015-01-29 date: 2015-01-29
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Bouchra Ghaoui" author: "Bouchra Ghaoui"
authorRole: "Senior Engagement Manager" authorRole: "Senior Engagement Manager"
company: "Capgemini" company: "Capgemini"
url: https://www.linkedin.com/in/bouchra-ghaoui-46509a10
date: 2011-12-09 date: 2011-12-09
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Daniel Gall" author: "Daniel Gall"
authorRole: "Consultant" authorRole: "Consultant"
company: "Taos Conseil" company: "Taos Conseil"
url: https://www.linkedin.com/in/daniel-g-385524
date: 2011-12-07 date: 2011-12-07
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Grégoire Lacoste" author: "Grégoire Lacoste"
authorRole: "Chief Product Officer" authorRole: "Chief Product Officer"
company: "CertifiCall" company: "CertifiCall"
url: https://www.linkedin.com/in/gregoirelacoste
date: 2020-12-08 date: 2020-12-08
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Guillaume Gendrillon" author: "Guillaume Gendrillon"
authorRole: "Lead designer Information Voyageur et signalétique" authorRole: "Lead designer Information Voyageur et signalétique"
company: "RATPgroup" company: "RATPgroup"
url: https://www.linkedin.com/in/guillaumegendrillon
date: 2011-12-05 date: 2011-12-05
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,8 @@
author: "Laurent Perez" author: "Laurent Perez"
authorRole: "Senior Developer" authorRole: "Senior Developer"
company: "itk" company: "itk"
avatar: laurent-perez.jpg
url: https://www.linkedin.com/in/laurentperez
date: 2009-09-07 date: 2009-09-07
lang: "en" lang: "en"
--- ---

View file

@ -2,6 +2,8 @@
author: "Matthieu Diouron" author: "Matthieu Diouron"
authorRole: "Director of Business Development" authorRole: "Director of Business Development"
company: "T-Systems France" company: "T-Systems France"
avatar: matthieu-diouron.jpg
url: https://www.linkedin.com/in/mdiouron
date: 2009-09-09 date: 2009-09-09
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,8 @@
author: "Maxime Boudier" author: "Maxime Boudier"
authorRole: "Staff Web Engineer" authorRole: "Staff Web Engineer"
company: "SNCF Connect & Tech" company: "SNCF Connect & Tech"
avatar: maxime-boudier.jpg
url: https://www.linkedin.com/in/maximeboudier
date: 2020-12-12 date: 2020-12-12
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Olivier Cornudet" author: "Olivier Cornudet"
authorRole: "Consultant manager" authorRole: "Consultant manager"
company: "e-THEMIS" company: "e-THEMIS"
url: https://www.linkedin.com/in/olivier-cornudet-9a398738
date: 2015-02-11 date: 2015-02-11
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,8 @@
author: "Pascal Gentil" author: "Pascal Gentil"
authorRole: "Chef de projet Sage X3" authorRole: "Chef de projet Sage X3"
company: "YOUR PARTNER" company: "YOUR PARTNER"
avatar: pascal-gentil.jpg
url: https://www.linkedin.com/in/pascalgentil
date: 2015-02-08 date: 2015-02-08
lang: "fr" lang: "fr"
--- ---

View file

@ -2,6 +2,7 @@
author: "Vanessa Boissard" author: "Vanessa Boissard"
authorRole: "Psychologue sociale" authorRole: "Psychologue sociale"
company: "AlterAlliance" company: "AlterAlliance"
url: https://www.linkedin.com/in/vanessaboissard
date: 2011-12-05 date: 2011-12-05
lang: "fr" lang: "fr"
--- ---

View file

@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
company={rec.data.company} company={rec.data.company}
text={rec.text} text={rec.text}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
/> />
))} ))}
</div> </div>

View file

@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
text={rec.body || ''} text={rec.body || ''}
date={rec.data.date} date={rec.data.date}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
lang={rec.data.lang} lang={rec.data.lang}
/> />
</div> </div>

View file

@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
company={rec.data.company} company={rec.data.company}
text={rec.text} text={rec.text}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
/> />
))} ))}
</div> </div>

View file

@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
text={rec.body || ''} text={rec.body || ''}
date={rec.data.date} date={rec.data.date}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
lang={rec.data.lang} lang={rec.data.lang}
/> />
</div> </div>

View file

@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
company={rec.data.company} company={rec.data.company}
text={rec.text} text={rec.text}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
/> />
))} ))}
</div> </div>

View file

@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
text={rec.body || ''} text={rec.body || ''}
date={rec.data.date} date={rec.data.date}
avatar={rec.data.avatar} avatar={rec.data.avatar}
url={rec.data.url}
lang={rec.data.lang} lang={rec.data.lang}
/> />
</div> </div>