feat: implement category fetching and integrate into header menus
This commit is contained in:
parent
cfc864cb43
commit
25e136ba72
25
app/apis/common/get-categories.ts
Normal file
25
app/apis/common/get-categories.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { HttpServer, type THttpServer } from '~/libs/http-server'
|
||||||
|
|
||||||
|
const categorySchema = z.object({
|
||||||
|
data: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
code: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type TCategorySchema = z.infer<typeof categorySchema>
|
||||||
|
|
||||||
|
export const getCategories = async (parameters?: THttpServer) => {
|
||||||
|
try {
|
||||||
|
const { data } = await HttpServer(parameters).get(`/api/category`)
|
||||||
|
return categorySchema.parse(data)
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line unicorn/no-useless-promise-resolve-reject
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,18 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Link } from 'react-router'
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
|
import type { TCategorySchema } from '~/apis/common/get-categories'
|
||||||
import { CloseIcon } from '~/components/icons/close'
|
import { CloseIcon } from '~/components/icons/close'
|
||||||
import { MenuIcon } from '~/components/icons/menu'
|
import { MenuIcon } from '~/components/icons/menu'
|
||||||
import { useNewsContext } from '~/contexts/news'
|
import { useNewsContext } from '~/contexts/news'
|
||||||
import { HeaderSearch } from '~/layouts/news/header-search'
|
import { HeaderSearch } from '~/layouts/news/header-search'
|
||||||
|
|
||||||
import { MENU } from './menu'
|
type THeaderMenuMobile = {
|
||||||
|
menu?: TCategorySchema['data']
|
||||||
|
}
|
||||||
|
|
||||||
export default function HeaderMenuMobile() {
|
export default function HeaderMenuMobile(properties: THeaderMenuMobile) {
|
||||||
|
const { menu } = properties
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||||
const { setIsLoginOpen } = useNewsContext()
|
const { setIsLoginOpen } = useNewsContext()
|
||||||
|
|
||||||
@ -38,19 +42,19 @@ export default function HeaderMenuMobile() {
|
|||||||
|
|
||||||
{/* List Menu */}
|
{/* List Menu */}
|
||||||
<ul className="mx-10 mt-10 max-lg:space-y-3 lg:ml-14 lg:flex lg:gap-x-5">
|
<ul className="mx-10 mt-10 max-lg:space-y-3 lg:ml-14 lg:flex lg:gap-x-5">
|
||||||
{MENU.map((item, index) => (
|
{menu?.map((item, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="px-3 max-lg:border-b max-lg:py-3"
|
className="px-3 max-lg:border-b max-lg:py-3"
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
key={item.title}
|
key={item.id}
|
||||||
to={item.url}
|
to={`/category/${item.code}`}
|
||||||
className={
|
className={
|
||||||
'flex h-full items-center justify-center border-white px-[35px] sm:border-r'
|
'flex h-full items-center justify-center border-white px-[35px] sm:border-r'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,28 +1,31 @@
|
|||||||
import { Link } from 'react-router'
|
import { Link, useRouteLoaderData } from 'react-router'
|
||||||
|
|
||||||
import HeaderMenuMobile from '~/layouts/news/header-menu-mobile'
|
import HeaderMenuMobile from '~/layouts/news/header-menu-mobile'
|
||||||
|
import type { loader } from '~/routes/_layout'
|
||||||
|
|
||||||
import { HeaderSearch } from './header-search'
|
import { HeaderSearch } from './header-search'
|
||||||
import { MENU } from './menu'
|
|
||||||
|
|
||||||
export const HeaderMenu = () => {
|
export const HeaderMenu = () => {
|
||||||
|
const loaderData = useRouteLoaderData<typeof loader>('routes/_layout')
|
||||||
|
const menu = loaderData?.categoriesData
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="hidden h-[60px] items-center justify-between bg-[#2E2F7C] text-xl font-medium text-white sm:flex">
|
<div className="hidden h-[60px] items-center justify-between bg-[#2E2F7C] text-xl font-medium text-white sm:flex">
|
||||||
{MENU.map((item) => (
|
{menu?.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.title}
|
key={item.id}
|
||||||
to={item.url}
|
to={`/category/${item.code}`}
|
||||||
className={
|
className={
|
||||||
'flex h-full items-center justify-center border-r border-white px-[35px]'
|
'flex h-full items-center justify-center border-r border-white px-[35px]'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
<HeaderSearch />
|
<HeaderSearch />
|
||||||
</div>
|
</div>
|
||||||
<HeaderMenuMobile />
|
<HeaderMenuMobile menu={menu} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,37 +18,6 @@ type TFooterMenu = {
|
|||||||
items: TMenu[]
|
items: TMenu[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MENU: TMenu[] = [
|
|
||||||
{
|
|
||||||
title: 'Spotlight',
|
|
||||||
url: '/category/spotlight',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Berita',
|
|
||||||
url: '/category/berita',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Kasus',
|
|
||||||
url: '/category/kasus',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Kajian',
|
|
||||||
url: '/category/kajian',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lifestyle',
|
|
||||||
url: '/category/lifestyle',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Event',
|
|
||||||
url: '/category/event',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Travel',
|
|
||||||
url: '/category/travel',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const FOOTER_MENU: TFooterMenu[] = [
|
export const FOOTER_MENU: TFooterMenu[] = [
|
||||||
{
|
{
|
||||||
group: 'About Us',
|
group: 'About Us',
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Outlet } from 'react-router'
|
import { Outlet } from 'react-router'
|
||||||
|
|
||||||
|
import { getCategories } from '~/apis/common/get-categories'
|
||||||
import { getSubscriptions } from '~/apis/common/get-subscriptions'
|
import { getSubscriptions } from '~/apis/common/get-subscriptions'
|
||||||
import { NewsProvider } from '~/contexts/news'
|
import { NewsProvider } from '~/contexts/news'
|
||||||
import { NewsDefaultLayout } from '~/layouts/news/default'
|
import { NewsDefaultLayout } from '~/layouts/news/default'
|
||||||
@ -10,10 +11,12 @@ import type { Route } from './+types/_layout'
|
|||||||
export const loader = async ({ request }: Route.LoaderArgs) => {
|
export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||||
const { userToken } = await handleCookie(request)
|
const { userToken } = await handleCookie(request)
|
||||||
const { data: subscriptionsData } = await getSubscriptions()
|
const { data: subscriptionsData } = await getSubscriptions()
|
||||||
|
const { data: categoriesData } = await getCategories()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userToken,
|
userToken,
|
||||||
subscriptionsData,
|
subscriptionsData,
|
||||||
|
categoriesData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user