--- import { Icon } from 'astro-icon/components' import { Markdown } from 'astro-remote' import { z } from 'astro:content' import { orderBy } from 'lodash-es' import BadgeStandard from '../components/BadgeStandard.astro' import MyPicture from '../components/MyPicture.astro' import SortArrowIcon from '../components/SortArrowIcon.astro' import { getAttributeCategoryInfo } from '../constants/attributeCategories' import { getAttributeTypeInfo } from '../constants/attributeTypes' import { getVerificationStatusInfo } from '../constants/verificationStatus' import BaseLayout from '../layouts/BaseLayout.astro' import { nonDbAttributes, 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' const { data: filters } = zodParseQueryParamsStoringErrors( { 'sort-by': z.enum(['name', 'category', 'type', 'privacy', 'trust']), 'sort-order': z.enum(['asc', 'desc']).default('asc'), }, Astro ) const attributes = await Astro.locals.banners.try( 'Error fetching attributes', async () => prisma.attribute.findMany({ select: { id: true, slug: true, title: true, description: true, category: true, type: true, privacyPoints: true, trustPoints: true, services: { select: { service: { select: { id: true, slug: true, name: true, imageUrl: true, overallScore: true, verificationStatus: true, }, }, }, }, }, }), [] ) const sortBy = filters['sort-by'] const mergedAttributes = [ ...nonDbAttributes.map((attribute) => ({ ...attribute, services: [], id: attribute.slug })), ...attributes, ] const sortedAttributes = sortBy ? orderBy( sortAttributes(mergedAttributes), sortBy === 'type' ? (attribute) => getAttributeTypeInfo(attribute.type).order : sortBy === 'category' ? (attribute) => getAttributeCategoryInfo(attribute.category).order : sortBy === 'name' ? 'title' : sortBy === 'privacy' ? 'privacyPoints' : 'trustPoints', filters['sort-order'] ) : sortAttributes(mergedAttributes) const attributesWithInfo = sortedAttributes.map((attribute) => ({ ...attribute, categoryInfo: getAttributeCategoryInfo(attribute.category), typeInfo: getAttributeTypeInfo(attribute.type), services: orderBy( attribute.services.map(({ service }) => ({ ...service, verificationStatusInfo: getVerificationStatusInfo(service.verificationStatus), overallScoreInfo: makeOverallScoreInfo(service.overallScore), })), [ (service) => (service.verificationStatus === 'VERIFICATION_FAILED' ? 1 : -1), 'overallScore', () => Math.random(), ], ['asc', 'desc', 'asc'] ), })) const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => { const sortOrder = filters['sort-by'] === slug ? (filters['sort-order'] === 'asc' ? 'desc' : 'asc') : 'asc' return `/attributes?sort-by=${slug}&sort-order=${sortOrder}` } ---

Service attributes

Characteristics or features of services that affect their scores.

Learn more about attributes

{ attributesWithInfo.map((attribute) => (

{attribute.title}

0, 'opacity-50': attribute.privacyPoints === 0, })} > {formatNumber(attribute.privacyPoints, { showSign: true })} Privacy
0, 'opacity-50': attribute.trustPoints === 0, })} > {formatNumber(attribute.trustPoints, { showSign: true })} Trust
{attribute.services.length > 0 && (
Show services
)}
)) }