Compare commits

..

No commits in common. "e476e3a3ada1fc908fb65b2b234fb829d6ff5682" and "7e8276842381fe43f56c1d09e3cd9f4f5a9252c5" have entirely different histories.

30 changed files with 189 additions and 505 deletions

View File

@ -1,13 +1,8 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import 'tailwindcss';
@plugin "@tailwindcss/typography";
@import 'datatables.net-dt';
/* @import 'datatables.net-responsive-dt';
@import 'datatables.net-select-dt'; */
@theme {
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';

View File

@ -8,7 +8,7 @@ import {
import type { ReactNode } from 'react'
import { LeftArrow } from '~/components/icons/left-arrow'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
type ModalProperties = {
isOpen: boolean

View File

@ -9,35 +9,47 @@ import type { ReactNode } from 'react'
import { LeftArrow } from '~/components/icons/left-arrow'
import { Button } from '~/components/ui/button'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
export type ModalProperties = {
type ModalProperties = {
isOpen: boolean
onClose: () => void
children?: ReactNode
isOpen?: 'error' | 'warning' | 'resetPassword' | 'register' | 'payment'
type:
| 'success'
| 'error'
| 'warning'
| 'success-reset-password'
| 'success-register'
| 'success-payment'
}
type DescriptionMap = {
[key in Exclude<ModalProperties['isOpen'], undefined>]: string
[key in ModalProperties['type']]: string
}
const DESCRIPTIONS: DescriptionMap = {
resetPassword: 'Link Reset Password telah dikirimkan ke email anda',
register: 'Selamat! Pendaftaran anda berhasil!',
payment: 'Selamat! Pembayaran anda berhasil!',
'success-reset-password':
'Link Reset Password telah dikirimkan ke email anda',
'success-register': 'Selamat! pendaftaran anda berhasil!',
'success-payment': 'Selamat! Pembayaran anda berhasil!',
warning:
'Mohon maaf fitur berikut hanya untuk anggota yang sudah tersubscribe',
error: 'Terjadi kesalahan. Silakan coba lagi.',
success: '',
}
export const SuccessModal = ({ isOpen, onClose }: ModalProperties) => {
const message = isOpen
? DESCRIPTIONS[isOpen]
: 'Terjadi kesalahan. Silakan coba lagi.'
export const SuccessModal = ({
isOpen,
onClose,
type = 'success-register',
}: ModalProperties) => {
if (!isOpen) return
const message = DESCRIPTIONS[type] || 'Terjadi kesalahan. Silakan coba lagi.'
return (
<Dialog
open={!!isOpen}
open={isOpen}
onClose={onClose}
className="relative z-50"
transition
@ -47,10 +59,7 @@ export const SuccessModal = ({ isOpen, onClose }: ModalProperties) => {
transition
/>
<div className="fixed inset-0 flex w-screen justify-center overflow-y-auto p-4 max-sm:bg-white sm:items-center">
<DialogPanel
className="max-w-lg space-y-6 rounded-lg bg-white p-8 duration-300 ease-out data-[closed]:scale-95 data-[closed]:opacity-0 sm:shadow-lg"
transition
>
<DialogPanel className="max-w-lg space-y-6 rounded-lg bg-white p-8 duration-300 ease-out data-[closed]:scale-95 data-[closed]:opacity-0 sm:shadow-lg">
<DialogTitle className="relative flex justify-center">
<button
onClick={onClose}
@ -75,24 +84,27 @@ export const SuccessModal = ({ isOpen, onClose }: ModalProperties) => {
<div className="relative">
<div className="relative flex flex-col items-center justify-center">
<div className="mb-4 p-4 text-center">
{isOpen &&
['resetPassword', 'register', 'payment'].includes(isOpen) && (
<div className="justify-center">
<img
src={'/images/back-to-home.svg'}
alt={APP.title}
className="h-[300px]"
/>
<Button
className="mt-5 w-full rounded-md"
variant={'newsPrimary'}
onClick={onClose}
>
Back to Home
</Button>
</div>
)}
{isOpen === 'warning' && (
{[
'success-reset-password',
'success-register',
'success-payment',
].includes(type) && (
<div className="justify-center">
<img
src={'/images/back-to-home.svg'}
alt={APP.title}
className="h-[300px]"
/>
<Button
className="mt-5 w-full rounded-md"
variant={'newsPrimary'}
onClick={onClose}
>
Back to Home
</Button>
</div>
)}
{type === 'warning' && (
<div className="justify-center">
<img
src={'/images/warning.svg'}

View File

@ -1,6 +1,6 @@
import { Link } from 'react-router'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
export const Banner = () => {
return (

View File

@ -4,16 +4,14 @@ import { twMerge } from 'tailwind-merge'
import { CarouselNextIcon } from '~/components/icons/carousel-next'
import { CarouselPreviousIcon } from '~/components/icons/carousel-previous'
import { useNewsContext } from '~/contexts/news'
import type { TNews } from '~/types/news'
import { Button } from './button'
export const Carousel = (properties: TNews) => {
const { setIsSuccessOpen } = useNewsContext()
const [currentIndex, setCurrentIndex] = useState(0)
const { pathname } = useLocation()
const hasCategory = pathname.includes('/category/')
const location = useLocation()
const hasCategory = location.pathname.includes('/category/')
const { title, description, items, type } = properties
const itemsPerPage = type === 'hero' ? 1 : 3
@ -67,7 +65,7 @@ export const Carousel = (properties: TNews) => {
>
{items
.slice(currentIndex * itemsPerPage, (currentIndex + 1) * itemsPerPage)
.map(({ featured, title, content, tags, slug, isPremium }, index) => (
.map(({ featured, title, content, tags, slug }, index) => (
<div
key={index}
className={twMerge(
@ -91,25 +89,15 @@ export const Carousel = (properties: TNews) => {
type === 'hero' ? 'gap-7' : 'gap-4',
)}
>
<div
className={twMerge(
'uppercase',
type === 'hero' ? 'hidden' : '',
)}
>
{tags?.map((item) => (
<div className={`${type === 'hero' ? 'hidden' : ''} `}>
{tags?.map((item, index) => (
<span
key={`${index}-${item}`}
className="my-3 mr-2 inline-block rounded bg-[#F4F4F4] px-3 py-1 font-bold text-[#777777]"
key={index}
className="my-3 mr-2 inline-block rounded bg-gray-200 px-3 py-1"
>
{item}
</span>
))}
{isPremium && (
<span className="my-3 mr-2 inline-block rounded bg-[#D1C675] px-3 py-1 font-bold text-[#9D761D]">
Premium Content
</span>
)}
</div>
<div>
@ -129,14 +117,8 @@ export const Carousel = (properties: TNews) => {
</div>
<Button
size="block"
{...(isPremium
? {
onClick: () => {
setIsSuccessOpen('warning')
},
to: '',
}
: { as: Link, to: `/news/detail/${slug}` })}
as={Link}
to={`detail/${slug}`}
className={twMerge('', type === 'hero' ? '' : 'mb-5')}
>
View More

View File

@ -1,31 +0,0 @@
import { MENU as ADMIN_MENU } from '~/layouts/admin/menu'
export const APP = {
title: 'LegalGo',
description:
'Bonus judex secundum aequum et\n bonum judicat et aequitatem stricto juri praefert',
logo: '/images/logo.png',
}
type TMetaTitleConfig = {
path: string
title: string
}[]
export const META_TITLE_CONFIG: TMetaTitleConfig = [
{
path: '/news',
title: 'Home',
},
{
path: '/admin/auth/login',
title: 'Login',
},
{
path: '/admin/auth/register',
title: 'Register',
},
...ADMIN_MENU.flatMap((menu) =>
menu.items.map((item) => ({ path: item.url, title: item.title })),
),
]

View File

@ -1,7 +0,0 @@
export const USER_COOKIES = {
token: '__lg-usr-tkn',
}
export const ADMIN_COOKIES = {
token: '__lg-adm-tkn',
}

View File

@ -7,21 +7,15 @@ import {
type SetStateAction,
} from 'react'
import type { ModalProperties } from '~/components/popup/success-modal'
export type NewsContextProperties = {
type NewsContextProperties = {
isLoginOpen: boolean
setIsLoginOpen: Dispatch<SetStateAction<boolean>>
isRegisterOpen: boolean
setIsRegisterOpen: Dispatch<SetStateAction<boolean>>
isForgetOpen: boolean
setForgetOpen: Dispatch<SetStateAction<boolean>>
isSuccessOpen: ModalProperties['isOpen']
setIsSuccessOpen: Dispatch<
SetStateAction<ModalProperties['isOpen'] | undefined>
>
isInitSubscribeOpen: boolean
setIsInitSubscribeOpen: Dispatch<SetStateAction<boolean>>
isSuccessModalOpen: boolean
setIsSuccessModalOpen: Dispatch<SetStateAction<boolean>>
}
const NewsContext = createContext<NewsContextProperties | undefined>(undefined)
@ -30,9 +24,7 @@ export const NewsProvider = ({ children }: PropsWithChildren) => {
const [isLoginOpen, setIsLoginOpen] = useState(false)
const [isRegisterOpen, setIsRegisterOpen] = useState(false)
const [isForgetOpen, setForgetOpen] = useState(false)
const [isSuccessOpen, setIsSuccessOpen] =
useState<ModalProperties['isOpen']>()
const [isInitSubscribeOpen, setIsInitSubscribeOpen] = useState(false)
const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false)
return (
<NewsContext.Provider
@ -43,10 +35,8 @@ export const NewsProvider = ({ children }: PropsWithChildren) => {
setIsRegisterOpen,
isForgetOpen,
setForgetOpen,
isSuccessOpen,
setIsSuccessOpen,
isInitSubscribeOpen,
setIsInitSubscribeOpen,
isSuccessModalOpen,
setIsSuccessModalOpen,
}}
>
{children}

21
app/data/meta.ts Normal file
View File

@ -0,0 +1,21 @@
export const APP = {
title: 'LegalGo',
description:
'Bonus judex secundum aequum et\n bonum judicat et aequitatem stricto juri praefert',
logo: '/images/logo.png',
}
export const META_TITLE_CONFIG = [
{
path: '/news',
title: 'Home',
},
{
path: '/auth/login',
title: 'Login',
},
{
path: '/auth/register',
title: 'Register',
},
]

View File

@ -2,8 +2,8 @@ import { Link } from 'react-router'
import { ChevronIcon } from '~/components/icons/chevron'
import { NotificationIcon } from '~/components/icons/notification'
import { APP } from '~/configs/meta'
import { useAdminContext } from '~/contexts/admin'
import { APP } from '~/data/meta'
export const Navbar = () => {
const { adminProfile } = useAdminContext()

View File

@ -10,7 +10,6 @@ import { FormRegister } from '~/layouts/news/form-register'
import { FooterLinks } from './footer-links'
import { FooterNewsletter } from './footer-newsletter'
import FormSubscription from './form-subscription'
import { HeaderMenu } from './header-menu'
import { HeaderTop } from './header-top'
@ -23,10 +22,8 @@ export const NewsDefaultLayout = (properties: PropsWithChildren) => {
setIsRegisterOpen,
isForgetOpen,
setForgetOpen,
isSuccessOpen,
setIsSuccessOpen,
isInitSubscribeOpen,
setIsInitSubscribeOpen,
isSuccessModalOpen,
setIsSuccessModalOpen,
} = useNewsContext()
return (
<main className="relative min-h-dvh bg-[#ECECEC]">
@ -53,7 +50,7 @@ export const NewsDefaultLayout = (properties: PropsWithChildren) => {
setIsRegisterOpen={setIsRegisterOpen}
setIsLoginOpen={setIsLoginOpen}
setIsForgetOpen={setForgetOpen}
setIsInitSubscribeOpen={setIsInitSubscribeOpen}
setIsSuccessModalOpen={setIsSuccessModalOpen}
/>
</PopupModal>
@ -73,19 +70,12 @@ export const NewsDefaultLayout = (properties: PropsWithChildren) => {
<FormForgotPassword />
</PopupModal>
<PopupModal
isOpen={isInitSubscribeOpen}
onClose={() => setIsInitSubscribeOpen(false)}
description="Selamat Datang, silakan Pilih Subscription Anda untuk melanjutkan!"
>
<FormSubscription />
</PopupModal>
<SuccessModal
isOpen={isSuccessOpen}
isOpen={isSuccessModalOpen}
onClose={() => {
setIsSuccessOpen(undefined)
setIsSuccessModalOpen(false)
}}
type="warning"
/>
</main>
)

View File

@ -1,6 +1,6 @@
import { Link } from 'react-router'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
import { COPYRIGHT_MENU, FOOTER_MENU } from './menu'

View File

@ -1,7 +1,7 @@
import { Link } from 'react-router'
import { Button } from '~/components/ui/button'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
export const FooterNewsletter = () => {
return (

View File

@ -1,11 +1,11 @@
// import { EyeIcon, EyeOffIcon } from 'lucide-react'
import { zodResolver } from '@hookform/resolvers/zod'
import { type Dispatch, type SetStateAction } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { z } from 'zod'
import { Button } from '~/components/ui/button'
import { Input } from '~/components/ui/input'
import type { NewsContextProperties } from '~/contexts/news'
const loginSchema = z.object({
email: z.string().email('Email tidak valid'),
@ -15,10 +15,10 @@ const loginSchema = z.object({
type TLoginSchema = z.infer<typeof loginSchema>
type TProperties = {
setIsRegisterOpen: NewsContextProperties['setIsRegisterOpen']
setIsLoginOpen: NewsContextProperties['setIsLoginOpen']
setIsForgetOpen: NewsContextProperties['setForgetOpen']
setIsInitSubscribeOpen: NewsContextProperties['setIsInitSubscribeOpen']
setIsRegisterOpen: Dispatch<SetStateAction<boolean>>
setIsLoginOpen: Dispatch<SetStateAction<boolean>>
setIsForgetOpen: Dispatch<SetStateAction<boolean>>
setIsSuccessModalOpen: Dispatch<SetStateAction<boolean>>
}
export const FormLogin = (properties: TProperties) => {
@ -26,7 +26,7 @@ export const FormLogin = (properties: TProperties) => {
setIsRegisterOpen,
setIsLoginOpen,
setIsForgetOpen,
setIsInitSubscribeOpen,
setIsSuccessModalOpen,
} = properties
const formMethods = useForm<TLoginSchema>({
@ -37,7 +37,7 @@ export const FormLogin = (properties: TProperties) => {
const onSubmit = handleSubmit((data) => {
console.log('data', data) // eslint-disable-line no-console
setIsInitSubscribeOpen(true)
setIsSuccessModalOpen(true)
setIsLoginOpen(false)
})

View File

@ -6,7 +6,7 @@ export const HeaderSearch = () => {
<form className="flex flex-1 justify-between gap-[15px] px-[35px]">
<input
placeholder="Cari..."
className="flex-1 text-xl placeholder:text-white focus:ring-0 focus:outline-none"
className="flex-1 text-xl placeholder:text-white focus:ring-0 focus:outline-none sm:text-2xl"
size={1}
/>
<Button

View File

@ -1,8 +1,8 @@
import { Link } from 'react-router'
import { Button } from '~/components/ui/button'
import { APP } from '~/configs/meta'
import { useNewsContext } from '~/contexts/news'
import { APP } from '~/data/meta'
export const HeaderTop = () => {
const { setIsLoginOpen } = useNewsContext()

View File

@ -1,13 +0,0 @@
import { createCookie } from 'react-router'
import { ADMIN_COOKIES, USER_COOKIES } from '~/configs/storages'
export const userTokenCookie = createCookie(USER_COOKIES.token, {
secure: process.env.NODE_ENV === 'production',
path: '/news',
})
export const adminTokenCookie = createCookie(ADMIN_COOKIES.token, {
secure: process.env.NODE_ENV === 'production',
path: '/admin',
})

View File

@ -1,31 +0,0 @@
import xior, { merge } from 'xior'
const baseURL = import.meta.env.VITE_API_URL
const HttpClient = (token?: string) => {
const instance = xior.create({
baseURL,
})
instance.interceptors.request.use((config) => {
// eslint-disable-next-line no-console
console.info(`🚀requesting ${config.url}`)
return merge(config, {
headers: {
...(token && { Authorization: `Bearer ${token}` }),
},
})
})
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
return Promise.reject(error)
},
)
return instance
}
export default HttpClient

View File

@ -1,11 +0,0 @@
import { USER_COOKIES } from '~/configs/storages'
export const setUserLogoutHeaders = () => {
const responseHeaders = new Headers()
responseHeaders.append(
'Set-Cookie',
`${USER_COOKIES.token}=; Path=/news; Max-Age=0`,
)
return responseHeaders
}

View File

@ -1,102 +0,0 @@
type TContens = {
id: number
createdAt: string
author: string
title: string
tags: string
category: string
status: string
}
export const CONTENTS: TContens[] = [
{
id: 1,
createdAt: '24/10/2024',
author: 'John Doe',
title: 'Introduction to TypeScript',
tags: 'typescript, programming',
category: 'Education',
status: 'Published',
},
{
id: 2,
createdAt: '24/10/2024',
author: 'Jane Smith',
title: 'Advanced React Patterns',
tags: 'react, javascript',
category: 'Development',
status: 'Draft',
},
{
id: 3,
createdAt: '24/10/2024',
author: 'Alice Johnson',
title: 'Understanding Redux',
tags: 'redux, state management',
category: 'Development',
status: 'Published',
},
{
id: 4,
createdAt: '24/10/2024',
author: 'Bob Brown',
title: 'CSS Grid Layout',
tags: 'css, design',
category: 'Design',
status: 'Published',
},
{
id: 5,
createdAt: '24/10/2024',
author: 'Charlie Davis',
title: 'Node.js Best Practices',
tags: 'nodejs, backend',
category: 'Development',
status: 'Published',
},
{
id: 6,
createdAt: '24/10/2024',
author: 'Diana Evans',
title: 'GraphQL for Beginners',
tags: 'graphql, api',
category: 'Development',
status: 'Draft',
},
{
id: 7,
createdAt: '24/10/2024',
author: 'Evan Harris',
title: 'Building RESTful APIs',
tags: 'rest, api',
category: 'Development',
status: 'Published',
},
{
id: 8,
createdAt: '24/10/2024',
author: 'Fiona Green',
title: 'Introduction to Docker',
tags: 'docker, devops',
category: 'DevOps',
status: 'Published',
},
{
id: 9,
createdAt: '24/10/2024',
author: 'George King',
title: 'Microservices Architecture',
tags: 'microservices, architecture',
category: 'Development',
status: 'Draft',
},
{
id: 10,
createdAt: '24/10/2024',
author: 'Hannah Lee',
title: 'Kubernetes Essentials',
tags: 'kubernetes, devops',
category: 'DevOps',
status: 'Published',
},
]

View File

@ -1,36 +0,0 @@
import DT from 'datatables.net-dt'
import DataTable from 'datatables.net-react'
import { TitleDashboard } from '~/components/ui/title-dashboard'
import { CONTENTS } from './data'
export const ContentsPage = () => {
DataTable.use(DT)
const columns = [
{ title: 'No', data: 'id' },
{ title: 'Tanggal Kontent', data: 'createdAt' },
{ title: 'Nama Penulis', data: 'author' },
{ title: 'Judul', data: 'title' },
{ title: 'Kategori', data: 'category' },
{ title: 'Tags', data: 'tags' },
{ title: 'Action', data: 'id', render: () => 'Edit' },
]
return (
<div className="relative">
<TitleDashboard title="Konten" />
<DataTable
className="cell-border"
data={CONTENTS}
columns={columns}
options={{
paging: true,
searching: true,
ordering: true,
info: true,
}}
></DataTable>
</div>
)
}

View File

@ -1,5 +1,20 @@
import type { TNews } from '~/types/news'
export const SPOTLIGHT: TNews = {
title: 'SPOTLIGHT',
description: 'Berita Terhangat hari ini',
type: 'hero',
items: [
{
title: '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',
},
],
}
export const BERITA: TNews = {
title: 'BERITA',
description: 'Berita Terhangat hari ini',
@ -122,8 +137,39 @@ export const BERITA: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
},
],
}
export const KAJIAN: TNews = {
title: 'KAJIAN',
description: 'Berita Terhangat hari ini',
type: 'grid',
items: [
{
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: '/images/news-2.jpg',
tags: ['Hukum Property', 'PREMIUM CONTENT'],
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: ['Hukum Property', 'PREMIUM CONTENT'],
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: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
},
],

View File

@ -64,8 +64,7 @@ export const BERITA: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
},
],

View File

@ -55,7 +55,7 @@ export const BERITA: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
tags: ['Hukum Property', 'PREMIUM CONTENT'],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
@ -80,7 +80,7 @@ export const BERITA: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
tags: ['Hukum Property', 'PREMIUM CONTENT'],
isPremium: true,
slug: 'helping-a-local-business-reinvent-itself',
},
@ -97,8 +97,7 @@ export const KAJIAN: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-2.jpg',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
@ -106,8 +105,7 @@ export const KAJIAN: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: 'https://placehold.co/600x400.png',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'travelling-as-a-way-of-self-discovery-and-progress',
},
{
@ -115,8 +113,7 @@ export const KAJIAN: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-3.jpg',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'how-does-writing-influence-your-personal-brand',
},
{
@ -124,8 +121,7 @@ export const KAJIAN: TNews = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.',
featured: '/images/news-4.jpg',
tags: ['Hukum Property'],
isPremium: true,
tags: ['Hukum Property', 'PREMIUM CONTENT'],
slug: 'helping-a-local-business-reinvent-itself',
},
],

View File

@ -11,7 +11,7 @@ import {
import type { Route } from './+types/root'
import './app.css'
import { APP, META_TITLE_CONFIG } from './configs/meta'
import { APP, META_TITLE_CONFIG } from './data/meta'
export const links: Route.LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },

View File

@ -3,7 +3,7 @@ import { Link } from 'react-router'
import { EyeIcon } from '~/components/icons/eye'
import { Button } from '~/components/ui/button'
import { APP } from '~/configs/meta'
import { APP } from '~/data/meta'
const AuthLayout = () => {
const [showPassword, setShowPassword] = useState(false)

View File

@ -1,6 +1,4 @@
import { ContentsPage } from '~/pages/dashboard-contents'
const DashboardContentsLayout = () => {
return <ContentsPage />
return <div>Contents Page</div>
}
export default DashboardContentsLayout

View File

@ -8,8 +8,6 @@ import tsEslintParser from '@typescript-eslint/parser'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import reactPlugin from 'eslint-plugin-react'
import pluginQuery from '@tanstack/eslint-plugin-query'
import unusedImports from 'eslint-plugin-unused-imports'
import eslintConfigPrettier from 'eslint-config-prettier'
export default tseslint.config(
{ ignores: ['dist', 'node_modules', '.react-router'] },
@ -20,7 +18,7 @@ export default tseslint.config(
jsxA11y.flatConfigs.recommended,
importPlugin.flatConfigs.recommended,
eslintPluginUnicorn.configs['flat/recommended'],
eslintConfigPrettier,
...pluginQuery.configs['flat/recommended'],
],
files: ['**/*.{ts,tsx}'],
languageOptions: {
@ -30,8 +28,6 @@ export default tseslint.config(
plugins: {
'react-hooks': reactHooks,
react: reactPlugin,
'unused-imports': unusedImports,
'@tanstack/query': pluginQuery,
},
languageOptions: {
parser: tsEslintParser,
@ -46,16 +42,23 @@ export default tseslint.config(
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
react: {
version: 'detect',
},
},
rules: {
...reactHooks.configs.recommended.rules,
...tseslint.configs.recommended.rules,
...reactPlugin.configs.recommended.rules,
'@tanstack/query/exhaustive-deps': 'error',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'unicorn/filename-case': [
'error',
{
@ -87,8 +90,7 @@ export default tseslint.config(
'~/layouts/*/*/*/*',
'~/schemas/*/*/*',
'~/types/*/*',
'~/utils/*/*',
'~/libs/*/*',
'~/utils/*/**',
],
},
],
@ -123,16 +125,6 @@ export default tseslint.config(
],
},
],
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'error',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
'react/react-in-jsx-scope': 'off',
'react/jsx-key': [
'error',

View File

@ -24,7 +24,6 @@
"class-variance-authority": "^0.7.1",
"datatables.net-dt": "^2.2.2",
"datatables.net-react": "^1.0.0",
"embla-carousel-react": "^8.5.2",
"html-react-parser": "^5.2.2",
"isbot": "^5.1.17",
"react": "^19.0.0",
@ -33,7 +32,6 @@
"react-hook-form": "^7.54.2",
"react-router": "^7.1.3",
"tailwind-merge": "^3.0.1",
"xior": "^0.6.3",
"zod": "^3.24.2"
},
"devDependencies": {
@ -51,14 +49,12 @@
"@types/react-dom": "^19.0.1",
"@typescript-eslint/parser": "^8.22.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^10.0.1",
"eslint-import-resolver-typescript": "^3.8.3",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-unicorn": "^56.0.1",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^15.14.0",
"husky": "^9.1.7",
"knip": "^5.43.6",

128
pnpm-lock.yaml generated
View File

@ -38,9 +38,6 @@ importers:
datatables.net-react:
specifier: ^1.0.0
version: 1.0.0
embla-carousel-react:
specifier: ^8.5.2
version: 8.5.2(react@19.0.0)
html-react-parser:
specifier: ^5.2.2
version: 5.2.2(@types/react@19.0.8)(react@19.0.0)
@ -65,9 +62,6 @@ importers:
tailwind-merge:
specifier: ^3.0.1
version: 3.0.1
xior:
specifier: ^0.6.3
version: 0.6.3
zod:
specifier: ^3.24.2
version: 3.24.2
@ -114,15 +108,12 @@ importers:
eslint:
specifier: ^8.0.1
version: 8.57.1
eslint-config-prettier:
specifier: ^10.0.1
version: 10.0.1(eslint@8.57.1)
eslint-import-resolver-typescript:
specifier: ^3.8.3
version: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
specifier: ^3.7.0
version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-plugin-import:
specifier: ^2.31.0
version: 2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
version: 2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
eslint-plugin-jsx-a11y:
specifier: ^6.10.2
version: 6.10.2(eslint@8.57.1)
@ -135,9 +126,6 @@ importers:
eslint-plugin-unicorn:
specifier: ^56.0.1
version: 56.0.1(eslint@8.57.1)
eslint-plugin-unused-imports:
specifier: ^4.1.4
version: 4.1.4(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)
globals:
specifier: ^15.14.0
version: 15.14.0
@ -2054,19 +2042,6 @@ packages:
electron-to-chromium@1.5.90:
resolution: {integrity: sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==}
embla-carousel-react@8.5.2:
resolution: {integrity: sha512-Tmx+uY3MqseIGdwp0ScyUuxpBgx5jX1f7od4Cm5mDwg/dptEiTKf9xp6tw0lZN2VA9JbnVMl/aikmbc53c6QFA==}
peerDependencies:
react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
embla-carousel-reactive-utils@8.5.2:
resolution: {integrity: sha512-QC8/hYSK/pEmqEdU1IO5O+XNc/Ptmmq7uCB44vKplgLKhB/l0+yvYx0+Cv0sF6Ena8Srld5vUErZkT+yTahtDg==}
peerDependencies:
embla-carousel: 8.5.2
embla-carousel@8.5.2:
resolution: {integrity: sha512-xQ9oVLrun/eCG/7ru3R+I5bJ7shsD8fFwLEY7yPe27/+fDHCNj0OT5EoG5ZbFyOxOcG6yTwW8oTz/dWyFnyGpg==}
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
@ -2167,17 +2142,11 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-config-prettier@10.0.1:
resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
eslint-import-resolver-typescript@3.8.3:
resolution: {integrity: sha512-A0bu4Ks2QqDWNpeEgTQMPTngaMhuDu4yv6xpftBMAf+1ziXnpx+eSR1WRfoPTe2BAiAjHFZ7kSNx1fvr5g5pmQ==}
eslint-import-resolver-typescript@3.7.0:
resolution: {integrity: sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
eslint: '*'
@ -2244,15 +2213,6 @@ packages:
peerDependencies:
eslint: '>=8.56.0'
eslint-plugin-unused-imports@4.1.4:
resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==}
peerDependencies:
'@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
eslint: ^9.0.0 || ^8.0.0
peerDependenciesMeta:
'@typescript-eslint/eslint-plugin':
optional: true
eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -2329,14 +2289,6 @@ packages:
fastq@1.18.0:
resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==}
fdir@6.4.3:
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -3959,17 +3911,9 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-lru@11.2.11:
resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==}
engines: {node: '>=12'}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyglobby@0.2.12:
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
engines: {node: '>=12.0.0'}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@ -3984,10 +3928,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
ts-deepmerge@7.0.2:
resolution: {integrity: sha512-akcpDTPuez4xzULo5NwuoKwYRtjQJ9eoNfBACiBMaXwNAx7B1PKfe5wqUFJuW5uKzQ68YjDFwPaWHDG1KnFGsA==}
engines: {node: '>=14.13.1'}
tsconfck@3.1.4:
resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==}
engines: {node: ^18 || >=20}
@ -4235,9 +4175,6 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xior@0.6.3:
resolution: {integrity: sha512-WxDMGk7W2duFoCS0M59Pll/BIGfWiadZp4MMEY0/56K+3Vz400DUTiEZLpuaVcSnv+pCSz05MJz8kohn8wivhg==}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@ -6253,18 +6190,6 @@ snapshots:
electron-to-chromium@1.5.90: {}
embla-carousel-react@8.5.2(react@19.0.0):
dependencies:
embla-carousel: 8.5.2
embla-carousel-reactive-utils: 8.5.2(embla-carousel@8.5.2)
react: 19.0.0
embla-carousel-reactive-utils@8.5.2(embla-carousel@8.5.2):
dependencies:
embla-carousel: 8.5.2
embla-carousel@8.5.2: {}
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {}
@ -6432,10 +6357,6 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.0.1(eslint@8.57.1):
dependencies:
eslint: 8.57.1
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7
@ -6444,33 +6365,34 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1):
eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0
enhanced-resolve: 5.18.0
eslint: 8.57.1
fast-glob: 3.3.3
get-tsconfig: 4.10.0
is-bun-module: 1.3.0
is-glob: 4.0.3
stable-hash: 0.0.4
tinyglobby: 0.2.12
optionalDependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.22.0(eslint@8.57.1)(typescript@5.7.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@ -6481,7 +6403,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@ -6564,12 +6486,6 @@ snapshots:
semver: 7.7.0
strip-indent: 3.0.0
eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1):
dependencies:
eslint: 8.57.1
optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)
eslint-scope@7.2.2:
dependencies:
esrecurse: 4.3.0
@ -6714,10 +6630,6 @@ snapshots:
dependencies:
reusify: 1.0.4
fdir@6.4.3(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@ -8320,15 +8232,8 @@ snapshots:
through@2.3.8: {}
tiny-lru@11.2.11: {}
tinyexec@0.3.2: {}
tinyglobby@0.2.12:
dependencies:
fdir: 6.4.3(picomatch@4.0.2)
picomatch: 4.0.2
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@ -8339,8 +8244,6 @@ snapshots:
dependencies:
typescript: 5.7.3
ts-deepmerge@7.0.2: {}
tsconfck@3.1.4(typescript@5.7.3):
optionalDependencies:
typescript: 5.7.3
@ -8597,11 +8500,6 @@ snapshots:
wrappy@1.0.2: {}
xior@0.6.3:
dependencies:
tiny-lru: 11.2.11
ts-deepmerge: 7.0.2
xtend@4.0.2: {}
y18n@5.0.8: {}