diff --git a/src/app/(home)/_components/ArticleFeed.tsx b/src/app/(home)/_components/ArticleFeed.tsx index a4b6e84..20c56ec 100644 --- a/src/app/(home)/_components/ArticleFeed.tsx +++ b/src/app/(home)/_components/ArticleFeed.tsx @@ -1,14 +1,11 @@ "use client"; import * as articleActions from "@/backend/services/article.actions"; -import * as seriesActions from "@/backend/services/series.action"; import ArticleCard from "@/components/ArticleCard"; -import SeriesCard from "@/components/SeriesCard"; import VisibilitySensor from "@/components/VisibilitySensor"; import { readingTime } from "@/lib/utils"; import getFileUrl from "@/utils/getFileUrl"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { Loader } from "lucide-react"; import { useState } from "react"; const ArticleFeed = () => { @@ -53,10 +50,22 @@ const ArticleFeed = () => {
{articleFeedQuery.isPending && ( <> -
-
-
-
+
+
+
+
)} diff --git a/src/app/(home)/_components/HomeLeftSidebar.tsx b/src/app/(home)/_components/HomeLeftSidebar.tsx index 32aaea0..c40c724 100644 --- a/src/app/(home)/_components/HomeLeftSidebar.tsx +++ b/src/app/(home)/_components/HomeLeftSidebar.tsx @@ -96,7 +96,7 @@ const tags = [ { icon: "https://res.cloudinary.com/techdiary-dev/image/upload/v1620782239/static-assets/tag-icons/erfbu54l2mquphszheck.svg", label: "react", - link: "/tags/186e052a-9c5b-4ffe-b753-ea172ac2e663", + link: "/tags/reactjs", }, { icon: "https://res.cloudinary.com/techdiary-dev/image/upload/v1620782240/static-assets/tag-icons/rh7xfiz28bxklfzymftd.svg", diff --git a/src/app/tags/[tag_id]/_components/TagArticleFeed.tsx b/src/app/tags/[tag_name]/_components/TagArticleFeed.tsx similarity index 76% rename from src/app/tags/[tag_id]/_components/TagArticleFeed.tsx rename to src/app/tags/[tag_name]/_components/TagArticleFeed.tsx index 7dea926..9655e11 100644 --- a/src/app/tags/[tag_id]/_components/TagArticleFeed.tsx +++ b/src/app/tags/[tag_name]/_components/TagArticleFeed.tsx @@ -1,23 +1,26 @@ "use client"; +import { Tag } from "@/backend/models/domain-models"; import * as articleActions from "@/backend/services/article.actions"; import ArticleCard from "@/components/ArticleCard"; import VisibilitySensor from "@/components/VisibilitySensor"; +import { useTranslation } from "@/i18n/use-translation"; import { readingTime } from "@/lib/utils"; import getFileUrl from "@/utils/getFileUrl"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { useMemo } from "react"; +import React, { useMemo } from "react"; interface TagArticleFeedProps { - tagId: string; + tag: Tag; } -const TagArticleFeed: React.FC = ({ tagId }) => { +const TagArticleFeed: React.FC = ({ tag }) => { + const { _t } = useTranslation(); const tagFeedQuery = useInfiniteQuery({ - queryKey: ["tag-articles", tagId], + queryKey: ["tag-articles", tag.id], queryFn: ({ pageParam }) => articleActions.articlesByTag({ - tag_id: tagId, + tag_id: tag.id, limit: 5, page: pageParam, }), @@ -37,18 +40,14 @@ const TagArticleFeed: React.FC = ({ tagId }) => { return tagFeedQuery.data?.pages?.[0]?.meta?.total ?? 0; }, [tagFeedQuery.data]); - const tagName = useMemo(() => { - return tagFeedQuery.data?.pages?.[0]?.tagName ?? "Unknown Tag"; - }, [tagFeedQuery.data]); - // Show loading skeletons if (tagFeedQuery.isPending) { return (
-
-
-
-
+
+
+
+
); } @@ -78,10 +77,10 @@ const TagArticleFeed: React.FC = ({ tagId }) => { return (

- No articles found + {_t("No articles found")}

- No articles have been tagged with “{tagName}” yet. + {_t(`No articles have been tagged with "$" yet.`, [tag.name])}

); @@ -91,10 +90,10 @@ const TagArticleFeed: React.FC = ({ tagId }) => { <>

- Articles tagged with “{tagName}” + {_t(`Articles tagged with "$"`, [tag.name])}

- Found {totalArticles} articles + {_t(`Found $ articles`, [totalArticles])}

@@ -106,7 +105,9 @@ const TagArticleFeed: React.FC = ({ tagId }) => { handle={article?.handle ?? ""} title={article?.title ?? ""} excerpt={article?.excerpt ?? ""} - coverImage={article?.cover_image ? getFileUrl(article.cover_image) : ""} + coverImage={ + article?.cover_image ? getFileUrl(article.cover_image) : "" + } author={{ id: article?.user?.id ?? "", name: article?.user?.name ?? "", @@ -133,4 +134,4 @@ const TagArticleFeed: React.FC = ({ tagId }) => { ); }; -export default TagArticleFeed; \ No newline at end of file +export default TagArticleFeed; diff --git a/src/app/tags/[tag_id]/page.tsx b/src/app/tags/[tag_name]/page.tsx similarity index 70% rename from src/app/tags/[tag_id]/page.tsx rename to src/app/tags/[tag_name]/page.tsx index a4c20fb..9d8f323 100644 --- a/src/app/tags/[tag_id]/page.tsx +++ b/src/app/tags/[tag_name]/page.tsx @@ -3,15 +3,22 @@ import HomeRightSidebar from "@/app/(home)/_components/HomeRightSidebar"; import SidebarToggleButton from "@/app/(home)/_components/SidebarToggleButton"; import HomepageLayout from "@/components/layout/HomepageLayout"; import TagArticleFeed from "./_components/TagArticleFeed"; +import { getTag, getTags } from "@/backend/services/tag.action"; +import { notFound } from "next/navigation"; interface TagPageProps { params: Promise<{ - tag_id: string; + tag_name: string; }>; } export default async function TagPage({ params }: TagPageProps) { - const { tag_id } = await params; + const { tag_name } = await params; + const tag = await getTag({ name: tag_name }); + + if (!tag?.success) { + throw notFound(); + } return ( } >
- +
); } export async function generateMetadata({ params }: TagPageProps) { - const { tag_id } = await params; + const { tag_name } = await params; // For now, use tag_id in the title. Later we can fetch the tag name if needed return { - title: `Tag ${tag_id} - Tech Diary`, + title: `Tag ${tag_name} - Tech Diary`, description: `Browse all articles with this tag on Tech Diary`, openGraph: { - title: `Tag ${tag_id} - Tech Diary`, + title: `Tag ${tag_name} - Tech Diary`, description: `Browse all articles with this tag on Tech Diary`, }, }; diff --git a/src/backend/services/inputs/tag.input.ts b/src/backend/services/inputs/tag.input.ts index 87d98e3..8e4a732 100644 --- a/src/backend/services/inputs/tag.input.ts +++ b/src/backend/services/inputs/tag.input.ts @@ -12,6 +12,9 @@ export const TagRepositoryInput = { color: z.string().optional().nullable(), description: z.string().optional().nullable(), }), + getTag: z.object({ + name: z.string(), + }), updateInput: z.object({ tag_id: z.string(), name: z.string().optional(), diff --git a/src/backend/services/tag.action.ts b/src/backend/services/tag.action.ts index bc068d7..ae020af 100644 --- a/src/backend/services/tag.action.ts +++ b/src/backend/services/tag.action.ts @@ -25,6 +25,24 @@ export const getTags = async ( } }; +export const getTag = async ( + _input: z.infer +) => { + try { + const input = await TagRepositoryInput.createInput.parseAsync(_input); + const response = await persistenceRepository.tags.find({ + where: eq("name", input.name), + }); + + return { + data: response[0], + success: true as const, + }; + } catch (error) { + handleActionException(error); + } +}; + export const createTag = async ( _input: z.infer ) => {