first
This commit is contained in:
commit
8538ab969a
434 changed files with 44051 additions and 0 deletions
17
apps/web/modules/marketing/blog/components/PostContent.tsx
Normal file
17
apps/web/modules/marketing/blog/components/PostContent.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import { MDXContent } from "@content-collections/mdx/react";
|
||||
import { mdxComponents } from "../utils/mdx-components";
|
||||
|
||||
export function PostContent({ content }: { content: string }) {
|
||||
return (
|
||||
<div className="prose dark:prose-invert mx-auto mt-6 max-w-2xl">
|
||||
<MDXContent
|
||||
code={content}
|
||||
components={{
|
||||
a: mdxComponents.a,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
apps/web/modules/marketing/blog/components/PostListItem.tsx
Normal file
80
apps/web/modules/marketing/blog/components/PostListItem.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
"use client";
|
||||
|
||||
import { LocaleLink } from "@i18n/routing";
|
||||
import type { Post } from "@marketing/blog/types";
|
||||
import Image from "next/image";
|
||||
|
||||
export function PostListItem({ post }: { post: Post }) {
|
||||
const { title, excerpt, authorName, image, date, path, authorImage, tags } =
|
||||
post;
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border bg-card/50 p-6">
|
||||
{image && (
|
||||
<div className="-mx-4 -mt-4 relative mb-4 aspect-16/9 overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover object-center"
|
||||
/>
|
||||
<LocaleLink
|
||||
href={`/blog/${path}`}
|
||||
className="absolute inset-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tags && (
|
||||
<div className="mb-2 flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="font-semibold text-primary text-xs uppercase tracking-wider"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LocaleLink
|
||||
href={`/blog/${path}`}
|
||||
className="font-semibold text-xl"
|
||||
>
|
||||
{title}
|
||||
</LocaleLink>
|
||||
{excerpt && <p className="opacity-50">{excerpt}</p>}
|
||||
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
{authorName && (
|
||||
<div className="flex items-center">
|
||||
{authorImage && (
|
||||
<div className="relative mr-2 size-8 overflow-hidden rounded-full">
|
||||
<Image
|
||||
src={authorImage}
|
||||
alt={authorName}
|
||||
fill
|
||||
sizes="96px"
|
||||
className="object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="font-semibold text-sm opacity-50">
|
||||
{authorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mr-0 ml-auto">
|
||||
<p className="text-sm opacity-30">
|
||||
{Intl.DateTimeFormat("en-US").format(new Date(date))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
apps/web/modules/marketing/blog/types.ts
Normal file
3
apps/web/modules/marketing/blog/types.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import type { Post as ContentCollectionsPost } from "content-collections";
|
||||
|
||||
export type Post = Omit<ContentCollectionsPost, "_meta">;
|
||||
25
apps/web/modules/marketing/blog/utils/lib/posts.ts
Normal file
25
apps/web/modules/marketing/blog/utils/lib/posts.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { Post } from "@marketing/blog/types";
|
||||
import { allPosts } from "content-collections";
|
||||
|
||||
export async function getAllPosts(): Promise<Post[]> {
|
||||
// ... add a custom loader here for your posts and map it to the post schema
|
||||
|
||||
return Promise.resolve(allPosts);
|
||||
}
|
||||
|
||||
export async function getPostBySlug(
|
||||
slug: string,
|
||||
options?: {
|
||||
locale?: string;
|
||||
},
|
||||
): Promise<Post | null> {
|
||||
// ... add a custom loader here for your posts and map it to the post schema
|
||||
|
||||
return Promise.resolve(
|
||||
allPosts.find(
|
||||
(post) =>
|
||||
post.path === slug &&
|
||||
(!options?.locale || post.locale === options.locale),
|
||||
) ?? null,
|
||||
);
|
||||
}
|
||||
103
apps/web/modules/marketing/blog/utils/mdx-components.tsx
Normal file
103
apps/web/modules/marketing/blog/utils/mdx-components.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { LocaleLink } from "@i18n/routing";
|
||||
import { slugifyHeadline } from "@shared/lib/content";
|
||||
import type { MDXComponents } from "mdx/types";
|
||||
import type { ImageProps } from "next/image";
|
||||
import Image from "next/image";
|
||||
|
||||
export const mdxComponents = {
|
||||
a: (props) => {
|
||||
const { href, children, ...rest } = props;
|
||||
const isInternalLink =
|
||||
href && (href.startsWith("/") || href.startsWith("#"));
|
||||
|
||||
return isInternalLink ? (
|
||||
<LocaleLink href={href} {...rest}>
|
||||
{children}
|
||||
</LocaleLink>
|
||||
) : (
|
||||
<a target="_blank" rel="noopener noreferrer" href={href} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
img: (props) =>
|
||||
props.src ? (
|
||||
<Image
|
||||
{...(props as ImageProps)}
|
||||
sizes="100vw"
|
||||
style={{ width: "100%", height: "auto" }}
|
||||
className="rounded-lg shadow"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : null,
|
||||
h1: ({ children, ...rest }) => (
|
||||
<h1
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-6 font-bold text-4xl"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children, ...rest }) => (
|
||||
<h2
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-4 font-bold text-2xl"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children, ...rest }) => (
|
||||
<h3
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-4 font-bold text-xl"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children, ...rest }) => (
|
||||
<h4
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-4 font-bold text-lg"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
h5: ({ children, ...rest }) => (
|
||||
<h5
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-4 font-bold text-base"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h5>
|
||||
),
|
||||
h6: ({ children, ...rest }) => (
|
||||
<h6
|
||||
id={slugifyHeadline(children as string)}
|
||||
className="mb-4 font-bold text-sm"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</h6>
|
||||
),
|
||||
p: ({ children, ...rest }) => (
|
||||
<p className="mb-6 text-foreground/60 leading-relaxed" {...rest}>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children, ...rest }) => (
|
||||
<ul className="mb-6 list-inside list-disc space-y-2 pl-4" {...rest}>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children, ...rest }) => (
|
||||
<ol className="mb-6 list-inside list-decimal space-y-2 pl-4" {...rest}>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children, ...rest }) => <li {...rest}>{children}</li>,
|
||||
} satisfies MDXComponents;
|
||||
Loading…
Add table
Add a link
Reference in a new issue