Implement internationalization in pages and update CMS clients
The commit mainly revamps the code to support internationalization in various pages like pricing, docs, blog, etc. It modifies the code to generate metadata asynchronously, accommodating internationalized page titles and subtitles. Also, the commit restructures CMS Client scripts, particularly for ContentLayer and Wordpress. For Wordpress, it updates API fetch routes and handles embedded children data. Furthermore, unnecessary logging statements are cleaned up, and minor updates are done for better UI and code efficiency.
This commit is contained in:
@@ -41,7 +41,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{`$${mrr[1]}`}</Figure>
|
<Figure>{`$${mrr[1]}`}</Figure>
|
||||||
<Trend trend={'up'}>20%</Trend>
|
<Trend trend={'up'}>20%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +56,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{`$${netRevenue[1]}`}</Figure>
|
<Figure>{`$${netRevenue[1]}`}</Figure>
|
||||||
<Trend trend={'up'}>12%</Trend>
|
<Trend trend={'up'}>12%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{`$${fees[1]}`}</Figure>
|
<Figure>{`$${fees[1]}`}</Figure>
|
||||||
<Trend trend={'up'}>9%</Trend>
|
<Trend trend={'up'}>9%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,7 +86,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{`${newCustomers[1]}`}</Figure>
|
<Figure>{`${newCustomers[1]}`}</Figure>
|
||||||
<Trend trend={'down'}>-25%</Trend>
|
<Trend trend={'down'}>-25%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +101,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{visitors[1]}</Figure>
|
<Figure>{visitors[1]}</Figure>
|
||||||
<Trend trend={'down'}>-4.3%</Trend>
|
<Trend trend={'down'}>-4.3%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +116,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{returningVisitors[1]}</Figure>
|
<Figure>{returningVisitors[1]}</Figure>
|
||||||
<Trend trend={'stale'}>10%</Trend>
|
<Trend trend={'stale'}>10%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +131,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{churn[1]}%</Figure>
|
<Figure>{churn[1]}%</Figure>
|
||||||
<Trend trend={'up'}>-10%</Trend>
|
<Trend trend={'up'}>-10%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +146,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{tickets[1]}</Figure>
|
<Figure>{tickets[1]}</Figure>
|
||||||
<Trend trend={'up'}>-30%</Trend>
|
<Trend trend={'up'}>-30%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +163,7 @@ export default function DashboardDemo() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={'flex justify-between'}>
|
<div className={'flex items-center justify-between'}>
|
||||||
<Figure>{activeUsers[1]}</Figure>
|
<Figure>{activeUsers[1]}</Figure>
|
||||||
<Trend trend={'up'}>10%</Trend>
|
<Trend trend={'up'}>10%</Trend>
|
||||||
</div>
|
</div>
|
||||||
@@ -217,13 +217,23 @@ function Chart(
|
|||||||
return (
|
return (
|
||||||
<div className={'h-36'}>
|
<div className={'h-36'}>
|
||||||
<ResponsiveContainer width={'100%'} height={'100%'}>
|
<ResponsiveContainer width={'100%'} height={'100%'}>
|
||||||
<LineChart width={400} height={100} data={props.data}>
|
<LineChart
|
||||||
|
width={400}
|
||||||
|
height={100}
|
||||||
|
data={props.data}
|
||||||
|
margin={{
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
left: 10,
|
||||||
|
bottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Line
|
<Line
|
||||||
className={'text-primary'}
|
className={'text-primary'}
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={2.5}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -319,7 +329,7 @@ function BadgeWithTrend(props: React.PropsWithChildren<{ trend: string }>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Figure(props: React.PropsWithChildren) {
|
function Figure(props: React.PropsWithChildren) {
|
||||||
return <div className={'text-4xl font-bold'}>{props.children}</div>;
|
return <div className={'text-3xl font-extrabold'}>{props.children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Trend(
|
function Trend(
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
.MDX h1 {
|
.HTML h1 {
|
||||||
@apply mt-14 text-4xl font-bold;
|
@apply mt-14 text-4xl font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX h2 {
|
.HTML h2 {
|
||||||
@apply mb-4 mt-12 text-2xl font-semibold lg:text-3xl;
|
@apply mb-4 mt-12 text-2xl font-semibold lg:text-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX h3 {
|
.HTML h3 {
|
||||||
@apply mt-10 text-2xl font-bold;
|
@apply mt-10 text-2xl font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX h4 {
|
.HTML h4 {
|
||||||
@apply mt-8 text-xl font-bold;
|
@apply mt-8 text-xl font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX h5 {
|
.HTML h5 {
|
||||||
@apply mt-6 text-lg font-semibold;
|
@apply mt-6 text-lg font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX h6 {
|
.HTML h6 {
|
||||||
@apply mt-2 text-base font-medium;
|
@apply mt-2 text-base font-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,71 +27,71 @@ Tailwind "dark" variants do not work with CSS Modules
|
|||||||
We work it around using :global(.dark)
|
We work it around using :global(.dark)
|
||||||
For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-770215347
|
For more info: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-770215347
|
||||||
*/
|
*/
|
||||||
:global(.dark) .MDX h1,
|
:global(.dark) .HTML h1,
|
||||||
:global(.dark) .MDX h2,
|
:global(.dark) .HTML h2,
|
||||||
:global(.dark) .MDX h3,
|
:global(.dark) .HTML h3,
|
||||||
:global(.dark) .MDX h4,
|
:global(.dark) .HTML h4,
|
||||||
:global(.dark) .MDX h5,
|
:global(.dark) .HTML h5,
|
||||||
:global(.dark) .MDX h6 {
|
:global(.dark) .HTML h6 {
|
||||||
@apply text-white;
|
@apply text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX p {
|
.HTML p {
|
||||||
@apply mb-4 mt-2 text-base leading-7;
|
@apply mb-4 mt-2 text-base leading-7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX li {
|
.HTML li {
|
||||||
@apply relative my-1.5 text-base leading-7;
|
@apply relative my-1.5 text-base leading-7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX ul > li:before {
|
.HTML ul > li:before {
|
||||||
content: '-';
|
content: '-';
|
||||||
|
|
||||||
@apply mr-2;
|
@apply mr-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX ol > li:before {
|
.HTML ol > li:before {
|
||||||
@apply inline-flex font-medium;
|
@apply inline-flex font-medium;
|
||||||
|
|
||||||
content: counters(counts, '.') '. ';
|
content: counters(counts, '.') '. ';
|
||||||
font-feature-settings: 'tnum';
|
font-feature-settings: 'tnum';
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX b,
|
.HTML b,
|
||||||
.MDX strong {
|
.HTML strong {
|
||||||
@apply font-bold;
|
@apply font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .MDX b,
|
:global(.dark) .HTML b,
|
||||||
:global(.dark) .MDX strong {
|
:global(.dark) .HTML strong {
|
||||||
@apply text-white;
|
@apply text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX img,
|
.HTML img,
|
||||||
.MDX video {
|
.HTML video {
|
||||||
@apply rounded-md;
|
@apply rounded-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX ul,
|
.HTML ul,
|
||||||
.MDX ol {
|
.HTML ol {
|
||||||
@apply pl-1;
|
@apply pl-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX ol > li {
|
.HTML ol > li {
|
||||||
counter-increment: counts;
|
counter-increment: counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX ol > li:before {
|
.HTML ol > li:before {
|
||||||
@apply mr-2 inline-flex font-semibold;
|
@apply mr-2 inline-flex font-semibold;
|
||||||
|
|
||||||
content: counters(counts, '.') '. ';
|
content: counters(counts, '.') '. ';
|
||||||
font-feature-settings: 'tnum';
|
font-feature-settings: 'tnum';
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX blockquote {
|
.HTML blockquote {
|
||||||
@apply my-4 border-l-4 border-primary bg-muted px-6 py-4 text-lg font-medium text-gray-600;
|
@apply my-4 border-l-8 border border-primary px-6 py-4 text-lg font-medium text-muted-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
.MDX pre {
|
.HTML pre {
|
||||||
@apply my-6 text-sm text-current;
|
@apply my-6 text-sm text-current;
|
||||||
}
|
}
|
||||||
@@ -10,35 +10,32 @@ export const PostHeader: React.FC<{
|
|||||||
}> = ({ post }) => {
|
}> = ({ post }) => {
|
||||||
const { title, publishedAt, description, image } = post;
|
const { title, publishedAt, description, image } = post;
|
||||||
|
|
||||||
// NB: change this to display the post's image
|
console.log(post);
|
||||||
const displayImage = true;
|
|
||||||
const preloadImage = true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex flex-col space-y-4'}>
|
<div className={'flex flex-col space-y-8'}>
|
||||||
<div className={'flex flex-col space-y-4'}>
|
<div className={'flex flex-col space-y-2'}>
|
||||||
<Heading level={1}>{title}</Heading>
|
<Heading level={1}>{title}</Heading>
|
||||||
|
|
||||||
<Heading level={3}>
|
<Heading level={3}>
|
||||||
<span className={'font-normal text-muted-foreground'}>
|
<p
|
||||||
{description}
|
className={'font-normal text-muted-foreground'}
|
||||||
</span>
|
dangerouslySetInnerHTML={{ __html: description ?? '' }}
|
||||||
|
/>
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex flex-row items-center space-x-2 text-muted-foreground">
|
||||||
<div className="flex flex-row items-center space-x-2 text-sm text-gray-600 dark:text-gray-400">
|
<div className={'text-sm'}>
|
||||||
<div>
|
|
||||||
<DateFormatter dateString={publishedAt.toISOString()} />
|
<DateFormatter dateString={publishedAt.toISOString()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<If condition={displayImage && image}>
|
<If condition={image}>
|
||||||
{(imageUrl) => (
|
{(imageUrl) => (
|
||||||
<div className="relative mx-auto h-[378px] w-full justify-center">
|
<div className="relative mx-auto h-[378px] w-full justify-center">
|
||||||
<CoverImage
|
<CoverImage
|
||||||
preloadImage={preloadImage}
|
preloadImage
|
||||||
className="rounded-md"
|
className="rounded-md"
|
||||||
title={title}
|
title={title}
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ export function PostPreview({
|
|||||||
}: React.PropsWithChildren<Props>) {
|
}: React.PropsWithChildren<Props>) {
|
||||||
const { title, image, publishedAt, description } = post;
|
const { title, image, publishedAt, description } = post;
|
||||||
const height = imageHeight ?? DEFAULT_IMAGE_HEIGHT;
|
const height = imageHeight ?? DEFAULT_IMAGE_HEIGHT;
|
||||||
const url = post.url;
|
const slug = `/blog/${post.slug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl transition-shadow duration-500">
|
<div className="rounded-xl transition-shadow duration-500">
|
||||||
<If condition={image}>
|
<If condition={image}>
|
||||||
{(imageUrl) => (
|
{(imageUrl) => (
|
||||||
<div className="relative mb-2 w-full" style={{ height }}>
|
<div className="relative mb-2 w-full" style={{ height }}>
|
||||||
<Link href={url}>
|
<Link href={slug}>
|
||||||
<CoverImage
|
<CoverImage
|
||||||
preloadImage={preloadImage}
|
preloadImage={preloadImage}
|
||||||
title={title}
|
title={title}
|
||||||
@@ -42,7 +42,7 @@ export function PostPreview({
|
|||||||
<div className={'px-1'}>
|
<div className={'px-1'}>
|
||||||
<div className="flex flex-col space-y-1 py-2">
|
<div className="flex flex-col space-y-1 py-2">
|
||||||
<h3 className="text-2xl font-bold leading-snug dark:text-white">
|
<h3 className="text-2xl font-bold leading-snug dark:text-white">
|
||||||
<Link href={url} className="hover:underline">
|
<Link href={slug} className="hover:underline">
|
||||||
{title}
|
{title}
|
||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -54,9 +54,10 @@ export function PostPreview({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mb-4 text-sm leading-relaxed text-muted-foreground">
|
<p
|
||||||
{description}
|
className="mb-4 text-sm leading-relaxed text-muted-foreground"
|
||||||
</p>
|
dangerouslySetInnerHTML={{ __html: description ?? '' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Cms } from '@kit/cms';
|
import type { Cms } from '@kit/cms';
|
||||||
import { ContentRenderer } from '@kit/cms';
|
import { ContentRenderer } from '@kit/cms';
|
||||||
|
|
||||||
|
import styles from './html-renderer.module.css';
|
||||||
import { PostHeader } from './post-header';
|
import { PostHeader } from './post-header';
|
||||||
|
|
||||||
export const Post: React.FC<{
|
export const Post: React.FC<{
|
||||||
@@ -8,10 +9,10 @@ export const Post: React.FC<{
|
|||||||
content: string;
|
content: string;
|
||||||
}> = ({ post, content }) => {
|
}> = ({ post, content }) => {
|
||||||
return (
|
return (
|
||||||
<div className={'mx-auto my-8 max-w-2xl'}>
|
<div className={'mx-auto my-8 flex max-w-2xl flex-col space-y-6'}>
|
||||||
<PostHeader post={post} />
|
<PostHeader post={post} />
|
||||||
|
|
||||||
<article className={'mx-auto flex justify-center'}>
|
<article className={styles.HTML}>
|
||||||
<ContentRenderer content={content} />
|
<ContentRenderer content={content} />
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import type { Metadata } from 'next';
|
|
||||||
|
|
||||||
import { createCmsClient } from '@kit/cms';
|
import { createCmsClient } from '@kit/cms';
|
||||||
|
|
||||||
import { GridList } from '~/(marketing)/_components/grid-list';
|
import { GridList } from '~/(marketing)/_components/grid-list';
|
||||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||||
import { PostPreview } from '~/(marketing)/blog/_components/post-preview';
|
import { PostPreview } from '~/(marketing)/blog/_components/post-preview';
|
||||||
import appConfig from '~/config/app.config';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const generateMetadata = async () => {
|
||||||
title: `Blog - ${appConfig.name}`,
|
const { t } = await createI18nServerInstance();
|
||||||
description: `Tutorials, Guides and Updates from our team`,
|
|
||||||
|
return {
|
||||||
|
title: t('marketing:blog'),
|
||||||
|
description: t('marketing:blogSubtitle'),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function BlogPage() {
|
async function BlogPage() {
|
||||||
|
const { t } = await createI18nServerInstance();
|
||||||
const cms = await createCmsClient();
|
const cms = await createCmsClient();
|
||||||
|
|
||||||
const posts = await cms.getContentItems({
|
const posts = await cms.getContentItems({
|
||||||
@@ -24,8 +27,8 @@ async function BlogPage() {
|
|||||||
<div className={'container mx-auto'}>
|
<div className={'container mx-auto'}>
|
||||||
<div className={'my-8 flex flex-col space-y-16'}>
|
<div className={'my-8 flex flex-col space-y-16'}>
|
||||||
<SitePageHeader
|
<SitePageHeader
|
||||||
title={'Blog'}
|
title={t('marketing:blog')}
|
||||||
subtitle={'Tutorials, Guides and Updates from our team'}
|
subtitle={t('marketing:blogSubtitle')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GridList>
|
<GridList>
|
||||||
|
|||||||
@@ -2,30 +2,29 @@ import { cache } from 'react';
|
|||||||
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
||||||
|
|
||||||
import { ContentRenderer, createCmsClient } from '@kit/cms';
|
import { ContentRenderer, createCmsClient } from '@kit/cms';
|
||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
|
|
||||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||||
import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
|
import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
|
||||||
import { DocumentationPageLink } from '~/(marketing)/docs/_components/documentation-page-link';
|
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
import styles from '../../blog/_components/html-renderer.module.css';
|
||||||
|
|
||||||
const getPageBySlug = cache(async (slug: string) => {
|
const getPageBySlug = cache(async (slug: string) => {
|
||||||
const client = await createCmsClient();
|
const client = await createCmsClient();
|
||||||
|
|
||||||
return client.getContentItemById(slug);
|
return client.getContentItemById(slug, 'pages');
|
||||||
});
|
});
|
||||||
|
|
||||||
interface PageParams {
|
interface PageParams {
|
||||||
params: {
|
params: {
|
||||||
slug: string[];
|
slug: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: PageParams) => {
|
export const generateMetadata = async ({ params }: PageParams) => {
|
||||||
const page = await getPageBySlug(params.slug.join('/'));
|
const page = await getPageBySlug(params.slug);
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
notFound();
|
notFound();
|
||||||
@@ -40,7 +39,7 @@ export const generateMetadata = async ({ params }: PageParams) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function DocumentationPage({ params }: PageParams) {
|
async function DocumentationPage({ params }: PageParams) {
|
||||||
const page = await getPageBySlug(params.slug.join('/'));
|
const page = await getPageBySlug(params.slug);
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
notFound();
|
notFound();
|
||||||
@@ -57,7 +56,9 @@ async function DocumentationPage({ params }: PageParams) {
|
|||||||
className={'items-start'}
|
className={'items-start'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContentRenderer content={page.content} />
|
<article className={styles.HTML}>
|
||||||
|
<ContentRenderer content={page.content} />
|
||||||
|
</article>
|
||||||
|
|
||||||
<If condition={page.children}>
|
<If condition={page.children}>
|
||||||
<DocsCards pages={page.children ?? []} />
|
<DocsCards pages={page.children ?? []} />
|
||||||
@@ -6,7 +6,7 @@ export const DocsCard: React.FC<
|
|||||||
React.PropsWithChildren<{
|
React.PropsWithChildren<{
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string | null;
|
subtitle?: string | null;
|
||||||
link?: { url: string; label: string };
|
link: { url: string; label: string };
|
||||||
}>
|
}>
|
||||||
> = ({ title, subtitle, children, link }) => {
|
> = ({ title, subtitle, children, link }) => {
|
||||||
return (
|
return (
|
||||||
@@ -15,11 +15,13 @@ export const DocsCard: React.FC<
|
|||||||
className={`flex grow flex-col space-y-2.5 border bg-background p-6
|
className={`flex grow flex-col space-y-2.5 border bg-background p-6
|
||||||
${link ? 'rounded-t-2xl border-b-0' : 'rounded-2xl'}`}
|
${link ? 'rounded-t-2xl border-b-0' : 'rounded-2xl'}`}
|
||||||
>
|
>
|
||||||
<h3 className="mt-0 text-lg font-semibold dark:text-white">{title}</h3>
|
<h3 className="mt-0 text-lg font-semibold dark:text-white">
|
||||||
|
<Link href={link.url}>{title}</Link>
|
||||||
|
</h3>
|
||||||
|
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
<div className="text-sm text-muted-foreground">
|
||||||
<p>{subtitle}</p>
|
<p dangerouslySetInnerHTML={{ __html: subtitle }}></p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function DocsCards({ pages }: { pages: Cms.ContentItem[] }) {
|
|||||||
title={item.title}
|
title={item.title}
|
||||||
subtitle={item.description}
|
subtitle={item.description}
|
||||||
link={{
|
link={{
|
||||||
url: item.url,
|
url: `/docs/${item.slug}`,
|
||||||
label: 'Read more',
|
label: 'Read more',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
import { createCmsClient } from '@kit/cms';
|
import { createCmsClient } from '@kit/cms';
|
||||||
|
import { PageBody } from '@kit/ui/page';
|
||||||
|
|
||||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||||
import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
|
import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
|
||||||
import appConfig from '~/config/app.config';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
export const metadata = {
|
export const generateMetadata = async () => {
|
||||||
title: `Documentation - ${appConfig.name}`,
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('marketing:documentation'),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function DocsPage() {
|
async function DocsPage() {
|
||||||
const client = await createCmsClient();
|
const client = await createCmsClient();
|
||||||
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
const docs = await client.getContentItems({
|
const docs = await client.getContentItems({
|
||||||
type: 'page',
|
type: 'page',
|
||||||
@@ -18,18 +24,16 @@ async function DocsPage() {
|
|||||||
depth: 1,
|
depth: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(docs);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'my-8 flex flex-col space-y-16'}>
|
<div className={'my-8 flex flex-col space-y-16'}>
|
||||||
<SitePageHeader
|
<SitePageHeader
|
||||||
title={'Documentation'}
|
title={t('marketing:documentation')}
|
||||||
subtitle={'Get started with our guides and tutorials'}
|
subtitle={t('marketing:documentationSubtitle')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<PageBody>
|
||||||
<DocsCards pages={docs} />
|
<DocsCards pages={docs} />
|
||||||
</div>
|
</PageBody>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,49 @@
|
|||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
|
||||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||||
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
export const metadata = {
|
export const generateMetadata = async () => {
|
||||||
title: 'FAQ',
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('marketing:faq'),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const faqItems = [
|
async function FAQPage() {
|
||||||
{
|
const { t } = await createI18nServerInstance();
|
||||||
question: `Do you offer a free trial?`,
|
|
||||||
answer: `Yes, we offer a 14-day free trial. You can cancel at any time during the trial period and you won't be charged.`,
|
// replace this content
|
||||||
},
|
// with translations
|
||||||
{
|
const faqItems = [
|
||||||
question: `Can I cancel my subscription?`,
|
{
|
||||||
answer: `You can cancel your subscription at any time. You can do this from your account settings.`,
|
question: `Do you offer a free trial?`,
|
||||||
},
|
answer: `Yes, we offer a 14-day free trial. You can cancel at any time during the trial period and you won't be charged.`,
|
||||||
{
|
},
|
||||||
question: `Where can I find my invoices?`,
|
{
|
||||||
answer: `You can find your invoices in your account settings.`,
|
question: `Can I cancel my subscription?`,
|
||||||
},
|
answer: `You can cancel your subscription at any time. You can do this from your account settings.`,
|
||||||
{
|
},
|
||||||
question: `What payment methods do you accept?`,
|
{
|
||||||
answer: `We accept all major credit cards and PayPal.`,
|
question: `Where can I find my invoices?`,
|
||||||
},
|
answer: `You can find your invoices in your account settings.`,
|
||||||
{
|
},
|
||||||
question: `Can I upgrade or downgrade my plan?`,
|
{
|
||||||
answer: `Yes, you can upgrade or downgrade your plan at any time. You can do this from your account settings.`,
|
question: `What payment methods do you accept?`,
|
||||||
},
|
answer: `We accept all major credit cards and PayPal.`,
|
||||||
{
|
},
|
||||||
question: `Do you offer discounts for non-profits?`,
|
{
|
||||||
answer: `Yes, we offer a 50% discount for non-profits. Please contact us to learn more.`,
|
question: `Can I upgrade or downgrade my plan?`,
|
||||||
},
|
answer: `Yes, you can upgrade or downgrade your plan at any time. You can do this from your account settings.`,
|
||||||
];
|
},
|
||||||
|
{
|
||||||
|
question: `Do you offer discounts for non-profits?`,
|
||||||
|
answer: `Yes, we offer a 50% discount for non-profits. Please contact us to learn more.`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const FAQPage = () => {
|
|
||||||
const structuredData = {
|
const structuredData = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
@@ -61,8 +70,8 @@ const FAQPage = () => {
|
|||||||
<div className={'container mx-auto'}>
|
<div className={'container mx-auto'}>
|
||||||
<div className={'my-8 flex flex-col space-y-16'}>
|
<div className={'my-8 flex flex-col space-y-16'}>
|
||||||
<SitePageHeader
|
<SitePageHeader
|
||||||
title={'FAQ'}
|
title={t('marketing:faq')}
|
||||||
subtitle={'Frequently Asked Questions'}
|
subtitle={t('marketing:faqSubtitle')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -80,7 +89,7 @@ const FAQPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default withI18n(FAQPage);
|
export default withI18n(FAQPage);
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,26 @@ import { PricingTable } from '@kit/billing-gateway/components';
|
|||||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||||
import billingConfig from '~/config/billing.config';
|
import billingConfig from '~/config/billing.config';
|
||||||
import pathsConfig from '~/config/paths.config';
|
import pathsConfig from '~/config/paths.config';
|
||||||
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
export const metadata = {
|
export const generateMetadata = async () => {
|
||||||
title: 'Pricing',
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('marketing:pricing'),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function PricingPage() {
|
async function PricingPage() {
|
||||||
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'container mx-auto'}>
|
<div className={'container mx-auto'}>
|
||||||
<div className={'my-8 flex flex-col space-y-16'}>
|
<div className={'my-8 flex flex-col space-y-16'}>
|
||||||
<SitePageHeader
|
<SitePageHeader
|
||||||
title={'Pricing'}
|
title={t('marketing:pricing')}
|
||||||
subtitle={'Our pricing is designed to scale with your business.'}
|
subtitle={t('marketing:pricingSubtitle')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -79,5 +79,9 @@ function getRemotePatterns() {
|
|||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
hostname: '127.0.0.1',
|
hostname: '127.0.0.1',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: 'http',
|
||||||
|
hostname: 'localhost',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,10 @@
|
|||||||
{}
|
{
|
||||||
|
"blog": "Blog",
|
||||||
|
"blogSubtitle": "News and updates about the platform",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"documentationSubtitle": "Tutorials and guide to get started with the platform",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"faqSubtitle": "Frequently asked questions about the platform",
|
||||||
|
"pricing": "Pricing",
|
||||||
|
"pricingSubtitle": "Pricing plans and payment options"
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ Implementation of the CMS layer using the [Contentlayer](https://contentlayer.de
|
|||||||
This implementation is used when the host app's environment variable is set as:
|
This implementation is used when the host app's environment variable is set as:
|
||||||
|
|
||||||
```
|
```
|
||||||
CMS_TYPE=contentlayer
|
CMS_CLIENT=contentlayer
|
||||||
```
|
```
|
||||||
@@ -162,7 +162,6 @@ export class ContentlayerClient implements CmsClient {
|
|||||||
post: Post | DocumentationPage,
|
post: Post | DocumentationPage,
|
||||||
children: Array<Post | DocumentationPage> = [],
|
children: Array<Post | DocumentationPage> = [],
|
||||||
): Cms.ContentItem {
|
): Cms.ContentItem {
|
||||||
console.log(post);
|
|
||||||
return {
|
return {
|
||||||
id: post.slug,
|
id: post.slug,
|
||||||
title: post.title,
|
title: post.title,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Mdx } from './mdx/mdx-renderer';
|
import { Mdx } from './mdx-renderer';
|
||||||
|
|
||||||
export function ContentRenderer(props: { content: string }) {
|
export function MDXContentRenderer(props: { content: string }) {
|
||||||
return <Mdx code={props.content} />;
|
return <Mdx code={props.content} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from './client';
|
export * from './client';
|
||||||
export * from './mdx/mdx-renderer';
|
|
||||||
export * from './content-renderer';
|
export * from './content-renderer';
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { getMDXComponent } from 'next-contentlayer/hooks';
|
|||||||
|
|
||||||
import { MDXComponents } from '@kit/ui/mdx-components';
|
import { MDXComponents } from '@kit/ui/mdx-components';
|
||||||
|
|
||||||
// @ts-ignore: ignore weird error
|
|
||||||
import styles from './mdx-renderer.module.css';
|
|
||||||
|
|
||||||
export function Mdx({
|
export function Mdx({
|
||||||
code,
|
code,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
@@ -14,8 +11,6 @@ export function Mdx({
|
|||||||
const Component = getMDXComponent(code);
|
const Component = getMDXComponent(code);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.MDX}>
|
<Component components={MDXComponents as unknown as MDXComponentsType} />
|
||||||
<Component components={MDXComponents as unknown as MDXComponentsType} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,10 @@ export abstract class CmsClient {
|
|||||||
options?: Cms.GetContentItemsOptions,
|
options?: Cms.GetContentItemsOptions,
|
||||||
): Promise<Cms.ContentItem[]>;
|
): Promise<Cms.ContentItem[]>;
|
||||||
|
|
||||||
abstract getContentItemById(id: string): Promise<Cms.ContentItem | undefined>;
|
abstract getContentItemById(
|
||||||
|
id: string,
|
||||||
|
type?: string,
|
||||||
|
): Promise<Cms.ContentItem | undefined>;
|
||||||
|
|
||||||
abstract getCategories(
|
abstract getCategories(
|
||||||
options?: Cms.GetCategoriesOptions,
|
options?: Cms.GetCategoriesOptions,
|
||||||
|
|||||||
@@ -9,9 +9,19 @@ export async function ContentRenderer({
|
|||||||
}) {
|
}) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'contentlayer': {
|
case 'contentlayer': {
|
||||||
const { ContentRenderer } = await import('@kit/contentlayer');
|
const { MDXContentRenderer } = await import(
|
||||||
|
'../../contentlayer/src/content-renderer'
|
||||||
|
);
|
||||||
|
|
||||||
return ContentRenderer({ content });
|
return MDXContentRenderer({ content });
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'wordpress': {
|
||||||
|
const { WordpressContentRenderer } = await import(
|
||||||
|
'../../wordpress/src/content-renderer'
|
||||||
|
);
|
||||||
|
|
||||||
|
return WordpressContentRenderer({ content });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { CmsType } from './cms.type';
|
|||||||
export async function createCmsClient(
|
export async function createCmsClient(
|
||||||
type: CmsType = process.env.CMS_CLIENT as CmsType,
|
type: CmsType = process.env.CMS_CLIENT as CmsType,
|
||||||
): Promise<CmsClient> {
|
): Promise<CmsClient> {
|
||||||
|
return cmsClientFactory(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cmsClientFactory(type: CmsType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'contentlayer':
|
case 'contentlayer':
|
||||||
return getContentLayerClient();
|
return getContentLayerClient();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Implementation of the CMS layer using the [Wordpress](https://wordpress.org) lib
|
|||||||
This implementation is used when the host app's environment variable is set as:
|
This implementation is used when the host app's environment variable is set as:
|
||||||
|
|
||||||
```
|
```
|
||||||
CMS_TYPE=wordpress
|
CMS_CLIENT=wordpress
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, please set the following environment variables:
|
Additionally, please set the following environment variables:
|
||||||
@@ -26,4 +26,19 @@ or
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
from this package's root directory.
|
from this package's root directory.
|
||||||
|
|
||||||
|
The credentials for the Wordpress instance are:
|
||||||
|
|
||||||
|
```
|
||||||
|
WORDPRESS_DB_HOST=db
|
||||||
|
WORDPRESS_DB_USER=wordpress
|
||||||
|
WORDPRESS_DB_PASSWORD=wordpress
|
||||||
|
WORDPRESS_DB_NAME=wordpress
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be asked to set up the Wordpress instance when you visit `http://localhost:8080` for the first time.
|
||||||
|
|
||||||
|
## Note for Wordpress REST API
|
||||||
|
|
||||||
|
To make the REST API in your Wordpress instance work, please change the permalink structure to `/%post%/` from the Wordpress admin panel.
|
||||||
@@ -19,7 +19,7 @@ services:
|
|||||||
wordpress:
|
wordpress:
|
||||||
image: wordpress:latest
|
image: wordpress:latest
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 8080:80
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- WORDPRESS_DB_HOST=db
|
- WORDPRESS_DB_HOST=db
|
||||||
|
|||||||
3
packages/cms/wordpress/src/content-renderer.tsx
Normal file
3
packages/cms/wordpress/src/content-renderer.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function WordpressContentRenderer(props: { content: string }) {
|
||||||
|
return <div dangerouslySetInnerHTML={{ __html: props.content }} />;
|
||||||
|
}
|
||||||
@@ -70,7 +70,7 @@ export class WordpressClient implements CmsClient {
|
|||||||
let children: Cms.ContentItem[] = [];
|
let children: Cms.ContentItem[] = [];
|
||||||
|
|
||||||
const embeddedChildren = (
|
const embeddedChildren = (
|
||||||
item._embedded ? item._embedded['wp:children'] : []
|
item._embedded ? item._embedded['wp:children'] ?? [] : []
|
||||||
) as WP_REST_API_Post[];
|
) as WP_REST_API_Post[];
|
||||||
|
|
||||||
if (options?.depth && options.depth > 0) {
|
if (options?.depth && options.depth > 0) {
|
||||||
@@ -128,8 +128,8 @@ export class WordpressClient implements CmsClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getContentItemById(slug: string) {
|
async getContentItemById(slug: string, type: 'posts' | 'pages' = 'posts') {
|
||||||
const url = `${this.apiUrl}/wp-json/wp/v2/posts?slug=${slug}`;
|
const url = `${this.apiUrl}/wp-json/wp/v2/${type}?slug=${slug}&_embed`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const data = (await response.json()) as WP_REST_API_Post[];
|
const data = (await response.json()) as WP_REST_API_Post[];
|
||||||
const item = data[0];
|
const item = data[0];
|
||||||
|
|||||||
@@ -17,14 +17,10 @@ export function useUpdateUser() {
|
|||||||
emailRedirectTo: redirectTo,
|
emailRedirectTo: redirectTo,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw response.error;
|
throw response.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('response.data:', response.data);
|
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1066,7 +1066,6 @@ create policy billing_customers_read_self on public.billing_customers
|
|||||||
using (account_id = auth.uid()
|
using (account_id = auth.uid()
|
||||||
or has_role_on_account(account_id));
|
or has_role_on_account(account_id));
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
* Section: Subscriptions
|
* Section: Subscriptions
|
||||||
@@ -1319,7 +1318,6 @@ create policy subscription_items_read_self on public.subscription_items
|
|||||||
id = subscription_id and (account_id = auth.uid() or
|
id = subscription_id and (account_id = auth.uid() or
|
||||||
has_role_on_account(account_id))));
|
has_role_on_account(account_id))));
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
* Section: Orders
|
* Section: Orders
|
||||||
|
|||||||
Reference in New Issue
Block a user