Merge remote-tracking branch 'origin/master' into feature/slicing

This commit is contained in:
fredy.siswanto 2025-03-15 01:02:32 +07:00
commit 3ddc657cfb
21 changed files with 77 additions and 71 deletions

View File

@ -71,7 +71,7 @@ export const DialogDelete = (properties: TProperties) => {
> >
<Button <Button
type="submit" type="submit"
variant="newsDanger" variant="danger"
className="text-md h-[42px] rounded-md" className="text-md h-[42px] rounded-md"
disabled={fetcher.state !== 'idle'} disabled={fetcher.state !== 'idle'}
isLoading={fetcher.state !== 'idle'} isLoading={fetcher.state !== 'idle'}

View File

@ -92,7 +92,7 @@ export const DialogSuccess = ({ isOpen, onClose }: ModalProperties) => {
/> />
<Button <Button
className="mt-5 w-full rounded-md" className="mt-5 w-full rounded-md"
variant="newsPrimary" variant="primary"
as={Link} as={Link}
to="/" to="/"
onClick={onClose} onClick={onClose}
@ -111,7 +111,7 @@ export const DialogSuccess = ({ isOpen, onClose }: ModalProperties) => {
{userData ? ( {userData ? (
<Button <Button
className="mt-5 w-full rounded-md" className="mt-5 w-full rounded-md"
variant="newsSecondary" variant="outline"
onClick={() => { onClick={() => {
onClose() onClose()
setIsSubscribeOpen(true) setIsSubscribeOpen(true)
@ -122,7 +122,7 @@ export const DialogSuccess = ({ isOpen, onClose }: ModalProperties) => {
) : ( ) : (
<Button <Button
className="mt-5 w-full rounded-md" className="mt-5 w-full rounded-md"
variant="newsPrimary" variant="primary"
onClick={() => { onClick={() => {
onClose() onClose()
setIsLoginOpen(true) setIsLoginOpen(true)

View File

@ -1,25 +0,0 @@
import type { JSX, SVGProps } from 'react'
/**
* Note: `ChevronIcon` default mengarah ke bawah.
* Gunakan class `rotate-xx` untuk mengubah arah ikon.
*/
export const ChevronIcon = (
properties: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) => {
return (
<svg
width={21}
height={21}
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={properties.className}
{...properties}
>
<path
d="M10.197 13.623l5.008-5.008-1.177-1.18-3.83 3.834-3.831-3.833-1.178 1.178 5.008 5.009z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -21,7 +21,7 @@ import {
StrikethroughIcon, StrikethroughIcon,
SwatchIcon, SwatchIcon,
XCircleIcon, XCircleIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import { import {
Bars3BottomCenterIcon, Bars3BottomCenterIcon,
QuotationMarkIcon, QuotationMarkIcon,

View File

@ -1,4 +1,4 @@
import { CodeBracketSquareIcon } from '@heroicons/react/20/solid' import { CodeBracketSquareIcon } from '@heroicons/react/24/solid'
import MonacoEditor from '@monaco-editor/react' import MonacoEditor from '@monaco-editor/react'
import type { Dispatch, SetStateAction } from 'react' import type { Dispatch, SetStateAction } from 'react'
import { Controller } from 'react-hook-form' import { Controller } from 'react-hook-form'

View File

@ -1,5 +1,5 @@
import { Button as HeadlessButton } from '@headlessui/react' import { Button as HeadlessButton } from '@headlessui/react'
import { ArrowPathIcon } from '@heroicons/react/20/solid' import { ArrowPathIcon } from '@heroicons/react/24/solid'
import { cva, type VariantProps } from 'class-variance-authority' import { cva, type VariantProps } from 'class-variance-authority'
import type { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react' import type { ReactNode, ElementType, ComponentPropsWithoutRef } from 'react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
@ -9,16 +9,18 @@ const buttonVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
newsPrimary: primary:
'bg-[#2E2F7C] text-white text-lg hover:bg-[#4C5CA0] hover:shadow transition active:bg-[#6970B4]', 'bg-[#2E2F7C] text-white text-lg hover:bg-[#4C5CA0] hover:shadow transition active:bg-[#6970B4]',
newsDanger: danger:
'bg-[#EF4444] text-white text-lg hover:shadow transition active:bg-[#FEE2E2] hover:bg-[#FCA5A5]', 'bg-[#EF4444] text-white text-lg hover:shadow transition active:bg-[#FEE2E2] hover:bg-[#FCA5A5]',
newsPrimaryOutline: primaryOutline:
'border-[3px] bg-[#2E2F7C] border-white text-white text-lg hover:bg-[#4C5CA0] hover:shadow-lg active:shadow-2xl transition active:bg-[#6970B4]', 'border-[3px] bg-[#2E2F7C] border-white text-white text-lg hover:bg-[#4C5CA0] hover:shadow-lg active:shadow-2xl transition active:bg-[#6970B4]',
newsSecondary: outline:
'border-[3px] bg-white hover:shadow-lg active:shadow-2xl border-[#2E2F7C] text-[#2E2F7C] hover:text-[#4C5CA0] active:text-[#6970B4] text-lg hover:border-[#4C5CA0] transition active:border-[#6970B4]', 'border-[3px] bg-white hover:shadow-lg active:shadow-2xl border-[#2E2F7C] text-[#2E2F7C] hover:text-[#4C5CA0] active:text-[#6970B4] text-lg hover:border-[#4C5CA0] transition active:border-[#6970B4]',
icon: '', icon: '',
link: 'font-semibold text-[#2E2F7C] hover:text-[#4C5CA0] active:text-[#6970B4] transition', link: 'font-semibold text-[#2E2F7C] hover:text-[#4C5CA0] active:text-[#6970B4] transition',
secondary:
'hover:bg-[#707FDD]/10 active:bg-[#707FDD]/20 hover:text-[#707FDD] text-[#273240]',
}, },
size: { size: {
default: 'h-[50px] w-[150px]', default: 'h-[50px] w-[150px]',
@ -30,7 +32,7 @@ const buttonVariants = cva(
}, },
}, },
defaultVariants: { defaultVariants: {
variant: 'newsPrimary', variant: 'primary',
size: 'default', size: 'default',
}, },
}, },
@ -42,6 +44,7 @@ type ButtonBaseProperties = {
size?: VariantProps<typeof buttonVariants>['size'] size?: VariantProps<typeof buttonVariants>['size']
className?: string className?: string
isLoading?: boolean isLoading?: boolean
icon?: ReactNode
} }
type PolymorphicReference<C extends ElementType> = type PolymorphicReference<C extends ElementType> =
@ -62,6 +65,7 @@ export const Button = <C extends ElementType = 'button'>(
size, size,
className, className,
isLoading = false, isLoading = false,
icon,
...restProperties ...restProperties
} = properties } = properties
const Component = as || HeadlessButton const Component = as || HeadlessButton
@ -72,7 +76,7 @@ export const Button = <C extends ElementType = 'button'>(
className={classes} className={classes}
{...restProperties} {...restProperties}
> >
{isLoading && <ArrowPathIcon className="animate-spin" />} {isLoading ? <ArrowPathIcon className="size-5 animate-spin" /> : icon}
{children} {children}
</Component> </Component>
) )

View File

@ -7,7 +7,7 @@ import {
ComboboxOptions, ComboboxOptions,
ComboboxOption, ComboboxOption,
} from '@headlessui/react' } from '@headlessui/react'
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' import { CheckIcon, ChevronDownIcon } from '@heroicons/react/24/solid'
import { useState, type ComponentProps, type ReactNode } from 'react' import { useState, type ComponentProps, type ReactNode } from 'react'
import { import {
get, get,

View File

@ -1,5 +1,5 @@
import { Field, Label, Input as HeadlessInput } from '@headlessui/react' import { Field, Label, Input as HeadlessInput } from '@headlessui/react'
import { CloudArrowUpIcon } from '@heroicons/react/20/solid' import { CloudArrowUpIcon } from '@heroicons/react/24/solid'
import { useEffect, type ComponentProps, type ReactNode } from 'react' import { useEffect, type ComponentProps, type ReactNode } from 'react'
import { get, type FieldError, type RegisterOptions } from 'react-hook-form' import { get, type FieldError, type RegisterOptions } from 'react-hook-form'
import { useRemixFormContext } from 'remix-hook-form' import { useRemixFormContext } from 'remix-hook-form'

View File

@ -45,7 +45,7 @@ export const Newsletter = (property: NewsletterProperties) => {
/> />
<Button <Button
type="submit" type="submit"
variant="newsPrimary" variant="primary"
size="block" size="block"
> >
Subscribe Subscribe

View File

@ -1,4 +1,4 @@
import { LinkIcon } from '@heroicons/react/20/solid' import { LinkIcon } from '@heroicons/react/24/solid'
import { useState } from 'react' import { useState } from 'react'
import { import {
FacebookShareButton, FacebookShareButton,

View File

@ -1,5 +1,5 @@
import { Field, Input, Label, Select } from '@headlessui/react' import { Field, Input, Label, Select } from '@headlessui/react'
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid' import { MagnifyingGlassIcon } from '@heroicons/react/24/solid'
import { useState } from 'react' import { useState } from 'react'
interface SearchFilterProperties { interface SearchFilterProperties {

View File

@ -19,6 +19,8 @@ type AdminContextProperties = {
setIsUploadOpen: Dispatch<SetStateAction<TUpload>> setIsUploadOpen: Dispatch<SetStateAction<TUpload>>
uploadedFile?: string uploadedFile?: string
setUploadedFile: Dispatch<SetStateAction<string | undefined>> setUploadedFile: Dispatch<SetStateAction<string | undefined>>
editProfile: boolean
setEditProfile: Dispatch<SetStateAction<boolean>>
} }
const AdminContext = createContext<AdminContextProperties | undefined>( const AdminContext = createContext<AdminContextProperties | undefined>(
@ -28,6 +30,7 @@ const AdminContext = createContext<AdminContextProperties | undefined>(
export const AdminProvider = ({ children }: PropsWithChildren) => { export const AdminProvider = ({ children }: PropsWithChildren) => {
const [isUploadOpen, setIsUploadOpen] = useState<TUpload>() const [isUploadOpen, setIsUploadOpen] = useState<TUpload>()
const [uploadedFile, setUploadedFile] = useState<string | undefined>() const [uploadedFile, setUploadedFile] = useState<string | undefined>()
const [editProfile, setEditProfile] = useState(false)
return ( return (
<AdminContext.Provider <AdminContext.Provider
@ -36,6 +39,8 @@ export const AdminProvider = ({ children }: PropsWithChildren) => {
setIsUploadOpen, setIsUploadOpen,
uploadedFile, uploadedFile,
setUploadedFile, setUploadedFile,
editProfile,
setEditProfile,
}} }}
> >
{children} {children}

View File

@ -7,7 +7,7 @@ import {
PresentationChartLineIcon, PresentationChartLineIcon,
TagIcon, TagIcon,
UsersIcon, UsersIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import type { SVGProps } from 'react' import type { SVGProps } from 'react'
type TMenu = { type TMenu = {

View File

@ -1,16 +1,22 @@
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react' import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
import {
ArrowRightStartOnRectangleIcon,
UserIcon,
} from '@heroicons/react/24/outline'
import { ChevronDownIcon } from '@heroicons/react/24/solid'
import { Link, useFetcher, useRouteLoaderData } from 'react-router' import { Link, useFetcher, useRouteLoaderData } from 'react-router'
import { ChevronIcon } from '~/components/icons/chevron'
import { ProfileIcon } from '~/components/icons/profile' import { ProfileIcon } from '~/components/icons/profile'
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
import { APP } from '~/configs/meta' import { APP } from '~/configs/meta'
import { useAdminContext } from '~/contexts/admin'
import type { loader } from '~/routes/_admin.lg-admin' import type { loader } from '~/routes/_admin.lg-admin'
export const Navbar = () => { export const Navbar = () => {
const loaderData = useRouteLoaderData<typeof loader>('routes/_admin.lg-admin') const loaderData = useRouteLoaderData<typeof loader>('routes/_admin.lg-admin')
const { staffData } = loaderData || {} const { staffData } = loaderData || {}
const fetcher = useFetcher() const fetcher = useFetcher()
const { setEditProfile } = useAdminContext()
return ( return (
<div className="flex h-20 items-center justify-between border-b border-[#ECECEC] bg-white px-10 py-5"> <div className="flex h-20 items-center justify-between border-b border-[#ECECEC] bg-white px-10 py-5">
@ -40,27 +46,43 @@ export const Navbar = () => {
<span className="text-sm">{staffData?.name}</span> <span className="text-sm">{staffData?.name}</span>
</div> </div>
<ChevronIcon className="opacity-50" /> <ChevronDownIcon className="size-4 opacity-50" />
</PopoverButton> </PopoverButton>
<PopoverPanel <PopoverPanel
anchor={{ to: 'bottom', gap: '8px' }} anchor={{ to: 'bottom', gap: '8px' }}
transition transition
className="flex w-3xs flex-col rounded-xl border border-[#ECECEC] bg-white p-3 transition duration-200 ease-in-out data-[closed]:-translate-y-1 data-[closed]:opacity-0" className="flex w-3xs flex-col divide-y divide-black/5 rounded-xl border border-[#ECECEC] bg-white transition duration-200 ease-in-out data-[closed]:-translate-y-1 data-[closed]:opacity-0"
> >
<fetcher.Form <div className="p-2">
method="POST"
action="/actions/admin/logout"
className="grid"
>
<Button <Button
disabled={fetcher.state !== 'idle'} variant="secondary"
isLoading={fetcher.state !== 'idle'} className="w-full justify-start rounded p-1 px-3 text-lg font-semibold"
type="submit" onClick={() => {
className="w-full rounded p-1" setEditProfile(true)
}}
> >
Logout <UserIcon className="size-5" />
<span>Profile</span>
</Button> </Button>
</fetcher.Form> </div>
<div className="p-2">
<fetcher.Form
method="POST"
action="/actions/admin/logout"
className="grid"
>
<Button
disabled={fetcher.state !== 'idle'}
isLoading={fetcher.state !== 'idle'}
type="submit"
className="w-full justify-start rounded p-1 px-3 text-lg font-semibold"
variant="secondary"
icon={<ArrowRightStartOnRectangleIcon className="size-5" />}
>
<span>Logout</span>
</Button>
</fetcher.Form>
</div>
</PopoverPanel> </PopoverPanel>
</Popover> </Popover>
</div> </div>

View File

@ -34,7 +34,7 @@ export const FooterNewsletter = () => {
/> />
<Button <Button
type="submit" type="submit"
variant="newsPrimaryOutline" variant="primaryOutline"
size="block" size="block"
> >
Subscribe Subscribe

View File

@ -70,7 +70,7 @@ export const HeaderMenuMobile = (properties: THeaderMenuMobile) => {
action="/actions/logout" action="/actions/logout"
> >
<Button <Button
variant="newsSecondary" variant="outline"
className="w-full px-[35px] py-3 text-center sm:hidden" className="w-full px-[35px] py-3 text-center sm:hidden"
type="submit" type="submit"
> >
@ -79,7 +79,7 @@ export const HeaderMenuMobile = (properties: THeaderMenuMobile) => {
</fetcher.Form> </fetcher.Form>
) : ( ) : (
<Button <Button
variant="newsSecondary" variant="outline"
className="w-full px-[35px] py-3 text-center sm:hidden" className="w-full px-[35px] py-3 text-center sm:hidden"
onClick={() => { onClick={() => {
setIsMenuOpen(false) setIsMenuOpen(false)

View File

@ -33,7 +33,7 @@ export const HeaderTop = () => {
action="/actions/logout" action="/actions/logout"
> >
<Button <Button
variant="newsSecondary" variant="outline"
className="hidden sm:flex" className="hidden sm:flex"
type="submit" type="submit"
disabled={fetcher.state !== 'idle'} disabled={fetcher.state !== 'idle'}
@ -44,7 +44,7 @@ export const HeaderTop = () => {
</fetcher.Form> </fetcher.Form>
) : ( ) : (
<Button <Button
variant="newsSecondary" variant="outline"
className="hidden sm:block" className="hidden sm:block"
onClick={() => setIsLoginOpen(true)} onClick={() => setIsLoginOpen(true)}
> >

View File

@ -2,7 +2,7 @@ import {
PencilSquareIcon, PencilSquareIcon,
PlusIcon, PlusIcon,
TrashIcon, TrashIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import type { ConfigColumns } from 'datatables.net-dt' import type { ConfigColumns } from 'datatables.net-dt'
import type { DataTableSlots } from 'datatables.net-react' import type { DataTableSlots } from 'datatables.net-react'
import { useState } from 'react' import { useState } from 'react'
@ -79,7 +79,7 @@ export const AdvertisementsPage = () => {
<Button <Button
type="button" type="button"
size="icon" size="icon"
variant="newsDanger" variant="danger"
onClick={() => setSelectedAds(data)} onClick={() => setSelectedAds(data)}
title="Hapus Banner Iklan" title="Hapus Banner Iklan"
> >

View File

@ -2,7 +2,7 @@ import {
PencilSquareIcon, PencilSquareIcon,
PlusIcon, PlusIcon,
TrashIcon, TrashIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' import DT, { type Config, type ConfigColumns } from 'datatables.net-dt'
import DataTable, { type DataTableSlots } from 'datatables.net-react' import DataTable, { type DataTableSlots } from 'datatables.net-react'
import { useState } from 'react' import { useState } from 'react'
@ -78,7 +78,7 @@ export const CategoriesPage = () => {
<Button <Button
type="button" type="button"
size="icon" size="icon"
variant="newsDanger" variant="danger"
onClick={() => setSelectedCategory(data)} onClick={() => setSelectedCategory(data)}
title="Hapus Kategori" title="Hapus Kategori"
> >

View File

@ -2,7 +2,7 @@ import {
PencilSquareIcon, PencilSquareIcon,
PlusIcon, PlusIcon,
TrashIcon, TrashIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import DT from 'datatables.net-dt' import DT from 'datatables.net-dt'
import DataTable from 'datatables.net-react' import DataTable from 'datatables.net-react'
import { useState } from 'react' import { useState } from 'react'
@ -91,7 +91,7 @@ export const SubscribePlanPage = () => {
<Button <Button
type="button" type="button"
size="icon" size="icon"
variant="newsDanger" variant="danger"
onClick={() => setSelectedSubscribePlan(data)} onClick={() => setSelectedSubscribePlan(data)}
title="Hapus Subscribe Plan" title="Hapus Subscribe Plan"
> >

View File

@ -2,7 +2,7 @@ import {
PencilSquareIcon, PencilSquareIcon,
PlusIcon, PlusIcon,
TrashIcon, TrashIcon,
} from '@heroicons/react/20/solid' } from '@heroicons/react/24/solid'
import DT, { type Config, type ConfigColumns } from 'datatables.net-dt' import DT, { type Config, type ConfigColumns } from 'datatables.net-dt'
import DataTable, { type DataTableSlots } from 'datatables.net-react' import DataTable, { type DataTableSlots } from 'datatables.net-react'
import { useState } from 'react' import { useState } from 'react'
@ -64,7 +64,7 @@ export const TagsPage = () => {
<Button <Button
type="button" type="button"
size="icon" size="icon"
variant="newsDanger" variant="danger"
onClick={() => setSelectedTag(data)} onClick={() => setSelectedTag(data)}
title="Hapus Tag" title="Hapus Tag"
> >