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/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/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" />
) 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/pages/news-detail/index.tsx b/app/pages/news-detail/index.tsx index d1a4c19..dd7cff9 100644 --- a/app/pages/news-detail/index.tsx +++ b/app/pages/news-detail/index.tsx @@ -1,7 +1,9 @@ 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' +import { ProfileIcon } from '~/components/icons/profile' import { Card } from '~/components/ui/card' import { CarouselSection } from '~/components/ui/carousel-section' import { SocialShareButtons } from '~/components/ui/social-share' @@ -14,10 +16,11 @@ export const NewsDetailPage = () => { 'routes/_news.detail.$slug', ) const currentUrl = globalThis.location - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { newsDetailData }: any = loaderData + const { newsDetailData } = loaderData || {} const { title, content, featured_image, author, live_at, tags } = - newsDetailData + newsDetailData || {} + + const { text } = useReadingTime(content || '') return (
@@ -27,17 +30,26 @@ export const NewsDetailPage = () => { {title} - {/* next planing create component for this section */} + {/* START TODO: create component for this section */}
- {author.name} + {author?.profile_picture ? ( + {author?.name} + ) : ( + + )} +
-

{author.name}

-

{formatDate(live_at)} . 5 min read

+

{author?.name}

+

+ {live_at && `${formatDate(live_at)}`} + ยท + {text} +

{/* */} @@ -46,7 +58,7 @@ export const NewsDetailPage = () => { title={title} />
- {/* end next planing create component for this section */} + {/* END TODO: create component for this section */}
{
- {htmlParse(content)} + {content && htmlParse(content)}
diff --git a/app/routes/_news.detail.$slug.tsx b/app/routes/_news.detail.$slug.tsx index cad0d64..7ca1c66 100644 --- a/app/routes/_news.detail.$slug.tsx +++ b/app/routes/_news.detail.$slug.tsx @@ -1,11 +1,12 @@ 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' 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,21 +15,30 @@ 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, } } +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 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 diff --git a/package.json b/package.json index e09abeb..871dc8e 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", "react-share": "^5.2.2", "remix-hook-form": "^6.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b000427..7f950c1 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) @@ -3894,6 +3897,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: @@ -8628,6 +8638,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