Release 2025-05-19
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { actions } from 'astro:actions'
|
||||
|
||||
import Chat from '../../../components/Chat.astro'
|
||||
import ServiceCard from '../../../components/ServiceCard.astro'
|
||||
import { getServiceSuggestionStatusInfo } from '../../../constants/serviceSuggestionStatus'
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro'
|
||||
import { cn } from '../../../lib/cn'
|
||||
import { parseIntWithFallback } from '../../../lib/numbers'
|
||||
import { prisma } from '../../../lib/prisma'
|
||||
import { makeLoginUrl } from '../../../lib/redirectUrls'
|
||||
|
||||
const user = Astro.locals.user
|
||||
if (!user?.admin) {
|
||||
return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Admin access required' }))
|
||||
}
|
||||
|
||||
const { id: serviceSuggestionIdRaw } = Astro.params
|
||||
const serviceSuggestionId = parseIntWithFallback(serviceSuggestionIdRaw)
|
||||
if (!serviceSuggestionId) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const serviceSuggestion = await Astro.locals.banners.try('Error fetching service suggestion', async () =>
|
||||
prisma.serviceSuggestion.findUnique({
|
||||
where: {
|
||||
id: serviceSuggestionId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
notes: true,
|
||||
createdAt: true,
|
||||
type: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
overallScore: true,
|
||||
kycLevel: true,
|
||||
imageUrl: true,
|
||||
verificationStatus: true,
|
||||
acceptedCurrencies: true,
|
||||
categories: {
|
||||
select: {
|
||||
name: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
picture: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (!serviceSuggestion) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const statusInfo = getServiceSuggestionStatusInfo(serviceSuggestion.status)
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
pageTitle={`${serviceSuggestion.service.name} | Admin Service Suggestion`}
|
||||
htmx
|
||||
widthClassName="max-w-screen-md"
|
||||
>
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<a
|
||||
href="/admin/service-suggestions"
|
||||
class="font-title inline-flex items-center justify-center rounded-md border border-green-500/30 bg-green-500/10 px-3 py-2 text-sm text-green-400 shadow-xs transition-colors duration-200 hover:bg-green-500/20 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-black focus:outline-hidden"
|
||||
>
|
||||
<Icon name="ri:arrow-left-s-line" class="mr-1 size-4" />
|
||||
Back
|
||||
</a>
|
||||
|
||||
<h1 class="font-title text-xl text-green-500">Service Suggestion</h1>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<ServiceCard service={serviceSuggestion.service} class="mx-auto max-w-full" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-green-500/30 bg-black/40 p-4 shadow-[0_0_15px_rgba(34,197,94,0.2)] backdrop-blur-xs"
|
||||
>
|
||||
<h2 class="font-title mb-3 text-lg text-green-500">Suggestion Details</h2>
|
||||
|
||||
<div class="mb-3 grid grid-cols-[auto_1fr] gap-x-3 gap-y-2 text-sm">
|
||||
<span class="font-title text-gray-400">Status:</span>
|
||||
<span
|
||||
class={cn(
|
||||
'inline-flex w-fit items-center rounded-full px-2.5 py-0.5 text-xs font-medium',
|
||||
statusInfo.iconClass
|
||||
)}
|
||||
>
|
||||
<Icon name={statusInfo.icon} class="mr-1 size-3" />
|
||||
{statusInfo.label}
|
||||
</span>
|
||||
|
||||
<span class="font-title text-gray-400">Submitted by:</span>
|
||||
<span class="text-gray-300">
|
||||
<a href={`/admin/users?name=${serviceSuggestion.user.name}`} class="hover:text-green-500">
|
||||
{serviceSuggestion.user.name}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span class="font-title text-gray-400">Submitted at:</span>
|
||||
<span class="text-gray-300">{serviceSuggestion.createdAt.toLocaleString()}</span>
|
||||
|
||||
<span class="font-title text-gray-400">Service page:</span>
|
||||
<a href={`/service/${serviceSuggestion.service.slug}`} class="text-green-400 hover:text-green-500">
|
||||
View Service <Icon
|
||||
name="ri:external-link-line"
|
||||
class="ml-0.5 inline-block size-3 align-[-0.05em]"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{
|
||||
serviceSuggestion.notes && (
|
||||
<div class="mb-4">
|
||||
<h3 class="font-title mb-1 text-sm text-gray-400">Notes from user:</h3>
|
||||
<div class="rounded-md border border-gray-700 bg-black/50 p-3 text-sm whitespace-pre-wrap text-gray-300">
|
||||
{serviceSuggestion.notes}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-green-500/30 bg-black/40 p-6 shadow-[0_0_15px_rgba(34,197,94,0.2)] backdrop-blur-xs"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="font-title text-lg text-green-500">Messages</h2>
|
||||
|
||||
<form method="POST" action={actions.admin.serviceSuggestions.update} class="flex gap-2">
|
||||
<input type="hidden" name="suggestionId" value={serviceSuggestion.id} />
|
||||
<select
|
||||
name="status"
|
||||
class="font-title w-full rounded-md border border-green-500/30 bg-black/50 p-2 text-sm text-gray-300 placeholder-gray-500 focus:border-green-500 focus:ring-green-500 disabled:opacity-50"
|
||||
>
|
||||
<option value="PENDING" selected={serviceSuggestion.status === 'PENDING'}> Pending </option>
|
||||
<option value="APPROVED" selected={serviceSuggestion.status === 'APPROVED'}> Approve </option>
|
||||
<option value="REJECTED" selected={serviceSuggestion.status === 'REJECTED'}> Reject </option>
|
||||
<option value="WITHDRAWN" selected={serviceSuggestion.status === 'WITHDRAWN'}> Withdrawn </option>
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
class="font-title inline-flex items-center justify-center rounded-md border border-green-500/30 bg-green-500/10 px-4 py-2 text-sm text-green-400 shadow-xs transition-colors duration-200 hover:bg-green-500/20 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-black focus:outline-hidden"
|
||||
>
|
||||
<Icon name="ri:save-line" class="mr-1 size-4" /> Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<Chat
|
||||
messages={serviceSuggestion.messages}
|
||||
userId={user.id}
|
||||
action={actions.admin.serviceSuggestions.message}
|
||||
formData={{
|
||||
suggestionId: serviceSuggestion.id,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -1,385 +0,0 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { actions } from 'astro:actions'
|
||||
import { z } from 'astro:content'
|
||||
import { orderBy as lodashOrderBy } from 'lodash-es'
|
||||
|
||||
import SortArrowIcon from '../../../components/SortArrowIcon.astro'
|
||||
import TimeFormatted from '../../../components/TimeFormatted.astro'
|
||||
import {
|
||||
getServiceSuggestionStatusInfo,
|
||||
serviceSuggestionStatuses,
|
||||
} from '../../../constants/serviceSuggestionStatus'
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro'
|
||||
import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters'
|
||||
import { prisma } from '../../../lib/prisma'
|
||||
import { makeLoginUrl } from '../../../lib/redirectUrls'
|
||||
|
||||
import type { Prisma, ServiceSuggestionStatus } from '@prisma/client'
|
||||
|
||||
const user = Astro.locals.user
|
||||
if (!user?.admin) {
|
||||
return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Admin access required' }))
|
||||
}
|
||||
|
||||
const search = Astro.url.searchParams.get('search') ?? ''
|
||||
const statusEnumValues = serviceSuggestionStatuses.map((s) => s.value) as [string, ...string[]]
|
||||
const statusParam = Astro.url.searchParams.get('status')
|
||||
const statusFilter = z
|
||||
.enum(statusEnumValues)
|
||||
.nullable()
|
||||
.parse(statusParam === '' ? null : statusParam) as ServiceSuggestionStatus | null
|
||||
|
||||
const { data: filters } = zodParseQueryParamsStoringErrors(
|
||||
{
|
||||
'sort-by': z.enum(['service', 'status', 'user', 'createdAt', 'messageCount']).default('createdAt'),
|
||||
'sort-order': z.enum(['asc', 'desc']).default('desc'),
|
||||
},
|
||||
Astro
|
||||
)
|
||||
|
||||
const sortBy = filters['sort-by']
|
||||
const sortOrder = filters['sort-order']
|
||||
|
||||
let prismaOrderBy: Prisma.ServiceSuggestionOrderByWithRelationInput = { createdAt: 'desc' }
|
||||
if (sortBy === 'createdAt') {
|
||||
prismaOrderBy = { createdAt: sortOrder }
|
||||
}
|
||||
|
||||
let suggestions = await prisma.serviceSuggestion.findMany({
|
||||
where: {
|
||||
...(search
|
||||
? {
|
||||
OR: [
|
||||
{ service: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ user: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ notes: { contains: search, mode: 'insensitive' } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
status: statusFilter ?? undefined,
|
||||
},
|
||||
orderBy: prismaOrderBy,
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
notes: true,
|
||||
createdAt: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
imageUrl: true,
|
||||
verificationStatus: true,
|
||||
categories: {
|
||||
select: {
|
||||
name: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
messages: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
let suggestionsWithDetails = suggestions.map((s) => ({
|
||||
...s,
|
||||
statusInfo: getServiceSuggestionStatusInfo(s.status),
|
||||
messageCount: s._count.messages,
|
||||
lastMessage: s.messages[0],
|
||||
}))
|
||||
|
||||
if (sortBy === 'service') {
|
||||
suggestionsWithDetails = lodashOrderBy(
|
||||
suggestionsWithDetails,
|
||||
[(s) => s.service.name.toLowerCase()],
|
||||
[sortOrder]
|
||||
)
|
||||
} else if (sortBy === 'status') {
|
||||
suggestionsWithDetails = lodashOrderBy(suggestionsWithDetails, [(s) => s.statusInfo.label], [sortOrder])
|
||||
} else if (sortBy === 'user') {
|
||||
suggestionsWithDetails = lodashOrderBy(
|
||||
suggestionsWithDetails,
|
||||
[(s) => s.user.name.toLowerCase()],
|
||||
[sortOrder]
|
||||
)
|
||||
} else if (sortBy === 'messageCount') {
|
||||
suggestionsWithDetails = lodashOrderBy(suggestionsWithDetails, ['messageCount'], [sortOrder])
|
||||
}
|
||||
|
||||
const suggestionCount = suggestionsWithDetails.length
|
||||
|
||||
const makeSortUrl = (slug: string) => {
|
||||
const currentSortBy = filters['sort-by']
|
||||
const currentSortOrder = filters['sort-order']
|
||||
const newSortOrder = currentSortBy === slug && currentSortOrder === 'asc' ? 'desc' : 'asc'
|
||||
const searchParams = new URLSearchParams(Astro.url.search)
|
||||
searchParams.set('sort-by', slug)
|
||||
searchParams.set('sort-order', newSortOrder)
|
||||
return `/admin/service-suggestions?${searchParams.toString()}`
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle="Service Suggestions" widthClassName="max-w-screen-xl">
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<h1 class="font-title text-2xl font-bold text-white">Service Suggestions</h1>
|
||||
<div class="mt-2 flex items-center gap-4 sm:mt-0">
|
||||
<span class="text-sm text-zinc-400">{suggestionCount} suggestions</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg border border-zinc-700 bg-zinc-800/50 p-4 shadow-lg">
|
||||
<form method="GET" class="grid gap-3 md:grid-cols-2 lg:grid-cols-4" autocomplete="off">
|
||||
<div class="lg:col-span-2">
|
||||
<label for="search" class="block text-xs font-medium text-zinc-400">Search</label>
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
id="search"
|
||||
value={search}
|
||||
placeholder="Search by service, user, notes..."
|
||||
class="mt-1 w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-200 placeholder-zinc-500 focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="status-filter" class="block text-xs font-medium text-zinc-400">Status</label>
|
||||
<select
|
||||
name="status"
|
||||
id="status-filter"
|
||||
class="mt-1 w-full rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2 text-sm text-zinc-200 focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="">All Statuses</option>
|
||||
{
|
||||
serviceSuggestionStatuses.map((status) => (
|
||||
<option value={status.value} selected={statusFilter === status.value}>
|
||||
{status.label}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-zinc-900 focus:outline-none"
|
||||
>
|
||||
<Icon name="ri:search-2-line" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-zinc-700 bg-zinc-800/50 shadow-lg">
|
||||
<div class="sticky top-0 z-10 border-b border-zinc-700 bg-zinc-800/90 px-4 py-3 backdrop-blur-sm">
|
||||
<h2 class="font-title font-semibold text-blue-400">Suggestions List</h2>
|
||||
<div class="mt-1 text-xs text-zinc-400 md:hidden">
|
||||
<span>Scroll horizontally to see more →</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-thin max-w-full overflow-x-auto">
|
||||
<div class="min-w-[900px]">
|
||||
<table class="w-full divide-y divide-zinc-700">
|
||||
<thead class="bg-zinc-900/30">
|
||||
<tr>
|
||||
<th
|
||||
class="w-[25%] px-4 py-3 text-left text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
<a href={makeSortUrl('service')} class="flex items-center hover:text-zinc-200">
|
||||
Service <SortArrowIcon
|
||||
active={filters['sort-by'] === 'service'}
|
||||
sortOrder={filters['sort-order']}
|
||||
/>
|
||||
</a>
|
||||
</th>
|
||||
<th
|
||||
class="w-[15%] px-4 py-3 text-left text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
<a href={makeSortUrl('user')} class="flex items-center hover:text-zinc-200">
|
||||
User <SortArrowIcon
|
||||
active={filters['sort-by'] === 'user'}
|
||||
sortOrder={filters['sort-order']}
|
||||
/>
|
||||
</a>
|
||||
</th>
|
||||
<th
|
||||
class="w-[15%] px-4 py-3 text-left text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
<a href={makeSortUrl('status')} class="flex items-center hover:text-zinc-200">
|
||||
Status <SortArrowIcon
|
||||
active={filters['sort-by'] === 'status'}
|
||||
sortOrder={filters['sort-order']}
|
||||
/>
|
||||
</a>
|
||||
</th>
|
||||
<th
|
||||
class="w-[15%] px-4 py-3 text-left text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
<a href={makeSortUrl('createdAt')} class="flex items-center hover:text-zinc-200">
|
||||
Created <SortArrowIcon
|
||||
active={filters['sort-by'] === 'createdAt'}
|
||||
sortOrder={filters['sort-order']}
|
||||
/>
|
||||
</a>
|
||||
</th>
|
||||
<th
|
||||
class="w-[10%] px-4 py-3 text-center text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
<a
|
||||
href={makeSortUrl('messageCount')}
|
||||
class="flex items-center justify-center hover:text-zinc-200"
|
||||
>
|
||||
Messages <SortArrowIcon
|
||||
active={filters['sort-by'] === 'messageCount'}
|
||||
sortOrder={filters['sort-order']}
|
||||
/>
|
||||
</a>
|
||||
</th>
|
||||
<th
|
||||
class="w-[20%] px-4 py-3 text-right text-xs font-medium tracking-wider text-zinc-400 uppercase"
|
||||
>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-700 bg-zinc-800/10">
|
||||
{
|
||||
suggestionsWithDetails.map((suggestion) => (
|
||||
<tr id={`suggestion-${suggestion.id}`} class="group hover:bg-zinc-700/30">
|
||||
<td class="px-4 py-3 text-sm font-medium text-zinc-200">
|
||||
<div class="truncate" title={suggestion.service.name}>
|
||||
<a
|
||||
href={`/service/${suggestion.service.slug}`}
|
||||
class="flex items-center gap-1 hover:text-green-500"
|
||||
>
|
||||
{suggestion.service.name}
|
||||
<Icon name="ri:external-link-line" class="inline-block size-4 align-[-0.05em]" />
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="text-2xs max-w-[220px] truncate text-zinc-500"
|
||||
title={suggestion.service.description}
|
||||
>
|
||||
{suggestion.service.description}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<a href={`/admin/users?name=${suggestion.user.name}`} class="hover:text-green-500">
|
||||
{suggestion.user.name}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<form method="POST" action={actions.admin.serviceSuggestions.update}>
|
||||
<input type="hidden" name="suggestionId" value={suggestion.id} />
|
||||
<select
|
||||
name="status"
|
||||
class="rounded-md border border-zinc-700 bg-zinc-900 px-2 py-1 text-xs text-zinc-200 focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
|
||||
value={suggestion.status}
|
||||
onchange="this.form.submit()"
|
||||
title="Change status"
|
||||
>
|
||||
{serviceSuggestionStatuses.map((status) => (
|
||||
<option value={status.value} selected={suggestion.status === status.value}>
|
||||
{status.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<TimeFormatted date={suggestion.createdAt} hourPrecision />
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span class="inline-flex items-center rounded-full bg-zinc-700 px-2.5 py-0.5 text-xs font-medium text-zinc-300">
|
||||
{suggestion.messageCount}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<a
|
||||
href={`/admin/service-suggestions/${suggestion.id}`}
|
||||
class="inline-flex items-center justify-center rounded-full border border-green-500/40 bg-green-500/10 p-1.5 text-green-400 shadow-xs transition-colors duration-200 hover:bg-green-500/20 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-black focus:outline-hidden"
|
||||
title="View"
|
||||
>
|
||||
<Icon name="ri:external-link-line" class="size-4" />
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
@keyframes highlight {
|
||||
0% {
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.text-2xs {
|
||||
font-size: 0.6875rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: rgba(30, 41, 59, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background: rgba(75, 85, 99, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(100, 116, 139, 0.6);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(75, 85, 99, 0.5) rgba(30, 41, 59, 0.2);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user