Merge remote-tracking branch 'origin/master' into feature/slicing
This commit is contained in:
commit
333fa32eda
@ -11,8 +11,6 @@ type TParameters = {
|
|||||||
slug: string
|
slug: string
|
||||||
} & THttpServer
|
} & THttpServer
|
||||||
|
|
||||||
export type TNewDetailResponse = z.infer<typeof dataResponseSchema>
|
|
||||||
|
|
||||||
export const getNewsBySlug = async (parameters: TParameters) => {
|
export const getNewsBySlug = async (parameters: TParameters) => {
|
||||||
const { slug, accessToken } = parameters
|
const { slug, accessToken } = parameters
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
import { Link } from 'react-router'
|
|
||||||
|
|
||||||
type BreadcrumbProperty = {
|
|
||||||
slug: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Breadcrumb = (property: BreadcrumbProperty) => {
|
|
||||||
const { slug } = property
|
|
||||||
return (
|
|
||||||
<div className="mb-5 flex items-center gap-2">
|
|
||||||
<Link to={'#'}>Blog</Link>
|
|
||||||
<div>{'>'}</div>
|
|
||||||
<Link
|
|
||||||
to={'#'}
|
|
||||||
className="text-ellipsis md:text-clip ..."
|
|
||||||
>
|
|
||||||
{slug.slice(0, 20) + '...'}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -25,16 +25,16 @@ export const Navbar = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-x-8">
|
<div className="flex items-center gap-x-8">
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<PopoverButton className="flex w-3xs cursor-pointer items-center justify-between focus:outline-none">
|
<PopoverButton className="flex w-3xs cursor-pointer items-center justify-between rounded-xl p-2 ring-2 ring-[#707FDD]/10 hover:shadow focus:outline-none">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center space-x-3">
|
||||||
{staffData?.profile_picture === '' ? (
|
{staffData?.profile_picture ? (
|
||||||
<ProfileIcon className="mr-3 h-8 w-8 rounded-full bg-[#C4C4C4]" />
|
|
||||||
) : (
|
|
||||||
<img
|
<img
|
||||||
src={staffData?.profile_picture}
|
src={staffData?.profile_picture}
|
||||||
alt={staffData?.name}
|
alt={staffData?.name}
|
||||||
className="mr-3 h-8 w-8 rounded-full bg-[#C4C4C4] object-cover"
|
className="h-8 w-8 rounded-full bg-[#C4C4C4] object-cover"
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<ProfileIcon className="h-8 w-8 rounded-full bg-[#C4C4C4]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<span className="text-xs">{staffData?.name}</span>
|
<span className="text-xs">{staffData?.name}</span>
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export const TagsPage = () => {
|
|||||||
className="text-md h-[42px] rounded-md"
|
className="text-md h-[42px] rounded-md"
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
Buat Tags
|
Buat Tag
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export const TagsPage = () => {
|
|||||||
columns={dataColumns}
|
columns={dataColumns}
|
||||||
options={dataOptions}
|
options={dataOptions}
|
||||||
slots={dataSlot}
|
slots={dataSlot}
|
||||||
title="Daftar Katgeori"
|
title="Daftar Tags"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
import type { TNewsDetail } from '~/types/news'
|
|
||||||
|
|
||||||
export const CONTENT: TNewsDetail = {
|
|
||||||
title: 'Hotman Paris Membuka Perpustakaan di tengah Diskotik',
|
|
||||||
content: ` <section>
|
|
||||||
<h1>Introduction</h1>
|
|
||||||
</secti>
|
|
||||||
<section>
|
|
||||||
<p>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.</p>
|
|
||||||
<p>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.</p>
|
|
||||||
<figure>
|
|
||||||
<img src="/images/dummy-image.png" alt="Image caption goes here">
|
|
||||||
<figcaption>Image caption goes here</figcaption>
|
|
||||||
</figure>
|
|
||||||
<p>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.</p>
|
|
||||||
<blockquote>
|
|
||||||
<p>"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."</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>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.</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Conclusion</h2>
|
|
||||||
<p>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.</p>
|
|
||||||
<p>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.</p>
|
|
||||||
<p>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.</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<span>MOM</span>
|
|
||||||
<span>FOOD</span>
|
|
||||||
<span>BOOKS</span>
|
|
||||||
<span>WORDPRESS</span>
|
|
||||||
</section>`,
|
|
||||||
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'],
|
|
||||||
}
|
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import htmlParse from 'html-react-parser'
|
import htmlParse from 'html-react-parser'
|
||||||
|
import { useReadingTime } from 'react-hook-reading-time'
|
||||||
import { useRouteLoaderData } from 'react-router'
|
import { useRouteLoaderData } from 'react-router'
|
||||||
|
|
||||||
import type { TTagResponse } from '~/apis/common/get-tags'
|
import type { TTagResponse } from '~/apis/common/get-tags'
|
||||||
|
import { ProfileIcon } from '~/components/icons/profile'
|
||||||
import { Card } from '~/components/ui/card'
|
import { Card } from '~/components/ui/card'
|
||||||
import { CarouselSection } from '~/components/ui/carousel-section'
|
import { CarouselSection } from '~/components/ui/carousel-section'
|
||||||
import { SocialShareButtons } from '~/components/ui/social-share'
|
import { SocialShareButtons } from '~/components/ui/social-share'
|
||||||
@ -14,10 +16,11 @@ export const NewsDetailPage = () => {
|
|||||||
'routes/_news.detail.$slug',
|
'routes/_news.detail.$slug',
|
||||||
)
|
)
|
||||||
const currentUrl = globalThis.location
|
const currentUrl = globalThis.location
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const { newsDetailData } = loaderData || {}
|
||||||
const { newsDetailData }: any = loaderData
|
|
||||||
const { title, content, featured_image, author, live_at, tags } =
|
const { title, content, featured_image, author, live_at, tags } =
|
||||||
newsDetailData
|
newsDetailData || {}
|
||||||
|
|
||||||
|
const { text } = useReadingTime(content || '')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sm-max:mx-5 relative">
|
<div className="sm-max:mx-5 relative">
|
||||||
@ -27,17 +30,26 @@ export const NewsDetailPage = () => {
|
|||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* next planing create component for this section */}
|
{/* START TODO: create component for this section */}
|
||||||
<div className="my-5 w-full items-center justify-between gap-2 align-middle sm:flex">
|
<div className="my-5 w-full items-center justify-between gap-2 align-middle sm:flex">
|
||||||
<div className="mb-2 flex items-center gap-2 align-middle">
|
<div className="mb-2 flex items-center gap-2 align-middle">
|
||||||
<img
|
{author?.profile_picture ? (
|
||||||
src={author.avatar}
|
<img
|
||||||
alt={author.name}
|
src={author?.profile_picture}
|
||||||
className="h-12 w-12 rounded-full"
|
alt={author?.name}
|
||||||
/>
|
className="h-12 w-12 rounded-full bg-[#C4C4C4] object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ProfileIcon className="h-12 w-12 rounded-full bg-[#C4C4C4]" />
|
||||||
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-md">{author.name}</h4>
|
<h4 className="text-md">{author?.name}</h4>
|
||||||
<p className="text-sm">{formatDate(live_at)} . 5 min read </p>
|
<p className="flex gap-1 text-sm">
|
||||||
|
<span>{live_at && `${formatDate(live_at)}`}</span>
|
||||||
|
<span>·</span>
|
||||||
|
<span>{text}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <IconsSocial className="flex-row" /> */}
|
{/* <IconsSocial className="flex-row" /> */}
|
||||||
@ -46,7 +58,7 @@ export const NewsDetailPage = () => {
|
|||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* end next planing create component for this section */}
|
{/* END TODO: create component for this section */}
|
||||||
<div className="w-full bg-amber-200">
|
<div className="w-full bg-amber-200">
|
||||||
<img
|
<img
|
||||||
src={featured_image}
|
src={featured_image}
|
||||||
@ -57,7 +69,7 @@ export const NewsDetailPage = () => {
|
|||||||
|
|
||||||
<div className="mt-8 flex items-center justify-center">
|
<div className="mt-8 flex items-center justify-center">
|
||||||
<article className="prose prose-stone">
|
<article className="prose prose-stone">
|
||||||
{htmlParse(content)}
|
{content && htmlParse(content)}
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div className="items-end justify-between border-b-gray-300 py-4 sm:flex">
|
<div className="items-end justify-between border-b-gray-300 py-4 sm:flex">
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { getNewsBySlug } from '~/apis/common/get-news-by-slug'
|
import { getNewsBySlug } from '~/apis/common/get-news-by-slug'
|
||||||
import { getUser } from '~/apis/news/get-user'
|
import { getUser } from '~/apis/news/get-user'
|
||||||
|
import { APP } from '~/configs/meta'
|
||||||
import { handleCookie } from '~/libs/cookies'
|
import { handleCookie } from '~/libs/cookies'
|
||||||
import { NewsDetailPage } from '~/pages/news-detail'
|
import { NewsDetailPage } from '~/pages/news-detail'
|
||||||
|
|
||||||
import type { Route } from './+types/_news.detail.$slug'
|
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)
|
const { userToken } = await handleCookie(request)
|
||||||
let userData
|
let userData
|
||||||
if (userToken) {
|
if (userToken) {
|
||||||
@ -14,21 +15,30 @@ export const loader = async ({ request }: Route.LoaderArgs) => {
|
|||||||
})
|
})
|
||||||
userData = data
|
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({
|
const { data: newsDetailData } = await getNewsBySlug({
|
||||||
slug: request.url.split('/').pop() ?? '',
|
slug: params.slug,
|
||||||
accessToken: userToken,
|
accessToken: userToken,
|
||||||
})
|
})
|
||||||
|
|
||||||
// const { data: categoriesData } = await getCategories()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
newsDetailData,
|
newsDetailData,
|
||||||
userData,
|
userData,
|
||||||
// categoriesData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const meta = ({ data }: Route.MetaArgs) => {
|
||||||
|
const { newsDetailData } = data
|
||||||
|
const metaTitle = APP.title
|
||||||
|
const title = `${newsDetailData.title} - ${metaTitle}`
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const NewsDetailLayout = () => <NewsDetailPage />
|
const NewsDetailLayout = () => <NewsDetailPage />
|
||||||
|
|
||||||
export default NewsDetailLayout
|
export default NewsDetailLayout
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export type TNews = {
|
|||||||
>[]
|
>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TNewsDetail = {
|
type TNewsDetail = {
|
||||||
title: string
|
title: string
|
||||||
content: string
|
content: string
|
||||||
featured: string
|
featured: string
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
|
"react-hook-reading-time": "^1.0.0",
|
||||||
"react-router": "^7.1.3",
|
"react-router": "^7.1.3",
|
||||||
"react-share": "^5.2.2",
|
"react-share": "^5.2.2",
|
||||||
"remix-hook-form": "^6.1.3",
|
"remix-hook-form": "^6.1.3",
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -92,6 +92,9 @@ importers:
|
|||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.54.2
|
specifier: ^7.54.2
|
||||||
version: 7.54.2(react@19.0.0)
|
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:
|
react-router:
|
||||||
specifier: ^7.1.3
|
specifier: ^7.1.3
|
||||||
version: 7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
@ -3894,6 +3897,13 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
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:
|
react-hotkeys-hook@4.6.1:
|
||||||
resolution: {integrity: sha512-XlZpbKUj9tkfgPgT9gA+1p7Ey6vFIZHttUjPqpTdyT5nqQ8mHL7elxvSbaC+dpSiHUSmr21Ya1mDxBZG3aje4Q==}
|
resolution: {integrity: sha512-XlZpbKUj9tkfgPgT9gA+1p7Ey6vFIZHttUjPqpTdyT5nqQ8mHL7elxvSbaC+dpSiHUSmr21Ya1mDxBZG3aje4Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -8628,6 +8638,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 19.0.0
|
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):
|
react-hotkeys-hook@4.6.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user