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
+2 -2
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: {
+4
View File
@@ -23,6 +23,10 @@ body {
@apply outline-none;
}
.ProseMirror-trailingBreak {
@apply hidden;
}
table.dataTable thead > tr {
border-bottom: 2px solid #c2c2c2;
}
+8 -2
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()}
+18 -8
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,
+3 -1
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',
+3 -5
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>
+1 -1
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>
@@ -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 }
}
+16 -1
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 />