This commit is contained in:
pluja
2025-05-19 21:31:29 +00:00
parent 636057f8e0
commit a21dc81099
13 changed files with 135 additions and 93 deletions

View File

@@ -55,7 +55,7 @@ export const adminUserActions = {
.default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
.transform((val) => val || null),
pictureFile: z.instanceof(File).optional(),
role: z.enum(['admin', 'verifier', 'spammer']),
type: z.array(z.enum(['admin', 'verifier', 'spammer'])),
verifiedLink: z
.string()
.url('Invalid URL')
@@ -69,7 +69,7 @@ export const adminUserActions = {
.default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
.transform((val) => val || null),
}),
handler: async ({ id, pictureFile, ...valuesToUpdate }) => {
handler: async ({ id, pictureFile, type, ...valuesToUpdate }) => {
const user = await prisma.user.findUnique({
where: {
id,
@@ -94,9 +94,15 @@ export const adminUserActions = {
const updatedUser = await prisma.user.update({
where: { id: user.id },
data: {
...valuesToUpdate,
name: valuesToUpdate.name,
link: valuesToUpdate.link,
verifiedLink: valuesToUpdate.verifiedLink,
displayName: valuesToUpdate.displayName,
verified: !!valuesToUpdate.verifiedLink,
picture: pictureUrl,
admin: type.includes('admin'),
verifier: type.includes('verifier'),
spammer: type.includes('spammer'),
},
select: selectUserReturnFields,
})

View File

@@ -203,7 +203,13 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin:
}
{
comment.author.verifier && !comment.author.admin && (
<BadgeSmall icon="ri:shield-check-fill" color="teal" text="Moderator" variant="faded" inlineIcon />
<BadgeSmall
icon="ri:graduation-cap-fill"
color="teal"
text="Moderator"
variant="faded"
inlineIcon
/>
)
}

View File

@@ -33,10 +33,10 @@ if (!user || !user.admin || !user.verifier) return null
---
<div {...divProps} class={cn('text-xs', className)}>
<input type="checkbox" id={`mod-toggle-${String(comment.id)}`} class="peer hidden" />
<input type="checkbox" id={`mod-toggle-${String(comment.id)}`} class="peer sr-only" />
<label
for={`mod-toggle-${String(comment.id)}`}
class="text-day-500 hover:text-day-300 flex cursor-pointer items-center gap-1"
class="text-day-500 hover:text-day-300 peer-focus-visible:ring-offset-night-700 inline-flex cursor-pointer items-center gap-1 rounded-sm peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2"
>
<Icon name="ri:shield-keyhole-line" class="h-3.5 w-3.5" />
<span class="text-xs">Moderation</span>
@@ -44,7 +44,7 @@ if (!user || !user.admin || !user.verifier) return null
</label>
<div
class="bg-night-600 border-night-500 mt-2 max-h-0 overflow-hidden rounded-md border opacity-0 transition-all duration-200 ease-in-out peer-checked:max-h-[500px] peer-checked:p-2 peer-checked:opacity-100"
class="bg-night-600 border-night-500 mt-2 hidden overflow-hidden rounded-md border peer-checked:block peer-checked:p-2"
>
<div class="border-night-500 flex flex-wrap gap-1 border-b pb-2">
<button

View File

@@ -20,6 +20,7 @@ type Props<Multiple extends boolean = false> = Omit<
iconClass?: string
description?: MarkdownString
disabled?: boolean
noTransitionPersist?: boolean
}[]
disabled?: boolean
selectedValue?: Multiple extends true ? string[] : string
@@ -39,13 +40,11 @@ const {
...wrapperProps
} = Astro.props
const inputId = Astro.locals.makeId(`input-${wrapperProps.name}`)
const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
---
{/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */}
<InputWrapper inputId={inputId} class={cn('@container', className)} {...wrapperProps}>
<InputWrapper class={cn('@container', className)} {...wrapperProps}>
<div
class={cn(
'grid grid-cols-[repeat(auto-fill,minmax(var(--card-min-size),1fr))] gap-2 rounded-lg',
@@ -71,7 +70,7 @@ const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
)}
>
<input
transition:persist
transition:persist={option.noTransitionPersist ? undefined : true}
type={multiple ? 'checkbox' : 'radio'}
name={wrapperProps.name}
value={option.value}

View File

@@ -42,7 +42,7 @@ const inputId = id ?? Astro.locals.makeId(`input-${wrapperProps.name}`)
{
ratings.toSorted().map((rating) => (
<label class="relative cursor-pointer [&:has(~_*:hover),&:hover]:[&>[data-star]]:opacity-100!">
<label class="relative cursor-pointer [&:has(~_*_*:checked)]:[&>[data-star]]:opacity-100 [&:has(~_*:hover),&:hover]:[&>[data-star]]:opacity-100!">
<input
type="radio"
name={wrapperProps.name}
@@ -54,7 +54,7 @@ const inputId = id ?? Astro.locals.makeId(`input-${wrapperProps.name}`)
<Icon name="ri:star-line" class="size-6 p-0.5 text-zinc-500" />
<Icon
name="ri:star-fill"
class="absolute top-0 left-0 size-6 p-0.5 text-yellow-400 not-peer-checked:opacity-0 group-hover/fieldset:opacity-0"
class="absolute top-0 left-0 size-6 p-0.5 text-yellow-400 not-peer-checked:opacity-0 group-hover/fieldset:opacity-0!"
data-star
/>
</label>

View File

@@ -17,7 +17,7 @@ const { name, options, selectedValue, class: className, ...rest } = Astro.props
<div
class={cn(
'bg-night-500 divide-night-700 flex divide-x-2 overflow-hidden rounded-md text-[0.6875rem]',
'bg-night-500 divide-night-700 has-focus-visible:ring-offset-night-700 flex divide-x-2 overflow-hidden rounded-md text-[0.6875rem] has-focus-visible:ring-2 has-focus-visible:ring-blue-500 has-focus-visible:ring-offset-2',
className
)}
{...rest}
@@ -30,7 +30,7 @@ const { name, options, selectedValue, class: className, ...rest } = Astro.props
name={name}
value={option.value}
checked={selectedValue === option.value}
class="peer hidden"
class="peer sr-only"
/>
<span class="peer-checked:bg-night-400 inline-block cursor-pointer px-1.5 py-0.5 text-white peer-checked:text-green-500">
{option.label}

View File

@@ -112,7 +112,7 @@ if (!z.string().url().safeParse(link.url).success) {
target="_blank"
rel="noopener noreferrer"
class={cn(
'2xs:text-sm 2xs:h-8 2xs:gap-2 inline-flex h-6 items-center gap-1 rounded-full bg-white text-xs whitespace-nowrap text-black',
'2xs:text-sm 2xs:h-8 2xs:gap-2 focus-visible:ring-offset-night-700 inline-flex h-6 items-center gap-1 rounded-full bg-white text-xs whitespace-nowrap text-black focus-visible:ring-4 focus-visible:ring-orange-500 focus-visible:ring-offset-2 focus-visible:outline-none',
className
)}
{...htmlProps}

View File

@@ -97,8 +97,7 @@ const {
<!-- Type Filter -->
<fieldset class="mb-6">
<legend class="font-title mb-3 leading-none text-green-500">Type</legend>
<input type="checkbox" id="show-more-categories" class="peer hidden" hx-preserve data-show-more-input />
<ul class="not-peer-checked:[&>li:not([data-show-always])]:hidden">
<ul class="[&:not(:has(~_.peer:checked))]:[&>li:not([data-show-always])]:hidden">
{
options.categories?.map((category) => (
<li data-show-always={category.showAlways ? '' : undefined}>
@@ -122,15 +121,22 @@ const {
{
options.categories.filter((category) => category.showAlways).length < options.categories.length && (
<>
<input
type="checkbox"
id="show-more-categories"
class="peer sr-only"
hx-preserve
data-show-more-input
/>
<label
for="show-more-categories"
class="mt-2 block cursor-pointer text-sm text-green-500 peer-checked:hidden"
class="peer-focus-visible:ring-offset-night-700 mt-2 block cursor-pointer rounded-sm text-sm text-green-500 peer-checked:hidden peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2"
>
+ Show more
</label>
<label
for="show-more-categories"
class="mt-2 hidden cursor-pointer text-sm text-green-500 peer-checked:block"
class="peer-focus-visible:ring-offset-night-700 mt-2 hidden cursor-pointer rounded-sm text-sm text-green-500 peer-checked:block peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2"
>
- Show less
</label>
@@ -289,14 +295,8 @@ const {
options.attributesByCategory.map(({ category, attributes }) => (
<fieldset class="min-w-0">
<legend class="font-title mb-0.5 text-xs tracking-wide text-white">{category}</legend>
<input
type="checkbox"
id={`show-more-attributes-${category}`}
class="peer hidden"
hx-preserve
data-show-more-input
/>
<ul class="not-peer-checked:[&>li:not([data-show-always])]:hidden">
<ul class="[:not(:has(~_.peer:checked))]:[&>li:not([data-show-always])]:hidden">
{attributes.map((attribute) => {
const inputName = `attr-${attribute.id}` as const
const yesId = `attr-${attribute.id}=yes` as const
@@ -306,13 +306,13 @@ const {
return (
<li data-show-always={attribute.showAlways ? '' : undefined} class="cursor-pointer">
<fieldset class="flex max-w-full min-w-0 cursor-pointer items-center text-sm text-white">
<fieldset class="relative flex max-w-full min-w-0 cursor-pointer items-center text-sm text-white">
<legend class="sr-only">
{attribute.title} ({attribute._count?.services})
</legend>
<input
type="radio"
class="peer/empty hidden"
class="peer/empty sr-only"
id={emptyId}
name={inputName}
value=""
@@ -324,7 +324,7 @@ const {
name={inputName}
value="yes"
id={yesId}
class="peer/yes hidden"
class="peer/yes sr-only"
checked={attribute.value === 'yes'}
aria-label="Include"
/>
@@ -333,38 +333,45 @@ const {
name={inputName}
value="no"
id={noId}
class="peer/no hidden"
class="peer/no sr-only"
checked={attribute.value === 'no'}
aria-label="Exclude"
/>
<div class="pointer-events-none absolute inset-y-0 -left-[2px] hidden w-[calc(var(--spacing)*4.5*2+1px)] rounded-md border-2 border-blue-500 peer-focus-visible/empty:block peer-focus-visible/no:block peer-focus-visible/yes:block" />
<label
for={yesId}
class="flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-l-sm bg-zinc-950 peer-checked/yes:hidden"
class="border-night-500 bg-night-600 relative flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-l-sm border border-r-0 peer-checked/yes:hidden before:absolute before:-inset-[3px] before:-right-[0.5px]"
aria-hidden="true"
>
<Icon name="ri:check-line" class="size-3" />
</label>
<label
for={emptyId}
class="hidden size-4 shrink-0 cursor-pointer items-center justify-center rounded-l-sm bg-green-600 peer-checked/yes:flex"
class="relative hidden h-4 w-[calc(var(--spacing)*4+0.5px)] shrink-0 cursor-pointer items-center justify-center rounded-l-sm bg-green-600 peer-checked/yes:flex before:absolute before:-inset-[2px] before:-right-[0.5px]"
aria-hidden="true"
>
<Icon name="ri:check-line" class="size-3" />
</label>
<span class="block h-4 w-px border-y-2 border-zinc-950 bg-zinc-800" aria-hidden="true" />
<span
class="bg-night-400 border-night-500 pointer-events-none block h-4 w-px border-y peer-checked/no:w-[0.5px] peer-checked/yes:w-[0.5px]"
aria-hidden="true"
>
<span class="bg-night-400 border-night-600 block h-full w-px border-y-2" />
</span>
<label
for={noId}
class="flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-r-sm bg-zinc-950 peer-checked/no:hidden"
class="border-night-500 bg-night-600 relative flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-r-sm border border-l-0 peer-checked/no:hidden before:absolute before:-inset-[3px] before:-left-[0.5px]"
aria-hidden="true"
>
<Icon name="ri:close-line" class="size-3" />
</label>
<label
for={emptyId}
class="hidden size-4 shrink-0 cursor-pointer items-center justify-center rounded-r-sm bg-red-600 peer-checked/no:flex"
class="relative hidden size-4 w-[calc(var(--spacing)*4+0.5px)] shrink-0 cursor-pointer items-center justify-center rounded-r-sm bg-red-600 peer-checked/no:flex before:absolute before:-inset-[2px] before:-left-[0.5px]"
aria-hidden="true"
>
<Icon name="ri:close-line" class="size-3" />
@@ -376,8 +383,8 @@ const {
aria-hidden="true"
>
<Icon
name={attribute.icon}
class={cn('mr-2 size-3 shrink-0 opacity-80', attribute.iconClass)}
name={attribute.info.icon}
class={cn('mr-2 size-3 shrink-0 opacity-80', attribute.info.classNames.icon)}
aria-hidden="true"
/>
<span class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">
@@ -391,8 +398,8 @@ const {
aria-hidden="true"
>
<Icon
name={attribute.icon}
class={cn('mr-2 size-3 shrink-0 opacity-100', attribute.iconClass)}
name={attribute.info.icon}
class={cn('mr-2 size-3 shrink-0 opacity-100', attribute.info.classNames.icon)}
aria-hidden="true"
/>
<span class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap">
@@ -405,17 +412,25 @@ const {
)
})}
</ul>
{attributes.filter((attribute) => attribute.showAlways).length < attributes.length && (
<>
<input
type="checkbox"
id={`show-more-attributes-${category}`}
class="peer sr-only"
hx-preserve
data-show-more-input
/>
<label
for={`show-more-attributes-${category}`}
class="mt-2 block cursor-pointer text-sm text-green-500 peer-checked:hidden"
class="peer-focus-visible:ring-offset-night-700 mt-2 block cursor-pointer rounded-sm text-sm text-green-500 peer-checked:hidden peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2"
>
+ Show more
</label>
<label
for={`show-more-attributes-${category}`}
class="mt-2 hidden cursor-pointer text-sm text-green-500 peer-checked:block"
class="peer-focus-visible:ring-offset-night-700 mt-2 hidden cursor-pointer rounded-sm text-sm text-green-500 peer-checked:block peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2"
>
- Show less
</label>

View File

@@ -34,7 +34,7 @@ export const {
value,
slug: value ? value.toLowerCase() : '',
label: value ? transformCase(value, 'title') : String(value),
icon: 'ri:question-line',
icon: 'ri:question-fill',
order: Infinity,
classNames: {
container: 'bg-current/30',
@@ -50,7 +50,7 @@ export const {
value: 'BAD',
slug: 'bad',
label: 'Bad',
icon: 'ri:close-line',
icon: 'ri:close-circle-fill',
order: 1,
classNames: {
container: 'bg-red-600/30',
@@ -65,7 +65,7 @@ export const {
value: 'WARNING',
slug: 'warning',
label: 'Warning',
icon: 'ri:alert-line',
icon: 'ri:alert-fill',
order: 2,
classNames: {
container: 'bg-yellow-600/30',
@@ -80,7 +80,7 @@ export const {
value: 'GOOD',
slug: 'good',
label: 'Good',
icon: 'ri:check-line',
icon: 'ri:checkbox-circle-fill',
order: 3,
classNames: {
container: 'bg-green-600/30',
@@ -95,7 +95,7 @@ export const {
value: 'INFO',
slug: 'info',
label: 'Info',
icon: 'ri:information-line',
icon: 'ri:information-fill',
order: 4,
classNames: {
container: 'bg-blue-600/30',

View File

@@ -49,7 +49,7 @@ export const {
value: 'MODERATOR',
slug: 'moderator',
label: 'Moderator',
icon: 'ri:glasses-2-line',
icon: 'ri:graduation-cap-fill',
order: 3,
color: 'teal',
},

View File

@@ -117,7 +117,6 @@ if (!user) return Astro.rewrite('/404')
<BaseLayout
pageTitle={`User: ${user.name}`}
htmx
widthClassName="max-w-screen-lg"
className={{ main: 'space-y-24' }}
>
@@ -140,9 +139,9 @@ if (!user) return Astro.rewrite('/404')
</h1>
<div class="mb-4 flex flex-wrap justify-center gap-2">
{user.admin && <BadgeSmall color="green" text="Admin" icon="ri:shield-check-fill" />}
{user.admin && <BadgeSmall color="green" text="Admin" icon="ri:shield-star-fill" />}
{user.verified && <BadgeSmall color="cyan" text="Verified" icon="ri:verified-badge-fill" />}
{user.verifier && <BadgeSmall color="blue" text="Verifier" icon="ri:check-fill" />}
{user.verifier && <BadgeSmall color="blue" text="Moderator" icon="ri:graduation-cap-fill" />}
{user.spammer && <BadgeSmall color="red" text="Spammer" icon="ri:alert-fill" />}
</div>
@@ -172,7 +171,13 @@ if (!user) return Astro.rewrite('/404')
</div>
</div>
<form method="POST" action={actions.admin.user.update} enctype="multipart/form-data" class="space-y-2">
<form
method="POST"
action={actions.admin.user.update}
enctype="multipart/form-data"
class="space-y-2"
data-astro-reload
>
<h2 class="font-title text-center text-3xl leading-none font-bold">Edit profile</h2>
<input type="hidden" name="id" value={user.id} />
@@ -217,13 +222,19 @@ if (!user) return Astro.rewrite('/404')
/>
<InputCardGroup
name="role"
label="Role"
name="type"
label="Type"
options={[
{ label: 'Admin', value: 'admin', icon: 'ri:shield-check-fill' },
{ label: 'Verified', value: 'verified', icon: 'ri:verified-badge-fill', disabled: true },
{ label: 'Verifier', value: 'verifier', icon: 'ri:check-fill' },
{ label: 'Admin', value: 'admin', icon: 'ri:shield-star-fill' },
{ label: 'Moderator', value: 'verifier', icon: 'ri:graduation-cap-fill' },
{ label: 'Spammer', value: 'spammer', icon: 'ri:alert-fill' },
{
label: 'Verified',
value: 'verified',
icon: 'ri:verified-badge-fill',
disabled: true,
noTransitionPersist: true,
},
]}
selectedValue={[
user.admin ? 'admin' : null,
@@ -235,7 +246,7 @@ if (!user) return Astro.rewrite('/404')
cardSize="sm"
iconSize="sm"
multiple
error={updateInputErrors.role}
error={updateInputErrors.type}
/>
<InputSubmitButton label="Save" icon="ri:save-line" hideCancel />
@@ -280,7 +291,12 @@ if (!user) return Astro.rewrite('/404')
<Icon name="ri:edit-line" class="size-5" />
</label>
<form method="POST" action={actions.admin.user.internalNotes.delete} class="contents">
<form
method="POST"
action={actions.admin.user.internalNotes.delete}
class="contents"
data-astro-reload
>
<input type="hidden" name="noteId" value={note.id} />
<button type="submit" class="text-day-300 p-1 transition-colors hover:text-red-400">
<Icon name="ri:delete-bin-line" class="size-5" />
@@ -297,6 +313,7 @@ if (!user) return Astro.rewrite('/404')
method="POST"
action={actions.admin.user.internalNotes.update}
data-note-edit-form
data-astro-reload
class="mt-4 hidden space-y-4"
>
<input type="hidden" name="noteId" value={note.id} />
@@ -314,7 +331,12 @@ if (!user) return Astro.rewrite('/404')
)
}
<form method="POST" action={actions.admin.user.internalNotes.add} class="mt-10 space-y-2">
<form
method="POST"
action={actions.admin.user.internalNotes.add}
class="mt-10 space-y-2"
data-astro-reload
>
<h3 class="font-title mb-0 text-center text-xl leading-none font-bold">Add Note</h3>
<input type="hidden" name="userId" value={user.id} />

View File

@@ -10,6 +10,7 @@ import Pagination from '../components/Pagination.astro'
import ServiceFiltersPill from '../components/ServiceFiltersPill.astro'
import ServicesFilters from '../components/ServicesFilters.astro'
import ServicesSearchResults from '../components/ServicesSearchResults.astro'
import { getAttributeTypeInfo } from '../constants/attributeTypes'
import {
currencies,
currenciesZodEnumBySlug,
@@ -31,7 +32,7 @@ import { prisma } from '../lib/prisma'
import { makeSortSeed } from '../lib/sortSeed'
import { transformCase } from '../lib/strings'
import type { AttributeType, Prisma } from '@prisma/client'
import type { Prisma } from '@prisma/client'
const MIN_CATEGORIES_TO_SHOW = 8
const MIN_ATTRIBUTES_TO_SHOW = 8
@@ -324,7 +325,10 @@ const [categories, [services, totalServices, hadToIncludeCommunityContributed]]
})
let hadToIncludeCommunityContributed = false
if (totalServices === 0 && !where.verificationStatus.in.includes('COMMUNITY_CONTRIBUTED')) {
if (
totalServices === 0 &&
areEqualArraysWithoutOrder(where.verificationStatus.in, ['VERIFICATION_FAILED', 'APPROVED'])
) {
const [unsortedServiceCommunityServices, totalCommunityServices] =
await prisma.service.findManyAndCount({
where: {
@@ -408,25 +412,6 @@ const [categories, [services, totalServices, hadToIncludeCommunityContributed]]
],
])
const attributeIcons = {
GOOD: {
icon: 'ri:check-line',
iconClass: 'text-green-400',
},
BAD: {
icon: 'ri:close-line',
iconClass: 'text-red-400',
},
WARNING: {
icon: 'ri:alert-line',
iconClass: 'text-yellow-400',
},
INFO: {
icon: 'ri:information-line',
iconClass: 'text-blue-400',
},
} as const satisfies Record<AttributeType, { icon: string; iconClass: string }>
const attributes = await Astro.locals.banners.try(
'Unable to load attribute filters.',
() =>
@@ -451,12 +436,14 @@ const attributes = await Astro.locals.banners.try(
const attributesByCategory = orderBy(
Object.entries(
groupBy(
attributes.map((attr) => ({
...attr,
...attributeIcons[attr.type],
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
value: filters.attr?.[attr.id] || undefined,
})),
attributes.map((attr) => {
return {
info: getAttributeTypeInfo(attr.type),
...attr,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
value: filters.attr?.[attr.id] || undefined,
}
}),
'category'
)
).map(([category, attributes]) => ({
@@ -533,7 +520,9 @@ const activeAnnouncements = await prisma.announcement.findMany({
<AnnouncementBanner announcements={activeAnnouncements} />
<div class="flex flex-col gap-4 sm:flex-row sm:gap-8">
<div class="flex items-stretch sm:hidden">
<div
class='[&:has(~_#show-filters:focus-visible)_[for="show-filters"]]:ring-offset-night-700 flex items-stretch sm:hidden [&:has(~_#show-filters:focus-visible)_[for="show-filters"]]:ring-2 [&:has(~_#show-filters:focus-visible)_[for="show-filters"]]:ring-green-500 [&:has(~_#show-filters:focus-visible)_[for="show-filters"]]:ring-offset-2'
>
{
!hasDefaultFilters ? (
<div class="-ml-4 flex flex-1 items-center gap-2 overflow-x-auto mask-r-from-[calc(100%-var(--spacing)*16)] pr-12 pl-4">
@@ -656,11 +645,11 @@ const activeAnnouncements = await prisma.announcement.findMany({
type="checkbox"
id="show-filters"
name="show-filters"
class="peer hidden"
class="peer sr-only sm:hidden"
checked={Astro.url.searchParams.has('show-filters')}
/>
<div
class="bg-night-700 fixed top-0 left-0 z-50 h-dvh w-dvw shrink-0 translate-y-full overflow-y-auto overscroll-contain border-t border-green-500/30 px-8 pt-4 transition-transform peer-checked:translate-y-0 sm:relative sm:z-auto sm:h-auto sm:w-64 sm:translate-y-0 sm:overflow-visible sm:border-none sm:bg-none sm:p-0"
class="bg-night-700 fixed top-0 left-0 z-50 hidden h-dvh w-dvw shrink-0 translate-y-full overflow-y-auto overscroll-contain border-t border-green-500/30 px-8 pt-4 transition-transform peer-checked:translate-y-0 max-sm:peer-checked:block sm:relative sm:z-auto sm:block sm:h-auto sm:w-64 sm:translate-y-0 sm:overflow-visible sm:border-none sm:bg-none sm:p-0"
>
<ServicesFilters
searchResultsId="search-results"

View File

@@ -693,10 +693,15 @@ const ogImageTemplateData = {
</li>
))}
<input type="checkbox" class="peer hidden" id="show-more-links" checked={hiddenLinks.length === 0} />
<input
type="checkbox"
class="peer sr-only checked:hidden"
id="show-more-links"
checked={hiddenLinks.length === 0}
/>
{hiddenLinks.length > 0 && (
<li class="peer-checked:hidden">
<li class="peer-focus-visible:ring-offset-night-700 rounded-full peer-checked:hidden peer-focus-visible:ring-4 peer-focus-visible:ring-orange-500 peer-focus-visible:ring-offset-2">
<label
for="show-more-links"
class="2xs:text-sm 2xs:h-8 2xs:gap-2 2xs:px-4 text-day-100 bg-day-800 hover:bg-day-900 inline-flex h-6 cursor-pointer items-center gap-1 rounded-full px-2 text-xs whitespace-nowrap transition-colors duration-200"