feat: add contact page with internationalization support
- Create contact page with responsive design and form layout - Add localized messages for English and Chinese contact pages - Implement form inputs with labels and validation - Use Shadcn/ui components for consistent styling - Add Card and Textarea components for enhanced UI
This commit is contained in:
parent
bc4b5527eb
commit
e8b9dfb3c3
@ -22,7 +22,16 @@
|
|||||||
},
|
},
|
||||||
"ChangelogPage": {
|
"ChangelogPage": {
|
||||||
"title": "Changelog",
|
"title": "Changelog",
|
||||||
"subtitle": "Stay up to date with the latest changes in our product."
|
"subtitle": "Stay up to date with the latest changes in our product"
|
||||||
|
},
|
||||||
|
"ContactPage": {
|
||||||
|
"title": "Contact",
|
||||||
|
"subtitle": "We'll help you find the right plan for your business",
|
||||||
|
"formDescription": "If you have any questions or feedback, please reach out to our team",
|
||||||
|
"name": "Name",
|
||||||
|
"email": "Email",
|
||||||
|
"message": "Message",
|
||||||
|
"submit": "Submit"
|
||||||
},
|
},
|
||||||
"AuthPage": {
|
"AuthPage": {
|
||||||
"login": {
|
"login": {
|
||||||
|
@ -21,6 +21,15 @@
|
|||||||
"title": "更新日志",
|
"title": "更新日志",
|
||||||
"subtitle": "查看我们产品的最新动态"
|
"subtitle": "查看我们产品的最新动态"
|
||||||
},
|
},
|
||||||
|
"ContactPage": {
|
||||||
|
"title": "联系我们",
|
||||||
|
"subtitle": "我们帮助您找到合适的计划",
|
||||||
|
"formDescription": "如果您有任何问题或反馈,欢迎联系我们的团队",
|
||||||
|
"name": "姓名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"message": "消息",
|
||||||
|
"submit": "提交"
|
||||||
|
},
|
||||||
"AuthPage": {
|
"AuthPage": {
|
||||||
"login": {
|
"login": {
|
||||||
"title": "登录",
|
"title": "登录",
|
||||||
|
65
src/app/[locale]/(marketing)/(pages)/contact/page.tsx
Normal file
65
src/app/[locale]/(marketing)/(pages)/contact/page.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://nsui.irung.me/contact
|
||||||
|
*/
|
||||||
|
export default function ContactPage() {
|
||||||
|
const t = useTranslations('ContactPage');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto space-y-8">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-center text-3xl font-bold tracking-tight">
|
||||||
|
{t('title')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-center text-lg text-muted-foreground">
|
||||||
|
{t('subtitle')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<Card className="mx-auto max-w-lg p-8 shadow-md">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('formDescription')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="" className="mt-8 space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">
|
||||||
|
{t('name')}
|
||||||
|
</Label>
|
||||||
|
<Input type="text" id="name" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">
|
||||||
|
{t('email')}
|
||||||
|
</Label>
|
||||||
|
<Input type="email" id="email" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="msg">
|
||||||
|
{t('message')}
|
||||||
|
</Label>
|
||||||
|
<Textarea id="msg" rows={3} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
{t('submit')}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -3,7 +3,7 @@ import { PropsWithChildren } from 'react';
|
|||||||
|
|
||||||
export default function LegalLayout({ children }: PropsWithChildren) {
|
export default function LegalLayout({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<Container className="py-8 px-4">
|
<Container className="pt-8 pb-16 px-4">
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Mdx } from '@/components/shared/mdx-component';
|
import { Mdx } from '@/components/shared/mdx-component';
|
||||||
import { getLocaleDate } from '@/lib/utils';
|
import { getLocaleDate } from '@/lib/utils';
|
||||||
import { CalendarIcon } from 'lucide-react';
|
import { Separator } from '@radix-ui/react-separator';
|
||||||
|
import { Badge, CalendarIcon, TagIcon } from 'lucide-react';
|
||||||
|
import { version } from 'os';
|
||||||
|
import { Card, CardHeader, CardContent } from '../ui/card';
|
||||||
|
|
||||||
interface CustomPageProps {
|
interface CustomPageProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -29,9 +32,13 @@ export function CustomPage({ title, description, date, content }: CustomPageProp
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="prose prose-gray dark:prose-invert max-w-none">
|
<Card className="mb-8">
|
||||||
<Mdx code={content} />
|
<CardContent>
|
||||||
</div>
|
<div className="prose prose-gray dark:prose-invert max-w-none">
|
||||||
|
<Mdx code={content} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
22
src/components/ui/textarea.tsx
Normal file
22
src/components/ui/textarea.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
React.ComponentProps<"textarea">
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
@ -132,13 +132,6 @@ export function getMenuLinks(t: TranslationFunction): NestedMenuItem[] {
|
|||||||
href: Routes.Waitlist,
|
href: Routes.Waitlist,
|
||||||
external: false
|
external: false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t('Marketing.navbar.pages.items.changelog.title'),
|
|
||||||
description: t('Marketing.navbar.pages.items.changelog.description'),
|
|
||||||
icon: <ListChecksIcon className="size-5 shrink-0" />,
|
|
||||||
href: Routes.Changelog,
|
|
||||||
external: false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t('Marketing.navbar.pages.items.roadmap.title'),
|
title: t('Marketing.navbar.pages.items.roadmap.title'),
|
||||||
description: t('Marketing.navbar.pages.items.roadmap.description'),
|
description: t('Marketing.navbar.pages.items.roadmap.description'),
|
||||||
@ -146,6 +139,13 @@ export function getMenuLinks(t: TranslationFunction): NestedMenuItem[] {
|
|||||||
href: Routes.Roadmap,
|
href: Routes.Roadmap,
|
||||||
external: true
|
external: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Marketing.navbar.pages.items.changelog.title'),
|
||||||
|
description: t('Marketing.navbar.pages.items.changelog.description'),
|
||||||
|
icon: <ListChecksIcon className="size-5 shrink-0" />,
|
||||||
|
href: Routes.Changelog,
|
||||||
|
external: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('Marketing.navbar.pages.items.cookiePolicy.title'),
|
title: t('Marketing.navbar.pages.items.cookiePolicy.title'),
|
||||||
description: t('Marketing.navbar.pages.items.cookiePolicy.description'),
|
description: t('Marketing.navbar.pages.items.cookiePolicy.description'),
|
||||||
|
Loading…
Reference in New Issue
Block a user