Release 202506020353
This commit is contained in:
@@ -65,6 +65,14 @@ const adminLinks: AdminLink[] = [
|
||||
base: 'text-pink-300',
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'ri:notification-3-line',
|
||||
title: 'Notifications',
|
||||
href: '/admin/notifications',
|
||||
classNames: {
|
||||
base: 'text-indigo-300',
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: 'ri:rocket-2-line',
|
||||
title: 'Releases',
|
||||
|
||||
155
web/src/pages/admin/notifications.astro
Normal file
155
web/src/pages/admin/notifications.astro
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { actions, isInputError } from 'astro:actions'
|
||||
import { groupBy, round, uniq } from 'lodash-es'
|
||||
|
||||
import InputSubmitButton from '../../components/InputSubmitButton.astro'
|
||||
import InputText from '../../components/InputText.astro'
|
||||
import InputTextArea from '../../components/InputTextArea.astro'
|
||||
import MiniLayout from '../../layouts/MiniLayout.astro'
|
||||
import { cn } from '../../lib/cn'
|
||||
import { prisma } from '../../lib/prisma'
|
||||
|
||||
// Check if user is admin
|
||||
if (!Astro.locals.user?.admin) {
|
||||
return Astro.redirect('/access-denied')
|
||||
}
|
||||
|
||||
const testResult = Astro.getActionResult(actions.admin.notification.webPush.test)
|
||||
const testInputErrors = isInputError(testResult?.error) ? testResult.error.fields : {}
|
||||
|
||||
Astro.locals.banners.addIfSuccess(testResult, (data) => data.message)
|
||||
|
||||
const subscriptions = await Astro.locals.banners.try(
|
||||
'Error while fetching subscriptions by user',
|
||||
() =>
|
||||
prisma.pushSubscription.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[] as []
|
||||
)
|
||||
const totalSubscriptions = subscriptions.length
|
||||
const subscriptionsByUser = groupBy(subscriptions, 'user.id')
|
||||
|
||||
const totalUsers = Object.keys(subscriptionsByUser).length
|
||||
|
||||
const adminUsers = await prisma.user.findMany({
|
||||
where: {
|
||||
admin: true,
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
})
|
||||
|
||||
const stats = [
|
||||
{
|
||||
icon: 'ri:notification-4-line',
|
||||
iconClass: 'text-blue-400',
|
||||
title: 'Total Subscriptions',
|
||||
value: totalSubscriptions.toLocaleString(),
|
||||
},
|
||||
{
|
||||
icon: 'ri:user-3-line',
|
||||
iconClass: 'text-green-400',
|
||||
title: 'Subscribed Users',
|
||||
value: totalUsers.toLocaleString(),
|
||||
},
|
||||
{
|
||||
icon: 'ri:smartphone-line',
|
||||
iconClass: 'text-purple-400',
|
||||
title: 'Avg Devices/User',
|
||||
value: (totalUsers > 0 ? round(totalSubscriptions / totalUsers, 1) : 0).toLocaleString(),
|
||||
},
|
||||
] satisfies {
|
||||
icon: string
|
||||
iconClass: string
|
||||
title: string
|
||||
value: string
|
||||
}[]
|
||||
---
|
||||
|
||||
<MiniLayout
|
||||
pageTitle="Push notifications"
|
||||
description="Send test notifications"
|
||||
layoutHeader={{
|
||||
icon: 'ri:notification-3-line',
|
||||
title: 'Push notifications',
|
||||
subtitle: 'Send test notifications',
|
||||
}}
|
||||
>
|
||||
<div class="mb-6 grid gap-4 sm:grid-cols-3">
|
||||
{
|
||||
stats.map((stat) => (
|
||||
<div class="flex flex-col items-center gap-2 text-center">
|
||||
<div class="flex items-end gap-1">
|
||||
<span class="text-5xl leading-[0.8] font-bold text-white">{stat.value}</span>
|
||||
<Icon name={stat.icon} class={cn('size-5 shrink-0', stat.iconClass)} />
|
||||
</div>
|
||||
<span class="text-day-200 flex grow flex-col justify-center text-sm leading-none font-medium text-balance">
|
||||
{stat.title}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<h2 class="text-center text-lg font-semibold text-white">Send Test Notification</h2>
|
||||
|
||||
<form method="POST" action={actions.admin.notification.webPush.test} class="space-y-4">
|
||||
<InputTextArea
|
||||
label="Users"
|
||||
name="userNames"
|
||||
inputProps={{
|
||||
placeholder: 'john-doe, jane-doe',
|
||||
class: 'leading-tight min-h-24',
|
||||
}}
|
||||
value={uniq([Astro.locals.user, ...adminUsers].map((user) => user.name)).join('\n')}
|
||||
description={[
|
||||
'- Comma-separated list of user names.',
|
||||
'- Minimum 1 user name.',
|
||||
'- By default, all admin users are selected.',
|
||||
].join('\n')}
|
||||
error={testInputErrors.userNames}
|
||||
/>
|
||||
|
||||
<InputText
|
||||
label="Title"
|
||||
name="title"
|
||||
inputProps={{
|
||||
value: 'Test Notification',
|
||||
required: true,
|
||||
}}
|
||||
error={testInputErrors.title}
|
||||
/>
|
||||
|
||||
<InputTextArea
|
||||
label="Body"
|
||||
name="body"
|
||||
inputProps={{
|
||||
value: 'This is a test push notification from KYCNot.me',
|
||||
}}
|
||||
error={testInputErrors.body}
|
||||
/>
|
||||
|
||||
<InputText
|
||||
label="Action URL"
|
||||
name="url"
|
||||
inputProps={{
|
||||
placeholder: 'https://example.com/path',
|
||||
}}
|
||||
description="URL to open when the notification is clicked"
|
||||
error={testInputErrors.url}
|
||||
/>
|
||||
|
||||
<InputSubmitButton label="Send" icon="ri:send-plane-line" hideCancel color="danger" />
|
||||
</form>
|
||||
</MiniLayout>
|
||||
@@ -26,6 +26,7 @@ import { getAttributeTypeInfo } from '../../../../constants/attributeTypes'
|
||||
import { formatContactMethod } from '../../../../constants/contactMethods'
|
||||
import { currencies } from '../../../../constants/currencies'
|
||||
import { eventTypes, getEventTypeInfo } from '../../../../constants/eventTypes'
|
||||
import { kycLevelClarifications } from '../../../../constants/kycLevelClarifications'
|
||||
import { kycLevels } from '../../../../constants/kycLevels'
|
||||
import { serviceVisibilities } from '../../../../constants/serviceVisibility'
|
||||
import { verificationStatuses } from '../../../../constants/verificationStatus'
|
||||
@@ -415,6 +416,22 @@ const apiCalls = await Astro.locals.banners.try(
|
||||
class="[&>div]:grid-cols-2 [&>div]:[--card-min-size:16rem]"
|
||||
/>
|
||||
|
||||
<InputCardGroup
|
||||
name="kycLevelClarification"
|
||||
label="KYC Level Clarification"
|
||||
options={kycLevelClarifications.map((clarification) => ({
|
||||
label: clarification.label,
|
||||
value: clarification.value,
|
||||
icon: clarification.icon,
|
||||
description: clarification.description,
|
||||
noTransitionPersist: true,
|
||||
}))}
|
||||
selectedValue={service.kycLevelClarification ?? 'NONE'}
|
||||
iconSize="sm"
|
||||
cardSize="sm"
|
||||
error={serviceInputErrors.kycLevelClarification}
|
||||
/>
|
||||
|
||||
<InputCardGroup
|
||||
name="verificationStatus"
|
||||
label="Verification Status"
|
||||
|
||||
13
web/src/pages/internal-api/[...catchAll].ts
Normal file
13
web/src/pages/internal-api/[...catchAll].ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
export const ALL: APIRoute = (context) => {
|
||||
console.error('Endpoint not found', { url: context.url.href, method: context.request.method })
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Endpoint not found',
|
||||
}),
|
||||
{
|
||||
status: 404,
|
||||
}
|
||||
)
|
||||
}
|
||||
7
web/src/pages/internal-api/notifications/subscribe.ts
Normal file
7
web/src/pages/internal-api/notifications/subscribe.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { actions } from 'astro:actions'
|
||||
|
||||
import { makeEndpointFromAction } from '../../../lib/endpoints'
|
||||
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
export const POST: APIRoute = makeEndpointFromAction(actions.notification.webPush.subscribe)
|
||||
7
web/src/pages/internal-api/notifications/unsubscribe.ts
Normal file
7
web/src/pages/internal-api/notifications/unsubscribe.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { actions } from 'astro:actions'
|
||||
|
||||
import { makeEndpointFromAction } from '../../../lib/endpoints'
|
||||
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
export const POST: APIRoute = makeEndpointFromAction(actions.notification.webPush.unsubscribe)
|
||||
@@ -4,6 +4,7 @@ import { Icon } from 'astro-icon/components'
|
||||
import { actions } from 'astro:actions'
|
||||
|
||||
import Button from '../components/Button.astro'
|
||||
import PushNotificationBanner from '../components/PushNotificationBanner.astro'
|
||||
import TimeFormatted from '../components/TimeFormatted.astro'
|
||||
import Tooltip from '../components/Tooltip.astro'
|
||||
import { getNotificationTypeInfo } from '../constants/notificationTypes'
|
||||
@@ -28,131 +29,146 @@ const { data: params } = zodParseQueryParamsStoringErrors(
|
||||
)
|
||||
const skip = (params.page - 1) * PAGE_SIZE
|
||||
|
||||
const [dbNotifications, notificationPreferences, totalNotifications] = await Astro.locals.banners.tryMany([
|
||||
[
|
||||
'Error while fetching notifications',
|
||||
() =>
|
||||
prisma.notification.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
skip,
|
||||
take: PAGE_SIZE,
|
||||
select: {
|
||||
const [dbNotifications, notificationPreferences, totalNotifications, pushSubscriptions] =
|
||||
await Astro.locals.banners.tryMany([
|
||||
[
|
||||
'Error while fetching notifications',
|
||||
() =>
|
||||
prisma.notification.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
skip,
|
||||
take: PAGE_SIZE,
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
createdAt: true,
|
||||
read: true,
|
||||
aboutAccountStatusChange: true,
|
||||
aboutCommentStatusChange: true,
|
||||
aboutServiceVerificationStatusChange: true,
|
||||
aboutSuggestionStatusChange: true,
|
||||
aboutComment: {
|
||||
select: {
|
||||
id: true,
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
content: true,
|
||||
communityNote: true,
|
||||
service: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
select: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutServiceSuggestionId: true,
|
||||
aboutServiceSuggestion: {
|
||||
select: {
|
||||
status: true,
|
||||
service: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutServiceSuggestionMessage: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
suggestion: {
|
||||
select: {
|
||||
id: true,
|
||||
service: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutEvent: {
|
||||
select: {
|
||||
title: true,
|
||||
type: true,
|
||||
service: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutService: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
verificationStatus: true,
|
||||
},
|
||||
},
|
||||
aboutKarmaTransaction: {
|
||||
select: {
|
||||
points: true,
|
||||
action: true,
|
||||
description: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
],
|
||||
[
|
||||
'Error while fetching notification preferences',
|
||||
() =>
|
||||
getOrCreateNotificationPreferences(user.id, {
|
||||
id: true,
|
||||
type: true,
|
||||
createdAt: true,
|
||||
read: true,
|
||||
aboutAccountStatusChange: true,
|
||||
aboutCommentStatusChange: true,
|
||||
aboutServiceVerificationStatusChange: true,
|
||||
aboutSuggestionStatusChange: true,
|
||||
aboutComment: {
|
||||
select: {
|
||||
id: true,
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
content: true,
|
||||
communityNote: true,
|
||||
service: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
select: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
enableOnMyCommentStatusChange: true,
|
||||
enableAutowatchMyComments: true,
|
||||
enableNotifyPendingRepliesOnWatch: true,
|
||||
karmaNotificationThreshold: true,
|
||||
}),
|
||||
null,
|
||||
],
|
||||
[
|
||||
'Error while fetching total notifications',
|
||||
() => prisma.notification.count({ where: { userId: user.id } }),
|
||||
0,
|
||||
],
|
||||
[
|
||||
'Error while fetching push subscriptions',
|
||||
() =>
|
||||
prisma.pushSubscription.findMany({
|
||||
where: { userId: user.id },
|
||||
select: {
|
||||
endpoint: true,
|
||||
userAgent: true,
|
||||
},
|
||||
aboutServiceSuggestionId: true,
|
||||
aboutServiceSuggestion: {
|
||||
select: {
|
||||
status: true,
|
||||
service: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutServiceSuggestionMessage: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
suggestion: {
|
||||
select: {
|
||||
id: true,
|
||||
service: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutEvent: {
|
||||
select: {
|
||||
title: true,
|
||||
type: true,
|
||||
service: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aboutService: {
|
||||
select: {
|
||||
slug: true,
|
||||
name: true,
|
||||
verificationStatus: true,
|
||||
},
|
||||
},
|
||||
aboutKarmaTransaction: {
|
||||
select: {
|
||||
points: true,
|
||||
action: true,
|
||||
description: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
],
|
||||
[
|
||||
'Error while fetching notification preferences',
|
||||
() =>
|
||||
getOrCreateNotificationPreferences(user.id, {
|
||||
id: true,
|
||||
enableOnMyCommentStatusChange: true,
|
||||
enableAutowatchMyComments: true,
|
||||
enableNotifyPendingRepliesOnWatch: true,
|
||||
karmaNotificationThreshold: true,
|
||||
}),
|
||||
null,
|
||||
],
|
||||
[
|
||||
'Error while fetching total notifications',
|
||||
() => prisma.notification.count({ where: { userId: user.id } }),
|
||||
0,
|
||||
],
|
||||
])
|
||||
}),
|
||||
[],
|
||||
],
|
||||
])
|
||||
|
||||
if (!notificationPreferences) console.error('No notification preferences found')
|
||||
|
||||
const totalPages = Math.ceil(totalNotifications / PAGE_SIZE)
|
||||
|
||||
@@ -199,6 +215,17 @@ const notifications = dbNotifications.map((notification) => ({
|
||||
}}
|
||||
>
|
||||
<section class="mx-auto w-full">
|
||||
{
|
||||
notifications.length >= 5 && (
|
||||
<PushNotificationBanner
|
||||
class="mb-4"
|
||||
dismissable
|
||||
pushSubscriptions={pushSubscriptions}
|
||||
hideIfEnabled
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="font-title flex items-center text-2xl leading-tight font-bold tracking-wider">
|
||||
<Icon name="ri:notification-line" class="mr-2 size-6 text-zinc-400" />
|
||||
@@ -306,57 +333,57 @@ const notifications = dbNotifications.map((notification) => ({
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!notificationPreferences && (
|
||||
<div class="mt-8">
|
||||
<h2 class="font-title mb-3 flex items-center border-b border-zinc-800 text-lg font-bold">
|
||||
<Icon name="ri:settings-3-line" class="mr-2 size-5 text-zinc-400" />
|
||||
Notification Settings
|
||||
</h2>
|
||||
<h2 class="font-title mt-8 mb-3 flex items-center border-b border-zinc-800 text-lg font-bold">
|
||||
<Icon name="ri:settings-3-line" class="mr-2 size-5 text-zinc-400" />
|
||||
Notification Settings
|
||||
</h2>
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action={actions.notification.preferences.update}
|
||||
class="rounded-lg border border-zinc-800 bg-zinc-900 p-4 shadow-sm"
|
||||
>
|
||||
{notificationPreferenceFields.map((field) => (
|
||||
<label class="flex items-center justify-between rounded-md p-2 transition-colors duration-200 hover:bg-zinc-800">
|
||||
<span class="flex items-center text-zinc-300">
|
||||
<Icon name={field.icon} class="mr-2 size-5 text-zinc-400" />
|
||||
{field.label}
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={field.id}
|
||||
checked={notificationPreferences[field.id]}
|
||||
class="size-4 rounded border-zinc-700 bg-zinc-800 text-blue-600 focus:ring-blue-600"
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
<PushNotificationBanner class="mb-3" pushSubscriptions={pushSubscriptions} />
|
||||
|
||||
<div class="mt-4 flex items-center justify-between rounded-md p-2 transition-colors duration-200 hover:bg-zinc-800">
|
||||
<span class="flex items-center text-zinc-300">
|
||||
<Icon name="ri:award-line" class="mr-2 size-5 text-zinc-400" />
|
||||
Notify me when my karma changes by at least
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
name="karmaNotificationThreshold"
|
||||
value={notificationPreferences.karmaNotificationThreshold}
|
||||
min="1"
|
||||
class="w-20 rounded border border-zinc-700 bg-zinc-800 px-2 py-1 text-zinc-200 focus:border-blue-600 focus:ring-1 focus:ring-blue-600 focus:outline-none"
|
||||
/>
|
||||
<span class="text-zinc-400">points</span>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
method="POST"
|
||||
action={actions.notification.preferences.update}
|
||||
class="rounded-lg border border-zinc-800 bg-zinc-900 p-4 shadow-sm"
|
||||
>
|
||||
{
|
||||
notificationPreferenceFields.map((field) => (
|
||||
<label class="flex items-center justify-between rounded-md p-2 transition-colors duration-200 hover:bg-zinc-800">
|
||||
<span class="flex items-center text-zinc-300">
|
||||
<Icon name={field.icon} class="mr-2 size-5 text-zinc-400" />
|
||||
{field.label}
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={field.id}
|
||||
checked={notificationPreferences?.[field.id]}
|
||||
class="size-4 rounded border-zinc-700 bg-zinc-800 text-blue-600 focus:ring-blue-600"
|
||||
/>
|
||||
</label>
|
||||
))
|
||||
}
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button type="submit" label="Save" icon="ri:save-line" color="success" />
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="mt-4 flex items-center justify-between rounded-md p-2 transition-colors duration-200 hover:bg-zinc-800"
|
||||
>
|
||||
<span class="flex items-center text-zinc-300">
|
||||
<Icon name="ri:award-line" class="mr-2 size-5 text-zinc-400" />
|
||||
Notify me when my karma changes by at least
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
name="karmaNotificationThreshold"
|
||||
value={notificationPreferences?.karmaNotificationThreshold}
|
||||
min="1"
|
||||
class="w-20 rounded border border-zinc-700 bg-zinc-800 px-2 py-1 text-zinc-200 focus:border-blue-600 focus:ring-1 focus:ring-blue-600 focus:outline-none"
|
||||
/>
|
||||
<span class="text-zinc-400">points</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button type="submit" label="Save" icon="ri:save-line" color="success" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -20,6 +20,7 @@ import InputTextArea from '../../components/InputTextArea.astro'
|
||||
import { getAttributeCategoryInfo } from '../../constants/attributeCategories'
|
||||
import { getAttributeTypeInfo } from '../../constants/attributeTypes'
|
||||
import { currencies } from '../../constants/currencies'
|
||||
import { kycLevelClarifications } from '../../constants/kycLevelClarifications'
|
||||
import { kycLevels } from '../../constants/kycLevels'
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro'
|
||||
import { prisma } from '../../lib/prisma'
|
||||
@@ -242,6 +243,21 @@ const [categories, attributes] = await Astro.locals.banners.tryMany([
|
||||
error={inputErrors.kycLevel}
|
||||
/>
|
||||
|
||||
<InputCardGroup
|
||||
name="kycLevelClarification"
|
||||
label="KYC Level Clarification"
|
||||
options={kycLevelClarifications.map((clarification) => ({
|
||||
label: clarification.label,
|
||||
value: clarification.value,
|
||||
icon: clarification.icon,
|
||||
description: clarification.description,
|
||||
}))}
|
||||
selectedValue="NONE"
|
||||
iconSize="sm"
|
||||
cardSize="sm"
|
||||
error={inputErrors.kycLevelClarification}
|
||||
/>
|
||||
|
||||
<div class="xs:grid-cols-[1fr_2fr] grid grid-cols-1 items-stretch gap-x-4 gap-y-6">
|
||||
<InputCheckboxGroup
|
||||
name="categories"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { VerificationStepStatus, EventType } from '@prisma/client'
|
||||
import { VerificationStepStatus, EventType, KycLevelClarification } from '@prisma/client'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { Markdown } from 'astro-remote'
|
||||
import { Schema } from 'astro-seo-schema'
|
||||
@@ -32,6 +32,7 @@ import { getAttributeTypeInfo } from '../../constants/attributeTypes'
|
||||
import { formatContactMethod } from '../../constants/contactMethods'
|
||||
import { currencies, getCurrencyInfo } from '../../constants/currencies'
|
||||
import { getEventTypeInfo } from '../../constants/eventTypes'
|
||||
import { getKycLevelClarificationInfo } from '../../constants/kycLevelClarifications'
|
||||
import { getKycLevelInfo, kycLevels } from '../../constants/kycLevels'
|
||||
import { getServiceVisibilityInfo } from '../../constants/serviceVisibility'
|
||||
import { getTosHighlightRatingInfo } from '../../constants/tosHighlightRating'
|
||||
@@ -78,6 +79,7 @@ const [service, dbNotificationPreferences] = await Astro.locals.banners.tryMany(
|
||||
name: true,
|
||||
description: true,
|
||||
kycLevel: true,
|
||||
kycLevelClarification: true,
|
||||
overallScore: true,
|
||||
privacyScore: true,
|
||||
trustScore: true,
|
||||
@@ -308,6 +310,9 @@ const hiddenLinks = [
|
||||
]
|
||||
|
||||
const kycLevelInfo = getKycLevelInfo(`${service.kycLevel}`)
|
||||
|
||||
const kycLevelClarificationInfo = getKycLevelClarificationInfo(service.kycLevelClarification)
|
||||
|
||||
const userSentiment = service.userSentiment
|
||||
? { ...service.userSentiment, info: getUserSentimentInfo(service.userSentiment.sentiment) }
|
||||
: null
|
||||
@@ -896,8 +901,12 @@ const activeAlertOrWarningEvents = service.events.filter(
|
||||
'@type': 'Review',
|
||||
itemReviewed: { '@id': itemReviewedId },
|
||||
reviewAspect: 'KYC Level',
|
||||
name: kycLevelInfo.name,
|
||||
reviewBody: kycLevelInfo.description,
|
||||
name:
|
||||
kycLevelInfo.name +
|
||||
(kycLevelClarificationInfo.value !== 'NONE' ? ` (${kycLevelClarificationInfo.label})` : ''),
|
||||
reviewBody:
|
||||
kycLevelInfo.description +
|
||||
(kycLevelClarificationInfo.value !== 'NONE' ? ` ${kycLevelClarificationInfo.description}` : ''),
|
||||
reviewRating: {
|
||||
'@type': 'Rating',
|
||||
ratingValue: kycLevelInfo.value,
|
||||
@@ -918,9 +927,22 @@ const activeAlertOrWarningEvents = service.events.filter(
|
||||
</div>
|
||||
|
||||
<dl class="flex-grow-5 basis-0">
|
||||
<dt class="text-base font-bold text-pretty">{kycLevelInfo.name}</dt>
|
||||
<dt class="text-base font-bold text-pretty">
|
||||
{kycLevelInfo.name}
|
||||
{
|
||||
kycLevelClarificationInfo.value !== 'NONE' && (
|
||||
<>
|
||||
<span class="text-day-400 mx-1">•</span>
|
||||
<span class="text-blue-500">{kycLevelClarificationInfo.label}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</dt>
|
||||
<dd class="text-day-700 mt-1 font-sans text-sm text-pretty">
|
||||
{kycLevelInfo.description}
|
||||
<span class="font-medium text-blue-600">
|
||||
{kycLevelClarificationInfo.value !== 'NONE' && ` ${kycLevelClarificationInfo.description}`}
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user