feat: Nouvelle version
This commit is contained in:
@ -0,0 +1,179 @@
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import {
|
||||
Header,
|
||||
CommunesList,
|
||||
DepartementsList,
|
||||
RegionList,
|
||||
} from "@/components/index";
|
||||
import communes, {
|
||||
Commune,
|
||||
getCommuneFromSlug,
|
||||
getSlugFromCommune,
|
||||
getCodesPostaux,
|
||||
getChefLieu,
|
||||
CommuneDeleguee,
|
||||
} from "@/lib/communes";
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
ContentBody,
|
||||
ContentFull,
|
||||
ContentHalf,
|
||||
} from "@/components/Content/Content";
|
||||
|
||||
import departements, {
|
||||
getSlugFromDepartement,
|
||||
getDepartementFromSlug,
|
||||
} from "@/lib/departements";
|
||||
import { getRegionFromCode, getSlugFromRegion } from "@/lib/regions";
|
||||
import Link from "next/link";
|
||||
|
||||
import { getMSPFromCodesPostaux } from "@/lib/api-annuaire-sante";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Annuaire",
|
||||
description: "L'annuaire collaboratif des professionnels de la santé",
|
||||
};
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return communes.map((commune) => ({
|
||||
commune: getSlugFromCommune(commune),
|
||||
}));
|
||||
}
|
||||
|
||||
async function getData(codesPostaux: Array<string>) {
|
||||
const res = await fetch(
|
||||
`http://localhost:8000/fhir/v1/Organization?address-postalcode%3Aexact=${codesPostaux.join(
|
||||
"%2C",
|
||||
)}&_count=5000&type=https://mos.esante.gouv.fr/NOS/TRE_R66-CategorieEtablissement/FHIR/TRE-R66-CategorieEtablissement%7C603`,
|
||||
{ cache: "force-cache" },
|
||||
);
|
||||
// The return value is *not* serialized
|
||||
// You can return Date, Map, Set, etc.
|
||||
|
||||
if (!res.ok) {
|
||||
// This will activate the closest `error.js` Error Boundary
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export default async function CommunePage({
|
||||
params,
|
||||
}: {
|
||||
params: { regionSlug: string; departementSlug: string; communeSlug: string };
|
||||
}) {
|
||||
const commune = getCommuneFromSlug(params.communeSlug);
|
||||
if (!commune) return notFound();
|
||||
const departement = getDepartementFromSlug(params.departementSlug);
|
||||
if (!departement) return notFound();
|
||||
const region = getRegionFromCode(departement.region);
|
||||
if (!region) return notFound();
|
||||
|
||||
let chefLieu: Commune | Boolean = commune.chefLieu
|
||||
? getChefLieu(commune)
|
||||
: false;
|
||||
|
||||
const codesPostaux = commune.chefLieu
|
||||
? [commune.chefLieu]
|
||||
: commune.codesPostaux;
|
||||
const msps = await getMSPFromCodesPostaux(codesPostaux);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header searchField={true} />
|
||||
<Content>
|
||||
<div className="bg-primary pb-24">
|
||||
<div className="max-w-7xl mx-auto pt-16 px-10">
|
||||
<div className="rounded-md bg-base-100 p-8 w-full flex items-center">
|
||||
<div className="prose w-96 text-center">
|
||||
<h1 className="pb-0 mb-0">{commune.nom}</h1>
|
||||
<p className="mt-0 pt-0">{getCodesPostaux(commune)}</p>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Région</th>
|
||||
<td>
|
||||
<Link href={`/annuaire/${getSlugFromRegion(region)}`}>
|
||||
{region.nom}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Département</th>
|
||||
<td>
|
||||
<Link
|
||||
href={`/annuaire/${getSlugFromRegion(
|
||||
region,
|
||||
)}/${getSlugFromDepartement(departement)}`}
|
||||
>
|
||||
{departement.nom} ({departement.code})
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
{commune.population && (
|
||||
<tr>
|
||||
<th>Population</th>
|
||||
<td>
|
||||
{commune.population.toLocaleString("fr-FR")} habiants
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{chefLieu && (
|
||||
<tr>
|
||||
<th>Cheflieu</th>
|
||||
<td>
|
||||
<Link
|
||||
href={`/annuaire/${getSlugFromRegion(
|
||||
region,
|
||||
)}/${getSlugFromDepartement(
|
||||
departement,
|
||||
)}/${getSlugFromCommune(chefLieu)}`}
|
||||
>
|
||||
{chefLieu.nom}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentBody>
|
||||
<div className="max-w-7xl mx-auto pt-8 px-10 flex flex-wrap">
|
||||
{msps.entry && (
|
||||
<div className="w-6/12 pr-8">
|
||||
<div className="rounded-md bg-base-100 p-8 prose">
|
||||
<h2>Informations sur les {msps.total} structures</h2>
|
||||
<ul>
|
||||
{msps.entry.map((msp) => (
|
||||
<li key={msp.resource.name}>{msp.resource.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-6/12">
|
||||
<div className="rounded-md bg-base-100 p-8">
|
||||
<h2>Dirigeants et représentants de P4PILLON</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContentFull>test</ContentFull>
|
||||
</ContentBody>
|
||||
</Content>
|
||||
<RegionList selected={region} />
|
||||
<hr></hr>
|
||||
<DepartementsList selected={departement} region={region} />
|
||||
<hr></hr>
|
||||
<CommunesList selected={commune} departement={departement} />
|
||||
</>
|
||||
);
|
||||
}
|
48
app/annuaire/[regionSlug]/[departementSlug]/page.tsx
Normal file
48
app/annuaire/[regionSlug]/[departementSlug]/page.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import {
|
||||
Header,
|
||||
CommunesList,
|
||||
DepartementsList,
|
||||
RegionList,
|
||||
} from "@/components/index";
|
||||
|
||||
import departements, {
|
||||
getSlugFromDepartement,
|
||||
getDepartementFromSlug,
|
||||
} from "@/lib/departements";
|
||||
import { getRegionFromCode } from "@/lib/regions";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Annuaire",
|
||||
description: "L'annuaire collaboratif des professionnels de la santé",
|
||||
};
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return departements.map((departement) => ({
|
||||
departement: getSlugFromDepartement(departement),
|
||||
}));
|
||||
}
|
||||
|
||||
export default function DepartementPage({
|
||||
params,
|
||||
}: {
|
||||
params: { regionSlug: string; departementSlug: string };
|
||||
}) {
|
||||
const departement = getDepartementFromSlug(params.departementSlug);
|
||||
if (!departement) return notFound();
|
||||
const region = getRegionFromCode(departement.region);
|
||||
if (!region) return notFound();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header searchField={true} />
|
||||
<RegionList selected={region} />
|
||||
<hr></hr>
|
||||
<DepartementsList selected={departement} region={region} />
|
||||
<hr></hr>
|
||||
<CommunesList departement={departement} />
|
||||
</>
|
||||
);
|
||||
}
|
85
app/annuaire/[regionSlug]/page.tsx
Normal file
85
app/annuaire/[regionSlug]/page.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import regionsCpts from "../../../regions_cpts.json";
|
||||
|
||||
import regions, { getRegionFromSlug, getSlugFromRegion } from "@/lib/regions";
|
||||
import { Header, DepartementsList, RegionList } from "@/components/index";
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
ContentBody,
|
||||
ContentFull,
|
||||
ContentHalf,
|
||||
} from "@/components/Content/Content";
|
||||
import { getCommuneFromCodePostal } from "@/lib/communes";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Annuaire",
|
||||
description: "L'annuaire collaboratif des professionnels de la santé",
|
||||
};
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return regions.map((region) => ({
|
||||
region: getSlugFromRegion(region),
|
||||
}));
|
||||
}
|
||||
|
||||
export default function RegionPage({
|
||||
params,
|
||||
}: {
|
||||
params: { regionSlug: string };
|
||||
}) {
|
||||
const region = getRegionFromSlug(params.regionSlug);
|
||||
if (!region) return notFound();
|
||||
// const nbHabitants = getCommuesFromRegion()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header searchField={true} />
|
||||
<Content>
|
||||
<ContentHeader>
|
||||
<div className="prose w-96 text-center">
|
||||
<h1 className="pb-0 mb-0">{region.nom}</h1>
|
||||
<p className="mt-0 pt-0" title="Chef Lieu">
|
||||
{getCommuneFromCodePostal(region.chefLieu).nom}
|
||||
</p>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Nombre de départements</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nombre d'habitants</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nombre de SISA</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nombre de CPTS</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nombre de SISA</th>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ContentHeader>
|
||||
<ContentBody>
|
||||
<ContentFull>yep</ContentFull>
|
||||
</ContentBody>
|
||||
</Content>
|
||||
<RegionList selected={region} />
|
||||
<hr></hr>
|
||||
<DepartementsList region={region} />
|
||||
<hr></hr>
|
||||
</>
|
||||
);
|
||||
}
|
19
app/annuaire/page.tsx
Normal file
19
app/annuaire/page.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { Metadata } from "next";
|
||||
|
||||
import { Header, Hero, RegionList } from "@/components/index";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Annuaire",
|
||||
description: "L'annuaire collaboratif des professionnels de la santé",
|
||||
};
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Hero />
|
||||
<RegionList />
|
||||
<hr></hr>
|
||||
</>
|
||||
);
|
||||
}
|
123
app/blog/[slug]/page.tsx
Normal file
123
app/blog/[slug]/page.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { CustomMDX } from '@/components/Mdx'
|
||||
import { formatDate, getBlogPosts } from '@/app/blog/utils'
|
||||
import { baseUrl } from '@/app/sitemap'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
let posts = getBlogPosts()
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }) {
|
||||
let post = getBlogPosts().find((post) => post.slug === params.slug)
|
||||
if (!post) {
|
||||
return
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
publishedAt: publishedTime,
|
||||
summary: description,
|
||||
image,
|
||||
} = post.metadata
|
||||
let ogImage = image ? image : `${baseUrl}/og?title=${encodeURIComponent(title)}`
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: 'article',
|
||||
publishedTime,
|
||||
url: `${baseUrl}/blog/${post.slug}`,
|
||||
images: [
|
||||
{
|
||||
url: ogImage,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title,
|
||||
description,
|
||||
images: [ogImage],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default function Blog({ params }) {
|
||||
let post = getBlogPosts().find((post) => post.slug === params.slug)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-10 lg:py-20">
|
||||
<script
|
||||
type="application/ld+json"
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.metadata.title,
|
||||
datePublished: post.metadata.publishedAt,
|
||||
dateModified: post.metadata.publishedAt,
|
||||
description: post.metadata.summary,
|
||||
image: post.metadata.image
|
||||
? `${baseUrl}${post.metadata.image}`
|
||||
: `/og?title=${encodeURIComponent(post.metadata.title)}`,
|
||||
url: `${baseUrl}/blog/${post.slug}`,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: 'My Portfolio',
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-2xl mx-auto mb-12 text-center">
|
||||
{/* <a className="uppercase text-base lg:text-xl text-green-600 hover:text-green-700 hover:underline" href="#">Travel</a> */}
|
||||
<span className="text-base lg:text-xl text-gray-400">{formatDate(post.metadata.publishedAt)}</span>
|
||||
<div className="mt-2">
|
||||
<h1 className="mb-6 text-4xl lg:text-5xl font-bold font-heading">{post.metadata.title}</h1>
|
||||
{/* <div className="flex justify-center">
|
||||
<div className="mr-4">
|
||||
<img className="w-12 h-12 object-cover object-top rounded-full" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80" alt="" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<a href="#">
|
||||
<h3 className="text-gray-500 hover:text-gray-600 hover:underline font-bold">Alice Bradley</h3>
|
||||
</a>
|
||||
<a href="#">
|
||||
<span className="text-xs text-green-600 font-bold">Author</span>
|
||||
</a>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<CustomMDX source={post.content} />
|
||||
<p className="mb-6 leading-loose text-gray-500">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo est eget consequat imperdiet. Suspendisse laoreet scelerisque lobortis. Mauris facilisis hendrerit nulla at vehicula. Suspendisse potenti. Ut in nulla a purus bibendum convallis. Suspendisse id nunc maximus, suscipit ante ac, vulputate massa. Sed ut nunc suscipit, bibendum arcu a, interdum elit. Nullam laoreet mollis dictum. Ut suscipit, magna at elementum iaculis, erat erat fermentum justo, sit amet ultrices enim leo sit amet purus. Nulla sed erat molestie, auctor mauris lobortis, iaculis justo.</p>
|
||||
<p className="leading-loose text-gray-500">Duis hendrerit dui in dui ornare luctus. Nullam gravida tincidunt lorem cursus suscipit. Integer scelerisque sem et sem porta, eu volutpat mi tempor. Duis interdum sodales lacus non tempor. Nam mattis, sapien a commodo ultrices, nunc orci tincidunt ante, tempus tempus turpis metus laoreet lacus. Praesent condimentum, arcu ut fringilla tincidunt, augue diam pretium augue, sit amet vestibulum nunc felis vel metus. Duis dolor nulla, pellentesque non ultrices ut, convallis eu felis. Duis luctus tempor arcu, vitae elementum massa porta non. Morbi aliquet, neque ut volutpat sodales, dui enim facilisis enim, ut dictum lacus neque in urna. Nam metus elit, ullamcorper pretium nisi at, aliquet gravida lectus. Nullam id lectus pellentesque, suscipit dolor eget, consequat velit. Pellentesque finibus commodo nisl, id interdum leo. Maecenas aliquam felis justo, ut sagittis nunc maximus ut.</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* <h1 className="title font-semibold text-2xl tracking-tighter">
|
||||
{post.metadata.title}
|
||||
</h1>
|
||||
<div className="flex justify-between items-center mt-2 mb-8 text-sm">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{formatDate(post.metadata.publishedAt)}
|
||||
</p>
|
||||
</div>
|
||||
<article className="prose">
|
||||
<CustomMDX source={post.content} />
|
||||
</article> */}
|
||||
</section>
|
||||
)
|
||||
}
|
12
app/blog/page.tsx
Normal file
12
app/blog/page.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { BlogPosts } from '@/components/Posts'
|
||||
|
||||
export const metadata = {
|
||||
title: 'P4Pillon - Les actualités de P4Pillon',
|
||||
description: 'Lisez les dernières actualités de P4Pillon',
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<BlogPosts />
|
||||
)
|
||||
}
|
32
app/blog/posts/2024-05-03-nouveau-site-web.mdx
Normal file
32
app/blog/posts/2024-05-03-nouveau-site-web.mdx
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: 'Lancement du nouveau site web P4Pillon'
|
||||
description: 'Le premier article du blog pour vous présenter notre nouveau site web.'
|
||||
publishedAt: '2024-05-03'
|
||||
summary: 'Explore the enduring debate between using spaces and tabs for code indentation, and why this choice matters more than you might think.'
|
||||
---
|
||||
|
||||
The debate between using spaces and tabs for indentation in coding may seem trivial to the uninitiated, but it is a topic that continues to inspire passionate discussions among developers. This seemingly minor choice can affect code readability, maintenance, and even team dynamics.
|
||||
|
||||
Let's delve into the arguments for both sides and consider why this debate remains relevant in the software development world.
|
||||
|
||||
## The Case for Spaces
|
||||
|
||||
Advocates for using spaces argue that it ensures consistent code appearance across different editors, tools, and platforms. Because a space is a universally recognized character with a consistent width, code indented with spaces will look the same no matter where it's viewed. This consistency is crucial for maintaining readability and avoiding formatting issues when code is shared between team members or published online.
|
||||
|
||||
Additionally, some programming languages and style guides explicitly recommend spaces for indentation, suggesting a certain number of spaces (often two or four) per indentation level. Adhering to these recommendations can be essential for projects that aim for best practices in code quality and readability.
|
||||
|
||||
## The Case for Tabs
|
||||
|
||||
On the other side of the debate, proponents of tabs highlight the flexibility that tabs offer. Because the width of a tab can be adjusted in most text editors, individual developers can choose how much indentation they prefer to see, making the code more accessible and comfortable to read on a personal level. This adaptability can be particularly beneficial in teams with diverse preferences regarding code layout.
|
||||
|
||||
Tabs also have the advantage of semantic meaning. A tab is explicitly meant to represent indentation, whereas a space is used for many purposes within code. This distinction can make automated parsing and manipulation of code simpler, as tools can more easily recognize and adjust indentation levels without confusing them with spaces used for alignment.
|
||||
|
||||
## Hybrid Approaches and Team Dynamics
|
||||
|
||||
The debate often extends into discussions about hybrid approaches, where teams might use tabs for indentation and spaces for alignment within lines, attempting to combine the best of both worlds. However, such strategies require clear team agreements and disciplined adherence to coding standards to prevent formatting chaos.
|
||||
|
||||
Ultimately, the choice between spaces and tabs often comes down to team consensus and project guidelines. In environments where collaboration and code sharing are common, agreeing on a standard that everyone follows is more important than the individual preferences of spaces versus tabs. Modern development tools and linters can help enforce these standards, making the choice less about technical limitations and more about team dynamics and coding philosophy.
|
||||
|
||||
## Conclusion
|
||||
|
||||
While the spaces vs. tabs debate might not have a one-size-fits-all answer, it underscores the importance of consistency, readability, and team collaboration in software development. Whether a team chooses spaces, tabs, or a hybrid approach, the key is to make a conscious choice that serves the project's needs and to adhere to it throughout the codebase. As with many aspects of coding, communication and agreement among team members are paramount to navigating this classic programming debate.
|
52
app/blog/posts/static-typing.mdx
Normal file
52
app/blog/posts/static-typing.mdx
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: 'The Power of Static Typing in Programming'
|
||||
publishedAt: '2024-04-07'
|
||||
summary: 'In the ever-evolving landscape of software development, the debate between dynamic and static typing continues to be a hot topic.'
|
||||
---
|
||||
|
||||
In the ever-evolving landscape of software development, the debate between dynamic and static typing continues to be a hot topic. While dynamic typing offers flexibility and rapid development, static typing brings its own set of powerful advantages that can significantly improve the quality and maintainability of code. In this post, we'll explore why static typing is crucial for developers, accompanied by practical examples through markdown code snippets.
|
||||
|
||||
## Improved Code Quality and Safety
|
||||
|
||||
One of the most compelling reasons to use static typing is the improvement it brings to code quality and safety. By enforcing type checks at compile time, static typing catches errors early in the development process, reducing the chances of runtime errors.
|
||||
|
||||
```ts
|
||||
function greet(name: string): string {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
|
||||
// This will throw an error at compile time, preventing potential runtime issues.
|
||||
let message: string = greet(123)
|
||||
```
|
||||
|
||||
## Enhanced Readability and Maintainability
|
||||
|
||||
Static typing makes code more readable and maintainable. By explicitly declaring types, developers provide a clear contract of what the code does, making it easier for others (or themselves in the future) to understand and modify the codebase.
|
||||
|
||||
## Facilitates Tooling and Refactoring
|
||||
|
||||
Modern IDEs leverage static typing to offer advanced features like code completion, refactoring, and static analysis. These tools can automatically detect issues, suggest fixes, and safely refactor code, enhancing developer productivity and reducing the likelihood of introducing bugs during refactoring.
|
||||
|
||||
```csharp
|
||||
// Refactoring example: Renaming a method in C#
|
||||
public class Calculator {
|
||||
public int Add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
// After refactoring `Add` to `Sum`, all references are automatically updated.
|
||||
public class Calculator {
|
||||
public int Sum(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
Static typing can lead to better performance. Since types are known at compile time, compilers can optimize the generated code more effectively. This can result in faster execution times and lower resource consumption.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Static typing offers numerous benefits that contribute to the development of robust, efficient, and maintainable software. By catching errors early, enhancing readability, facilitating tooling, and enabling optimizations, static typing is an invaluable asset for developers. As the software industry continues to mature, the importance of static typing in ensuring code quality and performance cannot be overstated. Whether you're working on a large-scale enterprise application or a small project, embracing static typing can lead to better software development outcomes.
|
39
app/blog/posts/vim.mdx
Normal file
39
app/blog/posts/vim.mdx
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 'Embracing Vim: The Unsung Hero of Code Editors'
|
||||
publishedAt: '2024-04-09'
|
||||
summary: 'Discover why Vim, with its steep learning curve, remains a beloved tool among developers for editing code efficiently and effectively.'
|
||||
---
|
||||
|
||||
In the world of software development, where the latest and greatest tools frequently capture the spotlight, Vim stands out as a timeless classic. Despite its age and initial complexity, Vim has managed to retain a devoted following of developers who swear by its efficiency, versatility, and power.
|
||||
|
||||
This article delves into the reasons behind Vim's enduring appeal and why it continues to be a great tool for coding in the modern era.
|
||||
|
||||
## Efficiency and Speed
|
||||
|
||||
At the heart of Vim's philosophy is the idea of minimizing keystrokes to achieve maximum efficiency.
|
||||
|
||||
Unlike other text editors where the mouse is often relied upon for navigation and text manipulation, Vim's keyboard-centric design allows developers to perform virtually all coding tasks without leaving the home row. This not only speeds up coding but also reduces the risk of repetitive strain injuries.
|
||||
|
||||
## Highly Customizable
|
||||
|
||||
Vim can be extensively customized to suit any developer's preferences and workflow. With a vibrant ecosystem of plugins and a robust scripting language, users can tailor the editor to their specific needs, whether it's programming in Python, writing in Markdown, or managing projects.
|
||||
|
||||
This level of customization ensures that Vim remains relevant and highly functional for a wide range of programming tasks and languages.
|
||||
|
||||
## Ubiquity and Portability
|
||||
|
||||
Vim is virtually everywhere. It's available on all major platforms, and because it's lightweight and terminal-based, it can be used on remote servers through SSH, making it an indispensable tool for sysadmins and developers working in a cloud-based environment.
|
||||
|
||||
The ability to use the same editor across different systems without a graphical interface is a significant advantage for those who need to maintain a consistent workflow across multiple environments.
|
||||
|
||||
## Vibrant Community
|
||||
|
||||
Despite—or perhaps because of—its learning curve, Vim has cultivated a passionate and active community. Online forums, dedicated websites, and plugins abound, offering support, advice, and improvements.
|
||||
|
||||
This community not only helps newcomers climb the steep learning curve but also continually contributes to Vim's evolution, ensuring it remains adaptable and up-to-date with the latest programming trends and technologies.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Vim is not just a text editor; it's a way of approaching coding with efficiency and thoughtfulness. Its steep learning curve is a small price to pay for the speed, flexibility, and control it offers.
|
||||
|
||||
For those willing to invest the time to master its commands, Vim proves to be an invaluable tool that enhances productivity and enjoyment in coding. In an age of ever-changing development tools, the continued popularity of Vim is a testament to its enduring value and utility.
|
91
app/blog/utils.ts
Normal file
91
app/blog/utils.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
type Metadata = {
|
||||
title: string
|
||||
description: string
|
||||
publishedAt: string
|
||||
summary: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
function parseFrontmatter(fileContent: string) {
|
||||
let frontmatterRegex = /---\s*([\s\S]*?)\s*---/
|
||||
let match = frontmatterRegex.exec(fileContent)
|
||||
let frontMatterBlock = match![1]
|
||||
let content = fileContent.replace(frontmatterRegex, '').trim()
|
||||
let frontMatterLines = frontMatterBlock.trim().split('\n')
|
||||
let metadata: Partial<Metadata> = {}
|
||||
|
||||
frontMatterLines.forEach((line) => {
|
||||
let [key, ...valueArr] = line.split(': ')
|
||||
let value = valueArr.join(': ').trim()
|
||||
value = value.replace(/^['"](.*)['"]$/, '$1') // Remove quotes
|
||||
metadata[key.trim() as keyof Metadata] = value
|
||||
})
|
||||
|
||||
return { metadata: metadata as Metadata, content }
|
||||
}
|
||||
|
||||
function getMDXFiles(dir) {
|
||||
return fs.readdirSync(dir).filter((file) => path.extname(file) === '.mdx')
|
||||
}
|
||||
|
||||
function readMDXFile(filePath) {
|
||||
let rawContent = fs.readFileSync(filePath, 'utf-8')
|
||||
return parseFrontmatter(rawContent)
|
||||
}
|
||||
|
||||
function getMDXData(dir) {
|
||||
let mdxFiles = getMDXFiles(dir)
|
||||
return mdxFiles.map((file) => {
|
||||
let { metadata, content } = readMDXFile(path.join(dir, file))
|
||||
let slug = path.basename(file, path.extname(file))
|
||||
|
||||
return {
|
||||
metadata,
|
||||
slug,
|
||||
content,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getBlogPosts() {
|
||||
return getMDXData(path.join(process.cwd(), 'app', 'blog', 'posts'))
|
||||
}
|
||||
|
||||
export function formatDate(date: string, includeRelative = false) {
|
||||
let currentDate = new Date()
|
||||
if (!date.includes('T')) {
|
||||
date = `${date}T00:00:00`
|
||||
}
|
||||
let targetDate = new Date(date)
|
||||
|
||||
let yearsAgo = currentDate.getFullYear() - targetDate.getFullYear()
|
||||
let monthsAgo = currentDate.getMonth() - targetDate.getMonth()
|
||||
let daysAgo = currentDate.getDate() - targetDate.getDate()
|
||||
|
||||
let formattedDate = ''
|
||||
|
||||
if (yearsAgo > 0) {
|
||||
formattedDate = `${yearsAgo}y ago`
|
||||
} else if (monthsAgo > 0) {
|
||||
formattedDate = `${monthsAgo}mo ago`
|
||||
} else if (daysAgo > 0) {
|
||||
formattedDate = `${daysAgo}d ago`
|
||||
} else {
|
||||
formattedDate = 'Today'
|
||||
}
|
||||
|
||||
let fullDate = targetDate.toLocaleString('fr-fr', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})
|
||||
|
||||
if (!includeRelative) {
|
||||
return fullDate
|
||||
}
|
||||
|
||||
return `${fullDate} (${formattedDate})`
|
||||
}
|
@ -1,33 +1,50 @@
|
||||
@tailwind base;
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
.skew {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
.skewed-top-right .skew-top.mr-for-radius {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
.skewed-top-left .skew-top.ml-for-radius {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
.skewed-bottom-right .skew-bottom.mr-for-radius {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.skewed-bottom-left .skew-bottom.ml-for-radius {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.skewed-top-right .radius-for-skewed {
|
||||
border-top-right-radius: 6rem;
|
||||
}
|
||||
|
||||
.skewed-top-left .radius-for-skewed {
|
||||
border-top-left-radius: 6rem;
|
||||
}
|
||||
|
||||
.skewed-bottom-right .radius-for-skewed {
|
||||
border-bottom-right-radius: 6rem;
|
||||
}
|
||||
|
||||
.skewed-bottom-left .radius-for-skewed {
|
||||
border-bottom-left-radius: 6rem;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Protest';
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
src: url("/ProtestRevolution-Regular.woff2") format('woff2');
|
||||
}
|
||||
|
||||
h1 { font-family: 'Protest'; }
|
@ -2,6 +2,9 @@ import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
import Nav from "../components/Nav";
|
||||
import Footer from "../components/Footer";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@ -11,12 +14,16 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<html lang="fr">
|
||||
<body>
|
||||
<Nav />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
7
app/mdx-components.tsx
Normal file
7
app/mdx-components.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import type { MDXComponents } from 'mdx/types'
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...components,
|
||||
}
|
||||
}
|
44
app/mentions-legales/index.md
Normal file
44
app/mentions-legales/index.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Mentions légales"
|
||||
description: ""
|
||||
summary: ""
|
||||
date: 2023-09-07T17:19:07+02:00
|
||||
lastmod: 2023-09-07T17:19:07+02:00
|
||||
draft: false
|
||||
type: "legal"
|
||||
seo:
|
||||
title: "" # custom title (optional)
|
||||
description: "" # custom description (recommended)
|
||||
canonical: "" # custom canonical URL (optional)
|
||||
noindex: false # false (default) or true
|
||||
---
|
||||
|
||||
## Éditeur
|
||||
|
||||
Le site web [https://apps.p4pillon.org](https://apps.p4pillon.org) est édité par l'association [RésiLien](https://resilien.fr).
|
||||
|
||||
> SIRET : XXXXXXXXXXXXXX
|
||||
> Adresse : 315 impasse de la Meliora 42260 Crémeaux
|
||||
|
||||
## Directeur de publication
|
||||
|
||||
Simon Constans
|
||||
|
||||
## Hébergement
|
||||
|
||||
Le site web [https://apps.p4pillon.org](https://apps.p4pillon.org) est hébergé avec sobriété par l'association [RésiLien](https://resilien.fr).
|
||||
|
||||
> SIRET: XXXXXXXXXXXXXX
|
||||
> Adresse : 315 impasse de la Meliora 42260 Crémeaux
|
||||
|
||||
## Développement et Maintenance
|
||||
|
||||
Le site web [https://apps.p4pillon.org](https://apps.p4pillon.org) a été réalisé de façon écoresponsable par [RésiLien](https://resilien.fr).
|
||||
|
||||
## Liens hypertextes
|
||||
|
||||
Les liens hypertextes mis en œuvre en direction d’autres sites WEB ne sauraient engager la responsabilité de Jarnat, car nous n’exerçons aucun contrôle sur le contenu des sites auxquels ces liens renvoient.
|
||||
|
||||
## Cookie
|
||||
|
||||
Ce site n’installe aucun cookie, c’est pourquoi il n’y a aucun bandeau spécifique. Nous respectons la vie privée de nos visiteurs.
|
37
app/mentions-legales/page.tsx
Normal file
37
app/mentions-legales/page.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import Markdown from './index.md'
|
||||
|
||||
export default function MentionsLegales() {
|
||||
return (
|
||||
<>
|
||||
<section className="py-10 lg:py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-2xl mx-auto mb-12 text-center">
|
||||
{/* <a className="uppercase text-base lg:text-xl text-green-600 hover:text-green-700 hover:underline" href="#">Travel</a> */}
|
||||
<span className="text-base lg:text-xl text-gray-400">Dernière mise à jour le 3 mai 2024</span>
|
||||
<div className="mt-2">
|
||||
<h2 className="mb-6 text-4xl lg:text-5xl font-bold font-heading">Mentions légales</h2>
|
||||
{/* <div className="flex justify-center">
|
||||
<div className="mr-4">
|
||||
<img className="w-12 h-12 object-cover object-top rounded-full" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80" alt="" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<a href="#">
|
||||
<h3 className="text-gray-500 hover:text-gray-600 hover:underline font-bold">Alice Bradley</h3>
|
||||
</a>
|
||||
<a href="#">
|
||||
<span className="text-xs text-green-600 font-bold">Author</span>
|
||||
</a>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<p className="mb-6 leading-loose text-gray-500">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo est eget consequat imperdiet. Suspendisse laoreet scelerisque lobortis. Mauris facilisis hendrerit nulla at vehicula. Suspendisse potenti. Ut in nulla a purus bibendum convallis. Suspendisse id nunc maximus, suscipit ante ac, vulputate massa. Sed ut nunc suscipit, bibendum arcu a, interdum elit. Nullam laoreet mollis dictum. Ut suscipit, magna at elementum iaculis, erat erat fermentum justo, sit amet ultrices enim leo sit amet purus. Nulla sed erat molestie, auctor mauris lobortis, iaculis justo.</p>
|
||||
<p className="leading-loose text-gray-500">Duis hendrerit dui in dui ornare luctus. Nullam gravida tincidunt lorem cursus suscipit. Integer scelerisque sem et sem porta, eu volutpat mi tempor. Duis interdum sodales lacus non tempor. Nam mattis, sapien a commodo ultrices, nunc orci tincidunt ante, tempus tempus turpis metus laoreet lacus. Praesent condimentum, arcu ut fringilla tincidunt, augue diam pretium augue, sit amet vestibulum nunc felis vel metus. Duis dolor nulla, pellentesque non ultrices ut, convallis eu felis. Duis luctus tempor arcu, vitae elementum massa porta non. Morbi aliquet, neque ut volutpat sodales, dui enim facilisis enim, ut dictum lacus neque in urna. Nam metus elit, ullamcorper pretium nisi at, aliquet gravida lectus. Nullam id lectus pellentesque, suscipit dolor eget, consequat velit. Pellentesque finibus commodo nisl, id interdum leo. Maecenas aliquam felis justo, ut sagittis nunc maximus ut.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Markdown />
|
||||
</>
|
||||
);
|
||||
}
|
127
app/page.tsx
127
app/page.tsx
@ -1,113 +1,24 @@
|
||||
import Image from "next/image";
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
import Hero from "../components/Hero";
|
||||
import Newsletter from "../components/Newsletter";
|
||||
import Roadmap from "../components/Roadmap";
|
||||
import Concepts from "../components/Concepts";
|
||||
import Projects from "../components/Projects";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'P4Pillon - Composer une santé en commun',
|
||||
description: 'P4Pillon est une initiative de recherche et développement en soins de premier recours ayant pour objectif de changer le paradigme du système de santé.',
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">app/page.tsx</code>
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className="dark:invert"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
<Image
|
||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Docs{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Find in-depth information about Next.js features and API.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Learn{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with quizzes!
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Templates{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Explore starter templates for Next.js.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Deploy{" "}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<>
|
||||
<Hero />
|
||||
<Concepts />
|
||||
<Projects />
|
||||
<Roadmap />
|
||||
<Newsletter />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
17
app/sitemap.ts
Normal file
17
app/sitemap.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { getBlogPosts } from '@/app/blog/utils'
|
||||
|
||||
export const baseUrl = 'https://portfolio-blog-starter.vercel.app'
|
||||
|
||||
export default async function sitemap() {
|
||||
let blogs = getBlogPosts().map((post) => ({
|
||||
url: `${baseUrl}/blog/${post.slug}`,
|
||||
lastModified: post.metadata.publishedAt,
|
||||
}))
|
||||
|
||||
let routes = ['', '/blog'].map((route) => ({
|
||||
url: `${baseUrl}${route}`,
|
||||
lastModified: new Date().toISOString().split('T')[0],
|
||||
}))
|
||||
|
||||
return [...routes, ...blogs]
|
||||
}
|
Reference in New Issue
Block a user