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:
javayhu 2025-03-08 00:55:03 +08:00
parent 95ddd0a1dc
commit 675323fb69
14 changed files with 114 additions and 165 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>
)
))}

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -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>

View File

@ -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>("");

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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}
/>
);
}

View File

@ -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>
);
}

View File

@ -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);

View File

@ -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",
},
});