refactor: update news types and enhance news data handling in components

This commit is contained in:
Ardeman 2025-03-09 12:32:36 +08:00
parent 9f82779456
commit 7afabdaa03
8 changed files with 101 additions and 148 deletions

View File

@ -1,6 +1,7 @@
import useEmblaCarousel from 'embla-carousel-react' import useEmblaCarousel from 'embla-carousel-react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useRouteLoaderData } from 'react-router' import { useRouteLoaderData } from 'react-router'
import { stripHtml } from 'string-strip-html'
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
import { CarouselButton } from '~/components/ui/button-slide' import { CarouselButton } from '~/components/ui/button-slide'
@ -71,41 +72,43 @@ export const CarouselHero = (properties: TNews) => {
ref={emblaReference} ref={emblaReference}
> >
<div className="embla__container hero flex sm:gap-x-8"> <div className="embla__container hero flex sm:gap-x-8">
{items.map(({ featured, title, content, slug, isPremium }, index) => ( {items.map(
<div ({ featured_image, title, content, slug, is_premium }, index) => (
className="embla__slide hero w-full min-w-0 flex-none" <div
key={index} className="embla__slide hero w-full min-w-0 flex-none"
> key={index}
<div className="max-sm:mt-2 sm:flex"> >
<img <div className="max-sm:mt-2 sm:flex">
className="col-span-2 aspect-[174/100] object-cover" <img
src={featured} className="col-span-2 aspect-[174/100] object-cover"
alt={title} src={featured_image}
/> alt={title}
<div className="flex h-full flex-col justify-between gap-7 sm:px-5"> />
<div> <div className="flex h-full flex-col justify-between gap-7 sm:px-5">
<h3 className="mt-2 w-full text-2xl font-bold sm:mt-0 sm:text-4xl"> <div>
{title} <h3 className="mt-2 w-full text-2xl font-bold sm:mt-0 sm:text-4xl">
</h3> {title}
<p className="text-md mt-5 text-[#777777] sm:text-xl"> </h3>
{content} <p className="text-md mt-5 line-clamp-10 text-[#777777] sm:text-xl">
</p> {stripHtml(content).result}
</p>
</div>
<Button
size="block"
{...getPremiumAttribute({
isPremium: is_premium,
slug,
onClick: () => setIsSuccessOpen('warning'),
userData,
})}
>
View More
</Button>
</div> </div>
<Button
size="block"
{...getPremiumAttribute({
isPremium,
slug,
onClick: () => setIsSuccessOpen('warning'),
userData,
})}
>
View More
</Button>
</div> </div>
</div> </div>
</div> ),
))} )}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,7 @@
import useEmblaCarousel from 'embla-carousel-react' import useEmblaCarousel from 'embla-carousel-react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useRouteLoaderData } from 'react-router' import { useRouteLoaderData } from 'react-router'
import { stripHtml } from 'string-strip-html'
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
import { CarouselButton } from '~/components/ui/button-slide' import { CarouselButton } from '~/components/ui/button-slide'
@ -79,7 +80,10 @@ export const CarouselSection = (properties: TNews) => {
> >
<div className="embla__container col-span-3 flex max-h-[586px] sm:gap-x-8"> <div className="embla__container col-span-3 flex max-h-[586px] sm:gap-x-8">
{items.map( {items.map(
({ featured, title, content, tags, slug, isPremium }, index) => ( (
{ featured_image, title, content, tags, slug, is_premium },
index,
) => (
<div <div
className="embla__slide w-full min-w-0 flex-none sm:w-1/3" className="embla__slide w-full min-w-0 flex-none sm:w-1/3"
key={index} key={index}
@ -87,27 +91,27 @@ export const CarouselSection = (properties: TNews) => {
<div className="flex flex-col justify-between gap-3"> <div className="flex flex-col justify-between gap-3">
<img <img
className="aspect-[174/100] max-h-[280px] w-full rounded-md object-cover sm:aspect-[5/4]" className="aspect-[174/100] max-h-[280px] w-full rounded-md object-cover sm:aspect-[5/4]"
src={featured} src={featured_image}
alt={title} alt={title}
/> />
<div className={'flex flex-col justify-between gap-4'}> <div className={'flex flex-col justify-between gap-4'}>
<Tags <Tags
tags={tags || []} tags={tags || []}
is_premium={isPremium} is_premium={is_premium}
/> />
<div> <div>
<h3 className="mt-2 w-full text-xl font-bold sm:text-2xl lg:mt-0"> <h3 className="mt-2 w-full text-xl font-bold sm:text-2xl lg:mt-0">
{title} {title}
</h3> </h3>
<p className="text-md mt-5 text-[#777777] sm:text-xl"> <p className="text-md mt-5 line-clamp-3 text-[#777777] sm:text-xl">
{content} {stripHtml(content).result}
</p> </p>
</div> </div>
<Button <Button
size="block" size="block"
{...getPremiumAttribute({ {...getPremiumAttribute({
isPremium, isPremium: is_premium,
slug, slug,
onClick: () => setIsSuccessOpen('warning'), onClick: () => setIsSuccessOpen('warning'),
userData, userData,

View File

@ -1,4 +1,3 @@
import type { TNews } from '~/types/news'
type TBanner = { type TBanner = {
id: number id: number
urlImage: string urlImage: string
@ -8,93 +7,6 @@ type TBanner = {
createdAt?: string createdAt?: string
} }
const DUMMY_DESCRIPTION = 'Berita Terhangat hari ini'
export const SPOTLIGHT: TNews = {
title: 'SPOTLIGHT',
description: DUMMY_DESCRIPTION,
items: [
{
title: '01 Hotman Paris Membuka Perpustakaan di tengah Diskotik',
content:
'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..',
featured: '/images/news-1.jpg',
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
},
{
title: '02 Travelling as a way of self-discovery and progress',
content:
'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..',
featured: 'https://placehold.co/600x400.png',
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
},
{
title: '03 Travelling as a way of self-discovery and progress',
content:
'Pengacara Kondang, Hotman Paris Hutapea, membuka sebuah perpustakaan baru di dalam diskotik nya yang berlokasi di daerah Jakarta Pusat, Hotman berkata Perpustakaan ini dibuka dengan harapan untuk meningkatkan gairah membaca masyarakat Indonesia, namun sayangnya..',
featured: '/images/news-1.jpg',
slug: 'hotman-paris-membuka-perpustakaan-di-tengah-diskotik',
},
],
}
export const BERITA: TNews = {
title: 'BERITA',
description: DUMMY_DESCRIPTION,
items: [
{
title: '01 Travelling as a way of self-discovery and progress ',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: [{ id: '1', code: 'hukum-property', name: 'Hukum Property' }],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
title: '02 How does writing influence your personal brand?',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-3.jpg',
tags: [{ id: '2', code: 'hukum', name: 'Hukum' }],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: '03 Helping a local business reinvent itself',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: [{ id: '3', code: 'hukum-property', name: 'Hukum Property' }],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
{
title: 'Travelling as a way of self-discovery and progress',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: 'https://placehold.co/600x400.png',
tags: [{ id: '1', code: 'hukum-property', name: 'Hukum Property' }],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
title: 'How does writing influence your personal brand?',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-3.jpg',
tags: [{ id: '2', code: 'hukum', name: 'Hukum' }],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
title: 'Helping a local business reinvent itself',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: [{ id: '3', code: 'hukum-property', name: 'Hukum Property' }],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
],
}
export const BANNER: TBanner[] = [ export const BANNER: TBanner[] = [
{ {
id: 1, id: 1,

View File

@ -7,13 +7,18 @@ import { CarouselSection } from '~/components/ui/carousel-section'
import { NewsAuthor } from '~/components/ui/news-author' import { NewsAuthor } from '~/components/ui/news-author'
import { SocialShareButtons } from '~/components/ui/social-share' import { SocialShareButtons } from '~/components/ui/social-share'
import { Tags } from '~/components/ui/tags' import { Tags } from '~/components/ui/tags'
import { BERITA } from '~/data/contents'
import type { loader } from '~/routes/_news.detail.$slug' import type { loader } from '~/routes/_news.detail.$slug'
import type { TNews } from '~/types/news'
export const NewsDetailPage = () => { export const NewsDetailPage = () => {
const loaderData = useRouteLoaderData<typeof loader>( const loaderData = useRouteLoaderData<typeof loader>(
'routes/_news.detail.$slug', 'routes/_news.detail.$slug',
) )
const berita: TNews = {
title: loaderData?.beritaCategory?.name || '',
description: loaderData?.beritaCategory?.description || '',
items: loaderData?.beritaNews || [],
}
const currentUrl = globalThis.location const currentUrl = globalThis.location
const { title, content, featured_image, author, live_at, tags } = const { title, content, featured_image, author, live_at, tags } =
loaderData?.newsDetailData || {} loaderData?.newsDetailData || {}
@ -69,7 +74,7 @@ export const NewsDetailPage = () => {
</Card> </Card>
<Card className="bg-white p-5 max-sm:hidden"> <Card className="bg-white p-5 max-sm:hidden">
<CarouselSection {...BERITA} /> <CarouselSection {...berita} />
</Card> </Card>
</div> </div>
) )

View File

@ -1,21 +1,36 @@
import { useRouteLoaderData } from 'react-router'
import { Card } from '~/components/ui/card' import { Card } from '~/components/ui/card'
import { CarouselHero } from '~/components/ui/carousel-hero' import { CarouselHero } from '~/components/ui/carousel-hero'
import { CarouselSection } from '~/components/ui/carousel-section' import { CarouselSection } from '~/components/ui/carousel-section'
import { Newsletter } from '~/components/ui/newsletter' import { Newsletter } from '~/components/ui/newsletter'
import { BERITA, SPOTLIGHT } from '~/data/contents' import type { loader } from '~/routes/_news._index'
import type { TNews } from '~/types/news'
export const NewsPage = () => { export const NewsPage = () => {
const loaderData = useRouteLoaderData<typeof loader>('routes/_news._index')
const spotlight: TNews = {
title: loaderData?.spotlightCategory?.name || '',
description: loaderData?.spotlightCategory?.description || '',
items: loaderData?.spotlightNews || [],
}
const berita: TNews = {
title: loaderData?.beritaCategory?.name || '',
description: loaderData?.beritaCategory?.description || '',
items: loaderData?.beritaNews || [],
}
return ( return (
<div className="relative"> <div className="relative">
<Card> <Card>
<CarouselHero {...SPOTLIGHT} /> <CarouselHero {...spotlight} />
</Card> </Card>
<div className="min-h-[400px] sm:min-h-[300px]"> <div className="min-h-[400px] sm:min-h-[300px]">
<Newsletter className="mr-0 sm:-ml-14" /> <Newsletter className="mr-0 sm:-ml-14" />
</div> </div>
<Card> <Card>
<CarouselSection {...BERITA} /> <CarouselSection {...berita} />
</Card> </Card>
{/* <Card> {/* <Card>
<CarouselSection {...KAJIAN} /> <CarouselSection {...KAJIAN} />

View File

@ -1,5 +1,24 @@
import { getCategories } from '~/apis/common/get-categories'
import { getNews } from '~/apis/common/get-news'
import { NewsPage } from '~/pages/news' import { NewsPage } from '~/pages/news'
import type { Route } from './+types/_news._index'
export const loader = async ({}: Route.LoaderArgs) => {
const { data: categoriesData } = await getCategories()
const spotlightCode = 'spotlight'
const spotlightCategory = categoriesData.find(
(category) => category.code === spotlightCode,
)
const { data: spotlightNews } = await getNews({ categories: [spotlightCode] })
const beritaCode = 'berita'
const beritaCategory = categoriesData.find(
(category) => category.code === beritaCode,
)
const { data: beritaNews } = await getNews({ categories: [beritaCode] })
return { spotlightCategory, spotlightNews, beritaCategory, beritaNews }
}
const NewsIndexLayout = () => <NewsPage /> const NewsIndexLayout = () => <NewsPage />
export default NewsIndexLayout export default NewsIndexLayout

View File

@ -1,3 +1,5 @@
import { getCategories } from '~/apis/common/get-categories'
import { getNews } from '~/apis/common/get-news'
import { getNewsBySlug } from '~/apis/common/get-news-by-slug' import { getNewsBySlug } from '~/apis/common/get-news-by-slug'
import { APP } from '~/configs/meta' import { APP } from '~/configs/meta'
import { handleCookie } from '~/libs/cookies' import { handleCookie } from '~/libs/cookies'
@ -11,9 +13,17 @@ export const loader = async ({ request, params }: Route.LoaderArgs) => {
slug: params.slug, slug: params.slug,
accessToken: userToken, accessToken: userToken,
}) })
const { data: categoriesData } = await getCategories()
const beritaCode = 'berita'
const beritaCategory = categoriesData.find(
(category) => category.code === beritaCode,
)
const { data: beritaNews } = await getNews({ categories: [beritaCode] })
return { return {
newsDetailData, newsDetailData,
beritaCategory,
beritaNews,
} }
} }

View File

@ -1,22 +1,7 @@
import type { TTagResponse } from '~/apis/common/get-tags' import type { TNewsResponse } from '~/apis/common/get-news'
export type TNews = { export type TNews = {
title: string title: string
description: string description: string
items: Pick< items: TNewsResponse[]
TNewsDetail,
'title' | 'content' | 'featured' | 'slug' | 'tags' | 'isPremium'
>[]
}
type TNewsDetail = {
title: string
content: string
featured: string
author: string
date: Date
slug: string
tags?: TTagResponse[]
isPremium?: boolean
categories?: Array<string>
} }