refactor: enhance news handling and improve content display in various components
This commit is contained in:
parent
bad5294030
commit
4847ef2be3
@ -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: {
|
||||
|
||||
@ -23,6 +23,10 @@ body {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.ProseMirror-trailingBreak {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
table.dataTable thead > tr {
|
||||
border-bottom: 2px solid #c2c2c2;
|
||||
}
|
||||
|
||||
@ -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()}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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
103
pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user