refactor: replace Link with LocaleLink and clean up console logs
- Rename `Link` import to `LocaleLink` in navigation module - Remove console log statements in blog page components - Update blog card, blog category list, and other components to use `LocaleLink` - Simplify routing configuration by removing unnecessary pathname entries - Enhance type safety and internationalization consistency
This commit is contained in:
parent
95ddd0a1dc
commit
675323fb69
@ -64,7 +64,7 @@ export default async function BlogCategoryPage({
|
||||
if (!post.published || post.locale !== locale) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check if any of the post's categories match the current category slug
|
||||
return post.categories.some(category => category && category.slug === slug);
|
||||
}
|
||||
@ -80,12 +80,7 @@ export default async function BlogCategoryPage({
|
||||
const totalCount = filteredPosts.length;
|
||||
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
|
||||
|
||||
console.log(
|
||||
"BlogCategoryPage, totalCount",
|
||||
totalCount,
|
||||
", totalPages",
|
||||
totalPages,
|
||||
);
|
||||
// console.log("BlogCategoryPage, totalCount", totalCount, ", totalPages", totalPages);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -45,12 +45,7 @@ export default async function BlogPage({
|
||||
const totalCount = filteredPosts.length;
|
||||
const totalPages = Math.ceil(totalCount / POSTS_PER_PAGE);
|
||||
|
||||
console.log(
|
||||
"BlogPage, totalCount",
|
||||
totalCount,
|
||||
", totalPages",
|
||||
totalPages,
|
||||
);
|
||||
// console.log("BlogPage, totalCount", totalCount, ", totalPages", totalPages,);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import AllPostsButton from '@/components/blog/all-posts-button';
|
||||
import { BlogToc } from '@/components/blog/blog-toc';
|
||||
import { Mdx } from '@/components/marketing/blog/mdx-component';
|
||||
import { Link } from '@/i18n/navigation';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { getTableOfContents } from '@/lib/toc';
|
||||
import { getBaseUrl } from '@/lib/urls/get-base-url';
|
||||
import { getLocaleDate } from '@/lib/utils';
|
||||
@ -10,9 +10,9 @@ import { allPosts } from 'content-collections';
|
||||
import type { Metadata } from 'next';
|
||||
import Image from 'next/image';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
import '@/styles/mdx.css';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
/**
|
||||
* Gets the blog post from the params
|
||||
@ -162,7 +162,7 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
{post.categories?.filter(Boolean).map((category) => (
|
||||
category && (
|
||||
<li key={category.slug}>
|
||||
<Link
|
||||
<LocaleLink
|
||||
href={{
|
||||
pathname: "/blog/category/[slug]",
|
||||
params: { slug: category.slug }
|
||||
@ -170,7 +170,7 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
className="text-sm link-underline"
|
||||
>
|
||||
{category.name}
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
</li>
|
||||
)
|
||||
))}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LoginForm } from "@/components/auth/login-form";
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
import { constructMetadata } from "@/lib/metadata";
|
||||
import { Routes } from "@/routes";
|
||||
import { useTranslations } from "next-intl";
|
||||
@ -22,19 +22,19 @@ const LoginPage = () => {
|
||||
<LoginForm />
|
||||
<div className="text-balance text-center text-xs text-muted-foreground">
|
||||
{t("byClickingContinue")}
|
||||
<Link
|
||||
<LocaleLink
|
||||
href={Routes.TermsOfService as any}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
{t("termsOfService")}
|
||||
</Link>{" "}
|
||||
</LocaleLink>{" "}
|
||||
{t("and")}{" "}
|
||||
<Link
|
||||
<LocaleLink
|
||||
href={Routes.PrivacyPolicy as any}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
{t("privacyPolicy")}
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,9 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
interface BottomButtonProps {
|
||||
href: string
|
||||
@ -12,14 +11,14 @@ interface BottomButtonProps {
|
||||
|
||||
export const BottomButton = ({ href, label }: BottomButtonProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={href as ComponentProps<typeof Link>["href"]}
|
||||
<LocaleLink
|
||||
href={href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "link", size: "sm" }),
|
||||
"font-normal w-full text-muted-foreground hover:underline underline-offset-4 hover:text-primary"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
);
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { LoginSchema } from "@/lib/schemas";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -117,12 +117,12 @@ export const LoginForm = ({ className }: { className?: string }) => {
|
||||
asChild
|
||||
className="px-0 font-normal text-muted-foreground"
|
||||
>
|
||||
<Link
|
||||
<LocaleLink
|
||||
href={`${Routes.ForgotPassword}`}
|
||||
className="text-xs hover:underline hover:underline-offset-4 hover:text-primary"
|
||||
>
|
||||
{t("forgotPassword")}
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
</Button>
|
||||
</div>
|
||||
<FormControl>
|
||||
|
@ -24,6 +24,9 @@ import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import type * as z from "zod";
|
||||
|
||||
/**
|
||||
* https://www.better-auth.com/docs/authentication/email-password#forget-password
|
||||
*/
|
||||
export const ResetPasswordForm = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
@ -31,6 +34,13 @@ export const ResetPasswordForm = () => {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// If the token is valid, the user will be redirected to this URL with the token in the query string.
|
||||
// If the token is invalid, the user will be redirected to this URL with an error message in the query string ?error=invalid_token.
|
||||
// TODO: check if the token is valid, show error message instead of redirecting to the 404 page
|
||||
if (searchParams.get("error") === "invalid_token") {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const [error, setError] = useState<string | undefined>("");
|
||||
const [success, setSuccess] = useState<string | undefined>("");
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
|
||||
export default function AllPostsButton() {
|
||||
return (
|
||||
@ -12,13 +12,13 @@ export default function AllPostsButton() {
|
||||
className="inline-flex items-center gap-2 group"
|
||||
asChild
|
||||
>
|
||||
<Link href="/blog">
|
||||
<LocaleLink href="/blog">
|
||||
<ArrowLeftIcon
|
||||
className="w-5 h-5
|
||||
transition-transform duration-200 group-hover:-translate-x-1"
|
||||
/>
|
||||
<span>All Posts</span>
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { getLocaleDate } from "@/lib/utils";
|
||||
import { Post } from "content-collections";
|
||||
import Image from "next/image";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
|
||||
interface BlogCardProps {
|
||||
post: Post;
|
||||
@ -16,13 +16,16 @@ export default function BlogCard({ post }: BlogCardProps) {
|
||||
const slugParts = post.slugAsParams.split('/');
|
||||
|
||||
return (
|
||||
<div className="group cursor-pointer flex flex-col border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden h-full">
|
||||
{/* Image container - fixed aspect ratio */}
|
||||
<div className="group overflow-hidden relative aspect-[16/9] w-full">
|
||||
<Link href={{
|
||||
pathname: "/blog/[...slug]",
|
||||
params: { slug: slugParts }
|
||||
}}>
|
||||
<LocaleLink
|
||||
href={{
|
||||
pathname: "/blog/[...slug]",
|
||||
params: { slug: slugParts }
|
||||
}}
|
||||
className="block h-full"
|
||||
>
|
||||
<div className="group flex flex-col border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden h-full">
|
||||
{/* Image container - fixed aspect ratio */}
|
||||
<div className="group overflow-hidden relative aspect-[16/9] w-full">
|
||||
{post.image && (
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
@ -49,18 +52,13 @@ export default function BlogCard({ post }: BlogCardProps) {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Post info container */}
|
||||
<div className="flex flex-col justify-between p-4 flex-1">
|
||||
<div>
|
||||
{/* Post title */}
|
||||
<h3 className="text-lg line-clamp-2 font-medium">
|
||||
<Link href={{
|
||||
pathname: "/blog/[...slug]",
|
||||
params: { slug: slugParts }
|
||||
}}>
|
||||
{/* Post info container */}
|
||||
<div className="flex flex-col justify-between p-4 flex-1">
|
||||
<div>
|
||||
{/* Post title */}
|
||||
<h3 className="text-lg line-clamp-2 font-medium">
|
||||
<span
|
||||
className="bg-gradient-to-r from-green-200 to-green-100
|
||||
bg-[length:0px_10px] bg-left-bottom bg-no-repeat
|
||||
@ -72,46 +70,41 @@ export default function BlogCard({ post }: BlogCardProps) {
|
||||
>
|
||||
{post.title}
|
||||
</span>
|
||||
</Link>
|
||||
</h3>
|
||||
</h3>
|
||||
|
||||
{/* Post excerpt, hidden for now */}
|
||||
<div className="mt-2">
|
||||
{post.description && (
|
||||
<p className="line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Link href={{
|
||||
pathname: "/blog/[...slug]",
|
||||
params: { slug: slugParts }
|
||||
}}>
|
||||
{/* Post excerpt */}
|
||||
<div className="mt-2">
|
||||
{post.description && (
|
||||
<p className="line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{post.description}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Author and date */}
|
||||
<div className="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800 flex items-center justify-between space-x-4 text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-8 w-8 flex-shrink-0">
|
||||
{post?.author?.avatar && (
|
||||
<Image
|
||||
src={post?.author?.avatar}
|
||||
alt={`avatar for ${post?.author?.name}`}
|
||||
className="rounded-full object-cover border"
|
||||
fill
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate text-sm">{post?.author?.name}</span>
|
||||
</div>
|
||||
|
||||
<time className="truncate text-sm" dateTime={date}>
|
||||
{date}
|
||||
</time>
|
||||
{/* Author and date */}
|
||||
<div className="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800 flex items-center justify-between space-x-4 text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative h-8 w-8 flex-shrink-0">
|
||||
{post?.author?.avatar && (
|
||||
<Image
|
||||
src={post?.author?.avatar}
|
||||
alt={`avatar for ${post?.author?.name}`}
|
||||
className="rounded-full object-cover border"
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate text-sm">{post?.author?.name}</span>
|
||||
</div>
|
||||
|
||||
<time className="truncate text-sm" dateTime={date}>
|
||||
{date}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LocaleLink>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Category } from "content-collections";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
@ -18,50 +18,46 @@ export function BlogCategoryListDesktop({
|
||||
const t = useTranslations("BlogPage");
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Desktop View */}
|
||||
<div className="flex items-center justify-center">
|
||||
<ToggleGroup
|
||||
size="sm"
|
||||
type="single"
|
||||
value={slug || "All"}
|
||||
aria-label="Toggle blog category"
|
||||
className="h-9 overflow-hidden rounded-full border bg-background p-1 *:h-7 *:text-muted-foreground"
|
||||
<div className="flex items-center justify-center">
|
||||
<ToggleGroup
|
||||
size="sm"
|
||||
type="single"
|
||||
value={slug || "All"}
|
||||
aria-label="Toggle blog category"
|
||||
className="h-9 overflow-hidden rounded-full border bg-background p-1 *:h-7 *:text-muted-foreground"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
key="All"
|
||||
value="All"
|
||||
className={cn(
|
||||
"rounded-full px-5",
|
||||
"data-[state=on]:bg-primary data-[state=on]:text-primary-foreground",
|
||||
"hover:bg-muted hover:text-muted-foreground",
|
||||
)}
|
||||
aria-label={"Toggle all blog categories"}
|
||||
>
|
||||
<LocaleLink href={"/blog"}>
|
||||
<h2>{t("all")}</h2>
|
||||
</LocaleLink>
|
||||
</ToggleGroupItem>
|
||||
|
||||
{categoryList.map((category) => (
|
||||
<ToggleGroupItem
|
||||
key="All"
|
||||
value="All"
|
||||
key={category.slug}
|
||||
value={category.slug}
|
||||
className={cn(
|
||||
"rounded-full px-5",
|
||||
"data-[state=on]:bg-primary data-[state=on]:text-primary-foreground",
|
||||
"hover:bg-muted hover:text-muted-foreground",
|
||||
)}
|
||||
aria-label={"Toggle all blog categories"}
|
||||
aria-label={`Toggle blog category of ${category.name}`}
|
||||
>
|
||||
<Link href={"/blog"}>
|
||||
<h2>{t("all")}</h2>
|
||||
</Link>
|
||||
<LocaleLink href={`/blog/category/${category.slug}`}>
|
||||
<h2>{category.name}</h2>
|
||||
</LocaleLink>
|
||||
</ToggleGroupItem>
|
||||
|
||||
{categoryList.map((category) => (
|
||||
<ToggleGroupItem
|
||||
key={category.slug}
|
||||
value={category.slug}
|
||||
className={cn(
|
||||
"rounded-full px-5",
|
||||
"data-[state=on]:bg-primary data-[state=on]:text-primary-foreground",
|
||||
"hover:bg-muted hover:text-muted-foreground",
|
||||
)}
|
||||
aria-label={`Toggle blog category of ${category.name}`}
|
||||
>
|
||||
{/* TODO: fix as any */}
|
||||
<Link href={`/blog/category/${category.slug}` as any}>
|
||||
<h2>{category.name}</h2>
|
||||
</Link>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import clsx from "clsx";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
/**
|
||||
* This component is used to render a link in the navigation bar.
|
||||
*
|
||||
* https://next-intl.dev/docs/routing/navigation#link
|
||||
*/
|
||||
export default function NavigationLink({
|
||||
href,
|
||||
...rest
|
||||
}: ComponentProps<typeof Link>) {
|
||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||
const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : "/";
|
||||
const isActive = pathname === href;
|
||||
|
||||
return (
|
||||
<Link
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
className={clsx(
|
||||
"inline-block px-2 py-3 transition-colors",
|
||||
isActive ? "text-white" : "text-gray-400 hover:text-gray-200"
|
||||
)}
|
||||
href={href}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocaleLink } from "@/i18n/navigation";
|
||||
|
||||
interface FilterItemMobileProps {
|
||||
title: string;
|
||||
@ -18,8 +18,8 @@ export default function FilterItemMobile({
|
||||
}: FilterItemMobileProps) {
|
||||
return (
|
||||
<li className="mb-1 last:mb-0">
|
||||
<Link
|
||||
href={href as any}
|
||||
<LocaleLink
|
||||
href={href}
|
||||
onClick={clickAction}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-md px-3 py-2 text-sm hover:bg-muted",
|
||||
@ -27,7 +27,7 @@ export default function FilterItemMobile({
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</LocaleLink>
|
||||
</li>
|
||||
);
|
||||
}
|
@ -6,5 +6,5 @@ import { routing } from "./routing";
|
||||
*
|
||||
* https://next-intl.dev/docs/routing/navigation
|
||||
*/
|
||||
export const { Link, getPathname, redirect, usePathname, useRouter } =
|
||||
export const { Link: LocaleLink, getPathname, redirect, usePathname, useRouter } =
|
||||
createNavigation(routing);
|
||||
|
@ -33,17 +33,10 @@ export const routing = defineRouting({
|
||||
localePrefix: "as-needed",
|
||||
// The pathnames for each locale
|
||||
// https://next-intl.dev/docs/routing#pathnames
|
||||
// whenever use pathname in LocaleLink, you need to add it here
|
||||
pathnames: {
|
||||
"/": "/",
|
||||
"/blog": "/blog",
|
||||
"/blog/[...slug]": "/blog/[...slug]",
|
||||
"/blog/category/[slug]": "/blog/category/[slug]",
|
||||
// 认证相关路径
|
||||
"/auth/login": "/auth/login",
|
||||
"/auth/register": "/auth/register",
|
||||
"/auth/forgot-password": "/auth/forgot-password",
|
||||
"/auth/reset-password": "/auth/reset-password",
|
||||
"/auth/error": "/auth/error",
|
||||
},
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user