Create legal pages and refactor navigation
New pages for Cookie Policy, Terms of Service, and Privacy Policy were added. The navigation system was restructured using a mapped array of links instead of hard coded components. The 'Not Found' page's metadata handling was improved and its translation was updated. Certain components that aid this refactoring were created and some pre-existing components or functions were moved to more appropriate locations or renamed for clarity. The Site Navigation, Footer, and Page header components were updated in layout and content. Various pages including Blog and Documentation were updated or removed.
This commit is contained in:
17
apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx
Normal file
17
apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
function CookiePolicyPage() {
|
||||
return (
|
||||
<div className={'mt-8'}>
|
||||
<div className={'container mx-auto'}>
|
||||
<SitePageHeader
|
||||
title={`Cookie Policy`}
|
||||
subtitle={`This is the cookie policy page. It's a great place to put information about the cookies your site uses.`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(CookiePolicyPage);
|
||||
14
apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx
Normal file
14
apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
function PrivacyPolicyPage() {
|
||||
return (
|
||||
<div className={'mt-8'}>
|
||||
<div className={'container mx-auto'}>
|
||||
<SitePageHeader title={`Privacy Policy`} subtitle={``} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(PrivacyPolicyPage);
|
||||
14
apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx
Normal file
14
apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
function TermsOfServicePage() {
|
||||
return (
|
||||
<div className={'mt-8'}>
|
||||
<div className={'container mx-auto'}>
|
||||
<SitePageHeader title={`Terms of Service`} subtitle={``} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(TermsOfServicePage);
|
||||
@@ -1,5 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { AppLogo } from '~/components/app-logo';
|
||||
import appConfig from '~/config/app.config';
|
||||
|
||||
@@ -22,12 +24,12 @@ export function SiteFooter() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className={'text-sm text-gray-500 dark:text-gray-400'}>
|
||||
<p className={'text-sm text-muted-foreground'}>
|
||||
Add a short tagline about your product
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex text-xs text-gray-500 dark:text-gray-400'}>
|
||||
<div className={'flex text-xs text-muted-foreground'}>
|
||||
<p>
|
||||
© Copyright {YEAR} {appConfig.name}. All Rights Reserved.
|
||||
</p>
|
||||
@@ -43,54 +45,63 @@ export function SiteFooter() {
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<FooterSectionHeading>About</FooterSectionHeading>
|
||||
<div className={'flex flex-col space-y-2.5'}>
|
||||
<FooterSectionHeading>
|
||||
<Trans i18nKey={'marketing:about'} />
|
||||
</FooterSectionHeading>
|
||||
|
||||
<FooterSectionList>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Who we are</Link>
|
||||
<Link href={'/blog'}>
|
||||
<Trans i18nKey={'marketing:blog'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'/blog'}>Blog</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'/contact'}>Contact</Link>
|
||||
<Link href={'/contact'}>
|
||||
<Trans i18nKey={'marketing:contact'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
</FooterSectionList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<FooterSectionHeading>Product</FooterSectionHeading>
|
||||
<div className={'flex flex-col space-y-2.5'}>
|
||||
<FooterSectionHeading>
|
||||
<Trans i18nKey={'marketing:product'} />
|
||||
</FooterSectionHeading>
|
||||
|
||||
<FooterSectionList>
|
||||
<FooterLink>
|
||||
<Link href={'/docs'}>Documentation</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Help Center</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Changelog</Link>
|
||||
<Link href={'/docs'}>
|
||||
<Trans i18nKey={'marketing:documentation'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
</FooterSectionList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={'flex flex-col space-y-4'}>
|
||||
<FooterSectionHeading>Legal</FooterSectionHeading>
|
||||
<div className={'flex flex-col space-y-2.5'}>
|
||||
<FooterSectionHeading>
|
||||
<Trans i18nKey={'marketing:legal'} />
|
||||
</FooterSectionHeading>
|
||||
|
||||
<FooterSectionList>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Terms of Service</Link>
|
||||
<Link href={'/terms-of-service'}>
|
||||
<Trans i18nKey={'marketing:tos'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Privacy Policy</Link>
|
||||
<Link href={'/privacy-policy'}>
|
||||
<Trans i18nKey={'marketing:privacyPolicy'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
<FooterLink>
|
||||
<Link href={'#'}>Cookie Policy</Link>
|
||||
<Link href={'/cookie-policy'}>
|
||||
<Trans i18nKey={'marketing:cookiePolicy'} />
|
||||
</Link>
|
||||
</FooterLink>
|
||||
</FooterSectionList>
|
||||
</div>
|
||||
@@ -111,19 +122,14 @@ function FooterSectionHeading(props: React.PropsWithChildren) {
|
||||
}
|
||||
|
||||
function FooterSectionList(props: React.PropsWithChildren) {
|
||||
return (
|
||||
<ul className={'flex flex-col space-y-4 text-gray-500 dark:text-gray-400'}>
|
||||
{props.children}
|
||||
</ul>
|
||||
);
|
||||
return <ul className={'flex flex-col space-y-2.5'}>{props.children}</ul>;
|
||||
}
|
||||
|
||||
function FooterLink(props: React.PropsWithChildren) {
|
||||
return (
|
||||
<li
|
||||
className={
|
||||
'text-sm [&>a]:transition-colors [&>a]:hover:text-gray-800' +
|
||||
' dark:[&>a]:hover:text-white'
|
||||
'text-sm text-muted-foreground hover:underline [&>a]:transition-colors'
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { NavigationMenuItem } from '@kit/ui/navigation-menu';
|
||||
import { cn, isRouteActive } from '@kit/ui/utils';
|
||||
|
||||
const getClassName = (path: string, currentPathName: string) => {
|
||||
const isActive = isRouteActive(path, currentPathName);
|
||||
|
||||
return cn(`text-sm transition-all px-4 py-2 rounded-full font-medium`, {
|
||||
'bg-muted': isActive,
|
||||
'hover:bg-muted active:bg-muted/50': !isActive,
|
||||
});
|
||||
};
|
||||
|
||||
export function SiteNavigationItem({
|
||||
path,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
path: string;
|
||||
}>) {
|
||||
const currentPathName = usePathname();
|
||||
|
||||
return (
|
||||
<NavigationMenuItem key={path}>
|
||||
<Link className={getClassName(path, currentPathName)} href={path}>
|
||||
{children}
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
);
|
||||
}
|
||||
@@ -8,64 +8,47 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@kit/ui/dropdown-menu';
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuList,
|
||||
} from '@kit/ui/navigation-menu';
|
||||
import { NavigationMenu, NavigationMenuList } from '@kit/ui/navigation-menu';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { SiteNavigationItem } from '~/(marketing)/_components/site-navigation-item';
|
||||
|
||||
const links = {
|
||||
SignIn: {
|
||||
label: 'Sign In',
|
||||
path: '/auth/sign-in',
|
||||
},
|
||||
Blog: {
|
||||
label: 'Blog',
|
||||
label: 'marketing:blog',
|
||||
path: '/blog',
|
||||
},
|
||||
Docs: {
|
||||
label: 'Documentation',
|
||||
label: 'marketing:documentation',
|
||||
path: '/docs',
|
||||
},
|
||||
Pricing: {
|
||||
label: 'Pricing',
|
||||
label: 'marketing:pricing',
|
||||
path: '/pricing',
|
||||
},
|
||||
FAQ: {
|
||||
label: 'FAQ',
|
||||
label: 'marketing:faq',
|
||||
path: '/faq',
|
||||
},
|
||||
};
|
||||
|
||||
export function SiteNavigation() {
|
||||
const className = `hover:underline text-sm`;
|
||||
const NavItems = Object.values(links).map((item) => {
|
||||
return (
|
||||
<SiteNavigationItem key={item.path} path={item.path}>
|
||||
<Trans i18nKey={item.label} />
|
||||
</SiteNavigationItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'hidden items-center lg:flex'}>
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList className={'space-x-3'}>
|
||||
<NavigationMenuItem>
|
||||
<Link className={className} href={links.Blog.path}>
|
||||
{links.Blog.label}
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link className={className} href={links.Docs.path}>
|
||||
{links.Docs.label}
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link className={className} href={links.Pricing.path}>
|
||||
{links.Pricing.label}
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<Link className={className} href={links.FAQ.path}>
|
||||
{links.FAQ.label}
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuList
|
||||
className={'space-x-1.5 rounded-full border px-1 py-2'}
|
||||
>
|
||||
{NavItems}
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
|
||||
@@ -12,9 +12,9 @@ export function SitePageHeader(props: {
|
||||
>
|
||||
<Heading level={1}>{props.title}</Heading>
|
||||
|
||||
<Heading level={2} className={'text-muted-foreground'}>
|
||||
<h2 className={'text-center text-xl text-muted-foreground xl:text-2xl'}>
|
||||
<span className={'font-normal'}>{props.subtitle}</span>
|
||||
</Heading>
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { Heading } from '@kit/ui/heading';
|
||||
|
||||
export const metadata = {
|
||||
title: 'About',
|
||||
};
|
||||
|
||||
const AboutPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className={'container mx-auto'}>
|
||||
<div className={'my-8 flex flex-col space-y-14'}>
|
||||
<div className={'flex flex-col items-center space-y-4'}>
|
||||
<Heading level={1}>About us</Heading>
|
||||
|
||||
<Heading level={2}>
|
||||
We are a team of passionate developers and designers who love to
|
||||
build great products.
|
||||
</Heading>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
'm-auto flex w-full max-w-xl flex-col items-center space-y-8' +
|
||||
' justify-center text-gray-600 dark:text-gray-400'
|
||||
}
|
||||
>
|
||||
<div>
|
||||
We are a team of visionaries, dreamers, and doers who are on a
|
||||
mission to change the world for the better
|
||||
</div>
|
||||
|
||||
<div>
|
||||
With a passion for innovation and a commitment to excellence, we
|
||||
are dedicated to creating products and services that will improve
|
||||
people's lives and make a positive impact on society.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
It all started with a simple idea: to use technology to solve some
|
||||
of the biggest challenges facing humanity. We realized that with
|
||||
the right team and the right approach, we could make a difference
|
||||
and leave a lasting legacy. And so, with a lot of hard work and
|
||||
determination, we set out on a journey to turn our vision into
|
||||
reality.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Today, we are proud to be a leader in our field, and our products
|
||||
and services are used by millions of people all over the world.
|
||||
But we're not done yet. We still have big dreams and even
|
||||
bigger plans, and we're always looking for ways to push the
|
||||
boundaries of what's possible.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Our Values: At the heart of everything we do is a set of core
|
||||
values that guide us in all that we do. These values are what make
|
||||
us who we are, and they are what set us apart from the rest.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul className={'flex list-decimal flex-col space-y-1 pl-4'}>
|
||||
<li>
|
||||
Innovation: We are always looking for new and better ways to
|
||||
do things.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Excellence: We strive for excellence in all that we do, and we
|
||||
never settle for less.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Responsibility: We take our responsibilities seriously, and we
|
||||
always act with integrity.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Collaboration: We believe that by working together, we can
|
||||
achieve more than we can on our own.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>Yes, this was generated with ChatGPT</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPage;
|
||||
@@ -49,7 +49,11 @@ async function DocumentationPage({ params }: PageParams) {
|
||||
|
||||
return (
|
||||
<div className={'container mx-auto'}>
|
||||
<div className={'relative flex grow flex-col space-y-4 px-8 py-8'}>
|
||||
<div
|
||||
className={
|
||||
'relative mx-auto flex max-w-4xl grow flex-col space-y-4 px-8 py-8'
|
||||
}
|
||||
>
|
||||
<SitePageHeader
|
||||
title={page.title}
|
||||
subtitle={description}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { isBrowser } from '@kit/shared/utils';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Heading } from '@kit/ui/heading';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
import { cn, isRouteActive } from '@kit/ui/utils';
|
||||
|
||||
const DocsNavLink: React.FC<{
|
||||
label: string;
|
||||
@@ -20,7 +20,7 @@ const DocsNavLink: React.FC<{
|
||||
level: number;
|
||||
activePath: string;
|
||||
}> = ({ label, url, level, activePath }) => {
|
||||
const isCurrent = url == activePath;
|
||||
const isCurrent = isRouteActive(url, activePath, 0);
|
||||
const isFirstLevel = level === 0;
|
||||
|
||||
return (
|
||||
@@ -70,7 +70,7 @@ function Tree({
|
||||
activePath: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn('w-full space-y-2.5 pl-3', level > 0 ? 'border-l' : '')}>
|
||||
<div className={cn('w-full space-y-1 pl-3')}>
|
||||
{pages.map((treeNode, index) => (
|
||||
<Node
|
||||
key={index}
|
||||
@@ -84,7 +84,7 @@ function Tree({
|
||||
}
|
||||
|
||||
export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) {
|
||||
const activePath = usePathname().replace('/docs/', '');
|
||||
const activePath = usePathname();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -109,7 +109,7 @@ export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) {
|
||||
|
||||
function getNavLinkClassName(isCurrent: boolean, isFirstLevel: boolean) {
|
||||
return cn(
|
||||
'group flex h-8 items-center justify-between space-x-2 whitespace-nowrap rounded-md px-3 text-sm leading-none transition-colors',
|
||||
'group flex min-h-8 items-center justify-between space-x-2 whitespace-nowrap rounded-md px-3 text-sm transition-colors',
|
||||
{
|
||||
[`bg-muted`]: isCurrent,
|
||||
[`hover:bg-muted`]: !isCurrent,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from 'next/link';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
export function DocumentationPageLink({
|
||||
export function DocsPageLink({
|
||||
page,
|
||||
before,
|
||||
after,
|
||||
@@ -10,12 +10,10 @@ async function DocsLayout({ children }: React.PropsWithChildren) {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={'container mx-auto'}>
|
||||
<div className={'flex'}>
|
||||
<DocsNavigation pages={buildDocumentationTree(pages)} />
|
||||
<div className={'flex'}>
|
||||
<DocsNavigation pages={buildDocumentationTree(pages)} />
|
||||
|
||||
<div className={'flex w-full flex-col items-center'}>{children}</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,14 +26,16 @@ async function DocsPage() {
|
||||
const cards = docs.filter((item) => !item.parentId);
|
||||
|
||||
return (
|
||||
<div className={'my-8 flex flex-col space-y-16'}>
|
||||
<SitePageHeader
|
||||
title={t('marketing:documentation')}
|
||||
subtitle={t('marketing:documentationSubtitle')}
|
||||
/>
|
||||
<div className={'flex flex-1 flex-col'}>
|
||||
<PageBody className={'mt-8'}>
|
||||
<div className={'flex flex-col items-center space-y-16'}>
|
||||
<SitePageHeader
|
||||
title={t('marketing:documentation')}
|
||||
subtitle={t('marketing:documentationSubtitle')}
|
||||
/>
|
||||
|
||||
<PageBody>
|
||||
<DocsCards cards={cards} />
|
||||
<DocsCards cards={cards} />
|
||||
</div>
|
||||
</PageBody>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,11 +8,16 @@ import { Heading } from '@kit/ui/heading';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { SiteHeader } from '~/(marketing)/_components/site-header';
|
||||
import appConfig from '~/config/app.config';
|
||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
|
||||
export const metadata = {
|
||||
title: `Page not found - ${appConfig.name}`,
|
||||
export const generateMetadata = async () => {
|
||||
const i18n = await createI18nServerInstance();
|
||||
const title = i18n.t('common:notFound');
|
||||
|
||||
return {
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
const NotFoundPage = async () => {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"imageInputLabel": "Click here to upload an image",
|
||||
"cancel": "Cancel",
|
||||
"clear": "Clear",
|
||||
"notFound": "Not Found",
|
||||
"backToHomePage": "Back to Home Page",
|
||||
"genericServerError": "Sorry, something went wrong.",
|
||||
"genericServerErrorHeading": "Sorry, something went wrong while processing your request. Please contact us if the issue persists.",
|
||||
|
||||
@@ -6,5 +6,12 @@
|
||||
"faq": "FAQ",
|
||||
"faqSubtitle": "Frequently asked questions about the platform",
|
||||
"pricing": "Pricing",
|
||||
"pricingSubtitle": "Pricing plans and payment options"
|
||||
"pricingSubtitle": "Pricing plans and payment options",
|
||||
"contact": "Contact",
|
||||
"about": "About",
|
||||
"product": "Product",
|
||||
"legal": "Legal",
|
||||
"tos": "Terms of Service",
|
||||
"cookiePolicy": "Cookie Policy",
|
||||
"privacyPolicy": "Privacy Policy"
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './cn';
|
||||
export * from './is-route-active';
|
||||
|
||||
@@ -8,10 +8,10 @@ const ROOT_PATH = '/';
|
||||
* @param currentRoute - the current route
|
||||
* @param depth - how far down should segments be matched?
|
||||
*/
|
||||
export default function isRouteActive(
|
||||
export function isRouteActive(
|
||||
targetLink: string,
|
||||
currentRoute: string,
|
||||
depth: number,
|
||||
depth = 1,
|
||||
) {
|
||||
// we remove any eventual query param from the route's URL
|
||||
const currentRoutePath = currentRoute.split('?')[0] ?? '';
|
||||
Reference in New Issue
Block a user