Release 202506041641
This commit is contained in:
@@ -23,7 +23,7 @@ import Tooltip from '../../../../components/Tooltip.astro'
|
||||
import UserBadge from '../../../../components/UserBadge.astro'
|
||||
import { getAttributeCategoryInfo } from '../../../../constants/attributeCategories'
|
||||
import { getAttributeTypeInfo } from '../../../../constants/attributeTypes'
|
||||
import { formatContactMethod } from '../../../../constants/contactMethods'
|
||||
import { contactMethodUrlTypes, formatContactMethod } from '../../../../constants/contactMethods'
|
||||
import { currencies } from '../../../../constants/currencies'
|
||||
import { eventTypes, getEventTypeInfo } from '../../../../constants/eventTypes'
|
||||
import { kycLevelClarifications } from '../../../../constants/kycLevelClarifications'
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from '../../../../constants/verificationStepStatus'
|
||||
import BaseLayout from '../../../../layouts/BaseLayout.astro'
|
||||
import { DEPLOYMENT_MODE } from '../../../../lib/envVariables'
|
||||
import { listFiles } from '../../../../lib/fileStorage'
|
||||
import { makeAdminApiCallInfo } from '../../../../lib/makeAdminApiCallInfo'
|
||||
import { pluralize } from '../../../../lib/pluralize'
|
||||
import { prisma } from '../../../../lib/prisma'
|
||||
@@ -87,9 +88,36 @@ const internalNoteInputErrors = isInputError(internalNoteCreateResult?.error)
|
||||
? internalNoteCreateResult.error.fields
|
||||
: {}
|
||||
|
||||
const contactMethodUpdateResult = Astro.getActionResult(actions.admin.service.contactMethod.update)
|
||||
Astro.locals.banners.addIfSuccess(contactMethodUpdateResult, 'Contact method updated successfully')
|
||||
const contactMethodUpdateInputErrors = isInputError(contactMethodUpdateResult?.error)
|
||||
? contactMethodUpdateResult.error.fields
|
||||
: {}
|
||||
|
||||
const contactMethodAddResult = Astro.getActionResult(actions.admin.service.contactMethod.add)
|
||||
Astro.locals.banners.addIfSuccess(contactMethodAddResult, 'Contact method added successfully')
|
||||
const contactMethodAddInputErrors = isInputError(contactMethodAddResult?.error)
|
||||
? contactMethodAddResult.error.fields
|
||||
: {}
|
||||
|
||||
const internalNoteDeleteResult = Astro.getActionResult(actions.admin.service.internalNote.delete)
|
||||
Astro.locals.banners.addIfSuccess(internalNoteDeleteResult, 'Internal note deleted successfully')
|
||||
|
||||
const evidenceImageAddResult = Astro.getActionResult(actions.admin.service.evidenceImage.add)
|
||||
if (evidenceImageAddResult?.data?.imageUrl) {
|
||||
Astro.locals.banners.add({
|
||||
uiMessage: 'Evidence image added successfully',
|
||||
type: 'success',
|
||||
origin: 'action',
|
||||
})
|
||||
}
|
||||
const evidenceImageAddInputErrors = isInputError(evidenceImageAddResult?.error)
|
||||
? evidenceImageAddResult.error.fields
|
||||
: {}
|
||||
|
||||
const evidenceImageDeleteResult = Astro.getActionResult(actions.admin.service.evidenceImage.delete)
|
||||
Astro.locals.banners.addIfSuccess(evidenceImageDeleteResult, 'Evidence image deleted successfully')
|
||||
|
||||
const [service, categories, attributes] = await Astro.locals.banners.tryMany([
|
||||
[
|
||||
'Error fetching service',
|
||||
@@ -200,6 +228,12 @@ if (!service) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const evidenceImageUrls = await Astro.locals.banners.try(
|
||||
'Error listing evidence files',
|
||||
() => listFiles(`evidence/${service.slug}`),
|
||||
[] as string[]
|
||||
)
|
||||
|
||||
const apiCalls = await Astro.locals.banners.try(
|
||||
'Error fetching api calls',
|
||||
() =>
|
||||
@@ -426,7 +460,7 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
description: clarification.description,
|
||||
noTransitionPersist: true,
|
||||
}))}
|
||||
selectedValue={service.kycLevelClarification ?? 'NONE'}
|
||||
selectedValue={service.kycLevelClarification}
|
||||
iconSize="sm"
|
||||
cardSize="sm"
|
||||
error={serviceInputErrors.kycLevelClarification}
|
||||
@@ -1113,6 +1147,7 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
value: method.label,
|
||||
placeholder: contactMethodInfo.formattedValue,
|
||||
}}
|
||||
error={contactMethodUpdateInputErrors.label}
|
||||
/>
|
||||
|
||||
<InputText
|
||||
@@ -1122,6 +1157,7 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
value: method.value,
|
||||
placeholder: 'e.g., mailto:contact@example.com or https://t.me/example',
|
||||
}}
|
||||
error={contactMethodUpdateInputErrors.value}
|
||||
/>
|
||||
|
||||
<InputSubmitButton label="Update" icon="ri:save-line" hideCancel />
|
||||
@@ -1142,12 +1178,13 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
|
||||
<InputText
|
||||
label="Value"
|
||||
description="With protocol (e.g., `mailto:contact@example.com` or `https://t.me/example`)"
|
||||
description={`Accepts: ${contactMethodUrlTypes.map((type) => type.labelPlural).join(', ')}`}
|
||||
name="value"
|
||||
inputProps={{
|
||||
required: true,
|
||||
placeholder: 'mailto:contact@example.com',
|
||||
placeholder: 'contact@example.com',
|
||||
}}
|
||||
error={contactMethodAddInputErrors.value}
|
||||
/>
|
||||
|
||||
<InputSubmitButton label="Add" icon="ri:add-line" hideCancel />
|
||||
@@ -1164,6 +1201,16 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<div class="mb-6 flex justify-center">
|
||||
<Button
|
||||
as="a"
|
||||
href="/docs/api"
|
||||
icon="ri:book-open-line"
|
||||
color="gray"
|
||||
size="sm"
|
||||
label=" Documentation"
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
apiCalls.map((call) => (
|
||||
<FormSubSection title={`${call.method} ${call.path}`}>
|
||||
@@ -1176,5 +1223,73 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
))
|
||||
}
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="Evidence Images" id="evidence-images">
|
||||
<FormSubSection title="Existing Evidence Images">
|
||||
{
|
||||
evidenceImageUrls.length === 0 ? (
|
||||
<p class="border-night-600 bg-night-800 text-day-300 rounded-xl border p-6 text-center">
|
||||
No evidence images yet.
|
||||
</p>
|
||||
) : (
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
|
||||
{evidenceImageUrls.map((imageUrl: string) => (
|
||||
<div class="border-night-600 bg-night-800 group relative rounded-md border p-2">
|
||||
<MyPicture
|
||||
src={imageUrl}
|
||||
alt="Evidence image"
|
||||
class="aspect-square w-full rounded object-cover"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
<form
|
||||
method="POST"
|
||||
action={actions.admin.service.evidenceImage.delete}
|
||||
class="absolute top-1 right-1"
|
||||
>
|
||||
<input type="hidden" name="fileUrl" value={imageUrl} />
|
||||
<Button
|
||||
type="submit"
|
||||
variant="faded"
|
||||
color="danger"
|
||||
size="sm"
|
||||
icon="ri:delete-bin-line"
|
||||
iconOnly
|
||||
label="Delete Image"
|
||||
class="opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={``}
|
||||
class="bg-night-700 text-day-200 mt-2 w-full cursor-text rounded border p-2 font-mono text-xs select-all"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</FormSubSection>
|
||||
<FormSubSection title="Add New Evidence Image">
|
||||
<form
|
||||
method="POST"
|
||||
action={actions.admin.service.evidenceImage.add}
|
||||
class="space-y-4"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
<input type="hidden" name="serviceId" value={service.id} />
|
||||
<InputImageFile
|
||||
label="Upload Image"
|
||||
name="imageFile"
|
||||
description="Upload an evidence image."
|
||||
error={evidenceImageAddInputErrors.imageFile}
|
||||
required
|
||||
/>
|
||||
<InputSubmitButton label="Add Image" icon="ri:add-line" hideCancel />
|
||||
</form>
|
||||
</FormSubSection>
|
||||
</FormSection>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SOURCE_CODE_URL } from 'astro:env/server'
|
||||
import { kycLevels } from '../../constants/kycLevels'
|
||||
import { verificationStatuses } from '../../constants/verificationStatus'
|
||||
import { serviceVisibilities } from '../../constants/serviceVisibility'
|
||||
import { kycLevelClarifications } from '../../constants/kycLevelClarifications'
|
||||
|
||||
Access basic service data via our public API.
|
||||
|
||||
@@ -58,6 +59,12 @@ type ServiceResponse = {
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
kycLevelClarification: 'NONE' | 'DEPENDS_ON_PARTNERS' | ...
|
||||
kycLevelClarificationInfo: {
|
||||
value: 'NONE' | 'DEPENDS_ON_PARTNERS' | ...
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
categories: {
|
||||
name: string
|
||||
slug: string
|
||||
@@ -99,6 +106,16 @@ type ServiceResponse = {
|
||||
))}
|
||||
</ul>
|
||||
|
||||
#### KYC Level Clarifications
|
||||
|
||||
<ul>
|
||||
{kycLevelClarifications.map((clarification) => (
|
||||
<li key={clarification.value}>
|
||||
<strong>{clarification.value}</strong>: {clarification.description}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
### Examples
|
||||
|
||||
#### Request
|
||||
@@ -131,6 +148,11 @@ curl -X QUERY https://kycnot.me/api/v1/service/get \
|
||||
"name": "Guaranteed no KYC",
|
||||
"description": "Terms explicitly state KYC will never be requested."
|
||||
},
|
||||
"kycLevelClarification": "NONE",
|
||||
"kycLevelClarificationInfo": {
|
||||
"value": "NONE",
|
||||
"description": "No clarification needed."
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
"name": "Exchange",
|
||||
|
||||
@@ -19,6 +19,7 @@ import InputText from '../../components/InputText.astro'
|
||||
import InputTextArea from '../../components/InputTextArea.astro'
|
||||
import { getAttributeCategoryInfo } from '../../constants/attributeCategories'
|
||||
import { getAttributeTypeInfo } from '../../constants/attributeTypes'
|
||||
import { contactMethodUrlTypes } from '../../constants/contactMethods'
|
||||
import { currencies } from '../../constants/currencies'
|
||||
import { kycLevelClarifications } from '../../constants/kycLevelClarifications'
|
||||
import { kycLevels } from '../../constants/kycLevels'
|
||||
@@ -208,26 +209,42 @@ const [categories, attributes] = await Astro.locals.banners.tryMany([
|
||||
description="One per line. Accepts **Web**, **Onion**, and **I2P** URLs."
|
||||
name="allServiceUrls"
|
||||
inputProps={{
|
||||
placeholder: 'https://example1.com\nhttps://example2.onion\nhttps://example3.b32.i2p',
|
||||
class: 'min-h-24',
|
||||
placeholder: 'example1.com\nexample2.onion\nexample3.b32.i2p',
|
||||
class: 'md:min-h-20 min-h-24 h-full',
|
||||
required: true,
|
||||
}}
|
||||
class="row-span-2 flex flex-col self-stretch"
|
||||
class="flex flex-col self-stretch"
|
||||
error={inputErrors.allServiceUrls}
|
||||
/>
|
||||
|
||||
<InputTextArea
|
||||
label="ToS URLs"
|
||||
description="One per line"
|
||||
name="tosUrls"
|
||||
label="Contact Methods"
|
||||
description={[
|
||||
'One per line.',
|
||||
`Accepts: ${contactMethodUrlTypes.map((type) => type.labelPlural).join(', ')}`,
|
||||
].join('\n')}
|
||||
name="contactMethods"
|
||||
inputProps={{
|
||||
placeholder: 'https://example1.com/tos\nhttps://example2.com/tos',
|
||||
class: 'md:min-h-24',
|
||||
required: true,
|
||||
placeholder: 'contact@example.com\nt.me/example\n+123 456 7890',
|
||||
class: 'h-full',
|
||||
}}
|
||||
error={inputErrors.tosUrls}
|
||||
class="flex flex-col self-stretch"
|
||||
error={inputErrors.contactMethods}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<InputTextArea
|
||||
label="ToS URLs"
|
||||
description="One per line"
|
||||
name="tosUrls"
|
||||
inputProps={{
|
||||
placeholder: 'example.com/tos',
|
||||
required: true,
|
||||
class: 'min-h-10',
|
||||
}}
|
||||
error={inputErrors.tosUrls}
|
||||
/>
|
||||
|
||||
<InputCardGroup
|
||||
name="kycLevel"
|
||||
label="KYC Level"
|
||||
|
||||
Reference in New Issue
Block a user