Release 202506061009
This commit is contained in:
@@ -18,6 +18,7 @@ services:
|
|||||||
pyworker:
|
pyworker:
|
||||||
build:
|
build:
|
||||||
context: ./pyworker
|
context: ./pyworker
|
||||||
|
image: kycnotme/pyworker:${PYWORKER_IMAGE_TAG:-latest}
|
||||||
restart: always
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
@@ -184,16 +184,16 @@ Be concise but thorough, and make sure your output is properly formatted JSON.
|
|||||||
PROMPT_COMMENT_SENTIMENT_SUMMARY = """
|
PROMPT_COMMENT_SENTIMENT_SUMMARY = """
|
||||||
You will be given a list of user comments to a service.
|
You will be given a list of user comments to a service.
|
||||||
Your task is to summarize the comments in a way that is easy to understand and to the point.
|
Your task is to summarize the comments in a way that is easy to understand and to the point.
|
||||||
The summary should be concise and to the point, no more than 150 words.
|
The summary should be concise and to the point, no more than 100 words. Keep it short and concise.
|
||||||
Use markdown formatting to highlight in bold the most important information. Only bold is allowed.
|
Use markdown formatting to highlight in bold the most important information. Only bold is allowed.
|
||||||
|
|
||||||
You must format your response as a valid JSON object with the following structure:
|
You must format your response as a valid JSON object with the following structure:
|
||||||
|
|
||||||
interface CommentSummary {
|
interface CommentSummary {
|
||||||
summary: string;
|
summary: string; // Concise, 100 words max
|
||||||
sentiment: 'positive'|'negative'|'neutral';
|
sentiment: 'positive'|'negative'|'neutral';
|
||||||
whatUsersLike: string[]; // Concise, 2-3 words, max 4
|
whatUsersLike: string[]; // Concise, 2-3 words max
|
||||||
whatUsersDislike: string[]; // Concise, 2-3 words, max 4
|
whatUsersDislike: string[]; // Concise, 2-3 words max
|
||||||
}
|
}
|
||||||
|
|
||||||
Always avoid repeating information in the list of what users like or dislike. Also, make sure you keep the summary short and concise, no more than 150 words. Ignore irrelevant comments. Make an item for each like/dislike, avoid something like 'No logs / Audited', it should be 'No logs' and 'Audited' as separate items.
|
Always avoid repeating information in the list of what users like or dislike. Also, make sure you keep the summary short and concise, no more than 150 words. Ignore irrelevant comments. Make an item for each like/dislike, avoid something like 'No logs / Audited', it should be 'No logs' and 'Audited' as separate items.
|
||||||
|
|||||||
1265
web/package-lock.json
generated
1265
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,19 +23,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "0.9.4",
|
"@astrojs/check": "0.9.4",
|
||||||
"@astrojs/db": "0.14.14",
|
"@astrojs/db": "0.15.0",
|
||||||
"@astrojs/mdx": "4.2.6",
|
"@astrojs/mdx": "4.3.0",
|
||||||
"@astrojs/node": "9.2.1",
|
"@astrojs/node": "9.2.2",
|
||||||
"@astrojs/sitemap": "3.4.0",
|
"@astrojs/sitemap": "3.4.1",
|
||||||
"@fontsource-variable/space-grotesk": "5.2.7",
|
"@fontsource-variable/space-grotesk": "5.2.8",
|
||||||
"@fontsource/inter": "5.2.5",
|
"@fontsource/inter": "5.2.5",
|
||||||
"@fontsource/space-grotesk": "5.2.7",
|
"@fontsource/space-grotesk": "5.2.8",
|
||||||
"@prisma/client": "6.8.2",
|
"@prisma/client": "6.9.0",
|
||||||
"@tailwindcss/vite": "4.1.7",
|
"@tailwindcss/vite": "4.1.8",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "3.0.0",
|
||||||
"@types/pg": "8.15.4",
|
"@types/pg": "8.15.4",
|
||||||
"@vercel/og": "0.6.8",
|
"@vercel/og": "0.6.8",
|
||||||
"astro": "5.7.13",
|
"astro": "5.9.0",
|
||||||
"astro-loading-indicator": "0.7.0",
|
"astro-loading-indicator": "0.7.0",
|
||||||
"astro-remote": "0.3.4",
|
"astro-remote": "0.3.4",
|
||||||
"astro-seo-schema": "5.0.0",
|
"astro-seo-schema": "5.0.0",
|
||||||
@@ -43,59 +43,59 @@
|
|||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"htmx.org": "1.9.12",
|
"htmx.org": "1.9.12",
|
||||||
"javascript-time-ago": "2.5.11",
|
"javascript-time-ago": "2.5.11",
|
||||||
"libphonenumber-js": "1.12.8",
|
"libphonenumber-js": "1.12.9",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"mime-types": "3.0.1",
|
"mime-types": "3.0.1",
|
||||||
"object-to-formdata": "4.5.1",
|
"object-to-formdata": "4.5.1",
|
||||||
"pg": "8.16.0",
|
"pg": "8.16.0",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"redis": "5.0.1",
|
"redis": "5.5.6",
|
||||||
"schema-dts": "1.1.5",
|
"schema-dts": "1.1.5",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"sharp": "0.34.1",
|
"sharp": "0.34.2",
|
||||||
"slugify": "1.6.6",
|
"slugify": "1.6.6",
|
||||||
"tailwind-merge": "3.3.0",
|
"tailwind-merge": "3.3.0",
|
||||||
"tailwind-variants": "1.0.0",
|
"tailwind-variants": "1.0.0",
|
||||||
"tailwindcss": "4.1.7",
|
"tailwindcss": "4.1.8",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.8.3",
|
||||||
"unique-username-generator": "1.4.0",
|
"unique-username-generator": "1.4.0",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
"zod-form-data": "2.0.7"
|
"zod-form-data": "2.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.27.0",
|
"@eslint/js": "9.28.0",
|
||||||
"@faker-js/faker": "9.8.0",
|
"@faker-js/faker": "9.8.0",
|
||||||
"@iconify-json/material-symbols": "1.2.21",
|
"@iconify-json/material-symbols": "1.2.24",
|
||||||
"@iconify-json/mdi": "1.2.3",
|
"@iconify-json/mdi": "1.2.3",
|
||||||
"@iconify-json/ri": "1.2.5",
|
"@iconify-json/ri": "1.2.5",
|
||||||
"@stylistic/eslint-plugin": "4.2.0",
|
"@stylistic/eslint-plugin": "4.4.1",
|
||||||
"@tailwindcss/forms": "0.5.10",
|
"@tailwindcss/forms": "0.5.10",
|
||||||
"@tailwindcss/typography": "0.5.16",
|
"@tailwindcss/typography": "0.5.16",
|
||||||
"@types/eslint__js": "9.14.0",
|
"@types/eslint__js": "9.14.0",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/react": "19.1.4",
|
"@types/react": "19.1.6",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@types/web-push": "3.6.4",
|
"@types/web-push": "3.6.4",
|
||||||
"@typescript-eslint/parser": "8.32.1",
|
"@typescript-eslint/parser": "8.33.1",
|
||||||
"astro-icon": "1.1.5",
|
"astro-icon": "1.1.5",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"eslint": "9.27.0",
|
"eslint": "9.28.0",
|
||||||
"eslint-import-resolver-typescript": "4.3.5",
|
"eslint-import-resolver-typescript": "4.4.3",
|
||||||
"eslint-plugin-astro": "1.3.1",
|
"eslint-plugin-astro": "1.3.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
"globals": "16.1.0",
|
"globals": "16.2.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"prettier-plugin-astro": "0.14.1",
|
"prettier-plugin-astro": "0.14.1",
|
||||||
"prettier-plugin-tailwindcss": "0.6.11",
|
"prettier-plugin-tailwindcss": "0.6.12",
|
||||||
"prisma": "6.8.2",
|
"prisma": "6.9.0",
|
||||||
"prisma-json-types-generator": "3.4.1",
|
"prisma-json-types-generator": "3.4.2",
|
||||||
"tailwind-htmx": "0.1.2",
|
"tailwind-htmx": "0.1.2",
|
||||||
"ts-essentials": "10.0.4",
|
"ts-essentials": "10.0.4",
|
||||||
"ts-toolbelt": "9.6.0",
|
"ts-toolbelt": "9.6.0",
|
||||||
"tsx": "4.19.4",
|
"tsx": "4.19.4",
|
||||||
"typescript-eslint": "8.32.1"
|
"typescript-eslint": "8.33.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "OrderIdStatus" ADD VALUE 'WITHDRAWN';
|
||||||
@@ -25,6 +25,7 @@ enum OrderIdStatus {
|
|||||||
PENDING
|
PENDING
|
||||||
APPROVED
|
APPROVED
|
||||||
REJECTED
|
REJECTED
|
||||||
|
WITHDRAWN
|
||||||
}
|
}
|
||||||
|
|
||||||
model Comment {
|
model Comment {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { type Prisma, type ServiceUserRole, type PrismaClient } from '@prisma/client'
|
import { type Prisma, type ServiceUserRole } from '@prisma/client'
|
||||||
import { ActionError } from 'astro:actions'
|
import { ActionError } from 'astro:actions'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { defineProtectedAction } from '../../lib/defineProtectedAction'
|
import { defineProtectedAction } from '../../lib/defineProtectedAction'
|
||||||
import { saveFileLocally } from '../../lib/fileStorage'
|
import { saveFileLocally } from '../../lib/fileStorage'
|
||||||
import { prisma as prismaInstance } from '../../lib/prisma'
|
import { prisma } from '../../lib/prisma'
|
||||||
|
|
||||||
const prisma = prismaInstance as PrismaClient
|
|
||||||
|
|
||||||
const selectUserReturnFields = {
|
const selectUserReturnFields = {
|
||||||
id: true,
|
id: true,
|
||||||
|
|||||||
@@ -345,10 +345,11 @@ export const commentActions = {
|
|||||||
'order-id-status',
|
'order-id-status',
|
||||||
'kyc-requested',
|
'kyc-requested',
|
||||||
'funds-blocked',
|
'funds-blocked',
|
||||||
|
'toggle-rating-active',
|
||||||
]),
|
]),
|
||||||
value: z.union([
|
value: z.union([
|
||||||
z.enum(['PENDING', 'APPROVED', 'VERIFIED', 'REJECTED']),
|
z.enum(['PENDING', 'APPROVED', 'VERIFIED', 'REJECTED']),
|
||||||
z.enum(['PENDING', 'APPROVED', 'REJECTED']),
|
z.enum(['PENDING', 'APPROVED', 'REJECTED', 'WITHDRAWN']),
|
||||||
z.boolean(),
|
z.boolean(),
|
||||||
z.string(),
|
z.string(),
|
||||||
]),
|
]),
|
||||||
@@ -411,7 +412,7 @@ export const commentActions = {
|
|||||||
updateData.privateContext = input.value as string
|
updateData.privateContext = input.value as string
|
||||||
break
|
break
|
||||||
case 'order-id-status':
|
case 'order-id-status':
|
||||||
updateData.orderIdStatus = input.value as 'APPROVED' | 'PENDING' | 'REJECTED'
|
updateData.orderIdStatus = input.value as 'APPROVED' | 'PENDING' | 'REJECTED' | 'WITHDRAWN'
|
||||||
break
|
break
|
||||||
case 'kyc-requested':
|
case 'kyc-requested':
|
||||||
updateData.kycRequested = !!input.value
|
updateData.kycRequested = !!input.value
|
||||||
@@ -419,6 +420,9 @@ export const commentActions = {
|
|||||||
case 'funds-blocked':
|
case 'funds-blocked':
|
||||||
updateData.fundsBlocked = !!input.value
|
updateData.fundsBlocked = !!input.value
|
||||||
break
|
break
|
||||||
|
case 'toggle-rating-active':
|
||||||
|
updateData.ratingActive = !!input.value
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the comment
|
// Update the comment
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ type Props = HTMLAttributes<'div'> & {
|
|||||||
privateContext: true
|
privateContext: true
|
||||||
orderId: true
|
orderId: true
|
||||||
orderIdStatus: true
|
orderIdStatus: true
|
||||||
|
rating: true
|
||||||
|
ratingActive: true
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
@@ -46,10 +48,10 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
<div
|
<div
|
||||||
class="bg-night-600 border-night-500 mt-2 hidden overflow-hidden rounded-md border peer-checked:block peer-checked:p-2"
|
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">
|
<div class="border-night-500 flex flex-wrap items-center gap-1 border-b pb-2">
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.status === 'REJECTED'
|
comment.status === 'REJECTED'
|
||||||
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
||||||
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
||||||
@@ -59,42 +61,13 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
{comment.status === 'REJECTED' ? 'Unreject' : 'Reject'}
|
<Icon name="ri:close-circle-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.status === 'REJECTED' ? 'Unreject' : 'Reject'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.suspicious
|
|
||||||
? 'border border-yellow-500/30 bg-yellow-500/20 text-yellow-400'
|
|
||||||
: 'bg-night-700 hover:bg-yellow-500/20 hover:text-yellow-400'
|
|
||||||
)}
|
|
||||||
data-action="suspicious"
|
|
||||||
data-value={!comment.suspicious}
|
|
||||||
data-comment-id={comment.id}
|
|
||||||
data-user-id={user.id}
|
|
||||||
>
|
|
||||||
{comment.suspicious ? 'Not Spam' : 'Spam'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class={cn(
|
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
|
||||||
comment.requiresAdminReview
|
|
||||||
? 'border border-purple-500/30 bg-purple-500/20 text-purple-400'
|
|
||||||
: 'bg-night-700 hover:bg-purple-500/20 hover:text-purple-400'
|
|
||||||
)}
|
|
||||||
data-action="requires-admin-review"
|
|
||||||
data-value={!comment.requiresAdminReview}
|
|
||||||
data-comment-id={comment.id}
|
|
||||||
data-user-id={user.id}
|
|
||||||
>
|
|
||||||
{comment.requiresAdminReview ? 'No Admin Review' : 'Needs Admin Review'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class={cn(
|
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
|
||||||
comment.status === 'VERIFIED'
|
comment.status === 'VERIFIED'
|
||||||
? 'border border-green-500/30 bg-green-500/20 text-green-400'
|
? 'border border-green-500/30 bg-green-500/20 text-green-400'
|
||||||
: 'bg-night-700 hover:bg-green-500/20 hover:text-green-400'
|
: 'bg-night-700 hover:bg-green-500/20 hover:text-green-400'
|
||||||
@@ -104,12 +77,13 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
{comment.status === 'VERIFIED' ? 'Unverify' : 'Verify'}
|
<Icon name="ri:verified-badge-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.status === 'VERIFIED' ? 'Unverify' : 'Verify'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING'
|
comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING'
|
||||||
? 'border border-blue-500/30 bg-blue-500/20 text-blue-400'
|
? 'border border-blue-500/30 bg-blue-500/20 text-blue-400'
|
||||||
: 'bg-night-700 hover:bg-blue-500/20 hover:text-blue-400'
|
: 'bg-night-700 hover:bg-blue-500/20 hover:text-blue-400'
|
||||||
@@ -121,12 +95,49 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
{comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING' ? 'Approve' : 'Pending'}
|
<Icon name="ri:checkbox-circle-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>
|
||||||
|
{comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING' ? 'Approve' : 'Pending'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="bg-night-500 h-5 w-px"></div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={cn(
|
||||||
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
|
comment.suspicious
|
||||||
|
? 'border border-yellow-500/30 bg-yellow-500/20 text-yellow-400'
|
||||||
|
: 'bg-night-700 hover:bg-yellow-500/20 hover:text-yellow-400'
|
||||||
|
)}
|
||||||
|
data-action="suspicious"
|
||||||
|
data-value={!comment.suspicious}
|
||||||
|
data-comment-id={comment.id}
|
||||||
|
data-user-id={user.id}
|
||||||
|
>
|
||||||
|
<Icon name="ri:spam-2-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.suspicious ? 'Not Spam' : 'Spam'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
|
comment.requiresAdminReview
|
||||||
|
? 'border border-purple-500/30 bg-purple-500/20 text-purple-400'
|
||||||
|
: 'bg-night-700 hover:bg-purple-500/20 hover:text-purple-400'
|
||||||
|
)}
|
||||||
|
data-action="requires-admin-review"
|
||||||
|
data-value={!comment.requiresAdminReview}
|
||||||
|
data-comment-id={comment.id}
|
||||||
|
data-user-id={user.id}
|
||||||
|
>
|
||||||
|
<Icon name="ri:shield-user-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.requiresAdminReview ? 'No Admin Review' : 'Needs Admin Review'}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={cn(
|
||||||
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.kycRequested
|
comment.kycRequested
|
||||||
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
||||||
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
||||||
@@ -136,12 +147,13 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
{comment.kycRequested ? 'No KYC Issue' : 'KYC Issue'}
|
<Icon name="ri:bank-card-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.kycRequested ? 'No KYC Issue' : 'KYC Issue'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.fundsBlocked
|
comment.fundsBlocked
|
||||||
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
||||||
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
||||||
@@ -151,8 +163,31 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
{comment.fundsBlocked ? 'No Funds Issue' : 'Funds Issue'}
|
<Icon name="ri:lock-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.fundsBlocked ? 'No Funds Issue' : 'Funds Issue'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="bg-night-500 h-5 w-px"></div>
|
||||||
|
|
||||||
|
{
|
||||||
|
comment.rating && (
|
||||||
|
<button
|
||||||
|
class={cn(
|
||||||
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
|
comment.ratingActive
|
||||||
|
? 'border border-blue-500/30 bg-blue-500/20 text-blue-400'
|
||||||
|
: 'bg-night-700 hover:bg-blue-500/20 hover:text-blue-400'
|
||||||
|
)}
|
||||||
|
data-action="toggle-rating-active"
|
||||||
|
data-value={!comment.ratingActive}
|
||||||
|
data-comment-id={comment.id}
|
||||||
|
data-user-id={user.id}
|
||||||
|
>
|
||||||
|
<Icon name="ri:star-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>{comment.ratingActive ? 'Disable Rating' : 'Enable Rating'}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 space-y-1.5">
|
<div class="mt-2 space-y-1.5">
|
||||||
@@ -208,7 +243,7 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.orderIdStatus === 'APPROVED'
|
comment.orderIdStatus === 'APPROVED'
|
||||||
? 'border border-green-500/30 bg-green-500/20 text-green-400'
|
? 'border border-green-500/30 bg-green-500/20 text-green-400'
|
||||||
: 'bg-night-700 hover:bg-green-500/20 hover:text-green-400'
|
: 'bg-night-700 hover:bg-green-500/20 hover:text-green-400'
|
||||||
@@ -218,11 +253,12 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
Approve
|
<Icon name="ri:check-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>Approve</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.orderIdStatus === 'REJECTED'
|
comment.orderIdStatus === 'REJECTED'
|
||||||
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
? 'border border-red-500/30 bg-red-500/20 text-red-400'
|
||||||
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
: 'bg-night-700 hover:bg-red-500/20 hover:text-red-400'
|
||||||
@@ -232,11 +268,12 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
Reject
|
<Icon name="ri:close-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>Reject</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={cn(
|
class={cn(
|
||||||
'rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
comment.orderIdStatus === 'PENDING'
|
comment.orderIdStatus === 'PENDING'
|
||||||
? 'border border-blue-500/30 bg-blue-500/20 text-blue-400'
|
? 'border border-blue-500/30 bg-blue-500/20 text-blue-400'
|
||||||
: 'bg-night-700 hover:bg-blue-500/20 hover:text-blue-400'
|
: 'bg-night-700 hover:bg-blue-500/20 hover:text-blue-400'
|
||||||
@@ -246,7 +283,23 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
data-comment-id={comment.id}
|
data-comment-id={comment.id}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
>
|
>
|
||||||
Pending
|
<Icon name="ri:time-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>Pending</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={cn(
|
||||||
|
'inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs transition-colors',
|
||||||
|
comment.orderIdStatus === 'WITHDRAWN'
|
||||||
|
? 'border-night-400 bg-night-500/50 text-night-300 border'
|
||||||
|
: 'bg-night-700 hover:bg-night-500/50 hover:text-night-300'
|
||||||
|
)}
|
||||||
|
data-action="order-id-status"
|
||||||
|
data-value="WITHDRAWN"
|
||||||
|
data-comment-id={comment.id}
|
||||||
|
data-user-id={user.id}
|
||||||
|
>
|
||||||
|
<Icon name="ri:arrow-go-back-line" class="h-3.5 w-3.5" />
|
||||||
|
<span>Withdrawn</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,7 +333,8 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
action === 'suspicious' ||
|
action === 'suspicious' ||
|
||||||
action === 'requires-admin-review' ||
|
action === 'requires-admin-review' ||
|
||||||
action === 'kyc-requested' ||
|
action === 'kyc-requested' ||
|
||||||
action === 'funds-blocked'
|
action === 'funds-blocked' ||
|
||||||
|
action === 'toggle-rating-active'
|
||||||
? value === 'true'
|
? value === 'true'
|
||||||
: value,
|
: value,
|
||||||
})
|
})
|
||||||
@@ -290,7 +344,8 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
if (action === 'status') {
|
if (action === 'status') {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
} else if (action === 'suspicious') {
|
} else if (action === 'suspicious') {
|
||||||
btn.textContent = value === 'true' ? 'Not Sus' : 'Sus'
|
const span = btn.querySelector('span')
|
||||||
|
if (span) span.textContent = value === 'true' ? 'Not Spam' : 'Spam'
|
||||||
btn.classList.toggle('bg-yellow-500/20')
|
btn.classList.toggle('bg-yellow-500/20')
|
||||||
btn.classList.toggle('text-yellow-400')
|
btn.classList.toggle('text-yellow-400')
|
||||||
btn.classList.toggle('border-yellow-500/30')
|
btn.classList.toggle('border-yellow-500/30')
|
||||||
@@ -298,7 +353,8 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
btn.classList.toggle('bg-night-700')
|
btn.classList.toggle('bg-night-700')
|
||||||
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
||||||
} else if (action === 'requires-admin-review') {
|
} else if (action === 'requires-admin-review') {
|
||||||
btn.textContent = value === 'true' ? 'No Review' : 'Review'
|
const span = btn.querySelector('span')
|
||||||
|
if (span) span.textContent = value === 'true' ? 'No Admin Review' : 'Needs Admin Review'
|
||||||
btn.classList.toggle('bg-purple-500/20')
|
btn.classList.toggle('bg-purple-500/20')
|
||||||
btn.classList.toggle('text-purple-400')
|
btn.classList.toggle('text-purple-400')
|
||||||
btn.classList.toggle('border-purple-500/30')
|
btn.classList.toggle('border-purple-500/30')
|
||||||
@@ -309,7 +365,8 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
// Refresh to show updated order ID status
|
// Refresh to show updated order ID status
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
} else if (action === 'kyc-requested') {
|
} else if (action === 'kyc-requested') {
|
||||||
btn.textContent = value === 'true' ? 'No KYC Issue' : 'KYC Issue'
|
const span = btn.querySelector('span')
|
||||||
|
if (span) span.textContent = value === 'true' ? 'No KYC Issue' : 'KYC Issue'
|
||||||
btn.classList.toggle('bg-red-500/20')
|
btn.classList.toggle('bg-red-500/20')
|
||||||
btn.classList.toggle('text-red-400')
|
btn.classList.toggle('text-red-400')
|
||||||
btn.classList.toggle('border-red-500/30')
|
btn.classList.toggle('border-red-500/30')
|
||||||
@@ -317,13 +374,23 @@ if (!user || !user.admin || !user.moderator) return null
|
|||||||
btn.classList.toggle('bg-night-700')
|
btn.classList.toggle('bg-night-700')
|
||||||
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
||||||
} else if (action === 'funds-blocked') {
|
} else if (action === 'funds-blocked') {
|
||||||
btn.textContent = value === 'true' ? 'No Funds Issue' : 'Funds Issue'
|
const span = btn.querySelector('span')
|
||||||
|
if (span) span.textContent = value === 'true' ? 'No Funds Issue' : 'Funds Issue'
|
||||||
btn.classList.toggle('bg-red-500/20')
|
btn.classList.toggle('bg-red-500/20')
|
||||||
btn.classList.toggle('text-red-400')
|
btn.classList.toggle('text-red-400')
|
||||||
btn.classList.toggle('border-red-500/30')
|
btn.classList.toggle('border-red-500/30')
|
||||||
btn.classList.toggle('border')
|
btn.classList.toggle('border')
|
||||||
btn.classList.toggle('bg-night-700')
|
btn.classList.toggle('bg-night-700')
|
||||||
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
||||||
|
} else if (action === 'toggle-rating-active') {
|
||||||
|
const span = btn.querySelector('span')
|
||||||
|
if (span) span.textContent = value === 'true' ? 'Disable Rating' : 'Enable Rating'
|
||||||
|
btn.classList.toggle('bg-blue-500/20')
|
||||||
|
btn.classList.toggle('text-blue-400')
|
||||||
|
btn.classList.toggle('border-blue-500/30')
|
||||||
|
btn.classList.toggle('border')
|
||||||
|
btn.classList.toggle('bg-night-700')
|
||||||
|
btn.setAttribute('data-value', value === 'true' ? 'false' : 'true')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Error moderating comment:', error)
|
console.error('Error moderating comment:', error)
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { prisma } from './prisma'
|
import { prisma } from './prisma'
|
||||||
|
|
||||||
import type { Prisma } from '@prisma/client'
|
import type { Prisma, PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
export async function getOrCreateNotificationPreferences<T extends Prisma.NotificationPreferencesSelect>(
|
export async function getOrCreateNotificationPreferences<T extends Prisma.NotificationPreferencesSelect>(
|
||||||
userId: number,
|
userId: number,
|
||||||
select: { [K in keyof T]: K extends keyof Prisma.NotificationPreferencesSelect ? T[K] : never },
|
select: { [K in keyof T]: K extends keyof Prisma.NotificationPreferencesSelect ? T[K] : never },
|
||||||
tx: Prisma.TransactionClient = prisma
|
tx:
|
||||||
|
| Parameters<Parameters<(typeof prisma)['$transaction']>[0]>[0]
|
||||||
|
| Prisma.TransactionClient
|
||||||
|
| PrismaClient = prisma
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
(await tx.notificationPreferences.findUnique({ where: { userId }, select })) ??
|
(await tx.notificationPreferences.findUnique({ where: { userId }, select })) ??
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ To list a new service, it must fulfill these requirements:
|
|||||||
- Terms of service or FAQ document
|
- Terms of service or FAQ document
|
||||||
|
|
||||||
For examples:
|
For examples:
|
||||||
|
|
||||||
- Just a Telegram link or a criptocurrency itself is not a valid service.
|
- Just a Telegram link or a criptocurrency itself is not a valid service.
|
||||||
|
|
||||||
### Suggestion Review Process
|
### Suggestion Review Process
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ Fetches details for a single service by various lookup criteria.
|
|||||||
|
|
||||||
### Request Parameters
|
### Request Parameters
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
| ------------ | ------ | -------- | ----------- |
|
| ------------ | ------ | -------- | --------------------------------------------------------------------------- |
|
||||||
| `id` | number | No* | Service ID |
|
| `id` | number | No\* | Service ID |
|
||||||
| `slug` | string | No* | Service URL slug (lowercase letters, numbers, and hyphens only) |
|
| `slug` | string | No\* | Service URL slug (lowercase letters, numbers, and hyphens only) |
|
||||||
| `serviceUrl` | string | No* | Service URL. May be web, onion, or i2p. May just be a domain or a full URL. |
|
| `serviceUrl` | string | No\* | Service URL. May be web, onion, or i2p. May just be a domain or a full URL. |
|
||||||
|
|
||||||
\* At least one of the marked parameters is required.
|
\* At least one of the marked parameters is required.
|
||||||
|
|
||||||
@@ -79,41 +79,46 @@ type ServiceResponse = {
|
|||||||
#### KYC Levels
|
#### KYC Levels
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{kycLevels.map((level) => (
|
{kycLevels.map((level) => (
|
||||||
<li key={level.id}>
|
<li key={level.id}>
|
||||||
<strong>{level.id}</strong>: {level.name} - {level.description}
|
<strong>{level.id}</strong>: {level.name} - {level.description}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
#### Verification Status
|
#### Verification Status
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{verificationStatuses.map((status) => (
|
{verificationStatuses.map((status) => (
|
||||||
<li key={status.value}>
|
<li key={status.value}>
|
||||||
<strong>{status.value}</strong>: {status.description}
|
<strong>{status.value}</strong>: {status.description}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
#### Service Visibility
|
#### Service Visibility
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{serviceVisibilities.filter((visibility) => visibility.value === 'PUBLIC' || visibility.value === 'ARCHIVED' || visibility.value === 'UNLISTED').map((visibility) => (
|
{serviceVisibilities
|
||||||
<li key={visibility.value}>
|
.filter(
|
||||||
<strong>{visibility.value}</strong>: {visibility.longDescription}
|
(visibility) =>
|
||||||
</li>
|
visibility.value === 'PUBLIC' || visibility.value === 'ARCHIVED' || visibility.value === 'UNLISTED'
|
||||||
))}
|
)
|
||||||
|
.map((visibility) => (
|
||||||
|
<li key={visibility.value}>
|
||||||
|
<strong>{visibility.value}</strong>: {visibility.longDescription}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
#### KYC Level Clarifications
|
#### KYC Level Clarifications
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{kycLevelClarifications.map((clarification) => (
|
{kycLevelClarifications.map((clarification) => (
|
||||||
<li key={clarification.value}>
|
<li key={clarification.value}>
|
||||||
<strong>{clarification.value}</strong>: {clarification.description}
|
<strong>{clarification.value}</strong>: {clarification.description}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|||||||
Reference in New Issue
Block a user