Release 202505281348
This commit is contained in:
@@ -916,7 +916,7 @@ const specialUsersData = {
|
|||||||
verifiedLink: 'https://kycnot.me',
|
verifiedLink: 'https://kycnot.me',
|
||||||
totalKarma: 1001,
|
totalKarma: 1001,
|
||||||
link: 'https://kycnot.me',
|
link: 'https://kycnot.me',
|
||||||
picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L',
|
picture: 'https://kycnot.me/files/users/pictures/c277dc0f2f.png',
|
||||||
},
|
},
|
||||||
moderator: {
|
moderator: {
|
||||||
name: 'moderator_dev',
|
name: 'moderator_dev',
|
||||||
@@ -928,7 +928,7 @@ const specialUsersData = {
|
|||||||
verifiedLink: 'https://kycnot.me',
|
verifiedLink: 'https://kycnot.me',
|
||||||
totalKarma: 1001,
|
totalKarma: 1001,
|
||||||
link: 'https://kycnot.me',
|
link: 'https://kycnot.me',
|
||||||
picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L',
|
picture: 'https://kycnot.me/files/users/pictures/c277dc0f2f.png',
|
||||||
},
|
},
|
||||||
verified: {
|
verified: {
|
||||||
name: 'verified_dev',
|
name: 'verified_dev',
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ import slugify from 'slugify'
|
|||||||
import { defineProtectedAction } from '../../lib/defineProtectedAction'
|
import { defineProtectedAction } from '../../lib/defineProtectedAction'
|
||||||
import { saveFileLocally } from '../../lib/fileStorage'
|
import { saveFileLocally } from '../../lib/fileStorage'
|
||||||
import { prisma } from '../../lib/prisma'
|
import { prisma } from '../../lib/prisma'
|
||||||
import {
|
import { separateServiceUrlsByType } from '../../lib/urls'
|
||||||
imageFileSchema,
|
import { imageFileSchema, stringListOfUrlsSchemaRequired, zodCohercedNumber } from '../../lib/zodUtils'
|
||||||
stringListOfUrlsSchema,
|
|
||||||
stringListOfUrlsSchemaRequired,
|
|
||||||
zodCohercedNumber,
|
|
||||||
} from '../../lib/zodUtils'
|
|
||||||
|
|
||||||
const serviceSchemaBase = z.object({
|
const serviceSchemaBase = z.object({
|
||||||
id: z.number().int().positive(),
|
id: z.number().int().positive(),
|
||||||
@@ -19,11 +15,10 @@ const serviceSchemaBase = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.regex(/^[a-z0-9-]+$/, 'Allowed characters: lowercase letters, numbers, and hyphens')
|
.regex(/^[a-z0-9-]+$/, 'Allowed characters: lowercase letters, numbers, and hyphens')
|
||||||
.optional(),
|
.optional(),
|
||||||
name: z.string().min(1).max(20),
|
name: z.string().min(1).max(40),
|
||||||
description: z.string().min(1),
|
description: z.string().min(1),
|
||||||
serviceUrls: stringListOfUrlsSchemaRequired,
|
allServiceUrls: stringListOfUrlsSchemaRequired,
|
||||||
tosUrls: stringListOfUrlsSchemaRequired,
|
tosUrls: stringListOfUrlsSchemaRequired,
|
||||||
onionUrls: stringListOfUrlsSchema,
|
|
||||||
kycLevel: z.coerce.number().int().min(0).max(4),
|
kycLevel: z.coerce.number().int().min(0).max(4),
|
||||||
attributes: z.array(z.coerce.number().int().positive()),
|
attributes: z.array(z.coerce.number().int().positive()),
|
||||||
categories: z.array(z.coerce.number().int().positive()).min(1),
|
categories: z.array(z.coerce.number().int().positive()).min(1),
|
||||||
@@ -85,13 +80,20 @@ export const adminServiceActions = {
|
|||||||
? await saveFileLocally(input.imageFile, input.imageFile.name)
|
? await saveFileLocally(input.imageFile, input.imageFile.name)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
const {
|
||||||
|
web: serviceUrls,
|
||||||
|
onion: onionUrls,
|
||||||
|
i2p: i2pUrls,
|
||||||
|
} = separateServiceUrlsByType(input.allServiceUrls)
|
||||||
|
|
||||||
const service = await prisma.service.create({
|
const service = await prisma.service.create({
|
||||||
data: {
|
data: {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
serviceUrls: input.serviceUrls,
|
serviceUrls,
|
||||||
tosUrls: input.tosUrls,
|
tosUrls: input.tosUrls,
|
||||||
onionUrls: input.onionUrls,
|
onionUrls,
|
||||||
|
i2pUrls,
|
||||||
kycLevel: input.kycLevel,
|
kycLevel: input.kycLevel,
|
||||||
verificationStatus: input.verificationStatus,
|
verificationStatus: input.verificationStatus,
|
||||||
verificationSummary: input.verificationSummary,
|
verificationSummary: input.verificationSummary,
|
||||||
@@ -187,14 +189,21 @@ export const adminServiceActions = {
|
|||||||
const attributesToAdd = input.attributes.filter((aId) => !existingAttributeIds.includes(aId))
|
const attributesToAdd = input.attributes.filter((aId) => !existingAttributeIds.includes(aId))
|
||||||
const attributesToRemove = existingAttributeIds.filter((aId) => !input.attributes.includes(aId))
|
const attributesToRemove = existingAttributeIds.filter((aId) => !input.attributes.includes(aId))
|
||||||
|
|
||||||
|
const {
|
||||||
|
web: serviceUrls,
|
||||||
|
onion: onionUrls,
|
||||||
|
i2p: i2pUrls,
|
||||||
|
} = separateServiceUrlsByType(input.allServiceUrls)
|
||||||
|
|
||||||
const service = await prisma.service.update({
|
const service = await prisma.service.update({
|
||||||
where: { id: input.id },
|
where: { id: input.id },
|
||||||
data: {
|
data: {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
serviceUrls: input.serviceUrls,
|
serviceUrls,
|
||||||
tosUrls: input.tosUrls,
|
tosUrls: input.tosUrls,
|
||||||
onionUrls: input.onionUrls,
|
onionUrls,
|
||||||
|
i2pUrls,
|
||||||
kycLevel: input.kycLevel,
|
kycLevel: input.kycLevel,
|
||||||
verificationStatus: input.verificationStatus,
|
verificationStatus: input.verificationStatus,
|
||||||
verificationSummary: input.verificationSummary,
|
verificationSummary: input.verificationSummary,
|
||||||
|
|||||||
@@ -14,12 +14,8 @@ import { defineProtectedAction } from '../lib/defineProtectedAction'
|
|||||||
import { saveFileLocally } from '../lib/fileStorage'
|
import { saveFileLocally } from '../lib/fileStorage'
|
||||||
import { handleHoneypotTrap } from '../lib/honeypot'
|
import { handleHoneypotTrap } from '../lib/honeypot'
|
||||||
import { prisma } from '../lib/prisma'
|
import { prisma } from '../lib/prisma'
|
||||||
import {
|
import { separateServiceUrlsByType } from '../lib/urls'
|
||||||
imageFileSchemaRequired,
|
import { imageFileSchemaRequired, stringListOfUrlsSchemaRequired, zodCohercedNumber } from '../lib/zodUtils'
|
||||||
stringListOfUrlsSchema,
|
|
||||||
stringListOfUrlsSchemaRequired,
|
|
||||||
zodCohercedNumber,
|
|
||||||
} from '../lib/zodUtils'
|
|
||||||
|
|
||||||
import type { Prisma } from '@prisma/client'
|
import type { Prisma } from '@prisma/client'
|
||||||
|
|
||||||
@@ -161,9 +157,8 @@ export const serviceSuggestionActions = {
|
|||||||
{ message: 'Slug must be unique, try a different one' }
|
{ message: 'Slug must be unique, try a different one' }
|
||||||
),
|
),
|
||||||
description: z.string().min(1).max(SUGGESTION_DESCRIPTION_MAX_LENGTH),
|
description: z.string().min(1).max(SUGGESTION_DESCRIPTION_MAX_LENGTH),
|
||||||
serviceUrls: stringListOfUrlsSchemaRequired,
|
allServiceUrls: stringListOfUrlsSchemaRequired,
|
||||||
tosUrls: stringListOfUrlsSchemaRequired,
|
tosUrls: stringListOfUrlsSchemaRequired,
|
||||||
onionUrls: stringListOfUrlsSchema,
|
|
||||||
kycLevel: zodCohercedNumber(z.coerce.number().int().min(0).max(4)),
|
kycLevel: zodCohercedNumber(z.coerce.number().int().min(0).max(4)),
|
||||||
attributes: z.array(z.coerce.number().int().positive()),
|
attributes: z.array(z.coerce.number().int().positive()),
|
||||||
categories: z.array(z.coerce.number().int().positive()).min(1),
|
categories: z.array(z.coerce.number().int().positive()).min(1),
|
||||||
@@ -210,6 +205,12 @@ export const serviceSuggestionActions = {
|
|||||||
|
|
||||||
const imageUrl = await saveFileLocally(input.imageFile, input.imageFile.name)
|
const imageUrl = await saveFileLocally(input.imageFile, input.imageFile.name)
|
||||||
|
|
||||||
|
const {
|
||||||
|
web: serviceUrls,
|
||||||
|
onion: onionUrls,
|
||||||
|
i2p: i2pUrls,
|
||||||
|
} = separateServiceUrlsByType(input.allServiceUrls)
|
||||||
|
|
||||||
const { serviceSuggestion, service } = await prisma.$transaction(async (tx) => {
|
const { serviceSuggestion, service } = await prisma.$transaction(async (tx) => {
|
||||||
const serviceSelect = {
|
const serviceSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -221,9 +222,10 @@ export const serviceSuggestionActions = {
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
slug: input.slug,
|
slug: input.slug,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
serviceUrls: input.serviceUrls,
|
serviceUrls,
|
||||||
tosUrls: input.tosUrls,
|
tosUrls: input.tosUrls,
|
||||||
onionUrls: input.onionUrls,
|
onionUrls,
|
||||||
|
i2pUrls,
|
||||||
kycLevel: input.kycLevel,
|
kycLevel: input.kycLevel,
|
||||||
acceptedCurrencies: input.acceptedCurrencies,
|
acceptedCurrencies: input.acceptedCurrencies,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ type Props = HTMLAttributes<'div'> & {
|
|||||||
pageSize: number
|
pageSize: number
|
||||||
sortSeed?: string
|
sortSeed?: string
|
||||||
filters: ServicesFiltersObject
|
filters: ServicesFiltersObject
|
||||||
includeScams: boolean
|
|
||||||
countCommunityOnly: number | null
|
countCommunityOnly: number | null
|
||||||
inlineIcons?: boolean
|
inlineIcons?: boolean
|
||||||
}
|
}
|
||||||
@@ -35,15 +34,12 @@ const {
|
|||||||
sortSeed,
|
sortSeed,
|
||||||
class: className,
|
class: className,
|
||||||
filters,
|
filters,
|
||||||
includeScams,
|
|
||||||
countCommunityOnly,
|
countCommunityOnly,
|
||||||
inlineIcons,
|
inlineIcons,
|
||||||
...divProps
|
...divProps
|
||||||
} = Astro.props
|
} = Astro.props
|
||||||
|
|
||||||
const hasScams =
|
const hasScams = filters.verification.includes('VERIFICATION_FAILED')
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
||||||
filters.verification.includes('VERIFICATION_FAILED') || includeScams
|
|
||||||
const hasSomeScam = !!services?.some((service) => service.verificationStatus.includes('VERIFICATION_FAILED'))
|
const hasSomeScam = !!services?.some((service) => service.verificationStatus.includes('VERIFICATION_FAILED'))
|
||||||
|
|
||||||
const hasCommunityContributed = filters.verification.includes('COMMUNITY_CONTRIBUTED')
|
const hasCommunityContributed = filters.verification.includes('COMMUNITY_CONTRIBUTED')
|
||||||
|
|||||||
@@ -113,3 +113,28 @@ export function urlDomain(url: URL | string) {
|
|||||||
}
|
}
|
||||||
return url.origin
|
return url.origin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function separateServiceUrlsByType(allServiceUrls: string[]) {
|
||||||
|
const result: {
|
||||||
|
web: string[]
|
||||||
|
onion: string[]
|
||||||
|
i2p: string[]
|
||||||
|
} = {
|
||||||
|
web: [],
|
||||||
|
onion: [],
|
||||||
|
i2p: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const url of allServiceUrls) {
|
||||||
|
const parsedUrl = new URL(url)
|
||||||
|
if (parsedUrl.origin.endsWith('.onion')) {
|
||||||
|
result.onion.push(url)
|
||||||
|
} else if (parsedUrl.origin.endsWith('.b32.i2p')) {
|
||||||
|
result.i2p.push(url)
|
||||||
|
} else {
|
||||||
|
result.web.push(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const zodUrlOptionalProtocol = z.preprocess(
|
|||||||
const cleanInput = input.trim().replace(/\/$/, '')
|
const cleanInput = input.trim().replace(/\/$/, '')
|
||||||
return !/^\w+:\/\//i.test(cleanInput) ? `https://${cleanInput}` : cleanInput
|
return !/^\w+:\/\//i.test(cleanInput) ? `https://${cleanInput}` : cleanInput
|
||||||
},
|
},
|
||||||
z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
|
z.string().refine((value) => /^(https?):\/\/(?=.*\.[a-z0-9]{2,})[^\s$.?#].[^\s]*$/i.test(value), {
|
||||||
message: 'Invalid URL',
|
message: 'Invalid URL',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -233,16 +233,30 @@ if (!service) return Astro.rewrite('/404')
|
|||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="id" value={service.id} />
|
<input type="hidden" name="id" value={service.id} />
|
||||||
<InputText
|
|
||||||
label="Name"
|
|
||||||
name="name"
|
|
||||||
inputProps={{
|
|
||||||
required: true,
|
|
||||||
value: service.name,
|
|
||||||
}}
|
|
||||||
error={serviceInputErrors.name}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-x-4 gap-y-6 md:grid-cols-2">
|
||||||
|
<InputText
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
inputProps={{
|
||||||
|
required: true,
|
||||||
|
value: service.name,
|
||||||
|
}}
|
||||||
|
error={serviceInputErrors.name}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputText
|
||||||
|
label="Slug"
|
||||||
|
description="Auto-generated if empty"
|
||||||
|
name="slug"
|
||||||
|
inputProps={{
|
||||||
|
value: service.slug,
|
||||||
|
class: 'font-title',
|
||||||
|
}}
|
||||||
|
error={serviceInputErrors.slug}
|
||||||
|
class="font-title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<InputTextArea
|
<InputTextArea
|
||||||
label="Description"
|
label="Description"
|
||||||
name="description"
|
name="description"
|
||||||
@@ -254,76 +268,44 @@ if (!service) return Astro.rewrite('/404')
|
|||||||
error={serviceInputErrors.description}
|
error={serviceInputErrors.description}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputText
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
label="Slug"
|
|
||||||
description="Auto-generated if empty"
|
|
||||||
name="slug"
|
|
||||||
inputProps={{
|
|
||||||
value: service.slug,
|
|
||||||
class: 'font-title',
|
|
||||||
}}
|
|
||||||
error={serviceInputErrors.slug}
|
|
||||||
class="font-title"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-x-4 gap-y-6 md:grid-cols-2">
|
|
||||||
<InputTextArea
|
<InputTextArea
|
||||||
label="Service URLs"
|
label="Service URLs"
|
||||||
description="One per line"
|
description="One per line. Accepts **Web**, **Onion**, and **I2P** URLs."
|
||||||
name="serviceUrls"
|
name="allServiceUrls"
|
||||||
inputProps={{
|
inputProps={{
|
||||||
rows: 3,
|
placeholder: 'https://example1.com\nhttps://example2.onion\nhttps://example3.b32.i2p',
|
||||||
placeholder: 'https://example1.com\nhttps://example2.com',
|
class: 'grow min-h-24',
|
||||||
|
required: true,
|
||||||
}}
|
}}
|
||||||
value={service.serviceUrls.join('\n')}
|
class="row-span-2 flex flex-col self-stretch"
|
||||||
error={serviceInputErrors.serviceUrls}
|
value={[...service.serviceUrls, ...service.onionUrls, ...service.i2pUrls].join('\n\n')}
|
||||||
|
error={serviceInputErrors.allServiceUrls}
|
||||||
/>
|
/>
|
||||||
<InputTextArea
|
<InputTextArea
|
||||||
label="ToS URLs"
|
label="ToS URLs"
|
||||||
description="One per line"
|
description="One per line"
|
||||||
name="tosUrls"
|
name="tosUrls"
|
||||||
inputProps={{
|
inputProps={{
|
||||||
rows: 3,
|
|
||||||
placeholder: 'https://example1.com/tos\nhttps://example2.com/tos',
|
placeholder: 'https://example1.com/tos\nhttps://example2.com/tos',
|
||||||
required: true,
|
required: true,
|
||||||
}}
|
}}
|
||||||
value={service.tosUrls.join('\n')}
|
value={service.tosUrls.join('\n')}
|
||||||
error={serviceInputErrors.tosUrls}
|
error={serviceInputErrors.tosUrls}
|
||||||
/>
|
/>
|
||||||
<InputTextArea
|
<InputText
|
||||||
label="Onion URLs"
|
label="Referral link path"
|
||||||
description="One per line"
|
name="referral"
|
||||||
name="onionUrls"
|
|
||||||
inputProps={{
|
inputProps={{
|
||||||
rows: 3,
|
value: service.referral,
|
||||||
placeholder: 'http://example1.onion\nhttp://example2.onion',
|
placeholder: 'e.g. ?ref=123 or /ref/123',
|
||||||
}}
|
}}
|
||||||
value={service.onionUrls.join('\n')}
|
error={serviceInputErrors.referral}
|
||||||
error={serviceInputErrors.onionUrls}
|
class="self-end"
|
||||||
/>
|
description="Will be appended to the service URL"
|
||||||
<InputTextArea
|
|
||||||
label="I2P URLs"
|
|
||||||
description="One per line"
|
|
||||||
name="i2pUrls"
|
|
||||||
inputProps={{
|
|
||||||
rows: 3,
|
|
||||||
placeholder: 'http://example1.b32.i2p\nhttp://example2.b32.i2p',
|
|
||||||
}}
|
|
||||||
value={service.i2pUrls.join('\n')}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputText
|
|
||||||
label="Referral link path"
|
|
||||||
name="referral"
|
|
||||||
inputProps={{
|
|
||||||
value: service.referral,
|
|
||||||
placeholder: 'e.g. ?ref=123 or /ref/123',
|
|
||||||
}}
|
|
||||||
error={serviceInputErrors.referral}
|
|
||||||
description="Will be appended to the service URL"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<InputImageFile
|
<InputImageFile
|
||||||
label="Image"
|
label="Image"
|
||||||
|
|||||||
@@ -74,19 +74,19 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {}
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="serviceUrls" class="font-title mb-2 block text-sm text-green-500">serviceUrls</label>
|
<label for="allServiceUrls" class="font-title mb-2 block text-sm text-green-500">Service URLs</label>
|
||||||
<textarea
|
<textarea
|
||||||
transition:persist
|
transition:persist
|
||||||
class="font-title w-full rounded-md border border-green-500/30 bg-black/50 p-2 text-gray-300 placeholder-gray-500 focus:border-green-500 focus:ring-green-500"
|
class="font-title w-full rounded-md border border-green-500/30 bg-black/50 p-2 text-gray-300 placeholder-gray-500 focus:border-green-500 focus:ring-green-500"
|
||||||
name="serviceUrls"
|
name="allServiceUrls"
|
||||||
id="serviceUrls"
|
id="allServiceUrls"
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="https://example1.com https://example2.com"
|
placeholder="https://example1.com\nhttps://example2.onion\nhttps://example3.b32.i2p"
|
||||||
set:text=""
|
set:text=""
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
inputErrors.serviceUrls && (
|
inputErrors.allServiceUrls && (
|
||||||
<p class="font-title mt-1 text-sm text-red-500">{inputErrors.serviceUrls.join(', ')}</p>
|
<p class="font-title mt-1 text-sm text-red-500">{inputErrors.allServiceUrls.join(', ')}</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -109,24 +109,6 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {}
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="onionUrls" class="font-title mb-2 block text-sm text-green-500">onionUrls</label>
|
|
||||||
<textarea
|
|
||||||
transition:persist
|
|
||||||
class="font-title w-full rounded-md border border-green-500/30 bg-black/50 p-2 text-gray-300 placeholder-gray-500 focus:border-green-500 focus:ring-green-500"
|
|
||||||
name="onionUrls"
|
|
||||||
id="onionUrls"
|
|
||||||
rows={3}
|
|
||||||
placeholder="http://example.onion"
|
|
||||||
set:text=""
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
inputErrors.onionUrls && (
|
|
||||||
<p class="font-title mt-1 text-sm text-red-500">{inputErrors.onionUrls.join(', ')}</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="imageFile" class="font-title mb-2 block text-sm text-green-500">serviceImage</label>
|
<label for="imageFile" class="font-title mb-2 block text-sm text-green-500">serviceImage</label>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|||||||
@@ -712,7 +712,6 @@ const showFiltersId = 'show-filters'
|
|||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
sortSeed={filters['sort-seed']}
|
sortSeed={filters['sort-seed']}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
includeScams={includeScams}
|
|
||||||
countCommunityOnly={countCommunityOnly}
|
countCommunityOnly={countCommunityOnly}
|
||||||
inlineIcons
|
inlineIcons
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -201,34 +201,31 @@ const [categories, attributes] = await Astro.locals.banners.tryMany([
|
|||||||
error={inputErrors.description}
|
error={inputErrors.description}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InputTextArea
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
label="Service URLs"
|
<InputTextArea
|
||||||
name="serviceUrls"
|
label="Service URLs"
|
||||||
inputProps={{
|
description="One per line. Accepts **Web**, **Onion**, and **I2P** URLs."
|
||||||
required: true,
|
name="allServiceUrls"
|
||||||
placeholder: 'https://example1.com\nhttps://example2.org',
|
inputProps={{
|
||||||
}}
|
placeholder: 'https://example1.com\nhttps://example2.onion\nhttps://example3.b32.i2p',
|
||||||
error={inputErrors.serviceUrls}
|
class: 'min-h-24',
|
||||||
/>
|
required: true,
|
||||||
|
}}
|
||||||
<InputTextArea
|
class="row-span-2 flex flex-col self-stretch"
|
||||||
label="Terms of Service URLs"
|
error={inputErrors.allServiceUrls}
|
||||||
name="tosUrls"
|
/>
|
||||||
inputProps={{
|
<InputTextArea
|
||||||
required: true,
|
label="ToS URLs"
|
||||||
placeholder: 'https://example1.com/tos\nhttps://example2.org/terms',
|
description="One per line"
|
||||||
}}
|
name="tosUrls"
|
||||||
error={inputErrors.tosUrls}
|
inputProps={{
|
||||||
/>
|
placeholder: 'https://example1.com/tos\nhttps://example2.com/tos',
|
||||||
|
class: 'md:min-h-24',
|
||||||
<InputTextArea
|
required: true,
|
||||||
label="Onion URLs"
|
}}
|
||||||
name="onionUrls"
|
error={inputErrors.tosUrls}
|
||||||
inputProps={{
|
/>
|
||||||
placeholder: 'http://example1.onion\nhttp://example2.onion',
|
</div>
|
||||||
}}
|
|
||||||
error={inputErrors.onionUrls}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InputCardGroup
|
<InputCardGroup
|
||||||
name="kycLevel"
|
name="kycLevel"
|
||||||
|
|||||||
Reference in New Issue
Block a user