Merge pull request #19 from web3privacy/dk/dashboard-redesign

Dk/dashboard redesign
This commit is contained in:
MufCZ 2024-09-17 16:10:10 +02:00 committed by GitHub
commit 75f5e2c57b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 1940 additions and 1258 deletions

View file

@ -1,10 +1,14 @@
<script lang="ts" setup>
import type { ProjectShallow } from '~/types'
import type { ProjectRating, ProjectShallow } from '~/types'
defineProps<{
const props = defineProps<{
project: ProjectShallow
}>()
const { switcher } = storeToRefs(useData())
const { switcher, ecosystems } = storeToRefs(useData())
const ratings: { label: string, type: string, rating: ProjectRating }[] = (props.project.ratings || []).map(rating => ({ label: rating.name, type: 'rating', rating: rating }))
const ecosystem: { label: string[], type: string } = { label: ecosystems.value.filter(e => (props.project.ecosystem || []).includes(e.id)).map(e => e.icon!), type: 'ecosystem' }
const projectItems: { label: string | string[], type: string, rating?: ProjectRating }[] = [{ label: props.project.usecases || [], type: 'array' }, ...ratings, ecosystem, { label: [props.project.website || '', props.project.github || '', props.project.twitter || ''], type: 'links' }]
</script>
<template>
@ -18,230 +22,179 @@ const { switcher } = storeToRefs(useData())
transition-all
>
<div
relative
max-w="96px lg:200px"
grid
grid-cols="2 lg:10"
w-full
h="96px lg:200px"
:class="switcher ? '' : 'lg:max-w-full! lg:w-full '"
>
<div
col-span="1 lg:2"
flex
items-center
justify-center
gap="12px lg:16px"
relative
w-full
my-auto
h-full
h="48px lg:64px"
:class="switcher ? '' : 'lg:max-w-full! lg:w-full '"
>
<NuxtImg
:src="project?.image || '/no-image-1-1.svg'"
class="w-full h-auto"
max-h="md:196px 96px"
max-h="md:64px 48px"
max-w="md:64px 48px"
self-center
z-10
object-fit
bg="#121212"
/>
<div
flex
flex-col
gap-y-4px
lg:flex-row
justify-center
>
<div
w-fit
flex
items-center
gap-8px
@click.prevent="navigateTo(project.website, { external: true, open: { target: '_blank' } })"
>
<h1
text="14px app-white"
font-700
line-clamp-1
hover:underline
underline-offset-3
leading="20px lg:32px"
>
{{ project.title1 }}
</h1>
</div>
<p
text-12px
leading-16px
lg:hidden
>
{{ project.usecases?.join(', ') }}
</p>
</div>
</div>
<ClientOnly>
<Badge
v-if="project.percentage"
absolute
bottom-0.5
lg:bottom-0
right-0.5
lg:right-0
mr-2px
mb-2px
:text="`${project.percentage}%`"
class="leading-12px! text-12px! lg:text-18px! border-0!"
px="4px! lg:16px!"
py="4px! lg:8px!"
/>
</ClientOnly>
</div>
<div
h="96px lg:200px"
lg:py-24px
lg:pr-24px
flex
flex-col
justify-center
lg:justify-between
lg:gap-24px
w-full
text-white
:class="switcher ? '' : 'lg:p-16px! lg:py-16px!'"
>
<div
w-full
h-fit
flex
flex-col
gap-8px
>
<div
w-fit
flex
items-center
gap-8px
@click.prevent="navigateTo(project.website, { external: true, open: { target: '_blank' } })"
>
<h1
text="18px lg:24px app-white"
font-700
line-clamp-1
hover:underline
underline-offset-3
>
{{ project.title1 }}
</h1>
<UnoIcon
i-web-open
text-16px
/>
</div>
<h2
text="14px app-text-grey"
overflow-hidden
text-ellipsis
line-clamp-2
lg:line-clamp-2
>
{{ project.description }}
</h2>
</div>
<div
w-full
flex
justify-between
>
<div
w-full
max-w-692px
grid
whitespace-nowrap
:class="switcher ? 'grid-cols-5' : 'grid-cols-3'"
gap-24px
lg:grid
hidden
>
<ProjectInfoItem
:check-undefined="project?.github"
:link="project?.github"
title="Github"
bold
text-size="18px"
>
<div
flex
items-center
gap-8px
>
<UnoIcon
i-web-github
text-16px
/>
<span>{{ project?.github ? 'YES' : 'NO' }}</span>
</div>
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.readyness"
title="Readyness"
text-size="18px"
>
<div
flex
items-center
gap-12px
>
<UnoIcon
i-web-live
text-10px
:class="(project.readyness === 'Mainnet') ? 'color-#18FF2F' : (project.readyness === 'Testnet') ? 'color-#FFA800' : (project.readyness === 'Alpha') ? 'color-#FF0000' : ''"
/>
<span :class="(project.readyness === 'Alpha') ? 'color-#FFA800' : 'color-white'">{{ project.readyness }}</span>
</div>
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="true"
title="Team"
text-size="18px"
>
<span v-if="project.team?.length">{{ `${project.team?.length} members` }}</span>
<span
v-else
color="#FF0000"
>{{ 'Anonymous' }}</span>
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project?.docs"
:link="project?.docs"
:color="project?.docs ? '#18FF2F' : '#FF0000'"
title="Docs"
bold
text-size="18px"
>
{{ project?.docs ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.audits"
:link="project?.audits?.[0]?.link ?? undefined"
:color="project?.audits ? '#18FF2F' : '#FF0000'"
title="Audit"
bold
text-size="18px"
>
{{ project.audits ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
<div
v-for="(projectItem, index) of projectItems"
:key="projectItem.label.toString()"
hidden
lg:flex
items-center
text-14px
leading-24px
:class="{ 'col-span-1 lg:col-span-2': index === 0 }"
>
<p
v-if="projectItem.type === 'array'"
text-app-text-grey
>
{{ (projectItem.label as string[] || []).join(', ') }}
</p>
<div
v-if="projectItem.type === 'links'"
flex
items-center
justify-start
gap-16px
>
<NuxtLink
v-if="projectItem.label[1]"
:to="projectItem.label[1]"
external
target="_blank"
>
<UnoIcon
i-ic-baseline-language
text="24px app-text-grey"
/>
</NuxtLink>
<NuxtLink
v-if="projectItem.label[2]"
:to="projectItem.label[2]"
external
target="_blank"
>
<UnoIcon
i-mdi-github
text="24px app-text-grey"
text-27px
/>
</NuxtLink>
<NuxtLink
v-if="projectItem.label[0]"
:to="projectItem.label[0]"
external
target="_blank"
>
<UnoIcon
i-bi-twitter-x
text="24px app-text-grey"
/>
</NuxtLink>
</div>
<div
v-if="projectItem.type === 'ecosystem'"
flex
items-center
justify-start
gap-2px
>
<NuxtImg
v-for="ecosystem of projectItem.label"
:key="ecosystem"
:src="ecosystem"
w-24px
h-24px
rounded-full
/>
</div>
<ProjectRating
v-if="projectItem.type! === 'rating' && projectItem.rating"
:percentage="projectItem.rating.points"
:rating="projectItem.rating"
:type="projectItem.rating.type"
/>
</div>
<div
flex
items-center
justify-end
w-full
gap-16px
>
<UnoIcon
v-if="project.forum"
i-web-forum
text-28px
opacity-50
hover:opacity-100
@click.prevent="navigateTo(project.forum, { external: true, open: { target: '_blank' } })"
/>
<UnoIcon
v-if="project.explorer"
i-web-explorer
text-32px
opacity-50
hover:opacity-100
@click.prevent="navigateTo(project.explorer, { external: true, open: { target: '_blank' } })"
/>
<UnoIcon
v-if="project.twitter"
i-web-twitter_x
text-22px
opacity-50
hover:opacity-100
@click.prevent="navigateTo(project.twitter, { external: true, open: { target: '_blank' } })"
/>
<UnoIcon
v-if="project.coingecko"
i-web-coingecko
text-24px
opacity-50
hover:opacity-100
@click.prevent="navigateTo(project.coingecko, { external: true, open: { target: '_blank' } })"
/>
<UnoIcon
v-if="project.newsletter"
i-web-news
text-28px
opacity-50
hover:opacity-100
@click.prevent="navigateTo(project.newsletter, { external: true, open: { target: '_blank' } })"
block
lg:hidden
i-iconoir-internet
text="24px"
/>
<div
flex
items-center
justify-center
border="2px app-white"
text="14px md:18px"
leading="24px md:32px"
max-h-="28px md:32px"
max-w="48px md:56px"
w-full
font-700
whitespace-nowrap
>
{{ project.percentage }} %
</div>
</div>
</div>
</ClientOnly>
</div>
</NuxtLink>
</template>

View file

@ -5,6 +5,7 @@ const props = defineProps<{
options: InputOption[]
modelValue: string
count?: number
titleShowCount?: boolean
}>()
const emits = defineEmits(['update:modelValue', 'selected'])
@ -23,15 +24,18 @@ function onOptionSelected(value: string) {
v-model="selectedValue"
as="div"
>
<div class="relative mt-2 font-700 font-24px">
<div class="relative font-700 font-24px">
<HeadlessListboxButton
class="relative w-full cursor-pointer py-8px p-16px text-left border-2px text-app-white sm:text-sm sm:leading-6"
class="relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black sm:text-sm sm:leading-6"
>
<span class="block truncate mr-8px">{{ isOptionSelected?.label }} <span opacity-50>({{ isOptionSelected?.count }})</span></span>
<span class="block truncate mr-8px font">{{ isOptionSelected?.label }} <span
v-if="titleShowCount"
opacity-50
>({{ isOptionSelected?.count }})</span></span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
text-app-white
i-ic-baseline-arrow-drop-down
text-app-black
/>
</span>
</HeadlessListboxButton>
@ -42,7 +46,7 @@ function onOptionSelected(value: string) {
leave-to-class="opacity-0"
>
<HeadlessListboxOptions
class="absolute z-100 max-h-60 w-full divide-y-2px border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
class="absolute z-100 max-h-60 w-auto border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
>
<HeadlessListboxOption
v-for="option in props.options"
@ -53,15 +57,18 @@ function onOptionSelected(value: string) {
@click="onOptionSelected(option.value)"
>
<li
class="w-full relative cursor-pointer select-none py-8px p-16px"
:class="[active ? 'bg-#ffffff1a' : 'text-white']"
class="w-full relative cursor-pointer select-none py-8px p-16px bg-app-black text-app-white"
:class="[active ? 'text-app-white' : 'text-app-white']"
>
<span
class="block truncate"
:class="[selected ? 'font-semibold' : 'font-normal']"
>
{{ option.label }}
<span opacity-50>({{ option.count }})</span>
<span
v-if="titleShowCount"
opacity-50
>({{ option.count }})</span>
</span>
</li>
</HeadlessListboxOption>

View file

@ -13,6 +13,7 @@ defineProps<{
>
<UnoIcon
i-heroicons-solid-pencil
text-app-text-grey
text-24px
/>
<slot />

View file

@ -20,9 +20,6 @@ defineProps<{
gap-8px
text-16px
font-700
lg:max-w-320px
lg:w-full
lg:justify-end
>
<slot />
{{ title }}

View file

@ -6,8 +6,6 @@
<div
hidden
w-full
max-w-320px
lg:block
/>
<div w-full>
<slot />

View file

@ -2,15 +2,35 @@
import type { ProjectShallow } from '~/types'
const props = defineProps<{
projects: ProjectShallow[]
projects: { title: string, projects: ProjectShallow[] }[]
}>()
const { switcher } = storeToRefs(useData())
const { switcher, filter } = storeToRefs(useData())
const displayCount = ref(100)
const displayedProjects = computed(() => props.projects.slice(0, displayCount.value))
function showMoreProjects() {
displayCount.value += 50
const totalProjectsCount = props.projects.map(g => g.projects.length).reduce((a, b) => a + b, 0)
function onChangeSort(sortKey: string) {
if (filter.value.sortby === sortKey) {
if (filter.value.sortDirection === 'desc' && filter.value.sortby !== 'score') {
filter.value.sortby = 'score'
filter.value.sortDirection = 'desc'
return
}
filter.value.sortDirection = filter.value.sortDirection === 'asc' ? 'desc' : 'asc'
return
}
filter.value.sortby = sortKey
filter.value.sortDirection = sortKey === 'score' ? 'desc' : 'asc'
}
const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[]>([
{ label: 'Usecase', sortKey: 'usecase' },
{ label: 'Openess', sortKey: 'openess', togglable: true },
{ label: 'Technology', sortKey: 'technology', togglable: true },
{ label: 'Privacy', sortKey: 'privacy', togglable: true },
{ label: 'Ecosystem', sortKey: 'ecosystem' },
{ label: 'Links', sortKey: 'links' },
{ label: 'W3PN Score', sortKey: 'score', togglable: true },
])
</script>
<template>
@ -19,36 +39,138 @@ function showMoreProjects() {
flex-col
items-start
>
<div
v-if="displayedProjects.length"
grid
:class="switcher ? 'grid-cols-1 lg:grid-cols-1' : 'xl:grid-cols-3 lg:grid-cols-3 sm:grid-cols-2 grid-cols-1'"
gap-16px
text-white
w-full
<template
v-for="group in projects"
:key="group.title"
>
<Card
v-for="project in displayedProjects"
:key="project.id"
:project="project"
/>
</div>
<div v-else>
<div
flex
items-center
gap-x-12px
w-full
mb="8px lg:16px"
mt-22px
>
<h2
text="app-white 16px lg:24px"
font-700
leading="24px lg:32px"
whitespace-nowrap
>
{{ group.projects.length }} {{ group.title }}
</h2>
<div
h-2px
w-full
bg-app-text-grey
/>
<button
type="button"
i-ic-baseline-arrow-drop-down
text="app-text-grey 24px"
/>
</div>
<div
grid
grid-cols="2 lg:10"
w-full
mb-16px
>
<div
flex
items-center
gap-4px
col-span="1 lg:2"
:class="['title' === filter.sortby ? 'text-app-white' : 'text-app-text-grey', 'cursor-pointer']"
@click="onChangeSort('title')"
>
<p
text="12px lg:14px"
leading="16px lg:24px"
whitespace-nowrap
>
Project name
</p>
<button
type="button"
:class="['title' === filter.sortby ? filter.sortDirection === 'desc' ? 'i-ic-baseline-arrow-drop-up'
: 'i-ic-baseline-arrow-drop-down'
: 'i-ic-baseline-arrow-drop-down']"
text="20px"
/>
</div>
<div
flex
items-center
justify-end
gap-4px
lg:hidden
>
<p
text="12px lg:14px app-text-grey"
leading="16px lg:24px"
>
Sort by:
</p>
<p
text="12px lg:14px"
leading="16px lg:24px"
font-700
>
Score
</p>
<button
type="button"
i-ic-baseline-arrow-drop-down
text="app-text-grey 20px"
/>
</div>
<div
v-for="(title, index) in cardTitles"
:key="title.label"
lg:flex
items-center
justify-start
last:justify-end
gap-4px
hidden
:class="[title.sortKey === filter.sortby ? 'text-app-white' : 'text-app-text-grey', { 'cursor-pointer': title.togglable, 'col-span-1 lg:col-span-2': index === 0 }]"
@click="onChangeSort(title.sortKey)"
>
<p
text="12px lg:14px "
leading="16px lg:24px"
whitespace-nowrap
>
{{ title.label }}
</p>
<button
v-if="title.togglable"
type="button"
:class="[title.sortKey === filter.sortby ? filter.sortDirection === 'desc' ? 'i-ic-baseline-arrow-drop-up'
: 'i-ic-baseline-arrow-drop-down'
: 'i-ic-baseline-arrow-drop-down']"
text=" 20px"
/>
</div>
</div>
<div
grid
:class="switcher ? 'grid-cols-1 lg:grid-cols-1' : 'xl:grid-cols-3 lg:grid-cols-3 sm:grid-cols-2 grid-cols-1'"
gap-16px
text-white
w-full
>
<Card
v-for="project in group.projects"
:key="project.id"
:project="project"
/>
</div>
</template>
<div v-if="totalProjectsCount === 0">
<h3>No Projects found...</h3>
</div>
<button
v-if="displayedProjects.length < projects.length"
mt-29px
text="14px"
leading-24px
font-700
px-12px
py-4px
border-2px
border-app-white
@click="showMoreProjects"
>
Load more projects
</button>
</div>
</template>

View file

@ -1,17 +1,17 @@
<script lang="ts" setup>
import type { Project, ProjectIndexable } from '~/types'
import type { Project } from '~/types'
const props = defineProps<{
project: Project
}>()
const availableSupport = computed(() => {
const filteredKeys = ['forum', 'discord', 'twitter', 'lens', 'farcaster', 'telegram']
if (typeof props.project.links === 'object' && (props.project.links !== null || props.project.links !== undefined))
return Object.keys(props.project.links).filter(key => filteredKeys.includes(key)).length
// const availableSupport = computed(() => {
// const filteredKeys = ['forum', 'discord', 'twitter', 'lens', 'farcaster', 'telegram']
// if (typeof props.project.links === 'object' && (props.project.links !== null || props.project.links !== undefined))
// return Object.keys(props.project.links).filter(key => filteredKeys.includes(key)).length
return 0
})
// return 0
// })
/**
* From data points
@ -21,80 +21,42 @@ const availableSupport = computed(() => {
- team: anon / public
- audit: yes / no
*/
const calculateScore = computed(() => {
const criterias: { value: keyof ProjectIndexable, key: keyof ProjectIndexable | '' }[] = [
{ value: 'product_readiness', key: '' },
{ value: 'github', key: 'links' },
{ value: 'docs', key: 'links' },
{ value: 'team', key: '' },
{ value: 'audits', key: '' },
]
let matched = 0
for (let i = 0; i < criterias.length; i++) {
let value
// value = ((criterias[i].key ?? props.project[criterias[i].value as keyof typeof props.project]) ?? null === null) ? null : (props.project as ProjectIndexable)[criterias[i].key][criterias[i].value]
const indexableProject = props.project as ProjectIndexable
if (criterias[i].key !== '')
value = (indexableProject[criterias[i].key] as any)?.[criterias[i].value]
else
value = indexableProject?.[criterias[i].value]
// console.log(props.project?.links?.github);
// console.log(Object.keys(props.indexableProject["team"]).length);
if (value === null || value === undefined)
continue
if (fulfilled(value))
matched++
}
return 100 / criterias.length * matched
})
function fulfilled(value: any): boolean {
const type = typeof value
switch (type) {
case 'string':
if (value !== '')
return true
break
case 'object':
if (Object.keys(value!).length > 0)
return true
break
default:
return false
}
return false
}
const logo = props.project?.logos?.at(0)?.url
</script>
<template>
<div
lg:flex
flex
flex-col
gap-y-16px
lg:flex-row
items-center
lg:gap-32px
>
<NuxtImg
lg:max-w-320px
lg:max-h-320px
max-w-200px
max-h-200px
shrink
border="1px app-text-grey"
:src="logo ?? '/no-image-1-1.svg'"
class="bg-app-bg-grey object-cover max-w-full h-full vertical-align[middle] block border-0 w-full h-[300px]"
class="bg-app-bg-grey object-cover h-full vertical-align[middle] block w-full h-[300px] "
/>
<div grow>
<div
grow
w-full
>
<div
flex
flex-col
items-center
lg:items-start
justify-between
gap-32px
lg:flex-row
lg:items-center
gap-y="lg:12px 24px"
text-app-text-grey
w-full
>
<div mt-24px>
<div mt-4px>
<NuxtLink
:to="project.links?.web"
target="_blank"
@ -112,139 +74,154 @@ const logo = props.project?.logos?.at(0)?.url
{{ project.name }}
</h1>
<UnoIcon
i-web-openinnew
text-16px
i-ic-twotone-open-in-new
text="22px app-white"
/>
</NuxtLink>
<h2
text="16px app-text-grey"
leading-24px
mt-8px
>
{{ project.project_type ?? '---' }}
</h2>
</div>
<div
border-2px
class="border-app-black bg-app-white text-app-black"
flex
items-center
justify-center
px-32px
py-16px
text-32px
font-700
leading-40px
cursor-pointer
lg:grid
grid-cols-10
w-full
items-center
mt-4px
>
{{ `${calculateScore}%` }}
<h2
hidden
lg:block
>
Usecases:
</h2>
<p
text-app-white
col-span-9
>
Swap, Mixer
</p>
</div>
</div>
<div
grid
grid-cols-2
gap-16px
my-32px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.links?.github"
title="Github"
bold
text-size="18px"
<div
grid
grid-cols-2
gap-y-12px
lg:flex
lg:flex-col
order-3
lg:order-2
w-full
>
<template #prefix>
<UnoIcon i-web-code />
</template>
{{ project.links?.github ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.project_status?.version"
title="Product readyness"
bold
text-size="18px"
<div
flex
flex-col
gap-4px
lg:grid
lg:grid-cols-10
lg:items-center
w-full
>
<h2>
Categories:
</h2>
<p
text-app-white
col-span-9
>
Dapp, Network
</p>
</div>
<div
flex
flex-col
gap-4px
lg:grid
lg:grid-cols-10
lg:items-center
w-full
>
<h2>
Ecosystems:
</h2>
<p
text-app-white
col-span-9
>
Ethereum, Secret Network
</p>
</div>
</div>
<div
grid
grid-cols-4
w-full
items-center
order-2
lg:order-3
>
<template #prefix>
<UnoIcon i-web-cube />
</template>
{{ project.project_status?.version }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.team?.length"
title="Team"
bold
text-size="18px"
>
<template #prefix>
<UnoIcon i-web-team />
</template>
{{ `${project.team?.length} members` }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.links?.docs"
title="Docs"
bold
text-size="18px"
>
<template #prefix>
<UnoIcon
i-web-docs
text-28px
/>
</template>
{{ project.links?.docs ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.audits"
title="Audit"
bold
text-size="18px"
>
<template #prefix>
<UnoIcon
i-web-audit
text-28px
/>
</template>
{{ project.audits ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.links"
title="Available support"
bold
text-size="18px"
>
<template #prefix>
<UnoIcon
i-web-support
text-28px
/>
</template>
{{ `${availableSupport} channels` }}
</ProjectInfoItem>
</div>
<div
grid
grid-cols-2
gap-16px
my-32px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.network"
tooltip-link="/"
title="Ecosystem"
bold
>
{{ project.blockchain_features?.network }}
</ProjectInfoItem>
<ProjectInfoItem
invisible
title="Last update"
bold
>
17/11/2023 23:22
</ProjectInfoItem>
<div
flex
items-center
justify-between
lg:justify-start
lg:gap-24px
col-span-3
>
<div
v-for="rating of project.ratings"
:key="rating.name"
flex
flex-col
lg:flex-row
items-center
>
<p
text="12px lg:16px"
leading="16px lg:24px"
>
{{ rating.name }}:
</p>
<ProjectRating
:rating="rating"
:percentage="rating.points"
compact
/>
</div>
</div>
<div
flex
items-center
justify-end
w-full
gap-16px
mt--8px
lg:mt-0
>
<h2
hidden
lg:block
>
Total Score:
</h2>
<div
flex
items-center
justify-center
border="2px app-white"
text="16px lg:24px app-white"
leading="24px md:32px"
max-h-="32px md:40px"
max-w="84px"
w-full
h-full
font-700
whitespace-nowrap
py="2px lg:8px"
lg:py-4px
>
{{ calculateScore(project) }} %
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,36 @@
<script lang="ts" setup>
import type { Project } from '~/types'
defineProps<{
project: Project
}>()
</script>
<template>
<ProjectDetailCategoryDivider
title="HISTORY"
badge-text="3/10"
>
<UnoIcon
i-material-symbols-calendar-month
text-24px
/>
</ProjectDetailCategoryDivider>
<ProjectDetailContainer>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.history?.time"
title="Date of creation"
>
{{ formatDate(project.history!.time!) }}
</ProjectInfoItem>
</div>
</ProjectDetailContainer>
</template>

View file

@ -32,13 +32,6 @@ const githubProjectUrl = computed(() => {
md:block
>{{ 'BACK TO LIST' }}</span>
</NavigationButton>
<hr
hidden
md:block
border-t-2px
border-white
w-full
>
<div
flex
gap-16px

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10"
>
<UnoIcon
i-web-code_v2
i-bi-code-square
text-24px
/>
</ProjectDetailCategoryDivider>
@ -23,25 +23,98 @@ defineProps<{
leading-24px
font-400
>
<h3 text-app-text-grey>
{{ 'Project Description' }}
<h3
text-app-text-grey
mb-8px
>
Project Description
</h3>
<span
<p
text="14px sm:16px"
leading-20px
>{{ project.description ?? '---' }}</span>
>
{{ project.description ?? '---' }}
</p>
</div>
<div
grid
grid-cols="2 lg:4"
mt="24px lg:32px"
text="14px lg:16px"
gap-y-16px
>
<div>
<h3
text-app-text-grey
leading-20px
mb="4px lg:8px"
>
Project Phase
</h3>
<div
flex
items-center
gap-8px
>
<div
h-10px
w-10px
rounded-full
bg-green
/><p leading="20px lg:24px">
Mainnet
</p>
</div>
</div>
<div>
<h3
text-app-text-grey
leading-20px
mb="4px lg:8px"
>
Assets used
</h3>
<p leading="20px lg:24px">
ETH, DAI, USDC, FRAX
</p>
</div>
<div>
<h3
text-app-text-grey
leading-20px
mb="4px lg:8px"
>
Native token
</h3>
<p leading="20px lg:24px">
SCRT
</p>
</div>
<div>
<h3
text-app-text-grey
leading-20px
mb="4px lg:8px"
>
Project launch day
</h3>
<p leading="20px lg:24px">
06/2017
</p>
</div>
</div>
<div mt-24px>
<h3
text="14px sm:16px app-text-grey"
leading-20px
>
{{ 'Infrastructure links' }}
Infrastructure links:
</h3>
<div
grid
grid-cols-2
gap-16px
gap-x-16px
gap-y-12px
mt-8px
lg:grid-cols-4
>
@ -50,18 +123,12 @@ defineProps<{
:to="project.links?.web"
>
<template #prefix>
<UnoIcon i-web-website />
<UnoIcon
i-ic-baseline-language
text="24px app-text-grey"
/>
</template>
{{ 'Website' }}
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.blog"
:to="project.links.blog"
>
<template #prefix>
<UnoIcon i-web-website />
</template>
{{ 'Blog' }}
Website
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.github"
@ -69,12 +136,25 @@ defineProps<{
>
<template #prefix>
<UnoIcon
i-web-github
opacity-30
i-mdi-github
text="24px app-text-grey"
text-27px
/>
</template>
{{ 'Github' }}
Github
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.whitepaper"
:to="project.links?.whitepaper"
>
<template #prefix>
<UnoIcon
i-material-symbols-description
text="24px app-text-grey"
text-27px
/>
</template>
Whitepaper
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.docs"
@ -82,11 +162,38 @@ defineProps<{
>
<template #prefix>
<UnoIcon
i-web-documents
text-24px
i-material-symbols-contract
text="24px app-text-grey"
text-27px
/>
</template>
{{ 'Docs' }}
Docs
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.blog"
:to="project.links.blog"
>
<template #prefix>
<UnoIcon
i-material-symbols-newsmode
text="24px app-text-grey"
text-27px
/>
</template>
Blog
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.forum"
:to="project.links.forum"
>
<template #prefix>
<UnoIcon
i-material-symbols-forum-outline
text="24px app-text-grey"
text-27px
/>
</template>
Forum
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.block_explorer"
@ -94,48 +201,25 @@ defineProps<{
>
<template #prefix>
<UnoIcon
i-web-explorer
opacity-30
i-material-symbols-explore-outline
text="24px app-text-grey"
text-27px
/>
</template>
{{ 'Explorer' }}
Explorer
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.snapshot"
:to="project.links.snapshot"
v-if="project.links?.governance"
:to="project.links.governance"
>
<template #prefix>
<UnoIcon
i-web-snapshot
text-32px
i-material-symbols-how-to-vote-outline
text="24px app-text-grey"
text-27px
/>
</template>
{{ 'Snapshot' }}
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.token"
:to="project.links.token"
>
<template #prefix>
<UnoIcon
i-web-token
text-28px
/>
</template>
{{ 'Token' }}
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.coingecko"
:to="project.links.coingecko"
>
<template #prefix>
<UnoIcon
i-web-coingecko
opacity-30
text-24px
/>
</template>
{{ 'Coingecko' }}
Governance
</ProjectOpenessLink>
</div>
</div>
@ -149,22 +233,22 @@ defineProps<{
<div
grid
grid-cols-2
gap-16px
gap-x-16px
gap-y-12px
mt-8px
lg:grid-cols-4
>
<ProjectOpenessLink
v-if="project.links?.forum"
:to="project.links.forum"
v-if="project.links?.twitter"
:to="project.links.twitter"
>
<template #prefix>
<UnoIcon
i-web-forum
opacity-30
text-28px
i-bi-twitter-x
text="24px app-text-grey"
/>
</template>
{{ 'Forum' }}
Twitter
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.discord"
@ -172,24 +256,23 @@ defineProps<{
>
<template #prefix>
<UnoIcon
i-web-discord
text-27px
i-ic-baseline-discord
text="24px app-text-grey"
/>
</template>
{{ 'Discord' }}
Discord
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.twitter"
:to="project.links.twitter"
v-if="project.links?.telegram"
:to="project.links.telegram"
>
<template #prefix>
<UnoIcon
i-web-twitter_x
opacity-30
text-22px
i-mdi-telegram
text="24px app-text-grey"
/>
</template>
{{ 'Twitter' }}
Telegram
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.lens"
@ -198,10 +281,10 @@ defineProps<{
<template #prefix>
<UnoIcon
i-web-lens
text-32px
text="24px app-text-grey"
/>
</template>
{{ 'Lens' }}
Lens
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.farcaster"
@ -210,89 +293,63 @@ defineProps<{
<template #prefix>
<UnoIcon
i-web-farcaster
text-26px
text="24px app-text-grey"
/>
</template>
{{ 'Farcaster' }}
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.telegram"
:to="project.links.telegram"
>
<template #prefix>
<UnoIcon
i-web-telegram
opacity-30
text-22px
/>
</template>
{{ 'Telegram' }}
Farcaster
</ProjectOpenessLink>
</div>
</div>
<div mt-24px>
<ProjectOpenessTeamMembers :members="project.team" />
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-26px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.product_launch_day"
title="Product launch day"
<ProjectOpenessTeamMembers
v-if="project?.team"
:members="project.team"
mt-24px
/>
<div mt-32px>
<h3
text="app-text-grey 14px lg:16px"
leading-24px
>
{{ project.product_launch_day }}
</ProjectInfoItem>
<ProjectInfoItem
title="Opensource"
bold
:check-undefined="project.blockchain_features?.opensource"
:color=" project.blockchain_features?.opensource ? '#A8FF18' : '#FF0000'"
>
{{ project.blockchain_features?.opensource ? 'Yes' : 'No' }}
</ProjectInfoItem>
</div>
<div
mt-32px
w-full
>
<ProjectInfoItem
:check-undefined="project.funding"
title="Funding"
bold
Funding
</h3>
<div
v-for="funding of project.funding"
:key="funding.name"
flex
items-center
justify-between
rounded-full
w-full
bg-app-bg-funding-card
px-24px
py-12px
mt-12px
>
<template
v-for="fund in project.funding"
:key="fund.name"
<h3
text="14px lg:16px"
leading="20px lg:24px"
font-700
>
<div
mt-16px
grid
grid-cols-2
sm:grid-cols-4
>
<span v-if="fund.name">{{ fund.name }}</span>
<span
v-if="fund.time"
font-400
>{{ fund.time }}</span>
<span
v-if="fund.time"
font-400
text-app-text-grey
>{{ fund.type }}</span>
<span
v-if="fund.time"
font-400
>{{ fund.value }}</span>
</div>
</template>
</ProjectInfoItem>
{{ funding.name }}
</h3>
<NuxtLink
:to="funding.link"
external
target="_blank"
>
<UnoIcon
i-ic-twotone-open-in-new
text="22px app-text-grey"
/>
</NuxtLink>
</div>
<h3
v-if="!project.funding?.length"
text="app-text-grey 14px lg:16px"
leading-24px
>
N/A
</h3>
</div>
</ProjectDetailContainer>
</template>

View file

@ -32,58 +32,32 @@ const props = defineProps<{
>
<div
flex
items-center
gap-12px
>
<template v-if="member.link">
<!-- <template v-if="member.link">
<NuxtImg
:src="member.link"
width="48"
height="48"
:alt="member.name"
/>
</template>
<template v-else>
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
opacity="0.2"
cx="24"
cy="24"
r="23.5"
stroke="white"
/>
<g opacity="0.2">
<mask
id="mask0_2200_7716"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="12"
y="12"
width="24"
height="24"
>
<rect
x="12"
y="12"
width="24"
height="24"
fill="#D9D9D9"
/>
</mask>
<g mask="url(#mask0_2200_7716)">
<path
d="M24 24C22.9 24 21.9583 23.6083 21.175 22.825C20.3917 22.0417 20 21.1 20 20C20 18.9 20.3917 17.9583 21.175 17.175C21.9583 16.3917 22.9 16 24 16C25.1 16 26.0417 16.3917 26.825 17.175C27.6083 17.9583 28 18.9 28 20C28 21.1 27.6083 22.0417 26.825 22.825C26.0417 23.6083 25.1 24 24 24ZM16 32V29.2C16 28.6333 16.1458 28.1125 16.4375 27.6375C16.7292 27.1625 17.1167 26.8 17.6 26.55C18.6333 26.0333 19.6833 25.6458 20.75 25.3875C21.8167 25.1292 22.9 25 24 25C25.1 25 26.1833 25.1292 27.25 25.3875C28.3167 25.6458 29.3667 26.0333 30.4 26.55C30.8833 26.8 31.2708 27.1625 31.5625 27.6375C31.8542 28.1125 32 28.6333 32 29.2V32H16Z"
fill="white"
/>
</g>
</g>
</svg>
</template>
</template> -->
<div
flex
items-center
justify-center
text-black
w="40px lg:48px"
h="40px lg:48px"
rounded-full
bg-app-bg-team-grey
>
<div
i-heroicons-solid-user
text-24px
/>
</div>
<div
flex
flex-col
@ -92,10 +66,6 @@ const props = defineProps<{
text="14px sm:16px"
font-700
>{{ member.name }}</span>
<span
text="14px sm:16px app-text-grey"
font-400
>{{ member.role ?? 'N/A' }}</span>
</div>
</div>
</template>

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10"
>
<UnoIcon
i-web-code_v2
i-heroicons-solid-eye
text-24px
/>
</ProjectDetailCategoryDivider>
@ -26,11 +26,11 @@ defineProps<{
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.p2p"
bold
title="Peer to Peer (P2P)"
:check-undefined="project.default_privacy"
title="Default privacy"
:color="project.default_privacy ? '#18FF2F' : '#FF0000'"
>
{{ project.blockchain_features?.p2p ? 'YES' : 'NO' }}
{{ project.default_privacy ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.tracebility?.kyc"
@ -40,109 +40,6 @@ defineProps<{
>
{{ project.tracebility?.kyc ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.storage"
bold
title="Decentralized storage"
:color="project.storage?.decentralized ? '#18FF2F' : '#FF0000'"
>
{{ project.storage?.decentralized ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.default_privacy"
title="Default privacy"
:color="project.default_privacy ? '#18FF2F' : '#FF0000'"
>
{{ project.default_privacy ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.revealed_recipient"
:color="project.blockchain_features?.revealed_recipient ? '#FF0000' : '#18FF2F'"
bold
title="Revealed recipient"
>
{{ project.blockchain_features?.revealed_recipient ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.revealed_sender"
:color="project.blockchain_features?.revealed_sender ? '#FF0000' : '#18FF2F'"
bold
title="Revealed sender"
>
{{ project.blockchain_features?.revealed_sender ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.revealed_ammount"
:color="project.blockchain_features?.revealed_ammount ? '#FF0000' : '#18FF2F'"
bold
title="Revealed amount"
>
{{ project.blockchain_features?.revealed_ammount ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.reversability_condition"
bold
title="Reversability"
>
{{ project.blockchain_features?.reversability_condition }}
</ProjectInfoItem>
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.connected_tx"
:color="project.blockchain_features?.connected_tx ? '#FF0000' : '#18FF2F'"
bold
title="Connected Txs"
>
{{ project.blockchain_features?.connected_tx ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.data_masking"
bold
title="Data masking"
>
{{ project.blockchain_features?.data_masking }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.tx_history"
bold
title="Tx history"
>
{{ project.blockchain_features?.tx_history ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
<div my-24px>
<hr
border-t-2px
border-white
opacity-20
w-80px
>
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.privacy_policy"
:color="project.privacy_policy?.defined ? '#18FF2F' : '#FF0000'"
@ -150,6 +47,31 @@ defineProps<{
title="Privacy Policy"
>
{{ project.privacy_policy?.defined ? 'YES' : 'NO' }}
<NuxtLink
:to="project.privacy_policy?.link"
external
target="_blank"
>
<UnoIcon
i-ic-twotone-open-in-new
text="22px app-text-grey"
/>
</NuxtLink>
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.compliance"
color="#FFB800"
bold
title="Compliance with"
>
{{ project.compliance ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.tracebility?.sign_in_type_requirments"
bold
title="Sign-in requirements"
>
{{ project.tracebility?.sign_in_type_requirments }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.tracebility?.tracked_data"
@ -163,58 +85,6 @@ defineProps<{
>
{{ project.privacy_policy?.data_usage }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.frontend_anonymity"
bold
title="Frontend anonymity"
>
{{ project.blockchain_features?.frontend_anonymity }}
</ProjectInfoItem>
</div>
<div my-24px>
<hr
border-t-2px
border-white
opacity-20
w-80px
>
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
>
<ProjectInfoItem
:check-undefined="project.compliance"
:color="project.compliance ? '#FF0000' : '#18FF2F'"
bold
title="Compliance"
>
{{ project.compliance ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-16px
>
<ProjectInfoItem
:check-undefined="project.tracebility?.sign_in_type_requirments"
bold
title="Sign-in requirements"
>
{{ project.tracebility?.sign_in_type_requirments }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.identity_integration"
title="Identity integrations"
>
{{ project.blockchain_features?.identity_integration }}
</ProjectInfoItem>
</div>
</ProjectDetailContainer>
</template>

View file

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { ProjectRating } from '~/types'
// Define props for score
const props = defineProps<{
rating: ProjectRating
percentage: number
compact?: boolean
}>()
const colors = [
'#ff0000', // 0-10%
'#ff4500', // 11-20%
'#ff8c00', // 21-30%
'#ffd700', // 31-40%
'#adff2f', // 41-50%
'#7fff00', // 51-60%
'#00ff00', // 61-70%
'#32cd32', // 71-80%
'#00fa9a', // 81-90%
'#00ffff', // 91-100%
]
const backgroundColorByScore = computed(() => {
const normalizedPercentage = Math.min(Math.max(props.percentage, 0), 100)
const colorIndex = Math.floor(normalizedPercentage / 10)
return colors[colorIndex]
})
const isPopoverVisible = ref(false)
let hideTimeout: ReturnType<typeof setTimeout> | null = null
const showPopover = () => {
if (hideTimeout) {
clearTimeout(hideTimeout)
hideTimeout = null
}
isPopoverVisible.value = true
}
const hidePopover = () => {
hideTimeout = setTimeout(() => {
isPopoverVisible.value = false
}, 100) // Delay of 200ms before hiding
}
</script>
<template>
<div class="relative">
<!-- Main div that shows rating and triggers the popover on hover -->
<div
flex
items-center
p-12px
gap-4px
hover:bg-app-bg-rating-hover
hover:rounded-8px
@mouseenter="showPopover"
@mouseleave="hidePopover"
>
<div
v-for="point of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
:key="point"
:style="`background-color: ${percentage >= point * 10 ? backgroundColorByScore : '#494949'}`"
:class="[compact ? 'h-8px lg:h-10px w-4px lg:w-5px' : 'w-5px lg:w-6px h-10px lg:h-12px', point % 2 === 0 ? 'rounded-l-2px' : 'rounded-r-2px ml--4px', 'bg-app-bg-rating-default']"
/>
</div>
<!-- Popover panel that appears on hover -->
<transition
enter-active-class="transition duration-300 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-200 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<div
v-if="isPopoverVisible"
class="absolute mt-2 p-2 bg-app-bg-rating-hover w-240px shadow-lg rounded"
z-100
style="left: 50%; transform: translateX(-50%);"
flex
flex-col
gap-14px
px-16px
py-10px
@mouseenter="showPopover"
@mouseleave="hidePopover"
>
<div
v-for="item in rating.items"
:key="item.label"
flex
justify-between
items-center
text-12px
font-700
leading-20px
:class="[item.isValid ? 'text-app-white': 'text-app-text-rating-negative']"
>
<div
flex
items-center
gap-6px
>
<div
:class="[item.isValid ? 'i-ic-sharp-thumb-up' : 'i-ic-sharp-thumb-down']"
text-20px
mt--4px
/>
{{ item.label }}
</div>
<NuxtLink
v-if="item.isValid && item.positive === 'Link'"
:to="item.value"
target="_blank"
external
underline
@click.stop
>
{{ item.positive }}
</NuxtLink>
<div v-else>
{{ item.isValid ? item.positive : item.negative }}
</div>
</div>
</div>
</transition>
</div>
</template>

View file

@ -12,11 +12,36 @@ defineProps<{
badge-text="3/10"
>
<UnoIcon
i-web-code_v2
i-material-symbols-lock-outline
text-24px
/>
</ProjectDetailCategoryDivider>
<ProjectDetailContainer>
<div v-if="project.audits">
<h2
text-18px
text-app-text-grey
my-24px
>
Audits
</h2>
<div
flex
flex-col
gap="12px lg:16px"
>
<template
v-for="audit in project.audits"
:key="audit.name"
>
<ProjectSecurityAudit
:audit-name="audit.name"
:audit-url="audit.link"
:date="audit.time"
/>
</template>
</div>
</div>
<div
grid
grid-cols-2
@ -26,20 +51,11 @@ defineProps<{
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.asset_custody_type"
bold
title="Asset custody"
:check-undefined="project.technical_spof"
:color="project.technical_spof ? '#FF0000' : '#18FF2F'"
title="Technical dependency"
>
{{ project.blockchain_features?.asset_custody_type }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.upgradability"
title="Upgradability"
>
<b :color="project.blockchain_features?.upgradability?.enabled ? '#FF0000' : '#18FF2F'">
{{ project.blockchain_features?.upgradability?.enabled ? 'YES' : 'NO' }}
</b>
{{ ` ${project.blockchain_features?.upgradability?.type}` }}
{{ project.technical_spof }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.social_trust"
@ -55,47 +71,6 @@ defineProps<{
>
{{ project.third_party_dependency ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.technical_spof"
:color="project.technical_spof ? '#FF0000' : '#18FF2F'"
title="Technical dependency"
>
{{ project.technical_spof }}
</ProjectInfoItem>
</div>
<div my-24px>
<hr
border-t-2px
border-white
opacity-20
w-80px
>
</div>
<div v-if="project.audits">
<h2
text-18px
text-app-text-grey
my-24px
>
Audits
</h2>
<template
v-for="audit in project.audits"
:key="audit.name"
>
<ProjectSecurityAudit
:audit-name="audit.name"
:audit-url="audit.link"
:date="audit.time"
>
<NuxtImg
:src="audit.logo ?? '/no-image-1-1.svg'"
w-64px
h-64px
object-cover
/>
</ProjectSecurityAudit>
</template>
</div>
</ProjectDetailContainer>
</template>

View file

@ -9,36 +9,69 @@ defineProps<{
<template>
<div
flex
items-center
gap-16px
justify-between
lg:grid
lg:grid-cols-2
rounded-full
py-12px
px-16px
bg-app-bg-audits-card
>
<slot />
<div
flex
flex-col
text-14px
font-700
leading-24px
grid
grid-cols-3
w-full
>
<NuxtLink
hover:underline
flex
items-center
gap-8px
:to="auditUrl"
<h2
text="14px lg:16px"
leading-20px
font-700
col-span="3 lg:2"
>
{{ auditName }}
<UnoIcon
v-if="auditUrl"
i-web-openinnew
text-16px
/>
</NuxtLink>
<span
</h2>
<h2
text-14px
font-400
leading-24px
>{{ date }}</span>
leading-20px
hidden
lg:block
>
Overview of smart contracts
</h2>
</div>
<div
flex
items-center
justify-end
gap="16px lg:80px"
whitespace-nowrap
>
<h2
text-14px
leading-20px
>
{{ date ? formatDate(date) : 'N/A' }}
</h2>
<NuxtLink
flex
items-center
gap-4px
:to="auditUrl"
external
target="_blank"
text-14px
text-app-text-grey
leading-20px
hover:underline
><p
hidden
lg:block
>
More info
</p> <UnoIcon
i-ic-twotone-open-in-new
text-22px
/></NuxtLink>
</div>
</div>
</template>

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10"
>
<UnoIcon
i-web-code_v2
i-material-symbols-deployed-code-outline
text-24px
/>
</ProjectDetailCategoryDivider>
@ -21,100 +21,54 @@ defineProps<{
grid
grid-cols-2
items-start
mt-32px
mt-24px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.opensource"
title="Opensource"
bold
:color="project.blockchain_features?.opensource ? '#18FF2F' : '#FF0000'"
>
{{ project.blockchain_features?.opensource ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.asset_custody_type"
title="Asset custody"
:color="project.blockchain_features?.asset_custody_type === 'non-custody' ? '#18FF2F' : project.blockchain_features?.asset_custody_type === 'multisig' ? '#FFB800' : '#FF0000'"
bold
>
{{ project.blockchain_features?.asset_custody_type.toUpperCase() }}
</ProjectInfoItem>
<ProjectInfoItem
title="Upgradability"
bold
:check-undefined="project.blockchain_features?.upgradability"
:color="project.blockchain_features?.upgradability ? '#FF0000' : '#18FF2F' "
>
{{ project.blockchain_features?.upgradability ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.technology?.type"
title="Technology type"
bold
>
{{ project.technology?.type }}
{{ project.technology?.type.toUpperCase() }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.blockchain_features?.encryption"
title="Encryption"
:check-undefined="project.blockchain_features?.p2p"
title="Peer to Peer (P2P)"
bold
>
{{ project.blockchain_features?.encryption }}
{{ project.blockchain_features?.p2p ? 'YES' : 'NO' }}
</ProjectInfoItem>
<ProjectInfoItem
title="License"
bold
:check-undefined="project.licences"
>
{{ project.licences }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.links?.whitepaper"
title="Whitepaper"
bold
:link="project.links?.whitepaper"
>
{{ project.links?.whitepaper ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
<div
grid
grid-cols-1
items-start
mt-16px
gap-y-16px
lg:grid-cols-4
>
<ProjectInfoItem
:check-undefined="project.project_status"
title="Version"
:check-undefined="project.storage?.decentralized"
title="Decentralized storage"
bold
>
<div
v-if="project.project_status?.live_status"
flex
items-center
gap-12px
>
<UnoIcon
i-web-live
text-10px
class="color-#B5E26B"
/>
<span>{{ `Live on ${project.project_status.version}` }}</span>
</div>
<div
v-else
flex
items-center
gap-12px
>
<UnoIcon
i-web-live
text-10px
class="color-#e26b6b"
/>
<span>{{ 'Offline' }}</span>
</div>
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.technology?.features"
title="Features"
bold
>
{{ project.technology?.features.join(', ') }}
</ProjectInfoItem>
<ProjectInfoItem
:check-undefined="project.client_diversability"
title="Client diversability"
bold
>
<template
v-for="item in project.client_diversability"
:key="item.name"
>
<NuxtLink :to="item.link">
{{ item.name }}
</NuxtLink><br>
</template>
{{ project.storage?.decentralized ? 'YES' : 'NO' }}
</ProjectInfoItem>
</div>
</ProjectDetailContainer>

View file

@ -1,6 +1,8 @@
<script setup lang="ts">
import type { InputOption } from '~/types'
defineProps<{ placeholder?: string, includeSort?: boolean }>()
const { filter, switcher } = storeToRefs(useData())
const options: InputOption[] = [
{ label: 'A to Z', value: 'atoz' },
@ -22,8 +24,8 @@ const isSearchFocused = ref(false)
border-2px
flex
items-center
max-w-320px
w-full
h-40px
hover:opacity-100
:class="isSearchFocused ? 'opacity-100' : 'opacity-25'"
>
@ -34,7 +36,7 @@ const isSearchFocused = ref(false)
w-fit
>
<UnoIcon
i-web-search
i-heroicons-solid-magnifying-glass
text-16px
:class="isSearchFocused ? 'opacity-100' : 'opacity-50' "
class="uno-icon"
@ -42,6 +44,7 @@ const isSearchFocused = ref(false)
</div>
<input
v-model="filter.query"
:placeholder="placeholder"
type="text"
bg-transparent
border-transparent
@ -54,7 +57,7 @@ const isSearchFocused = ref(false)
@blur="isSearchFocused = false"
>
</div>
<div>
<div v-if="includeSort">
<div
flex
gap-24px

View file

@ -33,7 +33,7 @@ const selectedValue = useVModel(props, 'modelValue', emits)
<span class="block truncate mr-8px">{{ props.options.find(option => option.value === selectedValue)?.label }}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
i-ic-baseline-arrow-drop-down
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
/>
</span>

View file

@ -1,23 +1,45 @@
import type { Category, Project, ProjectShallow } from '~/types'
import type { Category, Project, ProjectRating, ProjectRatingItem, ProjectShallow } from '~/types'
import type { Asset } from '~/types/asset'
import type { Ecosystem } from '~/types/ecosystem'
import type { Feature } from '~/types/feature'
import type { Rank } from '~/types/rank'
import type { Usecase } from '~/types/usecase'
export const useData = defineStore('data', () => {
const categories = useState<Category[]>('categories')
const usecases = useState<Usecase[]>('usecases')
const features = useState<Feature[]>('features')
const assets = useState<Asset[]>('assets')
const ecosystems = useState<Ecosystem[]>('ecosystems')
const projects = useState<Project[]>('projects')
const selectedCategoryId = useState(() => 'defi')
const ranks = useState<Rank[]>('ranks')
const selectedCategoryId = useState(() => 'all')
const selectedUsecaseId = useState(() => 'all')
const selectedEcosystemId = useState(() => 'all')
const selectedAssetsUsedId = useState(() => 'all')
const selectedFeaturesId = useState(() => 'all')
const filter = reactive({
query: '',
sortby: 'atoz',
sortby: 'score',
sortDirection: 'desc',
})
const switcher = ref(true)
watch(selectedCategoryId, () => {
if (selectedCategoryId.value !== 'all')
watch([selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId], () => {
if (selectedCategoryId.value !== 'all' || selectedUsecaseId.value !== 'all' || selectedEcosystemId.value !== 'all' || selectedAssetsUsedId.value !== 'all' || selectedFeaturesId.value !== 'all')
filter.query = ''
})
watch(filter, () => {
if (filter.query !== '')
if (filter.query !== '') {
selectedCategoryId.value = 'all'
selectedUsecaseId.value = 'all'
selectedEcosystemId.value = 'all'
selectedAssetsUsedId.value = 'all'
selectedFeaturesId.value = 'all'
}
})
const fetchData = async () => {
@ -25,20 +47,32 @@ export const useData = defineStore('data', () => {
const data = await $fetch<{
categories: Category[]
projects: Project[]
usecases: Usecase[]
ecosystems: Ecosystem[]
assets: Asset[]
features: Feature[]
ranks: Rank[]
}>('/api/data')
projects.value = data.projects.filter(p => p.name)
projects.value = data.projects.map(project => ({
...project,
ratings: generateProjectRating(project),
})).filter(p => p.name)
categories.value = data.categories.map((c) => {
c.projectsCount = projects.value.filter(p =>
p.categories?.includes(c.id),
).length
return c
}).filter(c => c.projectsCount > 0)
usecases.value = data.usecases
ecosystems.value = data.ecosystems
assets.value = data.assets
features.value = data.features
ranks.value = data.ranks
}
catch (e) {
console.error(e)
return false
}
return true
}
@ -57,7 +91,7 @@ export const useData = defineStore('data', () => {
id: project.id,
title1: project.name,
description: project.description ?? 'N/A',
percentage: Math.floor(Math.random() * 91),
percentage: Math.round((project.ratings?.reduce((a, b) => a + b.points, 0) || 0) / 1.5),
forum: project.links?.forum,
explorer: project.links?.block_explorer,
twitter: project.links?.twitter,
@ -72,15 +106,33 @@ export const useData = defineStore('data', () => {
support: availableSupport(),
image: project.logos?.[0]?.url ?? '',
anonymity: true,
categories: project.categories,
usecases: project.usecases,
ecosystem: project.ecosystem,
assets_used: project.assets_used,
ratings: project.ratings,
}
}
const shallowProjects = computed(() => projects.value.map(project => projectToShallow(project)))
const getProjectsByCategory = <T extends ProjectShallow>(id: string, options?: { shallow: boolean }): T[] => {
if (id === 'all')
return projects.value.map(project => projectToShallow(project)) as T[]
else
return projects.value.filter(project => project.categories?.includes(id)).map(project => options?.shallow ? projectToShallow(project) : project) as T[]
const getProjectsByFilters = <T extends ProjectShallow>(options?: { shallow: boolean }): T[] => {
const filteredProjects = projects.value
.filter(project =>
selectedCategoryId.value !== 'all' ? project.categories.includes(selectedCategoryId.value) : true,
)
.filter(project =>
selectedUsecaseId.value !== 'all' ? project.usecases?.map(u => u.toLowerCase()).includes(selectedUsecaseId.value.toLowerCase()) : true,
)
.filter(project =>
selectedEcosystemId.value !== 'all' ? project.ecosystem?.map(e => e.toLowerCase()).includes(selectedEcosystemId.value.toLowerCase()) : true,
)
.filter(project =>
selectedAssetsUsedId.value !== 'all' ? project.assets_used?.map(a => a.toLowerCase()).includes(selectedAssetsUsedId.value.toLowerCase()) : true,
)
.filter(project =>
selectedFeaturesId.value !== 'all' ? project.technology?.features?.map(f => f.toLowerCase()).includes(selectedFeaturesId.value.toLowerCase()) : true,
)
return (filteredProjects.map(project => options?.shallow ? projectToShallow(project) : project) as T[])
}
const getProjectById = <T extends Project | ProjectShallow>(id: string, options?: { shallow: boolean }): T => {
@ -94,7 +146,7 @@ export const useData = defineStore('data', () => {
const query = filter.query.toLowerCase()
const filteredShallowProjects = getProjectsByCategory(selectedCategoryId.value, { shallow: true })
const filteredShallowProjects = getProjectsByFilters({ shallow: true })
.filter((project) => {
return (
project
@ -108,29 +160,123 @@ export const useData = defineStore('data', () => {
return true
}).sort((a, b) => {
if (filter.sortby === 'score')
return b.percentage - a.percentage
if (filter.sortby === 'atoz')
return a.title1.localeCompare(b.title1)
if (filter.sortDirection === 'asc')
return a.percentage - b.percentage
else
return b.percentage - a.percentage
if (filter.sortby === 'title')
if (filter.sortDirection === 'asc')
return a.title1.toLowerCase().localeCompare(b.title1.toLowerCase())
else
return b.title1.toLowerCase().localeCompare(a.title1.toLowerCase())
if (filter.sortby === 'openess' || filter.sortby === 'technology' || filter.sortby === 'privacy') {
const scoreA = a.ratings?.find(r => r.type === filter.sortby)?.points || 0
const scoreB = b.ratings?.find(r => r.type === filter.sortby)?.points || 0
if (filter.sortDirection === 'asc')
return scoreB - scoreA
else
return scoreA - scoreB
}
else
return 0
})
return filteredShallowProjects
})
const groupedProjectsPerCategory = computed(() => {
const groupedProjects = categories.value.map((category) => {
// Find all projects that include this category
const projectsInCategory = filteredProjects.value.filter(project =>
project.categories.includes(category.id),
)
return {
title: category.name,
projects: projectsInCategory,
}
}).sort((a, b) => b.projects.length - a.projects.length)
return groupedProjects
})
const filteredProjectsCount = computed(() => filteredProjects.value.length)
const getNestedField = (project: Project, field: string) => {
const fields = field.split('.')
return fields.reduce((acc: any, curr: string) => {
return acc && acc[curr as keyof typeof acc]
}, project)
}
const generateProjectRating = (project: Project) => {
const projectRatings: ProjectRating[] = ranks.value?.map((rank) => {
let rankPoints = 0
const ratingStats: ProjectRatingItem[] = rank.references?.map((ref) => {
let isValid = false
const field = ref.field.includes('.') ? getNestedField(project, ref.field) : project[ref.field]
let value
let positive
if (ref.condition.minLength) {
value = (field as any[])?.length
if (value) {
isValid = value >= ref.condition.minLength
positive = `${value} ${ref.label.positive}${value > 1 ? 's' : ''}`
}
}
if (ref.condition.equals) {
value = field
if (value)
isValid = value === ref.condition.equals
}
if (ref.condition.exists) {
value = field
if (value)
isValid = !!value
}
rankPoints += isValid ? ref.points : 0
return {
isValid,
label: ref.label.name,
positive: positive ? positive : ref.label.positive,
negative: ref.label.negative,
value,
} as ProjectRatingItem
})
return {
type: rank.id,
name: rank.name,
items: ratingStats,
points: rankPoints,
}
})
return projectRatings
}
return {
selectedCategoryId,
selectedUsecaseId,
selectedEcosystemId,
selectedAssetsUsedId,
selectedFeaturesId,
filter,
switcher,
categories,
usecases,
features,
ecosystems,
assets,
projects,
shallowProjects,
groupedProjectsPerCategory,
filteredProjectsCount,
fetchData,
getProjectById,
getProjectsByCategory,
filteredProjects,
projectToShallow,
}

View file

@ -1,21 +1,22 @@
<script lang="ts" setup>
import type { InputOption } from '~/types'
const { categories, filteredProjectsCount, selectedCategoryId } = storeToRefs(useData())
const categoriesOptions = ref(categories.value ? categories.value.map(c => ({ label: c.name, value: c.id, count: c.projectsCount })) : [])
const extendedOptions: InputOption[] = [
...categoriesOptions.value,
]
const { categories, usecases, ecosystems, assets, features, filteredProjectsCount, selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId } = storeToRefs(useData())
const selectedCategory = computed(() => {
return categories.value.find(c => c.id === selectedCategoryId.value)
})
const availableUsecases = computed(() => {
if (selectedCategoryId.value === 'all')
return usecases.value
return usecases.value.filter(u => selectedCategory.value?.usecases?.includes(u.id))
})
const sortedFilteredCategories = computed(() => ([
categories.value.find(c => c.id === 'defi')!,
...[...categories.value].sort((a, b) => a.name.localeCompare(b.name)).filter(c => c.id !== 'defi'),
]))
const categoryOptions = ref<InputOption[]>(categories.value ? [{ label: 'Category', value: 'all' }, ...categories.value.map(c => ({ label: c.name, value: c.id, count: c.projectsCount }))] : [])
const usecaseOptions = computed<InputOption[]>(() => availableUsecases.value.length ? [{ label: 'Usecase', value: 'all' }, ...availableUsecases.value.map(u => ({ label: u.name, value: u.id }))] : [])
const ecosystemOptions = ref<InputOption[]>(ecosystems.value ? [{ label: 'Ecosystem', value: 'all' }, ...ecosystems.value.map(e => ({ label: e.name, value: e.id }))] : [])
const assetOptions = ref<InputOption[]>(assets.value ? [{ label: 'Asset used', value: 'all' }, ...assets.value.map(a => ({ label: `${a.id.toUpperCase()} (${a.name})`, value: a.id }))] : [])
const featureOptions = ref<InputOption[]>(features.value ? [{ label: 'Feature', value: 'all' }, ...features.value.map(f => ({ label: f.name, value: f.id }))] : [])
const { showBar } = storeToRefs(useNavigaiton())
const swipeEl = ref()
@ -56,77 +57,67 @@ watch([scrollY, top, y], (newValues, oldValues) => {
w-full
xl:gap-32px
>
<div w-fit>
<div
ref="scrollEl"
class="no-scrollbar"
h-100vh
overflow-y-auto
sticky
top-32px
hidden
xl:block
min-w-234px
pb-48px
>
<Category
v-for="category in sortedFilteredCategories"
:key="category.id"
:title="category.name"
:count="category.projectsCount"
:selected="selectedCategoryId === category.id"
@click="[navigateTo(`/category/${category.id}`), selectedCategoryId = category.id]"
/>
</div>
</div>
<div w-full>
<div
flex
flex-col
w-full
>
<div
flex
flex-col
md:flex-row
md:justify-between
md:items-center
gap-16px
w-full
mb="16px md:32px"
>
<SearchBox
flex-1
placeholder:text-app-text-grey
:placeholder="`Search in ${filteredProjectsCount} Projects`"
/>
<div
xl:hidden
block
md:flex-2
flex
items-center
gap-16px
>
<h2
text-14px
font-700
>
Choose category
</h2>
<CategorySelectBox
v-model="selectedCategoryId"
:options="extendedOptions"
:options="categoryOptions"
name="categorySelect"
w-full
@selected="selectedCategoryId === 'all' ? navigateTo(`/`) : navigateTo(`/category/${selectedCategoryId}`)"
/>
<CategorySelectBox
v-if="usecases?.length"
v-model="selectedUsecaseId"
name="usecaseSelect"
:options="usecaseOptions"
w-full
/>
<CategorySelectBox
v-if="ecosystems?.length"
v-model="selectedEcosystemId"
name="ecosystemSelect"
:options="ecosystemOptions"
w-full
/>
<CategorySelectBox
v-if="assets?.length"
v-model="selectedAssetsUsedId"
name="assetsUsedSelect"
:options="assetOptions"
w-full
/>
<CategorySelectBox
v-if="features?.length"
v-model="selectedFeaturesId"
name="featuresSelect"
:options="featureOptions"
w-full
/>
</div>
<SearchBox />
</div>
<div
flex
gap-28px
items-center
my-24px
mt-28px
>
<h2
v-if="selectedCategoryId"
w-max
font-700
text-18px
sm:text-28px
whitespace-nowrap
>
{{ selectedCategoryId === 'all' ? `${filteredProjectsCount} All Projects` : `${filteredProjectsCount ?? 0} ${selectedCategory?.name}` }}
</h2>
<div
h-2px
w="full"
bg-white
/>
</div>
<slot />
</div>

View file

@ -1,7 +1,7 @@
{
"name": "develitesse-nuxt",
"private": true,
"packageManager": "pnpm@9.9.0",
"packageManager": "pnpm@9.10.0",
"scripts": {
"build": "nuxt build",
"cleanup": "nuxt cleanup",
@ -17,8 +17,14 @@
},
"devDependencies": {
"@formkit/auto-animate": "^0.8.2",
"@iconify-json/bi": "^1.2.0",
"@iconify-json/heroicons-outline": "^1.2.0",
"@iconify-json/heroicons-solid": "^1.2.0",
"@iconify-json/ic": "^1.2.0",
"@iconify-json/iconoir": "^1.2.0",
"@iconify-json/material-symbols": "^1.2.1",
"@iconify-json/mdi": "^1.2.0",
"@iconify-json/simple-icons": "^1.2.2",
"@nuxt/devtools": "^1.4.1",
"@nuxt/eslint": "0.5.5",
"@nuxt/image": "^1.8.0",
@ -46,5 +52,8 @@
},
"lint-staged": {
"*": "eslint --fix"
},
"dependencies": {
"moment": "^2.30.1"
}
}

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
const dataStore = useData()
const { selectedCategoryId, filteredProjects } = storeToRefs(dataStore)
const { selectedCategoryId, categories, filteredProjects } = storeToRefs(dataStore)
const route = useRoute()
@ -8,8 +8,10 @@ onMounted(() => {
if (route.params.id)
selectedCategoryId.value = route.params.id as string
})
const group = computed(() => [{ title: categories.value.find(c => c.id === selectedCategoryId.value)?.name || '', projects: filteredProjects.value }])
</script>
<template>
<ProjectGrid :projects="filteredProjects" />
<ProjectGrid :projects="group" />
</template>

View file

@ -7,9 +7,11 @@ useSeoMeta({
ogDescription: 'There are challenges in finding crucial technical details and comparing various privacy-focused projects.',
ogImage: '/web3privacy_eye.webp',
})
const { filteredProjects } = storeToRefs(useData())
const { groupedProjectsPerCategory } = storeToRefs(useData())
</script>
<template>
<ProjectGrid :projects="filteredProjects" />
<div>
<ProjectGrid :projects="groupedProjectsPerCategory" />
</div>
</template>

View file

@ -32,7 +32,10 @@ useSeoMeta({
<template>
<div v-if="project">
<div app-container>
<div
app-container
px-16px
>
<div
flex
flex-col
@ -46,8 +49,7 @@ useSeoMeta({
<ProjectTechnology :project="project" />
<ProjectPrivacy :project="project" />
<ProjectSecurity :project="project" />
<!-- <ProjectActivity :project="project" />
<ProjectMarket /> -->
<ProjectHistory :project="project" />
</div>
</div>
</div>

View file

@ -7,16 +7,38 @@ settings:
importers:
.:
dependencies:
moment:
specifier: ^2.30.1
version: 2.30.1
devDependencies:
'@formkit/auto-animate':
specifier: ^0.8.2
version: 0.8.2
'@iconify-json/bi':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/heroicons-outline':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/heroicons-solid':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/ic':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/iconoir':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/material-symbols':
specifier: ^1.2.1
version: 1.2.1
'@iconify-json/mdi':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/simple-icons':
specifier: ^1.2.2
version: 1.2.2
'@nuxt/devtools':
specifier: ^1.4.1
version: 1.4.1(rollup@4.21.2)(vite@5.4.2(@types/node@20.8.7)(terser@5.22.0))
@ -1088,12 +1110,30 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'}
'@iconify-json/bi@1.2.0':
resolution: {integrity: sha512-kaBV87cQlyeMkBBiMqsf3b43Nsxdk/rYKvR29dnktht57WUyHCnBAuH+ca/bscX856CzRpVX+sYs7arjrJD0qA==}
'@iconify-json/heroicons-outline@1.2.0':
resolution: {integrity: sha512-Qy1sRmQYqih6xRxwCtnX0hXJ4252t83C0CnNWAP3gF0fH0Qmp9RY66LMB0moYGxQxUhsTFIl2nNceSVSBUo8Tg==}
'@iconify-json/heroicons-solid@1.2.0':
resolution: {integrity: sha512-o+PjtMXPr4wk0veDS7Eh6H1BnTJT1vD7HcKl+I7ixdYQC8i1P2zdtk0C2v7C9OjJBMsiwJSCxT4qQ3OzONgyjw==}
'@iconify-json/ic@1.2.0':
resolution: {integrity: sha512-L4+m77xTQB08X3I+3xs1+IrvK+aNcuN/7ODu5aUPznHKLU+/8UYcsjUgNHze6vPOGPQ0AG+kCwvy91EYPXSRxw==}
'@iconify-json/iconoir@1.2.0':
resolution: {integrity: sha512-GbYAERFy9c1laIo1QZon9sBjRkX9rksirehmLIvvohn+W++fpPlvQnzVl0VpY/v2GCvs1pZ42spTKDj0AcofvA==}
'@iconify-json/material-symbols@1.2.1':
resolution: {integrity: sha512-r9yaBzlUmN87aCTSoCNtDCd7R9F0iVDjNPL9QHHhm1WglFJvTUKx9iBC5xcZpP0qN0bg9R5FkM90CndWxEBAnw==}
'@iconify-json/mdi@1.2.0':
resolution: {integrity: sha512-E9/3l5Syg3wfuarorFodhn4s8YorxhH3U3U20LaNBNiqw1kFNIDWhF6HymuzAD35k7RH0OBasJ+ZUyFtVVV6eg==}
'@iconify-json/simple-icons@1.2.2':
resolution: {integrity: sha512-VMgCoMnpvcCJ5b3rTOGPzW5j6959nIdRCk+8FGzK/vAaDd6f9sx65OcKOqP3C75llpybH/iQhk5yrJ/TOdQKeg==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@ -1671,9 +1711,6 @@ packages:
'@types/eslint-scope@3.7.6':
resolution: {integrity: sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==}
'@types/eslint@8.44.6':
resolution: {integrity: sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==}
'@types/eslint@9.6.1':
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
@ -2821,9 +2858,6 @@ packages:
errx@0.1.0:
resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==}
es-module-lexer@1.3.1:
resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==}
es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
@ -3827,6 +3861,9 @@ packages:
mlly@1.7.1:
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@ -6425,6 +6462,10 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {}
'@iconify-json/bi@1.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/heroicons-outline@1.2.0':
dependencies:
'@iconify/types': 2.0.0
@ -6433,6 +6474,26 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/ic@1.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/iconoir@1.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/material-symbols@1.2.1':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/mdi@1.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/simple-icons@1.2.2':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
'@iconify/utils@2.1.32':
@ -7223,13 +7284,8 @@ snapshots:
'@types/eslint-scope@3.7.6':
dependencies:
'@types/eslint': 8.44.6
'@types/estree': 1.0.3
'@types/eslint@8.44.6':
dependencies:
'@types/estree': 1.0.3
'@types/json-schema': 7.0.14
'@types/eslint': 9.6.1
'@types/estree': 1.0.5
'@types/eslint@9.6.1':
dependencies:
@ -7947,9 +8003,9 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
acorn-import-assertions@1.9.0(acorn@8.11.2):
acorn-import-assertions@1.9.0(acorn@8.12.1):
dependencies:
acorn: 8.11.2
acorn: 8.12.1
acorn-import-attributes@1.9.5(acorn@8.12.1):
dependencies:
@ -8662,8 +8718,6 @@ snapshots:
errx@0.1.0: {}
es-module-lexer@1.3.1: {}
es-module-lexer@1.5.4: {}
esbuild@0.19.10:
@ -9865,6 +9919,8 @@ snapshots:
pkg-types: 1.2.0
ufo: 1.5.4
moment@2.30.1: {}
mri@1.2.0: {}
mrmime@2.0.0: {}
@ -10793,7 +10849,7 @@ snapshots:
schema-utils@3.3.0:
dependencies:
'@types/json-schema': 7.0.14
'@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
@ -11141,7 +11197,7 @@ snapshots:
terser-webpack-plugin@5.3.9(esbuild@0.23.1)(webpack@5.89.0(esbuild@0.23.1)):
dependencies:
'@jridgewell/trace-mapping': 0.3.20
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
@ -11724,16 +11780,16 @@ snapshots:
webpack@5.89.0(esbuild@0.23.1):
dependencies:
'@types/eslint-scope': 3.7.6
'@types/estree': 1.0.3
'@types/estree': 1.0.5
'@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.2
acorn-import-assertions: 1.9.0(acorn@8.11.2)
browserslist: 4.22.2
acorn: 8.12.1
acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.3.1
es-module-lexer: 1.5.4
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1

View file

@ -1,4 +1,77 @@
{
"categories": [
{
"id": "infrastructure",
"name": "Infrastructure",
"usecases": [
"node",
"rpc-provider",
"infrastructure",
"eth-layer-2",
"research-and-development",
"computing",
"storage",
"data-management",
"other"
]
},
{
"id": "social-and-communications",
"name": "Social & Communications",
"usecases": [
"dao",
"nft-community",
"alliances",
"messaging",
"events",
"other"
]
},
{
"id": "hardware",
"name": "Hardware",
"usecases": [
"wallets",
"node",
"other"
]
},
{
"id": "applications",
"name": "Applications",
"usecases": [
"browser",
"vpn",
"did",
"operation-systems",
"dapps",
"wallets",
"ai",
"kyc-solution",
"other"
]
},
{
"id": "defi",
"name": "Defi",
"usecases": [
"bridge",
"defi",
"mixing-service",
"mixing-management",
"currency",
"other"
]
},
{
"id": "archived-projects",
"name": "Archived projects",
"usecases": [
"legacy-projects",
"deprecated-systems"
]
}
],
"projects": [
{
"id": "01-labs",
@ -15885,181 +15958,6 @@
]
}
],
"assets": [
{
"id": "eth",
"name": "Ethereum"
},
{
"id": "btc",
"name": "Bitcoin"
},
{
"id": "usdc",
"name": "Coinbase stable"
},
{
"id": "usdt",
"name": "Tether"
},
{
"id": "dai",
"name": "MakerDAO stable"
},
{
"id": "atom",
"name": "Cosmos"
},
{
"id": "scrt",
"name": "Secret Network"
},
{
"id": "dot",
"name": "Polkadot"
},
{
"id": "sol",
"name": "Solana"
},
{
"id": "zcash",
"name": "Zcash"
},
{
"id": "xmr",
"name": "Monero"
},
{
"id": "other",
"name": "Other"
}
],
"categories": [
{
"id": "infrastructure",
"name": "Infrastructure",
"usecases": [
"node",
"rpc-provider",
"infrastructure",
"eth-layer-2",
"research-and-development",
"computing",
"storage",
"data-management",
"other"
]
},
{
"id": "social-and-communications",
"name": "Social & Communications",
"usecases": [
"dao",
"nft-community",
"alliances",
"messaging",
"events",
"other"
]
},
{
"id": "hardware",
"name": "Hardware",
"usecases": [
"wallets",
"node",
"other"
]
},
{
"id": "applications",
"name": "Applications",
"usecases": [
"browser",
"vpn",
"did",
"operation-systems",
"dapps",
"wallets",
"ai",
"kyc-solution",
"other"
]
},
{
"id": "defi",
"name": "Defi",
"usecases": [
"bridge",
"defi",
"mixing-service",
"mixing-management",
"currency",
"other"
]
},
{
"id": "archived-projects",
"name": "Archived projects",
"usecases": [
"legacy-projects",
"deprecated-systems"
]
}
],
"ecosystems": [
{
"id": "ethereum",
"name": "Ethereum"
},
{
"id": "bitcoin",
"name": "Bitcoin"
},
{
"id": "solana",
"name": "Solana"
},
{
"id": "cosmos",
"name": "Cosmos"
},
{
"id": "monero",
"name": "Monero"
},
{
"id": "other",
"name": "Other"
}
],
"features": [
{
"id": "no-compliance",
"name": "No compliance"
},
{
"id": "non-kyc",
"name": "Non-KYC"
},
{
"id": "private-by-default",
"name": "Private by default"
},
{
"id": "non-custodial",
"name": "Non custodial"
},
{
"id": "opensource",
"name": "Opensource"
},
{
"id": "live-on-mainnet",
"name": "Live on Mainnet"
}
],
"usecases": [
{
"id": "wallets",
@ -16185,5 +16083,360 @@
"id": "other",
"name": "Other"
}
],
"assets": [
{
"id": "eth",
"name": "Ethereum"
},
{
"id": "btc",
"name": "Bitcoin"
},
{
"id": "usdc",
"name": "Coinbase stable"
},
{
"id": "usdt",
"name": "Tether"
},
{
"id": "dai",
"name": "MakerDAO stable"
},
{
"id": "atom",
"name": "Cosmos"
},
{
"id": "scrt",
"name": "Secret Network"
},
{
"id": "dot",
"name": "Polkadot"
},
{
"id": "sol",
"name": "Solana"
},
{
"id": "zcash",
"name": "Zcash"
},
{
"id": "xmr",
"name": "Monero"
},
{
"id": "other",
"name": "Other"
}
],
"features": [
{
"id": "no-compliance",
"name": "No compliance"
},
{
"id": "non-kyc",
"name": "Non-KYC"
},
{
"id": "private-by-default",
"name": "Private by default"
},
{
"id": "non-custodial",
"name": "Non custodial"
},
{
"id": "opensource",
"name": "Opensource"
},
{
"id": "live-on-mainnet",
"name": "Live on Mainnet"
}
],
"ecosystems": [
{
"id": "ethereum",
"name": "Ethereum",
"icon": "https://assets.coingecko.com/coins/images/279/standard/ethereum.png?1696501628"
},
{
"id": "bitcoin",
"name": "Bitcoin",
"icon": "https://assets.coingecko.com/coins/images/1/standard/bitcoin.png?1696501400"
},
{
"id": "solana",
"name": "Solana",
"icon": "https://assets.coingecko.com/coins/images/4128/standard/solana.png?1718769756"
},
{
"id": "cosmos",
"name": "Cosmos",
"icon": "https://assets.coingecko.com/coins/images/1481/standard/cosmos_hub.png?1696502525"
},
{
"id": "monero",
"name": "Monero",
"icon": "https://assets.coingecko.com/coins/images/69/standard/monero_logo.png?1696501460"
},
{
"id": "other",
"name": "Other"
}
],
"ranks": [
{
"id": "openess",
"name": "Openess",
"references": [
{
"field": "team.teammembers",
"label": {
"name": "Team",
"positive": "Member",
"negative": "Anonymous"
},
"condition": {
"minLength": 1
},
"points": 10
},
{
"field": "links.docs",
"label": {
"name": "Documentation",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 10
},
{
"field": "links.github",
"label": {
"name": "Github",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 10
},
{
"field": "links.twitter",
"label": {
"name": "Twitter",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 1
},
{
"field": "links.telegram",
"label": {
"name": "Twitter",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 1
},
{
"field": "links.discord",
"label": {
"name": "Discord",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 1
},
{
"field": "links.lens",
"label": {
"name": "Lens",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 1
},
{
"field": "links.farcaster",
"label": {
"name": "Farcaster",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 1
},
{
"field": "links.whitepaper",
"label": {
"name": "Whitepaper",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 10
},
{
"field": "funding.value",
"label": {
"name": "Funding",
"positive": "Investment",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 10
}
]
},
{
"id": "technology",
"name": "Technology",
"references": [
{
"field": "project_status.mainnet",
"label": {
"name": "Mainnet",
"positive": "Yes",
"negative": "No"
},
"condition": {
"exists": true
},
"points": 10
},
{
"field": "blockchain_features.opensource",
"label": {
"name": "Open Source",
"positive": "Yes",
"negative": "No"
},
"condition": {
"equals": true
},
"points": 20
},
{
"field": "blockchain_features.asset_custody_type",
"label": {
"name": "Non Custody",
"positive": "None",
"negative": "Custodial"
},
"condition": {
"equals": "non-custody"
},
"points": 10
},
{
"field": "blockchain_features.upgradability.enabled",
"label": {
"name": "Upgradability",
"positive": "Disabled",
"negative": "Enabled"
},
"condition": {
"equals": false
},
"points": 10
},
{
"field": "audits",
"label": {
"name": "Audits",
"positive": "Audit",
"negative": "None"
},
"condition": {
"minLength": 1
},
"points": 10
}
]
},
{
"id": "privacy",
"name": "Privacy",
"references": [
{
"field": "privacy_policy.link",
"label": {
"name": "Privacy Policy",
"positive": "Link",
"negative": "Not available"
},
"condition": {
"exists": true
},
"points": 10
},
{
"field": "traceability.kyc",
"label": {
"name": "KYC",
"positive": "No",
"negative": "Yes"
},
"condition": {
"equals": false
},
"points": 10
},
{
"field": "compliance",
"label": {
"name": "Compliance",
"positive": "No",
"negative": "OFAC"
},
"condition": {
"equals": true
},
"points": 5
},
{
"field": "default_privacy",
"label": {
"name": "Default Privacy",
"positive": "YES",
"negative": "No"
},
"condition": {
"equals": true
},
"points": 10
}
]
}
]
}

4
types/asset.ts Normal file
View file

@ -0,0 +1,4 @@
export interface Asset {
id: string
name: string
}

View file

@ -2,4 +2,5 @@ export interface Category {
id: string
name: string
projectsCount: number
usecases?: string[]
}

5
types/ecosystem.ts Normal file
View file

@ -0,0 +1,5 @@
export interface Ecosystem {
id: string
name: string
icon?: string
}

4
types/feature.ts Normal file
View file

@ -0,0 +1,4 @@
export interface Feature {
id: string
name: string
}

View file

@ -7,11 +7,13 @@ export interface Project {
id: string
name: string
categories: string[]
ecosystem?: string
usecases?: string[]
ecosystem?: string[]
product_readiness?: string
security?: string
have_token?: boolean
token_link?: string
assets_used?: string[]
tokens?: {
name?: string
symbol: string
@ -35,6 +37,7 @@ export interface Project {
telegram?: string
discord?: string
blog?: string
governance?: string
facebook?: string
block_explorer?: string
whitepaper?: string
@ -121,6 +124,7 @@ export interface Project {
url?: string
[k: string]: unknown
}[]
ratings?: ProjectRating[]
}
export interface ProjectShallow {
@ -129,6 +133,10 @@ export interface ProjectShallow {
title1: string
description: string
percentage: number
categories: string[]
usecases?: string[]
ecosystem?: string[]
assets_used?: string []
forum?: string | undefined
github?: string | undefined
website?: string | undefined
@ -142,8 +150,24 @@ export interface ProjectShallow {
audits?: Audit[] | undefined
support?: number | undefined
anonymity?: boolean | undefined
ratings?: ProjectRating[]
}
export interface ProjectIndexable extends Project {
[key: string]: unknown
}
export interface ProjectRating {
type: string
name: string
items: ProjectRatingItem[]
points: number
}
export interface ProjectRatingItem {
isValid: boolean
label: string
positive: string
negative: string
value: any
}

24
types/rank.ts Normal file
View file

@ -0,0 +1,24 @@
import type { Project } from './project'
export interface Rank {
id: string
name: string
references: Reference[]
}
interface Reference {
field: keyof Project
label: {
name: string
positive: string
negative: string
}
condition: Condition
points: number
}
interface Condition {
minLength?: number
exists?: boolean
equals?: boolean | string
}

4
types/usecase.ts Normal file
View file

@ -0,0 +1,4 @@
export interface Usecase {
id: string
name: string
}

View file

@ -35,5 +35,8 @@ export const collections = {
open1: '<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="gray" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></svg>',
matrix: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none"><path d="M1.266 1.10241V46.8989H4.5615V48.0014H0V0.00141309H4.5615V1.10391L1.266 1.10241ZM15.3525 15.6179V17.9384H15.414C16.032 17.0459 16.782 16.3679 17.6475 15.8819C18.516 15.3974 19.524 15.1559 20.6475 15.1559C21.726 15.1559 22.7175 15.3674 23.61 15.7814C24.5085 16.2029 25.1805 16.9454 25.6485 17.9999C26.157 17.2499 26.853 16.5854 27.7185 16.0154C28.587 15.4454 29.619 15.1559 30.813 15.1559C31.719 15.1559 32.5545 15.2654 33.3285 15.4919C34.11 15.7094 34.767 16.0619 35.3205 16.5464C35.868 17.0399 36.297 17.6729 36.6105 18.4544C36.915 19.2344 37.071 20.1809 37.071 21.2909V32.7434H32.3775V23.0414C32.3775 22.4699 32.3535 21.9239 32.3055 21.4154C32.2755 20.9549 32.157 20.5094 31.947 20.0954C31.7505 19.7189 31.4475 19.4069 31.071 19.2059C30.6885 18.9779 30.1575 18.8699 29.5005 18.8699C28.836 18.8699 28.305 18.9944 27.8985 19.2434C27.501 19.4939 27.165 19.8374 26.9385 20.2439C26.697 20.6744 26.541 21.1499 26.4765 21.6344C26.3985 22.1579 26.361 22.6829 26.352 23.2064V32.7449H21.6555V23.1434C21.6555 22.6349 21.648 22.1354 21.618 21.6434C21.6015 21.1679 21.5085 20.7074 21.3285 20.2694C21.1725 19.8479 20.877 19.4954 20.5005 19.2614C20.118 19.0109 19.5465 18.8789 18.7965 18.8789C18.57 18.8789 18.2745 18.9254 17.9145 19.0274C17.5545 19.1279 17.196 19.3154 16.86 19.5899C16.5165 19.8719 16.218 20.2694 15.9765 20.7854C15.735 21.2999 15.618 21.9794 15.618 22.8239V32.7539H10.9215V15.6224L15.3525 15.6179ZM46.734 46.8974V1.10091H43.4385V-0.00158691H48V47.9984H43.4385V46.8959L46.734 46.8974Z" fill="white"/></svg>',
news: '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none"><g opacity="1"><mask id="mask0_2258_10849" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="28" height="28"><rect width="28" height="28" fill="#D9D9D9"/></mask><g mask="url(#mask0_2258_10849)"><path d="M4.66146 24.5C4.01979 24.5 3.47049 24.2715 3.01354 23.8146C2.5566 23.3576 2.32812 22.8083 2.32812 22.1667V5.83333C2.32812 5.19167 2.5566 4.64236 3.01354 4.18542C3.47049 3.72847 4.01979 3.5 4.66146 3.5H23.3281C23.9698 3.5 24.5191 3.72847 24.976 4.18542C25.433 4.64236 25.6615 5.19167 25.6615 5.83333V22.1667C25.6615 22.8083 25.433 23.3576 24.976 23.8146C24.5191 24.2715 23.9698 24.5 23.3281 24.5H4.66146ZM4.66146 22.1667H23.3281V5.83333H4.66146V22.1667ZM6.99479 19.8333H20.9948V17.5H6.99479V19.8333ZM6.99479 15.1667H11.6615V8.16667H6.99479V15.1667ZM13.9948 15.1667H20.9948V12.8333H13.9948V15.1667ZM13.9948 10.5H20.9948V8.16667H13.9948V10.5Z" fill="white"/></g></g></svg>',
arrow_down: '<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 5L0 0H10L5 5Z" fill="black"/></svg>',
arrow_up: '<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 -4.37114e-07L10 5L0 5L5 -4.37114e-07Z" fill="white"/></svg>',
thumb_up: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask0_308_7787" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16"><rect width="16" height="16" fill="#D9D9D9"/></mask><g mask="url(#mask0_308_7787)"><path d="M11.9987 14.0013H5.33203V5.33464L9.9987 0.667969L10.832 1.5013C10.9098 1.57908 10.9737 1.68464 11.0237 1.81797C11.0737 1.9513 11.0987 2.07908 11.0987 2.2013V2.43464L10.3654 5.33464H13.9987C14.3543 5.33464 14.6654 5.46797 14.932 5.73463C15.1987 6.0013 15.332 6.31241 15.332 6.66797V8.0013C15.332 8.07908 15.3237 8.16241 15.307 8.2513C15.2904 8.34019 15.2654 8.42352 15.232 8.5013L13.232 13.2013C13.132 13.4235 12.9654 13.6124 12.732 13.768C12.4987 13.9235 12.2543 14.0013 11.9987 14.0013ZM3.9987 5.33464V14.0013H1.33203V5.33464H3.9987Z" fill="white"/></g></svg>',
},
}

View file

@ -50,9 +50,29 @@ export default defineConfig({
bg: {
grey: '#ffffff33',
dark_grey: '#161616',
rating: {
default: '#494949',
red: '#FF4218',
orange: '#FF9900',
yellow: '#FFE600',
green: '#42FF00',
hover: '#202020',
},
team: {
grey: '#404040',
},
funding: {
card: '#101010',
},
audits: {
card: '#191919',
},
},
text: {
grey: '#909090',
rating: {
negative: '#FFB800',
},
},
},
},

50
utils/score.ts Normal file
View file

@ -0,0 +1,50 @@
import type { Project, ProjectIndexable, ProjectShallow } from '~/types'
const fulfilled = (value: any): boolean => {
const type = typeof value
switch (type) {
case 'string':
if (value !== '')
return true
break
case 'object':
if (Object.keys(value!).length > 0)
return true
break
default:
return false
}
return false
}
export const calculateScore = (project: ProjectIndexable | ProjectShallow | Project) => {
const criterias: { value: keyof ProjectIndexable, key: keyof ProjectIndexable | '' }[] = [
{ value: 'product_readiness', key: '' },
{ value: 'github', key: 'links' },
{ value: 'docs', key: 'links' },
{ value: 'team', key: '' },
{ value: 'audits', key: '' },
]
let matched = 0
for (let i = 0; i < criterias.length; i++) {
let value
// value = ((criterias[i].key ?? props.project[criterias[i].value as keyof typeof props.project]) ?? null === null) ? null : (props.project as ProjectIndexable)[criterias[i].key][criterias[i].value]
const indexableProject = project as ProjectIndexable
if (criterias[i].key !== '')
value = (indexableProject[criterias[i].key] as any)?.[criterias[i].value]
else
value = indexableProject?.[criterias[i].value]
// console.log(props.project?.links?.github);
// console.log(Object.keys(props.indexableProject["team"]).length);
if (value === null || value === undefined)
continue
if (fulfilled(value))
matched++
}
return 100 / criterias.length * matched
}

5
utils/text.ts Normal file
View file

@ -0,0 +1,5 @@
import moment from 'moment'
export function formatDate(date: Date | string) {
return moment(date).format('MM / YYYY')
}