refactor: enhance news handling and improve content display in various components

This commit is contained in:
Ardeman 2025-03-09 11:36:47 +08:00
parent bad5294030
commit 4847ef2be3
11 changed files with 161 additions and 26 deletions

View File

@ -35,8 +35,8 @@ type TParameters = {
tags?: string[]
} & THttpServer
export const getNews = async (parameters: TParameters) => {
const { categories, tags, ...restParameters } = parameters
export const getNews = async (parameters?: TParameters) => {
const { categories, tags, ...restParameters } = parameters || {}
try {
const { data } = await HttpServer(restParameters).get(`/api/news`, {
params: {

View File

@ -23,6 +23,10 @@ body {
@apply outline-none;
}
.ProseMirror-trailingBreak {
@apply hidden;
}
table.dataTable thead > tr {
border-bottom: 2px solid #c2c2c2;
}

View File

@ -67,7 +67,13 @@ export const TextEditor = <TFormValues extends Record<string, unknown>>(
const editor = useEditor({
editable: !disabled,
extensions: [
StarterKit,
StarterKit.configure({
paragraph: {
HTMLAttributes: {
class: 'min-h-1',
},
},
}),
Highlight,
Image.configure({
inline: true,
@ -134,7 +140,7 @@ export const TextEditor = <TFormValues extends Record<string, unknown>>(
editor={editor}
id={id ?? generatedId}
className={twMerge(
'prose prose-headings:my-0 prose-p:my-0 max-h-96 max-w-none cursor-text overflow-y-auto rounded-b-md p-2',
'prose prose-headings:my-0.5 prose-p:my-0.5 max-h-96 max-w-none cursor-text overflow-y-auto rounded-b-md p-2',
inputClassName,
)}
onClick={() => editor?.commands.focus()}

View File

@ -1,14 +1,21 @@
import { useRouteLoaderData } from 'react-router'
import { stripHtml } from 'string-strip-html'
import { twMerge } from 'tailwind-merge'
import type { TNewsResponse } from '~/apis/common/get-news'
import { CarouselNextIcon } from '~/components/icons/carousel-next'
import { CarouselPreviousIcon } from '~/components/icons/carousel-previous'
import { Button } from '~/components/ui/button'
import { useNewsContext } from '~/contexts/news'
import type { loader } from '~/routes/_news'
import type { TNews } from '~/types/news'
import { getPremiumAttribute } from '~/utils/render'
type TNews = {
title: string
description: string
items: TNewsResponse[]
}
export const CategorySection = (properties: TNews) => {
const { setIsSuccessOpen } = useNewsContext()
const loaderData = useRouteLoaderData<typeof loader>('routes/_news')
@ -39,7 +46,10 @@ export const CategorySection = (properties: TNews) => {
<div className="grid sm:grid-cols-3 sm:gap-x-8">
{items.map(
({ featured, title, content, tags, slug, isPremium }, index) => (
(
{ featured_image, title, content, tags, slug, is_premium },
index,
) => (
<div
key={index}
className={twMerge('grid sm:gap-x-8')}
@ -48,7 +58,7 @@ export const CategorySection = (properties: TNews) => {
className={twMerge(
'aspect-[174/100] w-full rounded-md object-cover sm:aspect-[5/4]',
)}
src={featured}
src={featured_image}
alt={title}
/>
<div className={twMerge('flex flex-col justify-between gap-4')}>
@ -62,10 +72,10 @@ export const CategorySection = (properties: TNews) => {
key={index}
className="inline-block rounded bg-[#F4F4F4] px-3 py-1 font-bold text-[#777777]"
>
{item}
{item.name}
</span>
))}
{isPremium && (
{is_premium && (
<span className="inline-block rounded bg-[#D1C675] px-3 py-1 font-bold text-[#9D761D]">
Premium Content
</span>
@ -80,14 +90,14 @@ export const CategorySection = (properties: TNews) => {
>
{title}
</h3>
<p className="text-md mt-5 text-[#777777] sm:text-xl">
{content}
<p className="text-md mt-5 line-clamp-3 text-[#777777] sm:text-xl">
{stripHtml(content).result}
</p>
</div>
<Button
size="block"
{...getPremiumAttribute({
isPremium,
isPremium: is_premium,
slug,
onClick: () => setIsSuccessOpen('warning'),
userData,

View File

@ -48,7 +48,9 @@ export const contentSchema = z.object({
content: z.string().min(1, {
message: 'Konten is required',
}),
featured_image: z.string().optional(),
featured_image: z.string().url({
message: 'Gambar Unggulan must be a valid URL',
}),
is_premium: z.boolean().optional(),
live_at: z.string().min(1, {
message: 'Tanggal live is required',

View File

@ -4,14 +4,12 @@ import { Card } from '~/components/ui/card'
import { CategorySection } from '~/components/ui/category-section'
import type { loader } from '~/routes/_news.category.$code'
import { BERITA } from './data'
export const NewsCategoriesPage = () => {
const loaderData = useRouteLoaderData<typeof loader>(
'routes/_news.category.$code',
)
const { name, description } = loaderData?.categoryData || {}
const { items } = BERITA
const { categoryData, newsData } = loaderData || {}
const { name, description } = categoryData || {}
return (
<div className="relative">
@ -19,7 +17,7 @@ export const NewsCategoriesPage = () => {
<CategorySection
title={name || ''}
description={description || ''}
items={items}
items={newsData || []}
/>
</Card>
</div>

View File

@ -49,7 +49,7 @@ export const NewsDetailPage = () => {
</div>
<div className="mt-8 flex items-center justify-center">
<article className="prose prose-stone">
<article className="prose prose-headings:my-0.5 prose-p:my-0.5">
{content && htmlParse(content)}
</article>
</div>

View File

@ -1,14 +1,10 @@
import { getNews } from '~/apis/common/get-news'
import { handleCookie } from '~/libs/cookies'
import { ContentsPage } from '~/pages/dashboard-contents'
import type { Route } from './+types/_admin.lg-admin._dashboard.contents._index'
export const loader = async ({ request }: Route.LoaderArgs) => {
const { staffToken } = await handleCookie(request)
const { data: newsData } = await getNews({
accessToken: staffToken,
})
export const loader = async ({}: Route.LoaderArgs) => {
const { data: newsData } = await getNews()
return { newsData }
}

View File

@ -1,4 +1,6 @@
import { getCategories } from '~/apis/common/get-categories'
import { getNews } from '~/apis/common/get-news'
import { APP } from '~/configs/meta'
import { NewsCategoriesPage } from '~/pages/news-categories'
import type { Route } from './+types/_news.category.$code'
@ -8,7 +10,20 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
const categoryData = categoriesData.find(
(category) => category.code === params.code,
)
return { categoryData }
const { data: newsData } = await getNews({ categories: [params.code] })
return { categoryData, newsData }
}
export const meta = ({ data }: Route.MetaArgs) => {
const { categoryData } = data
const metaTitle = APP.title
const title = `${categoryData?.name} - ${metaTitle}`
return [
{
title,
},
]
}
const NewsCategoriesLayout = () => <NewsCategoriesPage />

View File

@ -46,6 +46,7 @@
"react-router": "^7.1.3",
"react-share": "^5.2.2",
"remix-hook-form": "^6.1.3",
"string-strip-html": "^13.4.12",
"tailwind-merge": "^3.0.1",
"xior": "^0.6.3",
"zod": "^3.24.2"

103
pnpm-lock.yaml generated
View File

@ -104,6 +104,9 @@ importers:
remix-hook-form:
specifier: ^6.1.3
version: 6.1.3(react-dom@19.0.0(react@19.0.0))(react-hook-form@7.54.2(react@19.0.0))(react-router@7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
string-strip-html:
specifier: ^13.4.12
version: 13.4.12
tailwind-merge:
specifier: ^3.0.1
version: 3.0.1
@ -1653,6 +1656,9 @@ packages:
'@types/linkify-it@5.0.0':
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.16':
resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==}
@ -1993,6 +1999,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
codsen-utils@1.6.7:
resolution: {integrity: sha512-M+9D3IhFAk4T8iATX62herVuIx1sp5kskWgxEegKD/JwTTSSGjGQs5Q5J4vVJ4mLcn1uhfxDYv6Yzr8zleHF3w==}
engines: {node: '>=14.18.0'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -2827,6 +2837,9 @@ packages:
html-dom-parser@5.0.13:
resolution: {integrity: sha512-B7JonBuAfG32I7fDouUQEogBrz3jK9gAuN1r1AaXpED6dIhtg/JwiSRhjGL7aOJwRz3HU4efowCjQBaoXiREqg==}
html-entities@2.5.2:
resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
html-react-parser@5.2.2:
resolution: {integrity: sha512-yA5012CJGSFWYZsgYzfr6HXJgDap38/AEP4ra8Cw+WHIi2ZRDXRX/QVYdumRf1P8zKyScKd6YOrWYvVEiPfGKg==}
peerDependencies:
@ -3272,6 +3285,9 @@ packages:
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
@ -3857,6 +3873,22 @@ packages:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
ranges-apply@7.0.19:
resolution: {integrity: sha512-imA03KuTSuSpQtq9SDhavUz7BtiddCPj+fsYM/XpdypRN/s8vyTayKzni6m5nYs7VMds1kSNK1V3jfwVrPUWBQ==}
engines: {node: '>=14.18.0'}
ranges-merge@9.0.18:
resolution: {integrity: sha512-2+6Eh4yxi5sudUmvCdvxVOSdXIXV+Brfutw8chhZmqkT0REqlzilpyQps1S5n8c7f0+idblqSAHGahTbf/Ar5g==}
engines: {node: '>=14.18.0'}
ranges-push@7.0.18:
resolution: {integrity: sha512-wzGHipEklSlY0QloQ88PNt+PkTURIB42PLLcQGY+WyYBlNpnrzps6EYooD3RqNXtdqMQ9kR8IVaF9itRYtuzLA==}
engines: {node: '>=14.18.0'}
ranges-sort@6.0.13:
resolution: {integrity: sha512-M3P0/dUnU3ihLPX2jq0MT2NJA1ls/q6cUAUVPD28xdFFqm3VFarPjTKKhnsBSvYCpZD8HdiElAGAyoPu6uOQjA==}
engines: {node: '>=14.18.0'}
raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
@ -4246,6 +4278,22 @@ packages:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
string-collapse-leading-whitespace@7.0.9:
resolution: {integrity: sha512-lEuTHlogBT9PWipfk0FOyvoMKX8syiE03QoFk5MDh8oS0AJ2C07IlstR5cGkxz48nKkOIuvkC28w9Rx/cVRNDg==}
engines: {node: '>=14.18.0'}
string-left-right@6.0.20:
resolution: {integrity: sha512-dz2mUgmsI7m/FMe+BoxZ2+73X1TUoQvjCdnq8vbIAnHlvWfVZleNUR+lw+QgHA2dlJig+hUWC9bFYdNFGGy2bA==}
engines: {node: '>=14.18.0'}
string-strip-html@13.4.12:
resolution: {integrity: sha512-mr1GM1TFcwDkYwLE7TNkHY+Lf3YFEBa19W9KntZoJJSbrKF07W4xmLkPnqf8cypEGyr+dc1H9hsdTw5VSNVGxg==}
engines: {node: '>=14.18.0'}
string-trim-spaces-only@5.0.12:
resolution: {integrity: sha512-Un5nIO1av+hzfnKGmY+bWe0AD4WH37TuDW+jeMPm81rUvU2r3VPRj9vEKdZkPmuhYAMuKlzarm7jDSKwJKOcpQ==}
engines: {node: '>=14.18.0'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@ -4358,6 +4406,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tiny-lru@11.2.11:
resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==}
engines: {node: '>=12'}
@ -6216,6 +6267,10 @@ snapshots:
'@types/linkify-it@5.0.0': {}
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.16
'@types/lodash@4.17.16': {}
'@types/markdown-it@14.1.2':
@ -6615,6 +6670,10 @@ snapshots:
clsx@2.1.1: {}
codsen-utils@1.6.7:
dependencies:
rfdc: 1.4.1
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -7632,6 +7691,8 @@ snapshots:
domhandler: 5.0.3
htmlparser2: 10.0.0
html-entities@2.5.2: {}
html-react-parser@5.2.2(@types/react@19.0.8)(react@19.0.0):
dependencies:
domhandler: 5.0.3
@ -8061,6 +8122,8 @@ snapshots:
dependencies:
p-locate: 6.0.0
lodash-es@4.17.21: {}
lodash.camelcase@4.3.0: {}
lodash.castarray@4.4.0: {}
@ -8586,6 +8649,25 @@ snapshots:
range-parser@1.2.1: {}
ranges-apply@7.0.19:
dependencies:
ranges-merge: 9.0.18
tiny-invariant: 1.3.3
ranges-merge@9.0.18:
dependencies:
ranges-push: 7.0.18
ranges-sort: 6.0.13
ranges-push@7.0.18:
dependencies:
codsen-utils: 1.6.7
ranges-sort: 6.0.13
string-collapse-leading-whitespace: 7.0.9
string-trim-spaces-only: 5.0.12
ranges-sort@6.0.13: {}
raw-body@2.5.2:
dependencies:
bytes: 3.1.2
@ -9045,6 +9127,25 @@ snapshots:
string-argv@0.3.2: {}
string-collapse-leading-whitespace@7.0.9: {}
string-left-right@6.0.20:
dependencies:
codsen-utils: 1.6.7
rfdc: 1.4.1
string-strip-html@13.4.12:
dependencies:
'@types/lodash-es': 4.17.12
codsen-utils: 1.6.7
html-entities: 2.5.2
lodash-es: 4.17.21
ranges-apply: 7.0.19
ranges-push: 7.0.18
string-left-right: 6.0.20
string-trim-spaces-only@5.0.12: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@ -9174,6 +9275,8 @@ snapshots:
through@2.3.8: {}
tiny-invariant@1.3.3: {}
tiny-lru@11.2.11: {}
tinyexec@0.3.2: {}