Personal site staging.colinozanne.co.uk
portfolio astro

feat: define page content in only one component, passing lang data from localised page

finxol.io 9aec1216 0dfb56bd

verified
+212 -206
+116
src/components/pages/index.astro
··· 1 + --- 2 + import Layout from "@/layouts/Layout.astro"; 3 + import "@/assets/styles/index.css"; 4 + import { Icon } from "astro-icon/components"; 5 + import { projects as projectsMetadata } from "@/data/projects"; 6 + 7 + interface Props { 8 + content: { 9 + intro: { 10 + title: string; 11 + description: string; 12 + ps: string[]; 13 + }; 14 + languages: string; 15 + blogSection: { 16 + linkText: string; 17 + }; 18 + projectsSection: { 19 + title: string; 20 + description: string; 21 + }; 22 + projects: Array<{ 23 + id: string; 24 + name: string; 25 + description: string; 26 + }>; 27 + }; 28 + } 29 + 30 + const { 31 + content: { intro, languages, blogSection, projectsSection, projects }, 32 + } = Astro.props; 33 + 34 + // Merge project descriptions with metadata 35 + const projectsWithMetadata = projects.map((proj) => { 36 + const metadata = projectsMetadata.find((p) => p.id === proj.id); 37 + if (!metadata) { 38 + throw new Error(`Project metadata not found for id: ${proj.id}`); 39 + } 40 + return { 41 + ...metadata, 42 + name: proj.name, 43 + description: proj.description, 44 + }; 45 + }); 46 + --- 47 + 48 + <Layout> 49 + <article> 50 + <section class="container layout content-intro"> 51 + <aside>👋</aside> 52 + <h3>{intro.title}</h3> 53 + <p>{intro.description}</p> 54 + <div> 55 + {intro.ps.map((p) => <p>{p}</p>)} 56 + </div> 57 + </section> 58 + 59 + <section class="container content-languages"> 60 + <Icon name="pixel:globe" /> 61 + <Fragment set:html={languages} /> 62 + </section> 63 + 64 + <section class="container content-blog"> 65 + <a 66 + href="https://finxol.io" 67 + target="_blank" 68 + rel="noopener noreferrer" 69 + > 70 + <Icon name="pixel:external-link" class="external-link" /> 71 + <span> {blogSection.linkText} </span> 72 + </a> 73 + </section> 74 + 75 + <section class="title-projects"> 76 + <aside>💻</aside> 77 + <h3>{projectsSection.title}</h3> 78 + <p> 79 + {projectsSection.description} 80 + </p> 81 + </section> 82 + 83 + <section class="container content-projects"> 84 + { 85 + projectsWithMetadata.map((project) => ( 86 + <div 87 + class="project" 88 + {...(project.colour && { 89 + style: `--container-color: var(${project.colour});`, 90 + })} 91 + > 92 + <picture style="view-transition-name: project-img"> 93 + <source 94 + srcset={project.imgs.webp.src} 95 + type="image/webp" 96 + /> 97 + <img 98 + src={project.imgs.jpg.src} 99 + alt={project.name} 100 + /> 101 + </picture> 102 + <a href={`/projects/${project.slug}`} class="cover"> 103 + {project.name} 104 + </a> 105 + <p>{project.description}</p> 106 + <ul> 107 + {project.tags.map((tag: string) => ( 108 + <li class="tag">{tag}</li> 109 + ))} 110 + </ul> 111 + </div> 112 + )) 113 + } 114 + </section> 115 + </article> 116 + </Layout>
+39
src/data/projects.ts
··· 1 + import type { ImageMetadata } from "astro"; 2 + 3 + import karrJpg from "@/assets/img/karr_demo.jpg"; 4 + import karrWebp from "@/assets/img/karr_demo.webp"; 5 + import blogJpg from "@/assets/img/finxol_blog_home.jpg"; 6 + import blogWebp from "@/assets/img/finxol_blog_home.webp"; 7 + 8 + export interface ProjectMetadata { 9 + id: string; 10 + slug: string; 11 + imgs: { 12 + jpg: ImageMetadata; 13 + webp: ImageMetadata; 14 + }; 15 + tags: string[]; 16 + colour?: string; 17 + } 18 + 19 + export const projects: ProjectMetadata[] = [ 20 + { 21 + id: "karr", 22 + slug: "karr", 23 + imgs: { 24 + jpg: karrJpg, 25 + webp: karrWebp, 26 + }, 27 + tags: ["Next.js", "Hono", "OpenAuth", "CI"], 28 + }, 29 + { 30 + id: "blog", 31 + slug: "blog", 32 + imgs: { 33 + jpg: blogJpg, 34 + webp: blogWebp, 35 + }, 36 + tags: ["Nuxt.js", "Markdown"], 37 + colour: "--yellow-300", 38 + }, 39 + ];
+29 -103
src/pages/en/index.astro
··· 1 1 --- 2 - import Layout from "@/layouts/Layout.astro"; 3 - import "@/assets/styles/index.css"; 4 - import { Icon } from "astro-icon/components"; 2 + import IndexPage from "@/components/pages/index.astro"; 5 3 6 - import karrJpg from "@/assets/img/karr_demo.jpg"; 7 - import karrWebp from "@/assets/img/karr_demo.webp"; 8 - import blogJpg from "@/assets/img/finxol_blog_home.jpg"; 9 - import blogWebp from "@/assets/img/finxol_blog_home.webp"; 10 - 11 - const projects = [ 12 - { 13 - name: "Karr", 14 - slug: "karr", 15 - description: "Federated carpool platform for businesses", 16 - imgs: { 17 - jpg: karrJpg, 18 - webp: karrWebp, 19 - }, 20 - tags: ["Next.js", "Hono", "OpenAuth", "CI"], 4 + const content = { 5 + intro: { 6 + title: "Hiya!", 7 + description: "I'm a 🇫🇷 French computer science student.", 8 + ps: ["I love building robust and performant websites."], 21 9 }, 22 - { 23 - name: "Technical Blog", 24 - slug: "blog", 25 - description: "Technical blog where I write articles about technology", 26 - imgs: { 27 - jpg: blogJpg, 28 - webp: blogWebp, 29 - }, 30 - tags: ["Nuxt.js", "Markdown"], 31 - colour: "--yellow-300", 10 + languages: `<p>I'm a native 🇬🇧 <strong>English</strong> and 🇫🇷 <strong>French</strong> speaker.</p>`, 11 + blogSection: { 12 + linkText: "Check out my blog for some technical write-ups!", 13 + }, 14 + projectsSection: { 15 + title: "Projects", 16 + description: 17 + "Here are my most interesting projects. Click through to get more details!", 32 18 }, 33 - ]; 19 + projects: [ 20 + { 21 + id: "karr", 22 + name: "Karr", 23 + description: "Federated carpool platform for businesses", 24 + }, 25 + { 26 + id: "blog", 27 + name: "Technical Blog", 28 + description: 29 + "Technical blog where I write articles about technology", 30 + }, 31 + ], 32 + }; 34 33 --- 35 34 36 - <Layout> 37 - <article> 38 - <section class="container layout content-intro"> 39 - <aside>👋</aside> 40 - <h3>Hiya!</h3> 41 - <p>I'm a 🇫🇷 French computer science student.</p> 42 - <div> 43 - <p>I love building robust and performant websites.</p> 44 - </div> 45 - </section> 46 - 47 - <section class="container content-languages"> 48 - <Icon name="pixel:globe" /> 49 - <p> 50 - I'm a native 🇬🇧 <strong> English </strong> and 🇫🇷 <strong> 51 - French 52 - </strong> speaker. 53 - </p> 54 - </section> 55 - 56 - <section class="container content-blog"> 57 - <a 58 - href="https://finxol.io" 59 - target="_blank" 60 - rel="noopener noreferrer" 61 - > 62 - <Icon name="pixel:external-link" class="external-link" /> 63 - <span> Check out my blog for some technical write-ups! </span> 64 - </a> 65 - </section> 66 - 67 - <section class="title-projects"> 68 - <aside>💻</aside> 69 - <h3>Projects</h3> 70 - <p> 71 - Here are my most interesting projects. Click through to get more 72 - details! 73 - </p> 74 - </section> 75 - 76 - <section class="container content-projects"> 77 - { 78 - projects.map((project) => ( 79 - <div 80 - class="project" 81 - {...(project.colour && { 82 - style: `--container-color: var(${project.colour});`, 83 - })} 84 - > 85 - <picture style="view-transition-name: project-img"> 86 - <source 87 - srcset={project.imgs.webp.src} 88 - type="image/webp" 89 - /> 90 - <img 91 - src={project.imgs.jpg.src} 92 - alt={project.name} 93 - /> 94 - </picture> 95 - <a href={`/projects/${project.slug}`} class="cover"> 96 - {project.name} 97 - </a> 98 - <p>{project.description}</p> 99 - <ul> 100 - {project.tags.map((tag: string) => ( 101 - <li class="tag">{tag}</li> 102 - ))} 103 - </ul> 104 - </div> 105 - )) 106 - } 107 - </section> 108 - </article> 109 - </Layout> 35 + <IndexPage content={content} />
+28 -103
src/pages/fr/index.astro
··· 1 1 --- 2 - import Layout from "@/layouts/Layout.astro"; 3 - import "@/assets/styles/index.css"; 4 - import { Icon } from "astro-icon/components"; 2 + import IndexPage from "@/components/pages/index.astro"; 5 3 6 - import karrJpg from "@/assets/img/karr_demo.jpg"; 7 - import karrWebp from "@/assets/img/karr_demo.webp"; 8 - import blogJpg from "@/assets/img/finxol_blog_home.jpg"; 9 - import blogWebp from "@/assets/img/finxol_blog_home.webp"; 10 - 11 - const projects = [ 12 - { 13 - name: "Karr", 14 - slug: "karr", 15 - description: "Plateforme de covoiturage fédérée", 16 - imgs: { 17 - jpg: karrJpg, 18 - webp: karrWebp, 19 - }, 20 - tags: ["Next.js", "Hono", "OpenAuth", "CI"], 4 + const content = { 5 + intro: { 6 + title: "Bonjour!", 7 + description: "Je suis un étudiant en informatique 🇫🇷 Français.", 8 + ps: ["J'adore construire des sites web robustes et performants."], 9 + }, 10 + languages: `<p>Je suis natif en 🇬🇧 <strong>Anglais</strong> et 🇫🇷 <strong>Français</strong>.</p>`, 11 + blogSection: { 12 + linkText: "Voir mon blog pour des articles techniques !", 21 13 }, 22 - { 23 - name: "Blog Technique", 24 - slug: "blog", 14 + projectsSection: { 15 + title: "Projets", 25 16 description: 26 - "Blog personnel technique sur lequel j'écris des articles sur la technologie", 27 - imgs: { 28 - jpg: blogJpg, 29 - webp: blogWebp, 17 + "Voici mes projets les plus intéressants. Cliquez pour en savoir plus !", 18 + }, 19 + projects: [ 20 + { 21 + id: "karr", 22 + name: "Karr", 23 + description: "Plateforme de covoiturage fédérée", 24 + }, 25 + { 26 + id: "blog", 27 + name: "Blog Technique", 28 + description: 29 + "Blog personnel technique sur lequel j'écris des articles sur la technologie", 30 30 }, 31 - tags: ["Nuxt.js", "Markdown"], 32 - colour: "--yellow-300", 33 - }, 34 - ]; 31 + ], 32 + }; 35 33 --- 36 34 37 - <Layout> 38 - <article> 39 - <section class="container layout content-intro"> 40 - <aside>👋</aside> 41 - <h3>Bonjour!</h3> 42 - <p>Je suis un étudiant en informatique 🇫🇷 Français.</p> 43 - <div> 44 - <p>J'adore construire des sites web robustes et performants.</p> 45 - </div> 46 - </section> 47 - 48 - <section class="container content-languages"> 49 - <Icon name="pixel:globe" /> 50 - <p> 51 - Je suis natif en 🇬🇧 <strong>Anglais</strong> et 🇫🇷 <strong> 52 - Français</strong 53 - >. 54 - </p> 55 - </section> 56 - 57 - <section class="container content-blog"> 58 - <a 59 - href="https://finxol.io" 60 - target="_blank" 61 - rel="noopener noreferrer" 62 - > 63 - <Icon name="pixel:external-link" class="external-link" /> 64 - <span> Voir mon blog pour des articles techniques ! </span> 65 - </a> 66 - </section> 67 - 68 - <section class="title-projects"> 69 - <aside>💻</aside> 70 - <h3>Projets</h3> 71 - <p> 72 - Voici mes projets les plus intéressants. Cliquez pour en savoir 73 - plus ! 74 - </p> 75 - </section> 76 - 77 - <section class="container content-projects"> 78 - { 79 - projects.map((project) => ( 80 - <div 81 - class="project" 82 - {...(project.colour && { 83 - style: `--container-color: var(${project.colour});`, 84 - })} 85 - > 86 - <picture style="view-transition-name: project-img"> 87 - <source 88 - srcset={project.imgs.webp.src} 89 - type="image/webp" 90 - /> 91 - <img 92 - src={project.imgs.jpg.src} 93 - alt={project.name} 94 - /> 95 - </picture> 96 - <a href={`/projects/${project.slug}`} class="cover"> 97 - {project.name} 98 - </a> 99 - <p>{project.description}</p> 100 - <ul> 101 - {project.tags.map((tag: string) => ( 102 - <li class="tag">{tag}</li> 103 - ))} 104 - </ul> 105 - </div> 106 - )) 107 - } 108 - </section> 109 - </article> 110 - </Layout> 35 + <IndexPage content={content} />