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
BIN
src/assets/images/recommendations/anne-marchadier.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/images/recommendations/benoit-sarda.jpg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src/assets/images/recommendations/benoit-talbot.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/images/recommendations/laurent-perez.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/images/recommendations/matthieu-diouron.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/images/recommendations/maxime-boudier.jpg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/assets/images/recommendations/pascal-gentil.jpg
Normal file
|
After Width: | Height: | Size: 9 KiB |
|
|
@ -2,15 +2,25 @@
|
|||
import { Image } from "astro:assets";
|
||||
import type { ImageMetadata } from "astro";
|
||||
|
||||
const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||
'/src/assets/images/recommendations/*.{jpg,jpeg,png,webp}',
|
||||
{ eager: true }
|
||||
);
|
||||
|
||||
interface Props {
|
||||
author: string;
|
||||
authorRole: string;
|
||||
company: 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;
|
||||
|
||||
|
|
@ -27,9 +37,9 @@ const initials = author
|
|||
"{truncated}"
|
||||
</p>
|
||||
<div class="mt-3 flex items-center gap-2.5">
|
||||
{avatar ? (
|
||||
{avatarImage ? (
|
||||
<Image
|
||||
src={avatar}
|
||||
src={avatarImage}
|
||||
alt={author}
|
||||
width={28}
|
||||
height={28}
|
||||
|
|
@ -41,7 +51,14 @@ const initials = author
|
|||
</div>
|
||||
)}
|
||||
<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>
|
||||
</cite>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,27 @@
|
|||
import { Image } from "astro:assets";
|
||||
import type { ImageMetadata } from "astro";
|
||||
|
||||
const avatarImages = import.meta.glob<{ default: ImageMetadata }>(
|
||||
'/src/assets/images/recommendations/*.{jpg,jpeg,png,webp}',
|
||||
{ eager: true }
|
||||
);
|
||||
|
||||
interface Props {
|
||||
author: string;
|
||||
authorRole: string;
|
||||
company: string;
|
||||
text: string;
|
||||
date: Date;
|
||||
avatar?: ImageMetadata;
|
||||
avatar?: string;
|
||||
url?: 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 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} />
|
||||
|
||||
<div class="mt-5 pt-4 border-t border-white/[0.08] flex items-center gap-3">
|
||||
{avatar ? (
|
||||
{avatarImage ? (
|
||||
<Image
|
||||
src={avatar}
|
||||
src={avatarImage}
|
||||
alt={author}
|
||||
width={40}
|
||||
height={40}
|
||||
|
|
@ -58,7 +68,14 @@ const avatarGradient = colors[colorIndex];
|
|||
</div>
|
||||
)}
|
||||
<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/30 mt-0.5">{formattedDate}</span>
|
||||
</cite>
|
||||
|
|
|
|||
|
|
@ -72,11 +72,12 @@ const experiencesCollection = defineCollection({
|
|||
|
||||
const recommendationsCollection = defineCollection({
|
||||
loader: glob({ pattern: "**/*.md", base: "./src/content/recommendations" }),
|
||||
schema: ({ image }) => z.object({
|
||||
schema: z.object({
|
||||
author: z.string(),
|
||||
authorRole: z.string(),
|
||||
company: z.string(),
|
||||
avatar: image().optional(),
|
||||
avatar: z.string().optional(),
|
||||
url: z.string().url().optional(),
|
||||
date: z.date(),
|
||||
relationship: z.string().optional(),
|
||||
lang: z.enum(['fr', 'en']).default('fr'),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Anne Marchadier Valmont"
|
||||
authorRole: "Responsable administratif"
|
||||
company: "4CAD Group"
|
||||
avatar: anne-marchadier.jpg
|
||||
url: https://www.linkedin.com/in/anne-marchadier-valmont-50421a12
|
||||
date: 2009-08-19
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Antoine Wolff"
|
||||
authorRole: "Développeur, graphiste et chef de projet"
|
||||
company: "LeCollectif"
|
||||
url: https://www.linkedin.com/in/wolffantoine
|
||||
date: 2020-12-07
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Benoit Sarda"
|
||||
authorRole: "Sr Solution Architect, Manuf"
|
||||
company: "Amazon Web Services (AWS)"
|
||||
avatar: benoit-sarda.jpg
|
||||
url: https://www.linkedin.com/in/benoitsarda
|
||||
date: 2011-12-07
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Benoit Talbot"
|
||||
authorRole: "Consultant technico-fonctionel Sage ERP X3"
|
||||
company: "Concept ERP"
|
||||
avatar: benoit-talbot.jpg
|
||||
url: https://www.linkedin.com/in/benoit-talbot-65610a81
|
||||
date: 2015-01-29
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Bouchra Ghaoui"
|
||||
authorRole: "Senior Engagement Manager"
|
||||
company: "Capgemini"
|
||||
url: https://www.linkedin.com/in/bouchra-ghaoui-46509a10
|
||||
date: 2011-12-09
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Daniel Gall"
|
||||
authorRole: "Consultant"
|
||||
company: "Taos Conseil"
|
||||
url: https://www.linkedin.com/in/daniel-g-385524
|
||||
date: 2011-12-07
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Grégoire Lacoste"
|
||||
authorRole: "Chief Product Officer"
|
||||
company: "CertifiCall"
|
||||
url: https://www.linkedin.com/in/gregoirelacoste
|
||||
date: 2020-12-08
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Guillaume Gendrillon"
|
||||
authorRole: "Lead designer Information Voyageur et signalétique"
|
||||
company: "RATPgroup"
|
||||
url: https://www.linkedin.com/in/guillaumegendrillon
|
||||
date: 2011-12-05
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Laurent Perez"
|
||||
authorRole: "Senior Developer"
|
||||
company: "itk"
|
||||
avatar: laurent-perez.jpg
|
||||
url: https://www.linkedin.com/in/laurentperez
|
||||
date: 2009-09-07
|
||||
lang: "en"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Matthieu Diouron"
|
||||
authorRole: "Director of Business Development"
|
||||
company: "T-Systems France"
|
||||
avatar: matthieu-diouron.jpg
|
||||
url: https://www.linkedin.com/in/mdiouron
|
||||
date: 2009-09-09
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Maxime Boudier"
|
||||
authorRole: "Staff Web Engineer"
|
||||
company: "SNCF Connect & Tech"
|
||||
avatar: maxime-boudier.jpg
|
||||
url: https://www.linkedin.com/in/maximeboudier
|
||||
date: 2020-12-12
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Olivier Cornudet"
|
||||
authorRole: "Consultant manager"
|
||||
company: "e-THEMIS"
|
||||
url: https://www.linkedin.com/in/olivier-cornudet-9a398738
|
||||
date: 2015-02-11
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
author: "Pascal Gentil"
|
||||
authorRole: "Chef de projet Sage X3"
|
||||
company: "YOUR PARTNER"
|
||||
avatar: pascal-gentil.jpg
|
||||
url: https://www.linkedin.com/in/pascalgentil
|
||||
date: 2015-02-08
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
author: "Vanessa Boissard"
|
||||
authorRole: "Psychologue sociale"
|
||||
company: "AlterAlliance"
|
||||
url: https://www.linkedin.com/in/vanessaboissard
|
||||
date: 2011-12-05
|
||||
lang: "fr"
|
||||
---
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
|
|||
company={rec.data.company}
|
||||
text={rec.text}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
|
|||
text={rec.body || ''}
|
||||
date={rec.data.date}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
lang={rec.data.lang}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
|
|||
company={rec.data.company}
|
||||
text={rec.text}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
|
|||
text={rec.body || ''}
|
||||
date={rec.data.date}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
lang={rec.data.lang}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const recommendationTexts = recommendations.map((rec) => ({
|
|||
company={rec.data.company}
|
||||
text={rec.text}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const recommendations = (await getCollection("recommendations"))
|
|||
text={rec.body || ''}
|
||||
date={rec.data.date}
|
||||
avatar={rec.data.avatar}
|
||||
url={rec.data.url}
|
||||
lang={rec.data.lang}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||