From 1c6bf68b1e40c52604eaf330d748f1955c97ec6a Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 15:02:11 +0800 Subject: [PATCH 1/7] feat: remove unused breadcrumb and news detail data components, update TNewsDetail type definition --- app/apis/common/get-news-by-slug.ts | 2 -- app/components/ui/breadcrumb.tsx | 21 ---------------- app/pages/news-detail/data.ts | 39 ----------------------------- app/types/news.ts | 2 +- 4 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 app/components/ui/breadcrumb.tsx delete mode 100644 app/pages/news-detail/data.ts diff --git a/app/apis/common/get-news-by-slug.ts b/app/apis/common/get-news-by-slug.ts index cf40352..de39135 100644 --- a/app/apis/common/get-news-by-slug.ts +++ b/app/apis/common/get-news-by-slug.ts @@ -11,8 +11,6 @@ type TParameters = { slug: string } & THttpServer -export type TNewDetailResponse = z.infer - export const getNewsBySlug = async (parameters: TParameters) => { const { slug, accessToken } = parameters try { diff --git a/app/components/ui/breadcrumb.tsx b/app/components/ui/breadcrumb.tsx deleted file mode 100644 index 547aa5c..0000000 --- a/app/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Link } from 'react-router' - -type BreadcrumbProperty = { - slug: string -} - -export const Breadcrumb = (property: BreadcrumbProperty) => { - const { slug } = property - return ( -
- Blog -
{'>'}
- - {slug.slice(0, 20) + '...'} - -
- ) -} diff --git a/app/pages/news-detail/data.ts b/app/pages/news-detail/data.ts deleted file mode 100644 index 111f4fc..0000000 --- a/app/pages/news-detail/data.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { TNewsDetail } from '~/types/news' - -export const CONTENT: TNewsDetail = { - title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik', - content: `
-

Introduction

- -
-

Mi tincidunt elit, id quisque ligula ac diam, amet. Vel etiam suspendisse morbi eleifend faucibus eget vestibulum felis. Dictum quis montes, sit sit. Tellus aliquam enim urna, etiam. Mauris posuere vulputate arcu amet, vitae nisi, tellus tincidunt. At feugiat sapien varius id.

-

Eget quis mi enim, leo lacinia pharetra, semper. Eget in volutpat mollis at volutpat lectus velit, sed auctor. Porttitor fames arcu quis fusce augue enim. Quis at habitant diam at. Suscipit tristique risus, at donec. In turpis vel et quam imperdiet. Ipsum molestie aliquet sodales id est ac volutpat.

-
- Image caption goes here -
Image caption goes here
-
-

Dolor enim eu tortor urna sed duis nulla. Aliquam vestibulum, nulla odio nisl vitae. In aliquet pellentesque aenean hac vestibulum turpis mi bibendum diam. Tempor integer aliquam in vitae malesuada fringilla.

-
-

"Ipsum sit mattis nulla quam nulla. Gravida id gravida ac enim mauris id. Non pellentesque congue eget consectetur turpis. Sapien, dictum molestie sem tempor. Diam elit, orci, tincidunt aenean tempus."

-
-

Tristique odio senectus nam posuere ornare leo metus, ultricies. Blandit duis ultricies vulputate morbi feugiat cras placerat elit. Aliquam tellus lorem sed ac. Montes, sed mattis pellentesque suscipit accumsan. Cursus viverra aenean magna risus elementum faucibus molestie pellentesque. Arcu ultricies sed mauris vestibulum.

-
-
-

Conclusion

-

Morbi sed imperdiet in ipsum, adipiscing elit dui lectus. Tellus id scelerisque est ultricies ultricies. Duis est sit sed leo nisl, blandit elit sagittis. Quisque tristique consequat quam sed. Nisl at scelerisque amet nulla purus habitasse.

-

Nunc sed faucibus bibendum feugiat sed interdum. Ipsum egestas condimentum mi massa. In tincidunt pharetra consectetur sed duis facilisis metus. Etiam egestas in nec sed et. Quis lobortis at sit dictum eget nibh tortor commodo cursus.

-

Odio felis sagittis, morbi feugiat tortor vitae feugiat fusce aliquet. Nam elementum urna nisi aliquet erat dolor enim. Ornare id morbi eget ipsum. Aliquam senectus neque ut id eget consectetur dictum. Donec posuere pharetra odio consequat scelerisque et, nunc tortor. Nulla adipiscing erat a erat. Condimentum lorem posuere gravida enim posuere cursus diam.

-
-
- MOM - FOOD - BOOKS - WORDPRESS -
`, - featured: '/images/news-1.jpg', - slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik', - author: 'John Doe', - date: new Date(), - categories: [], - tags: ['Category', 'Popular', 'Trending', 'Latest'], -} diff --git a/app/types/news.ts b/app/types/news.ts index d04e998..32d2d5a 100644 --- a/app/types/news.ts +++ b/app/types/news.ts @@ -7,7 +7,7 @@ export type TNews = { >[] } -export type TNewsDetail = { +type TNewsDetail = { title: string content: string featured: string From 609866beefabb439df5cc94979a6c84a902b0f18 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 15:03:45 +0800 Subject: [PATCH 2/7] feat: update news detail loader to use route parameters and fix typo in TODO comment --- app/routes/_news.detail.$slug.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/routes/_news.detail.$slug.tsx b/app/routes/_news.detail.$slug.tsx index cad0d64..27c2d25 100644 --- a/app/routes/_news.detail.$slug.tsx +++ b/app/routes/_news.detail.$slug.tsx @@ -5,7 +5,7 @@ import { NewsDetailPage } from '~/pages/news-detail' import type { Route } from './+types/_news.detail.$slug' -export const loader = async ({ request }: Route.LoaderArgs) => { +export const loader = async ({ request, params }: Route.LoaderArgs) => { const { userToken } = await handleCookie(request) let userData if (userToken) { @@ -14,18 +14,15 @@ export const loader = async ({ request }: Route.LoaderArgs) => { }) userData = data } - // TODO need handel if user not accses non premium data + // TODO need handle if user not access non premium data const { data: newsDetailData } = await getNewsBySlug({ - slug: request.url.split('/').pop() ?? '', + slug: params.slug, accessToken: userToken, }) - // const { data: categoriesData } = await getCategories() - return { newsDetailData, userData, - // categoriesData, } } From 4b61fd9501d667811dc438292d5e68bf3b5cce23 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 15:23:22 +0800 Subject: [PATCH 3/7] feat: enhance navbar and news detail page with improved profile picture handling --- app/layouts/admin/navbar.tsx | 12 +++++------ app/pages/news-detail/index.tsx | 35 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/app/layouts/admin/navbar.tsx b/app/layouts/admin/navbar.tsx index 72ab2b3..2b38350 100644 --- a/app/layouts/admin/navbar.tsx +++ b/app/layouts/admin/navbar.tsx @@ -25,16 +25,16 @@ export const Navbar = () => {
- -
- {staffData?.profile_picture === '' ? ( - - ) : ( + +
+ {staffData?.profile_picture ? ( {staffData?.name} + ) : ( + )} {staffData?.name} diff --git a/app/pages/news-detail/index.tsx b/app/pages/news-detail/index.tsx index 6542ff8..632796e 100644 --- a/app/pages/news-detail/index.tsx +++ b/app/pages/news-detail/index.tsx @@ -2,6 +2,7 @@ import htmlParse from 'html-react-parser' import { useRouteLoaderData } from 'react-router' import type { TTagResponse } from '~/apis/common/get-tags' +import { ProfileIcon } from '~/components/icons/profile' import { Card } from '~/components/ui/card' import { CarouselSection } from '~/components/ui/carousel-section' import { IconsSocial } from '~/components/ui/social-share' @@ -14,10 +15,9 @@ export const NewsDetailPage = () => { 'routes/_news.detail.$slug', ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { newsDetailData }: any = loaderData + const { newsDetailData } = loaderData || {} const { title, content, featured_image, slug, author, live_at, tags } = - newsDetailData + newsDetailData || {} return (
@@ -27,22 +27,31 @@ export const NewsDetailPage = () => { {title} - {/* next planing create component for this section */} + {/* START TODO: create component for this section */}
- {title} + {author?.profile_picture ? ( + {author?.name} + ) : ( + + )} +
-

{author.name}

-

{formatDate(live_at)} . 5 min read

+

{author?.name}

+

+ {live_at && `${formatDate(live_at)}`} + · + 5 min read +

- {/* end next planing create component for this section */} + {/* END TODO: create component for this section */}
{
- {htmlParse(content)} + {content && htmlParse(content)}
From be633370245d2f64c25416c2f42a2910a11da3af Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 15:25:20 +0800 Subject: [PATCH 4/7] fix: correct button label and table title in TagsPage component --- app/pages/dashboard-tags/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/pages/dashboard-tags/index.tsx b/app/pages/dashboard-tags/index.tsx index e3825e6..3cdde55 100644 --- a/app/pages/dashboard-tags/index.tsx +++ b/app/pages/dashboard-tags/index.tsx @@ -71,7 +71,7 @@ export const TagsPage = () => { className="text-md h-[42px] rounded-md" size="lg" > - Buat Tags + Buat Tag
@@ -80,7 +80,7 @@ export const TagsPage = () => { columns={dataColumns} options={dataOptions} slots={dataSlot} - title="Daftar Katgeori" + title="Daftar Tags" />
) From 6e858f1860db311b7a0486b691617643d5dcb603 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 15:34:38 +0800 Subject: [PATCH 5/7] feat: add reading time calculation to news detail page --- app/pages/news-detail/index.tsx | 5 ++++- package.json | 1 + pnpm-lock.yaml | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/pages/news-detail/index.tsx b/app/pages/news-detail/index.tsx index 632796e..bf108df 100644 --- a/app/pages/news-detail/index.tsx +++ b/app/pages/news-detail/index.tsx @@ -1,4 +1,5 @@ import htmlParse from 'html-react-parser' +import { useReadingTime } from 'react-hook-reading-time' import { useRouteLoaderData } from 'react-router' import type { TTagResponse } from '~/apis/common/get-tags' @@ -19,6 +20,8 @@ export const NewsDetailPage = () => { const { title, content, featured_image, slug, author, live_at, tags } = newsDetailData || {} + const { text } = useReadingTime(content || '') + return (
@@ -45,7 +48,7 @@ export const NewsDetailPage = () => {

{live_at && `${formatDate(live_at)}`} · - 5 min read + {text}

diff --git a/package.json b/package.json index 707b41b..3464cf6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-colorful": "^5.6.1", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", + "react-hook-reading-time": "^1.0.0", "react-router": "^7.1.3", "remix-hook-form": "^6.1.3", "tailwind-merge": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c41056b..193c481 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@19.0.0) + react-hook-reading-time: + specifier: ^1.0.0 + version: 1.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-router: specifier: ^7.1.3 version: 7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -3888,6 +3891,13 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-hook-reading-time@1.0.0: + resolution: {integrity: sha512-kIudDiGHCTzBlV95WM6xPN3EBUViQynJvcul/NL+8My6fsl0BeKoCi9Dp19g69PlyF3WLA3QAyjVL99+Ucgs6A==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: ^16.13.1 + react-dom: ^16.13.1 + react-hotkeys-hook@4.6.1: resolution: {integrity: sha512-XlZpbKUj9tkfgPgT9gA+1p7Ey6vFIZHttUjPqpTdyT5nqQ8mHL7elxvSbaC+dpSiHUSmr21Ya1mDxBZG3aje4Q==} peerDependencies: @@ -8611,6 +8621,11 @@ snapshots: dependencies: react: 19.0.0 + react-hook-reading-time@1.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-hotkeys-hook@4.6.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: react: 19.0.0 From f3be8138c40a9b4481c0b5cf926fd5d5de65ed22 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 17:24:53 +0800 Subject: [PATCH 6/7] feat: add meta title generation for news detail page --- app/routes/_news.detail.$slug.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/routes/_news.detail.$slug.tsx b/app/routes/_news.detail.$slug.tsx index 27c2d25..d8a3ee8 100644 --- a/app/routes/_news.detail.$slug.tsx +++ b/app/routes/_news.detail.$slug.tsx @@ -1,5 +1,6 @@ import { getNewsBySlug } from '~/apis/common/get-news-by-slug' import { getUser } from '~/apis/news/get-user' +import { APP } from '~/configs/meta' import { handleCookie } from '~/libs/cookies' import { NewsDetailPage } from '~/pages/news-detail' @@ -26,6 +27,18 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => { } } +export const meta = ({ data }: Route.MetaArgs) => { + const { newsDetailData } = data + const metaTitle = APP.title + const title = `${newsDetailData.title} - ${metaTitle}` + + return [ + { + title, + }, + ] +} + const NewsDetailLayout = () => export default NewsDetailLayout From aad67720c118512193171fa5aecf76d3f7f33723 Mon Sep 17 00:00:00 2001 From: Ardeman Date: Sat, 8 Mar 2025 17:25:24 +0800 Subject: [PATCH 7/7] fix: correct TODO comment formatting in news detail loader --- app/routes/_news.detail.$slug.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/_news.detail.$slug.tsx b/app/routes/_news.detail.$slug.tsx index d8a3ee8..7ca1c66 100644 --- a/app/routes/_news.detail.$slug.tsx +++ b/app/routes/_news.detail.$slug.tsx @@ -15,7 +15,7 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => { }) userData = data } - // TODO need handle if user not access non premium data + // TODO: need handle if user not access non premium data const { data: newsDetailData } = await getNewsBySlug({ slug: params.slug, accessToken: userToken,