Release 2025-05-20-0D8p

This commit is contained in:
pluja
2025-05-20 01:47:50 +00:00
parent 587480d140
commit af3df8f79a
35 changed files with 1091 additions and 235 deletions

View File

@@ -4,6 +4,7 @@ title: About
author: KYCnot.me
pubDate: 2025-05-15
description: 'Learn how KYCnot.me website works and about our mission to protect privacy in cryptocurrency.'
icon: 'ri:information-line'
---
## What is this page?

View File

@@ -24,7 +24,7 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {}
<MiniLayout
pageTitle={`Edit Profile - ${user.name}`}
description="Edit your user profile"
ogImage={{ template: 'generic', title: 'Edit Profile' }}
ogImage={{ template: 'generic', title: 'Edit Profile', icon: 'ri:user-settings-line' }}
layoutHeader={{
icon: 'ri:edit-line',
title: 'Edit profile',

View File

@@ -25,7 +25,12 @@ const prettyToken = preGeneratedToken ? prettifyUserSecretToken(preGeneratedToke
<MiniLayout
pageTitle="Create Account"
description="Create a new account"
ogImage={{ template: 'generic', title: 'Create Account' }}
ogImage={{
template: 'generic',
title: 'Create Account',
description: 'Zero data, 100% anonymous',
icon: 'ri:user-add-line',
}}
layoutHeader={{
icon: 'ri:user-add-line',
title: 'New account',

View File

@@ -1,12 +1,11 @@
---
import { Icon } from 'astro-icon/components'
import { actions } from 'astro:actions'
import { Picture } from 'astro:assets'
import { sortBy } from 'lodash-es'
import defaultServiceImage from '../../assets/fallback-service-image.jpg'
import BadgeSmall from '../../components/BadgeSmall.astro'
import Button from '../../components/Button.astro'
import MyPicture from '../../components/MyPicture.astro'
import TimeFormatted from '../../components/TimeFormatted.astro'
import Tooltip from '../../components/Tooltip.astro'
import { getKarmaTransactionActionInfo } from '../../constants/karmaTransactionActions'
@@ -161,7 +160,12 @@ if (!user) return Astro.rewrite('/404')
<BaseLayout
pageTitle={`${user.name} - Account`}
description="Manage your user profile"
ogImage={{ template: 'generic', title: `${user.name} | Account` }}
ogImage={{
template: 'generic',
title: `${user.name} | Account`,
description: 'Manage your user profile',
icon: 'ri:user-3-line',
}}
widthClassName="max-w-screen-md"
className={{
main: 'space-y-6',
@@ -180,7 +184,13 @@ if (!user) return Astro.rewrite('/404')
<header class="flex items-center gap-4">
{
user.picture ? (
<img src={user.picture} alt="" class="ring-day-500/30 size-16 rounded-full ring-2" />
<MyPicture
src={user.picture}
alt=""
class="ring-day-500/30 size-16 rounded-full ring-2"
width={64}
height={64}
/>
) : (
<div class="bg-day-500/10 ring-day-500/30 text-day-400 flex size-16 items-center justify-center rounded-full ring-2">
<Icon name="ri:user-3-line" class="size-8" />
@@ -460,8 +470,9 @@ if (!user) return Astro.rewrite('/404')
href={`/service/${affiliation.service.slug}`}
class="text-day-300 group flex min-w-32 items-center gap-2 text-sm"
>
<Picture
src={affiliation.service.imageUrl ?? (defaultServiceImage as unknown as string)}
<MyPicture
src={affiliation.service.imageUrl}
fallback="service"
alt={affiliation.service.name}
width={40}
height={40}

View File

@@ -30,7 +30,12 @@ const message = Astro.url.searchParams.get('message')
<MiniLayout
pageTitle="Login"
description="Login to your account"
ogImage={{ template: 'generic', title: 'Login' }}
ogImage={{
template: 'generic',
title: 'Login',
description: message ?? 'Enter your login key',
icon: 'ri:login-box-line',
}}
layoutHeader={{
icon: 'ri:user-line',
title: 'Welcome back',

View File

@@ -17,7 +17,12 @@ const prettyToken = result ? prettifyUserSecretToken(result.data.token) : null
<MiniLayout
pageTitle="Welcome"
description="New account welcome page"
ogImage={{ template: 'generic', title: 'Welcome' }}
ogImage={{
template: 'generic',
title: 'Welcome',
description: 'New account welcome page',
icon: 'ri:key-2-line',
}}
layoutHeader={{
icon: 'ri:key-2-line',
title: 'Save your Login Key',

View File

@@ -8,8 +8,8 @@ import {
} from '@prisma/client'
import { Icon } from 'astro-icon/components'
import { actions, isInputError } from 'astro:actions'
import { Image } from 'astro:assets'
import MyPicture from '../../../../components/MyPicture.astro'
import { serviceVisibilities } from '../../../../constants/serviceVisibility'
import BaseLayout from '../../../../layouts/BaseLayout.astro'
import { cn } from '../../../../lib/cn'
@@ -334,7 +334,7 @@ const buttonSmallWarningClasses = cn(
{
service.imageUrl ? (
<div class="mt-2 shrink-0">
<Image
<MyPicture
src={service.imageUrl}
alt="Current service image"
width={100}

View File

@@ -2,9 +2,8 @@
import { ServiceVisibility, VerificationStatus, type Prisma } from '@prisma/client'
import { z } from 'astro/zod'
import { Icon } from 'astro-icon/components'
import { Image } from 'astro:assets'
import defaultImage from '../../../assets/fallback-service-image.jpg'
import MyPicture from '../../../components/MyPicture.astro'
import SortArrowIcon from '../../../components/SortArrowIcon.astro'
import { getKycLevelInfo } from '../../../constants/kycLevels'
import { getVerificationStatusInfo } from '../../../constants/verificationStatus'
@@ -343,23 +342,14 @@ const truncate = (text: string, length: number) => {
<td class="px-4 py-3">
<div class="flex items-center space-x-3">
<div class="h-10 w-10 flex-shrink-0">
{service.imageUrl ? (
<Image
src={service.imageUrl}
alt={service.name}
width={40}
height={40}
class="h-10 w-10 rounded-md object-cover"
/>
) : (
<Image
src={defaultImage}
alt={service.name}
width={40}
height={40}
class="h-10 w-10 rounded-md object-cover"
/>
)}
<MyPicture
src={service.imageUrl}
fallback="service"
alt={service.name}
width={40}
height={40}
class="h-10 w-10 rounded-md object-cover"
/>
</div>
<div class="min-w-0 flex-1">
<div class="text-sm font-medium text-zinc-200">{service.name}</div>

View File

@@ -1,7 +1,6 @@
---
import { Icon } from 'astro-icon/components'
import { actions, isInputError } from 'astro:actions'
import { Image } from 'astro:assets'
import BadgeSmall from '../../../components/BadgeSmall.astro'
import Button from '../../../components/Button.astro'
@@ -11,6 +10,7 @@ import InputSelect from '../../../components/InputSelect.astro'
import InputSubmitButton from '../../../components/InputSubmitButton.astro'
import InputText from '../../../components/InputText.astro'
import InputTextArea from '../../../components/InputTextArea.astro'
import MyPicture from '../../../components/MyPicture.astro'
import TimeFormatted from '../../../components/TimeFormatted.astro'
import { getServiceUserRoleInfo, serviceUserRoles } from '../../../constants/serviceUserRoles'
import BaseLayout from '../../../layouts/BaseLayout.astro'
@@ -123,7 +123,7 @@ if (!user) return Astro.rewrite('/404')
<div class="mt-12">
{
!!user.picture && (
<Image
<MyPicture
src={user.picture}
alt=""
width={80}
@@ -218,7 +218,7 @@ if (!user) return Astro.rewrite('/404')
value={user.picture}
error={updateInputErrors.pictureFile}
square
description="Upload a square image for best results. Supported formats: JPG, PNG, WebP, AVIF, JXL. Max size: 5MB."
description="Upload a square image for best results. Supported formats: JPG, PNG, WebP, AVIF. Max size: 5MB."
/>
<InputCardGroup
@@ -265,7 +265,7 @@ if (!user) return Astro.rewrite('/404')
<div class="mb-1 flex items-center justify-between gap-4">
<div class="flex items-center gap-1">
{!!note.addedByUser?.picture && (
<Image
<MyPicture
src={note.addedByUser.picture}
alt=""
width={12}

View File

@@ -1,12 +1,11 @@
---
import { Icon } from 'astro-icon/components'
import { Markdown } from 'astro-remote'
import { Picture } from 'astro:assets'
import { z } from 'astro:content'
import { orderBy } from 'lodash-es'
import BadgeStandard from '../components/BadgeStandard.astro'
import { makeOverallScoreInfo } from '../components/ScoreSquare.astro'
import MyPicture from '../components/MyPicture.astro'
import SortArrowIcon from '../components/SortArrowIcon.astro'
import { getAttributeCategoryInfo } from '../constants/attributeCategories'
import { getAttributeTypeInfo } from '../constants/attributeTypes'
@@ -15,6 +14,7 @@ import BaseLayout from '../layouts/BaseLayout.astro'
import { sortAttributes } from '../lib/attributes'
import { cn } from '../lib/cn'
import { formatNumber } from '../lib/numbers'
import { makeOverallScoreInfo } from '../lib/overallScore'
import { zodParseQueryParamsStoringErrors } from '../lib/parseUrlFilters'
import { prisma } from '../lib/prisma'
@@ -102,8 +102,13 @@ const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => {
<BaseLayout
pageTitle="Attributes"
description="Browse all available service attributes used to evaluate privacy and trust scores on KYCnot.me."
ogImage={{ template: 'generic', title: 'All attributes' }}
description="Browse all available service attributes used to evaluate privacy and trust scores."
ogImage={{
template: 'generic',
title: 'All attributes',
description: 'Browse all available service attributes',
icon: 'ri:list-radio',
}}
>
<h1 class="font-title mb-2 text-center text-3xl font-bold text-white">Service attributes</h1>
@@ -202,12 +207,11 @@ const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => {
class="flex items-center gap-2 rounded-md p-2 transition-colors hover:bg-zinc-800"
>
{service.imageUrl ? (
<Picture
<MyPicture
src={service.imageUrl}
alt={service.name}
width={24}
height={24}
formats={['jxl', 'avif', 'webp']}
class="size-6 shrink-0 rounded-xs object-contain"
/>
) : (
@@ -349,12 +353,11 @@ const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => {
class="flex items-center gap-2 rounded-md p-2 transition-colors hover:bg-zinc-800"
>
{service.imageUrl ? (
<Picture
<MyPicture
src={service.imageUrl}
alt={service.name}
width={24}
height={24}
formats={['jxl', 'avif', 'webp']}
class="size-6 shrink-0 rounded-xs object-contain"
/>
) : (

View File

@@ -1,11 +1,11 @@
---
import { z } from 'astro/zod'
import { Icon } from 'astro-icon/components'
import { Picture } from 'astro:assets'
import { orderBy } from 'lodash-es'
import Button from '../components/Button.astro'
import FormatTimeInterval from '../components/FormatTimeInterval.astro'
import MyPicture from '../components/MyPicture.astro'
import TimeFormatted from '../components/TimeFormatted.astro'
import {
eventTypes,
@@ -151,7 +151,12 @@ const createUrlWithoutFilter = (paramName: keyof typeof params) => {
description="Discover important events, updates, and news about KYC-free services in chronological order."
widthClassName="max-w-screen-lg"
className={{ main: 'sm:flex sm:items-start sm:gap-6' }}
ogImage={{ template: 'generic', title: 'Events' }}
ogImage={{
template: 'generic',
title: 'Events',
description: 'Discover important events, updates, and news about KYC-free services',
icon: 'ri:calendar-event-line',
}}
htmx
>
<h1 class="font-title mb-6 block text-center text-2xl font-bold text-white sm:hidden">
@@ -287,12 +292,11 @@ const createUrlWithoutFilter = (paramName: keyof typeof params) => {
class="group flex h-8 items-center gap-2 rounded-full border border-green-500/30 bg-black/40 px-3 text-sm text-white"
>
{service?.imageUrl && (
<Picture
<MyPicture
src={service.imageUrl}
alt={service.name}
width={16}
height={16}
formats={['jxl', 'avif', 'webp']}
class="size-4 shrink-0 rounded-xs object-contain"
/>
)}
@@ -385,12 +389,11 @@ const createUrlWithoutFilter = (paramName: keyof typeof params) => {
class="-m-1.5 flex w-fit items-center rounded-md p-1.5 leading-none transition-colors hover:bg-zinc-800"
>
{event.service.imageUrl && (
<Picture
<MyPicture
src={event.service.imageUrl}
alt={event.service.name}
width={16}
height={16}
formats={['jxl', 'avif', 'webp']}
class="size-4 shrink-0 rounded-xs object-contain"
/>
)}

View File

@@ -2,6 +2,7 @@
layout: ../layouts/MarkdownLayout.astro
title: How does karma work?
description: "KYCnot.me has a user karma system, here's how it works"
icon: 'ri:hearts-line'
author: KYCnot.me
pubDate: 2025-05-15
---

View File

@@ -183,7 +183,12 @@ const notifications = dbNotifications.map((notification) => ({
pageTitle="Notifications"
description="View your notifications and manage your notification preferences."
widthClassName="max-w-screen-lg"
ogImage={{ template: 'generic', title: 'Notifications' }}
ogImage={{
template: 'generic',
title: 'Notifications',
description: 'View and manage your notifications',
icon: 'ri:notification-line',
}}
>
<section class="mx-auto w-full">
<div class="mb-4 flex items-center justify-between">

View File

@@ -1,19 +1,31 @@
import { ogImageTemplates } from '../components/OgImage'
import { urlParamsToObject } from '../lib/urls'
import { ogImageTemplates, type OgImageAllTemplatesWithProps } from '../components/OgImage'
import type { APIRoute } from 'astro'
import type { Misc } from 'ts-toolbelt'
export const GET: APIRoute = (context) => {
const { template, ...props } = urlParamsToObject(context.url.searchParams)
function toJSON<T extends Misc.JSON.Value>(data: string | null | undefined): T | undefined {
if (!data) return undefined
try {
return JSON.parse(data) as T
} catch (_error) {
return undefined
}
}
if (!template) return ogImageTemplates.default({}, context)
export const GET: APIRoute = async (context) => {
const { template, ...props } = toJSON<OgImageAllTemplatesWithProps>(
context.url.searchParams.get('data')
) ?? { template: 'default' }
if (!template as unknown) return ogImageTemplates.default({}, context)
if (!(template in ogImageTemplates)) {
console.error(`Invalid template: "${template}"`)
return ogImageTemplates.default({}, context)
}
const response = ogImageTemplates[template as keyof typeof ogImageTemplates](props, context)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
const response = await ogImageTemplates[template](props as any, context)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!response) {
console.error(`Cannot generate image for template: ${template} and props: ${JSON.stringify(props)}`)

View File

@@ -86,7 +86,12 @@ const statusInfo = getServiceSuggestionStatusInfo(serviceSuggestion.status)
<BaseLayout
pageTitle={`${serviceSuggestion.service.name} | Service suggestion`}
description="View your service suggestion"
ogImage={{ template: 'generic', title: serviceSuggestion.service.name }}
ogImage={{
template: 'generic',
title: 'My service suggestions',
description: 'View and manage your service suggestion',
icon: 'ri:service-line',
}}
widthClassName="max-w-screen-md"
htmx
breadcrumbs={[

View File

@@ -65,7 +65,12 @@ if (!service) return Astro.rewrite('/404')
<BaseLayout
pageTitle="Edit service"
description="Suggest an edit to service"
ogImage={{ template: 'generic', title: 'Edit service' }}
ogImage={{
template: 'generic',
title: 'Edit service',
description: 'Suggest an edit to service',
icon: 'ri:edit-line',
}}
widthClassName="max-w-screen-md"
breadcrumbs={[
{

View File

@@ -1,11 +1,10 @@
---
import { Icon } from 'astro-icon/components'
import { actions } from 'astro:actions'
import { Picture } from 'astro:assets'
import { z } from 'astro:content'
import defaultServiceImage from '../../assets/fallback-service-image.jpg'
import Button from '../../components/Button.astro'
import MyPicture from '../../components/MyPicture.astro'
import TimeFormatted from '../../components/TimeFormatted.astro'
import Tooltip from '../../components/Tooltip.astro'
import {
@@ -72,7 +71,12 @@ const success = !!createResult && !createResult.error
<BaseLayout
pageTitle="My service suggestions"
description="Manage your service suggestions"
ogImage={{ template: 'generic', title: 'Service suggestions' }}
ogImage={{
template: 'generic',
title: 'Service suggestions',
description: 'Manage your service suggestions',
icon: 'ri:service-line',
}}
widthClassName="max-w-screen-md"
breadcrumbs={[
{
@@ -122,13 +126,13 @@ const success = !!createResult && !createResult.error
href={`/service/${suggestion.service.slug}`}
class="inline-flex w-full min-w-32 items-center gap-2 hover:underline"
>
<Picture
src={suggestion.service.imageUrl ?? (defaultServiceImage as unknown as string)}
<MyPicture
src={suggestion.service.imageUrl}
fallback="service"
alt={suggestion.service.name}
width={32}
height={32}
class="inline-block size-8 min-w-8 shrink-0 rounded-md"
formats={['jxl', 'avif', 'webp']}
/>
<span class="shrink truncate">{suggestion.service.name}</span>
</a>

View File

@@ -65,7 +65,12 @@ const [categories, attributes] = await Astro.locals.banners.tryMany([
<BaseLayout
pageTitle="New service"
description="Suggest a new service to be added to KYCnot.me"
ogImage={{ template: 'generic', title: 'New service' }}
ogImage={{
template: 'generic',
title: 'New service',
description: 'Suggest a new service to be listed',
icon: 'ri:add-circle-line',
}}
widthClassName="max-w-screen-md"
breadcrumbs={[
{

View File

@@ -4,8 +4,7 @@ import { Icon } from 'astro-icon/components'
import { Markdown } from 'astro-remote'
import { Schema } from 'astro-seo-schema'
import { actions } from 'astro:actions'
import { Picture } from 'astro:assets'
import { head, orderBy, shuffle, sortBy, tail } from 'lodash-es'
import { head, orderBy, pick, shuffle, sortBy, tail } from 'lodash-es'
import AdminOnly from '../../components/AdminOnly.astro'
import BadgeSmall from '../../components/BadgeSmall.astro'
@@ -17,6 +16,7 @@ import DropdownButton from '../../components/DropdownButton.astro'
import DropdownButtonItemForm from '../../components/DropdownButtonItemForm.astro'
import DropdownButtonItemLink from '../../components/DropdownButtonItemLink.astro'
import FormatTimeInterval from '../../components/FormatTimeInterval.astro'
import MyPicture from '../../components/MyPicture.astro'
import { makeOgImageUrl, type OgImageAllTemplatesWithProps } from '../../components/OgImage'
import ScoreGauge from '../../components/ScoreGauge.astro'
import ScoreSquare from '../../components/ScoreSquare.astro'
@@ -349,8 +349,12 @@ const getVerificationStepStatusInfo = (status: VerificationStepStatus) => {
const itemReviewedId = new URL(`/service/${service.slug}`, Astro.url).href
const ogImageTemplateData = {
template: 'generic',
template: 'service',
title: service.name,
description: service.description,
categories: service.categories.map((category) => pick(category, ['name', 'icon'])),
score: service.overallScore,
imageUrl: service.imageUrl,
} satisfies OgImageAllTemplatesWithProps
---
@@ -477,9 +481,8 @@ const ogImageTemplateData = {
<div class="flex items-center gap-4">
{
!!service.imageUrl && (
<Picture
<MyPicture
src={service.imageUrl}
formats={['jxl', 'avif', 'webp']}
alt={service.name || "Service's logo"}
class="size-12 shrink-0 rounded-sm object-contain"
width={48}

View File

@@ -1,14 +1,13 @@
---
import { Icon } from 'astro-icon/components'
import { actions } from 'astro:actions'
import { Picture } from 'astro:assets'
import { sortBy } from 'lodash-es'
import defaultServiceImage from '../../assets/fallback-service-image.jpg'
import AdminOnly from '../../components/AdminOnly.astro'
import BadgeSmall from '../../components/BadgeSmall.astro'
import InputSubmitButton from '../../components/InputSubmitButton.astro'
import InputTextArea from '../../components/InputTextArea.astro'
import MyPicture from '../../components/MyPicture.astro'
import TimeFormatted from '../../components/TimeFormatted.astro'
import Tooltip from '../../components/Tooltip.astro'
import { getKarmaTransactionActionInfo } from '../../constants/karmaTransactionActions'
@@ -178,7 +177,13 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id
<BaseLayout
pageTitle={`${user.name} - Account`}
description="Manage your user profile"
ogImage={{ template: 'generic', title: `${user.name} | Account` }}
ogImage={{
template: 'generic',
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
title: user.displayName || user.name,
description: 'User profile page',
icon: 'ri:user-3-line',
}}
widthClassName="max-w-screen-md"
className={{
main: 'space-y-6',
@@ -238,7 +243,13 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id
<header class="flex items-center gap-4">
{
user.picture ? (
<img src={user.picture} alt="" class="ring-day-500/30 size-16 rounded-full ring-2" />
<MyPicture
src={user.picture}
alt=""
class="ring-day-500/30 size-16 rounded-full ring-2"
width={64}
height={64}
/>
) : (
<div class="bg-day-500/10 ring-day-500/30 text-day-400 flex size-16 items-center justify-center rounded-full ring-2">
<Icon name="ri:user-3-line" class="size-8" />
@@ -555,8 +566,9 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id
href={`/service/${affiliation.service.slug}`}
class="text-day-300 group flex min-w-32 items-center gap-2 text-sm"
>
<Picture
src={affiliation.service.imageUrl ?? (defaultServiceImage as unknown as string)}
<MyPicture
src={affiliation.service.imageUrl}
fallback="service"
alt={affiliation.service.name}
width={40}
height={40}