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> <script lang="ts" setup>
import type { ProjectShallow } from '~/types' import type { ProjectRating, ProjectShallow } from '~/types'
defineProps<{ const props = defineProps<{
project: ProjectShallow 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> </script>
<template> <template>
@ -18,66 +22,38 @@ const { switcher } = storeToRefs(useData())
transition-all transition-all
> >
<div <div
relative grid
max-w="96px lg:200px" grid-cols="2 lg:10"
w-full w-full
h="96px lg:200px"
:class="switcher ? '' : 'lg:max-w-full! lg:w-full '"
> >
<div <div
col-span="1 lg:2"
flex flex
items-center items-center
justify-center gap="12px lg:16px"
relative
w-full w-full
my-auto
h-full h-full
h="48px lg:64px"
:class="switcher ? '' : 'lg:max-w-full! lg:w-full '"
> >
<NuxtImg <NuxtImg
:src="project?.image || '/no-image-1-1.svg'" :src="project?.image || '/no-image-1-1.svg'"
class="w-full h-auto" class="w-full h-auto"
max-h="md:196px 96px" max-h="md:64px 48px"
max-w="md:64px 48px"
self-center self-center
z-10 z-10
object-fit object-fit
bg="#121212" bg="#121212"
/> />
</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 <div
h="96px lg:200px"
lg:py-24px
lg:pr-24px
flex flex
flex-col flex-col
gap-y-4px
lg:flex-row
justify-center 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 <div
w-fit w-fit
@ -87,161 +63,138 @@ const { switcher } = storeToRefs(useData())
@click.prevent="navigateTo(project.website, { external: true, open: { target: '_blank' } })" @click.prevent="navigateTo(project.website, { external: true, open: { target: '_blank' } })"
> >
<h1 <h1
text="18px lg:24px app-white" text="14px app-white"
font-700 font-700
line-clamp-1 line-clamp-1
hover:underline hover:underline
underline-offset-3 underline-offset-3
leading="20px lg:32px"
> >
{{ project.title1 }} {{ project.title1 }}
</h1> </h1>
<UnoIcon
i-web-open
text-16px
/>
</div> </div>
<h2 <p
text="14px app-text-grey" text-12px
overflow-hidden leading-16px
text-ellipsis lg:hidden
line-clamp-2
lg:line-clamp-2
> >
{{ project.description }} {{ project.usecases?.join(', ') }}
</h2> </p>
</div> </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>
<ClientOnly>
<div <div
v-for="(projectItem, index) of projectItems"
:key="projectItem.label.toString()"
hidden hidden
lg:flex lg:flex
items-center 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 gap-16px
> >
<UnoIcon <UnoIcon
v-if="project.forum" block
i-web-forum lg:hidden
text-28px i-iconoir-internet
opacity-50 text="24px"
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' } })"
/> />
<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> </div>
</ClientOnly>
</div> </div>
</NuxtLink> </NuxtLink>
</template> </template>

View file

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

View file

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

View file

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

View file

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

View file

@ -2,15 +2,35 @@
import type { ProjectShallow } from '~/types' import type { ProjectShallow } from '~/types'
const props = defineProps<{ const props = defineProps<{
projects: ProjectShallow[] projects: { title: string, projects: ProjectShallow[] }[]
}>() }>()
const { switcher } = storeToRefs(useData()) const { switcher, filter } = storeToRefs(useData())
const displayCount = ref(100) const totalProjectsCount = props.projects.map(g => g.projects.length).reduce((a, b) => a + b, 0)
const displayedProjects = computed(() => props.projects.slice(0, displayCount.value))
function showMoreProjects() { function onChangeSort(sortKey: string) {
displayCount.value += 50 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> </script>
<template> <template>
@ -18,9 +38,124 @@ function showMoreProjects() {
flex flex
flex-col flex-col
items-start items-start
>
<template
v-for="group in projects"
:key="group.title"
> >
<div <div
v-if="displayedProjects.length"
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 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'" :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 gap-16px
@ -28,27 +163,14 @@ function showMoreProjects() {
w-full w-full
> >
<Card <Card
v-for="project in displayedProjects" v-for="project in group.projects"
:key="project.id" :key="project.id"
:project="project" :project="project"
/> />
</div> </div>
<div v-else> </template>
<div v-if="totalProjectsCount === 0">
<h3>No Projects found...</h3> <h3>No Projects found...</h3>
</div> </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> </div>
</template> </template>

View file

@ -1,17 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Project, ProjectIndexable } from '~/types' import type { Project } from '~/types'
const props = defineProps<{ const props = defineProps<{
project: Project project: Project
}>() }>()
const availableSupport = computed(() => { // const availableSupport = computed(() => {
const filteredKeys = ['forum', 'discord', 'twitter', 'lens', 'farcaster', 'telegram'] // const filteredKeys = ['forum', 'discord', 'twitter', 'lens', 'farcaster', 'telegram']
if (typeof props.project.links === 'object' && (props.project.links !== null || props.project.links !== undefined)) // 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 Object.keys(props.project.links).filter(key => filteredKeys.includes(key)).length
return 0 // return 0
}) // })
/** /**
* From data points * From data points
@ -21,80 +21,42 @@ const availableSupport = computed(() => {
- team: anon / public - team: anon / public
- audit: yes / no - 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 const logo = props.project?.logos?.at(0)?.url
</script> </script>
<template> <template>
<div <div
lg:flex flex
flex-col
gap-y-16px
lg:flex-row
items-center
lg:gap-32px lg:gap-32px
> >
<NuxtImg <NuxtImg
lg:max-w-320px max-w-200px
lg:max-h-320px max-h-200px
shrink shrink
border="1px app-text-grey"
:src="logo ?? '/no-image-1-1.svg'" :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 <div
flex flex
flex-col flex-col
items-center
lg:items-start
justify-between justify-between
gap-32px gap-y="lg:12px 24px"
lg:flex-row text-app-text-grey
lg:items-center w-full
> >
<div mt-24px> <div mt-4px>
<NuxtLink <NuxtLink
:to="project.links?.web" :to="project.links?.web"
target="_blank" target="_blank"
@ -112,139 +74,154 @@ const logo = props.project?.logos?.at(0)?.url
{{ project.name }} {{ project.name }}
</h1> </h1>
<UnoIcon <UnoIcon
i-web-openinnew i-ic-twotone-open-in-new
text-16px text="22px app-white"
/> />
</NuxtLink> </NuxtLink>
<h2
text="16px app-text-grey"
leading-24px
mt-8px
>
{{ project.project_type ?? '---' }}
</h2>
</div> </div>
<div <div
border-2px flex
class="border-app-black bg-app-white text-app-black" justify-center
lg:grid
grid-cols-10
w-full
items-center
mt-4px
>
<h2
hidden
lg:block
>
Usecases:
</h2>
<p
text-app-white
col-span-9
>
Swap, Mixer
</p>
</div>
<div
grid
grid-cols-2
gap-y-12px
lg:flex
lg:flex-col
order-3
lg:order-2
w-full
>
<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
>
<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 flex
items-center items-center
justify-center justify-center
px-32px border="2px app-white"
py-16px text="16px lg:24px app-white"
text-32px leading="24px md:32px"
max-h-="32px md:40px"
max-w="84px"
w-full
h-full
font-700 font-700
leading-40px whitespace-nowrap
cursor-pointer py="2px lg:8px"
lg:py-4px
> >
{{ `${calculateScore}%` }} {{ calculateScore(project) }} %
</div> </div>
</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"
>
<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"
>
<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>
<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> </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 md:block
>{{ 'BACK TO LIST' }}</span> >{{ 'BACK TO LIST' }}</span>
</NavigationButton> </NavigationButton>
<hr
hidden
md:block
border-t-2px
border-white
w-full
>
<div <div
flex flex
gap-16px gap-16px

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10" badge-text="3/10"
> >
<UnoIcon <UnoIcon
i-web-code_v2 i-bi-code-square
text-24px text-24px
/> />
</ProjectDetailCategoryDivider> </ProjectDetailCategoryDivider>
@ -23,25 +23,98 @@ defineProps<{
leading-24px leading-24px
font-400 font-400
> >
<h3 text-app-text-grey> <h3
{{ 'Project Description' }} text-app-text-grey
mb-8px
>
Project Description
</h3> </h3>
<span <p
text="14px sm:16px" text="14px sm:16px"
leading-20px 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>
<div mt-24px> <div mt-24px>
<h3 <h3
text="14px sm:16px app-text-grey" text="14px sm:16px app-text-grey"
leading-20px leading-20px
> >
{{ 'Infrastructure links' }} Infrastructure links:
</h3> </h3>
<div <div
grid grid
grid-cols-2 grid-cols-2
gap-16px gap-x-16px
gap-y-12px
mt-8px mt-8px
lg:grid-cols-4 lg:grid-cols-4
> >
@ -50,18 +123,12 @@ defineProps<{
:to="project.links?.web" :to="project.links?.web"
> >
<template #prefix> <template #prefix>
<UnoIcon i-web-website /> <UnoIcon
i-ic-baseline-language
text="24px app-text-grey"
/>
</template> </template>
{{ 'Website' }} Website
</ProjectOpenessLink>
<ProjectOpenessLink
v-if="project.links?.blog"
:to="project.links.blog"
>
<template #prefix>
<UnoIcon i-web-website />
</template>
{{ 'Blog' }}
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.github" v-if="project.links?.github"
@ -69,12 +136,25 @@ defineProps<{
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-github i-mdi-github
opacity-30 text="24px app-text-grey"
text-27px text-27px
/> />
</template> </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>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.docs" v-if="project.links?.docs"
@ -82,11 +162,38 @@ defineProps<{
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-documents i-material-symbols-contract
text-24px text="24px app-text-grey"
text-27px
/> />
</template> </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>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.block_explorer" v-if="project.links?.block_explorer"
@ -94,48 +201,25 @@ defineProps<{
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-explorer i-material-symbols-explore-outline
opacity-30 text="24px app-text-grey"
text-27px
/> />
</template> </template>
{{ 'Explorer' }} Explorer
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.snapshot" v-if="project.links?.governance"
:to="project.links.snapshot" :to="project.links.governance"
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-snapshot i-material-symbols-how-to-vote-outline
text-32px text="24px app-text-grey"
text-27px
/> />
</template> </template>
{{ 'Snapshot' }} Governance
</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' }}
</ProjectOpenessLink> </ProjectOpenessLink>
</div> </div>
</div> </div>
@ -149,22 +233,22 @@ defineProps<{
<div <div
grid grid
grid-cols-2 grid-cols-2
gap-16px gap-x-16px
gap-y-12px
mt-8px mt-8px
lg:grid-cols-4 lg:grid-cols-4
> >
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.forum" v-if="project.links?.twitter"
:to="project.links.forum" :to="project.links.twitter"
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-forum i-bi-twitter-x
opacity-30 text="24px app-text-grey"
text-28px
/> />
</template> </template>
{{ 'Forum' }} Twitter
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.discord" v-if="project.links?.discord"
@ -172,24 +256,23 @@ defineProps<{
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-discord i-ic-baseline-discord
text-27px text="24px app-text-grey"
/> />
</template> </template>
{{ 'Discord' }} Discord
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.twitter" v-if="project.links?.telegram"
:to="project.links.twitter" :to="project.links.telegram"
> >
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-twitter_x i-mdi-telegram
opacity-30 text="24px app-text-grey"
text-22px
/> />
</template> </template>
{{ 'Twitter' }} Telegram
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.lens" v-if="project.links?.lens"
@ -198,10 +281,10 @@ defineProps<{
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-lens i-web-lens
text-32px text="24px app-text-grey"
/> />
</template> </template>
{{ 'Lens' }} Lens
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink <ProjectOpenessLink
v-if="project.links?.farcaster" v-if="project.links?.farcaster"
@ -210,89 +293,63 @@ defineProps<{
<template #prefix> <template #prefix>
<UnoIcon <UnoIcon
i-web-farcaster i-web-farcaster
text-26px text="24px app-text-grey"
/> />
</template> </template>
{{ 'Farcaster' }} Farcaster
</ProjectOpenessLink> </ProjectOpenessLink>
<ProjectOpenessLink </div>
v-if="project.links?.telegram" </div>
:to="project.links.telegram" <ProjectOpenessTeamMembers
v-if="project?.team"
:members="project.team"
mt-24px
/>
<div mt-32px>
<h3
text="app-text-grey 14px lg:16px"
leading-24px
>
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
>
<h3
text="14px lg:16px"
leading="20px lg:24px"
font-700
>
{{ funding.name }}
</h3>
<NuxtLink
:to="funding.link"
external
target="_blank"
> >
<template #prefix>
<UnoIcon <UnoIcon
i-web-telegram i-ic-twotone-open-in-new
opacity-30 text="22px app-text-grey"
text-22px
/> />
</template> </NuxtLink>
{{ 'Telegram' }}
</ProjectOpenessLink>
</div> </div>
</div> <h3
<div mt-24px> v-if="!project.funding?.length"
<ProjectOpenessTeamMembers :members="project.team" /> text="app-text-grey 14px lg:16px"
</div> leading-24px
<div
grid
grid-cols-2
items-start
mt-32px
gap-y-26px
lg:grid-cols-4
> >
<ProjectInfoItem N/A
:check-undefined="project.product_launch_day" </h3>
title="Product launch day"
>
{{ 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
w-full
>
<template
v-for="fund in project.funding"
:key="fund.name"
>
<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>
</div> </div>
</ProjectDetailContainer> </ProjectDetailContainer>
</template> </template>

View file

@ -32,58 +32,32 @@ const props = defineProps<{
> >
<div <div
flex flex
items-center
gap-12px gap-12px
> >
<template v-if="member.link"> <!-- <template v-if="member.link">
<NuxtImg <NuxtImg
:src="member.link" :src="member.link"
width="48" width="48"
height="48" height="48"
:alt="member.name" :alt="member.name"
/> />
</template> </template> -->
<template v-else> <div
<svg flex
width="48" items-center
height="48" justify-center
viewBox="0 0 48 48" text-black
fill="none" w="40px lg:48px"
xmlns="http://www.w3.org/2000/svg" h="40px lg:48px"
rounded-full
bg-app-bg-team-grey
> >
<circle <div
opacity="0.2" i-heroicons-solid-user
cx="24" text-24px
cy="24"
r="23.5"
stroke="white"
/> />
<g opacity="0.2"> </div>
<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>
<div <div
flex flex
flex-col flex-col
@ -92,10 +66,6 @@ const props = defineProps<{
text="14px sm:16px" text="14px sm:16px"
font-700 font-700
>{{ member.name }}</span> >{{ member.name }}</span>
<span
text="14px sm:16px app-text-grey"
font-400
>{{ member.role ?? 'N/A' }}</span>
</div> </div>
</div> </div>
</template> </template>

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10" badge-text="3/10"
> >
<UnoIcon <UnoIcon
i-web-code_v2 i-heroicons-solid-eye
text-24px text-24px
/> />
</ProjectDetailCategoryDivider> </ProjectDetailCategoryDivider>
@ -26,11 +26,11 @@ defineProps<{
lg:grid-cols-4 lg:grid-cols-4
> >
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.blockchain_features?.p2p" :check-undefined="project.default_privacy"
bold title="Default privacy"
title="Peer to Peer (P2P)" :color="project.default_privacy ? '#18FF2F' : '#FF0000'"
> >
{{ project.blockchain_features?.p2p ? 'YES' : 'NO' }} {{ project.default_privacy ? 'YES' : 'NO' }}
</ProjectInfoItem> </ProjectInfoItem>
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.tracebility?.kyc" :check-undefined="project.tracebility?.kyc"
@ -40,109 +40,6 @@ defineProps<{
> >
{{ project.tracebility?.kyc ? 'YES' : 'NO' }} {{ project.tracebility?.kyc ? 'YES' : 'NO' }}
</ProjectInfoItem> </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 <ProjectInfoItem
:check-undefined="project.privacy_policy" :check-undefined="project.privacy_policy"
:color="project.privacy_policy?.defined ? '#18FF2F' : '#FF0000'" :color="project.privacy_policy?.defined ? '#18FF2F' : '#FF0000'"
@ -150,6 +47,31 @@ defineProps<{
title="Privacy Policy" title="Privacy Policy"
> >
{{ project.privacy_policy?.defined ? 'YES' : 'NO' }} {{ 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>
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.tracebility?.tracked_data" :check-undefined="project.tracebility?.tracked_data"
@ -163,58 +85,6 @@ defineProps<{
> >
{{ project.privacy_policy?.data_usage }} {{ project.privacy_policy?.data_usage }}
</ProjectInfoItem> </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> </div>
</ProjectDetailContainer> </ProjectDetailContainer>
</template> </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" badge-text="3/10"
> >
<UnoIcon <UnoIcon
i-web-code_v2 i-material-symbols-lock-outline
text-24px text-24px
/> />
</ProjectDetailCategoryDivider> </ProjectDetailCategoryDivider>
<ProjectDetailContainer> <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 <div
grid grid
grid-cols-2 grid-cols-2
@ -26,20 +51,11 @@ defineProps<{
lg:grid-cols-4 lg:grid-cols-4
> >
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.blockchain_features?.asset_custody_type" :check-undefined="project.technical_spof"
bold :color="project.technical_spof ? '#FF0000' : '#18FF2F'"
title="Asset custody" title="Technical dependency"
> >
{{ project.blockchain_features?.asset_custody_type }} {{ project.technical_spof }}
</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}` }}
</ProjectInfoItem> </ProjectInfoItem>
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.social_trust" :check-undefined="project.social_trust"
@ -55,47 +71,6 @@ defineProps<{
> >
{{ project.third_party_dependency ? 'YES' : 'NO' }} {{ project.third_party_dependency ? 'YES' : 'NO' }}
</ProjectInfoItem> </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> </div>
</ProjectDetailContainer> </ProjectDetailContainer>
</template> </template>

View file

@ -9,36 +9,69 @@ defineProps<{
<template> <template>
<div <div
flex flex
items-center justify-between
gap-16px lg:grid
lg:grid-cols-2
rounded-full
py-12px
px-16px
bg-app-bg-audits-card
> >
<slot />
<div <div
flex grid
flex-col grid-cols-3
text-14px w-full
font-700
leading-24px
> >
<NuxtLink <h2
hover:underline text="14px lg:16px"
flex leading-20px
items-center font-700
gap-8px col-span="3 lg:2"
:to="auditUrl"
> >
{{ auditName }} {{ auditName }}
<UnoIcon </h2>
v-if="auditUrl" <h2
i-web-openinnew
text-16px
/>
</NuxtLink>
<span
text-14px text-14px
font-400 leading-20px
leading-24px hidden
>{{ date }}</span> 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>
</div> </div>
</template> </template>

View file

@ -12,7 +12,7 @@ defineProps<{
badge-text="3/10" badge-text="3/10"
> >
<UnoIcon <UnoIcon
i-web-code_v2 i-material-symbols-deployed-code-outline
text-24px text-24px
/> />
</ProjectDetailCategoryDivider> </ProjectDetailCategoryDivider>
@ -21,100 +21,54 @@ defineProps<{
grid grid
grid-cols-2 grid-cols-2
items-start items-start
mt-32px mt-24px
gap-y-16px gap-y-16px
lg:grid-cols-4 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 <ProjectInfoItem
:check-undefined="project.technology?.type" :check-undefined="project.technology?.type"
title="Technology type" title="Technology type"
bold bold
> >
{{ project.technology?.type }} {{ project.technology?.type.toUpperCase() }}
</ProjectInfoItem> </ProjectInfoItem>
<ProjectInfoItem <ProjectInfoItem
:check-undefined="project.blockchain_features?.encryption" :check-undefined="project.blockchain_features?.p2p"
title="Encryption" title="Peer to Peer (P2P)"
bold bold
> >
{{ project.blockchain_features?.encryption }} {{ project.blockchain_features?.p2p ? 'YES' : 'NO' }}
</ProjectInfoItem> </ProjectInfoItem>
<ProjectInfoItem <ProjectInfoItem
title="License" :check-undefined="project.storage?.decentralized"
bold title="Decentralized storage"
: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"
bold bold
> >
<div {{ project.storage?.decentralized ? 'YES' : 'NO' }}
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>
</ProjectInfoItem> </ProjectInfoItem>
</div> </div>
</ProjectDetailContainer> </ProjectDetailContainer>

View file

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputOption } from '~/types' import type { InputOption } from '~/types'
defineProps<{ placeholder?: string, includeSort?: boolean }>()
const { filter, switcher } = storeToRefs(useData()) const { filter, switcher } = storeToRefs(useData())
const options: InputOption[] = [ const options: InputOption[] = [
{ label: 'A to Z', value: 'atoz' }, { label: 'A to Z', value: 'atoz' },
@ -22,8 +24,8 @@ const isSearchFocused = ref(false)
border-2px border-2px
flex flex
items-center items-center
max-w-320px
w-full w-full
h-40px
hover:opacity-100 hover:opacity-100
:class="isSearchFocused ? 'opacity-100' : 'opacity-25'" :class="isSearchFocused ? 'opacity-100' : 'opacity-25'"
> >
@ -34,7 +36,7 @@ const isSearchFocused = ref(false)
w-fit w-fit
> >
<UnoIcon <UnoIcon
i-web-search i-heroicons-solid-magnifying-glass
text-16px text-16px
:class="isSearchFocused ? 'opacity-100' : 'opacity-50' " :class="isSearchFocused ? 'opacity-100' : 'opacity-50' "
class="uno-icon" class="uno-icon"
@ -42,6 +44,7 @@ const isSearchFocused = ref(false)
</div> </div>
<input <input
v-model="filter.query" v-model="filter.query"
:placeholder="placeholder"
type="text" type="text"
bg-transparent bg-transparent
border-transparent border-transparent
@ -54,7 +57,7 @@ const isSearchFocused = ref(false)
@blur="isSearchFocused = false" @blur="isSearchFocused = false"
> >
</div> </div>
<div> <div v-if="includeSort">
<div <div
flex flex
gap-24px 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="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"> <span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon <UnoIcon
i-heroicons-solid-chevron-down i-ic-baseline-arrow-drop-down
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']" :class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
/> />
</span> </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', () => { export const useData = defineStore('data', () => {
const categories = useState<Category[]>('categories') 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 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({ const filter = reactive({
query: '', query: '',
sortby: 'atoz', sortby: 'score',
sortDirection: 'desc',
}) })
const switcher = ref(true) const switcher = ref(true)
watch(selectedCategoryId, () => { watch([selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId], () => {
if (selectedCategoryId.value !== 'all') if (selectedCategoryId.value !== 'all' || selectedUsecaseId.value !== 'all' || selectedEcosystemId.value !== 'all' || selectedAssetsUsedId.value !== 'all' || selectedFeaturesId.value !== 'all')
filter.query = '' filter.query = ''
}) })
watch(filter, () => { watch(filter, () => {
if (filter.query !== '') if (filter.query !== '') {
selectedCategoryId.value = 'all' selectedCategoryId.value = 'all'
selectedUsecaseId.value = 'all'
selectedEcosystemId.value = 'all'
selectedAssetsUsedId.value = 'all'
selectedFeaturesId.value = 'all'
}
}) })
const fetchData = async () => { const fetchData = async () => {
@ -25,20 +47,32 @@ export const useData = defineStore('data', () => {
const data = await $fetch<{ const data = await $fetch<{
categories: Category[] categories: Category[]
projects: Project[] projects: Project[]
usecases: Usecase[]
ecosystems: Ecosystem[]
assets: Asset[]
features: Feature[]
ranks: Rank[]
}>('/api/data') }>('/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) => { categories.value = data.categories.map((c) => {
c.projectsCount = projects.value.filter(p => c.projectsCount = projects.value.filter(p =>
p.categories?.includes(c.id), p.categories?.includes(c.id),
).length ).length
return c return c
}).filter(c => c.projectsCount > 0) }).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) { catch (e) {
console.error(e) console.error(e)
return false return false
} }
return true return true
} }
@ -57,7 +91,7 @@ export const useData = defineStore('data', () => {
id: project.id, id: project.id,
title1: project.name, title1: project.name,
description: project.description ?? 'N/A', 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, forum: project.links?.forum,
explorer: project.links?.block_explorer, explorer: project.links?.block_explorer,
twitter: project.links?.twitter, twitter: project.links?.twitter,
@ -72,15 +106,33 @@ export const useData = defineStore('data', () => {
support: availableSupport(), support: availableSupport(),
image: project.logos?.[0]?.url ?? '', image: project.logos?.[0]?.url ?? '',
anonymity: true, 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 shallowProjects = computed(() => projects.value.map(project => projectToShallow(project)))
const getProjectsByCategory = <T extends ProjectShallow>(id: string, options?: { shallow: boolean }): T[] => { const getProjectsByFilters = <T extends ProjectShallow>(options?: { shallow: boolean }): T[] => {
if (id === 'all') const filteredProjects = projects.value
return projects.value.map(project => projectToShallow(project)) as T[] .filter(project =>
else selectedCategoryId.value !== 'all' ? project.categories.includes(selectedCategoryId.value) : true,
return projects.value.filter(project => project.categories?.includes(id)).map(project => options?.shallow ? projectToShallow(project) : project) as T[] )
.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 => { 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 query = filter.query.toLowerCase()
const filteredShallowProjects = getProjectsByCategory(selectedCategoryId.value, { shallow: true }) const filteredShallowProjects = getProjectsByFilters({ shallow: true })
.filter((project) => { .filter((project) => {
return ( return (
project project
@ -108,29 +160,123 @@ export const useData = defineStore('data', () => {
return true return true
}).sort((a, b) => { }).sort((a, b) => {
if (filter.sortby === 'score') if (filter.sortby === 'score')
if (filter.sortDirection === 'asc')
return a.percentage - b.percentage
else
return b.percentage - a.percentage return b.percentage - a.percentage
if (filter.sortby === 'atoz') if (filter.sortby === 'title')
return a.title1.localeCompare(b.title1) 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 else
return 0 return 0
}) })
return filteredShallowProjects 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 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 { return {
selectedCategoryId, selectedCategoryId,
selectedUsecaseId,
selectedEcosystemId,
selectedAssetsUsedId,
selectedFeaturesId,
filter, filter,
switcher, switcher,
categories, categories,
usecases,
features,
ecosystems,
assets,
projects, projects,
shallowProjects, shallowProjects,
groupedProjectsPerCategory,
filteredProjectsCount, filteredProjectsCount,
fetchData, fetchData,
getProjectById, getProjectById,
getProjectsByCategory,
filteredProjects, filteredProjects,
projectToShallow, projectToShallow,
} }

View file

@ -1,21 +1,22 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { InputOption } from '~/types' import type { InputOption } from '~/types'
const { categories, filteredProjectsCount, selectedCategoryId } = storeToRefs(useData()) const { categories, usecases, ecosystems, assets, features, filteredProjectsCount, selectedCategoryId, selectedUsecaseId, selectedEcosystemId, selectedAssetsUsedId, selectedFeaturesId } = 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 selectedCategory = computed(() => { const selectedCategory = computed(() => {
return categories.value.find(c => c.id === selectedCategoryId.value) 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(() => ([ const categoryOptions = ref<InputOption[]>(categories.value ? [{ label: 'Category', value: 'all' }, ...categories.value.map(c => ({ label: c.name, value: c.id, count: c.projectsCount }))] : [])
categories.value.find(c => c.id === 'defi')!, const usecaseOptions = computed<InputOption[]>(() => availableUsecases.value.length ? [{ label: 'Usecase', value: 'all' }, ...availableUsecases.value.map(u => ({ label: u.name, value: u.id }))] : [])
...[...categories.value].sort((a, b) => a.name.localeCompare(b.name)).filter(c => c.id !== 'defi'), 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 { showBar } = storeToRefs(useNavigaiton())
const swipeEl = ref() const swipeEl = ref()
@ -56,77 +57,67 @@ watch([scrollY, top, y], (newValues, oldValues) => {
w-full w-full
xl:gap-32px 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 <div
flex flex
flex-col flex-col
gap-16px
w-full w-full
> >
<div <div
xl:hidden flex
block flex-col
md:flex-row
md:justify-between
md:items-center
gap-16px
mb="16px md:32px"
> >
<h2 <SearchBox
text-14px flex-1
font-700 placeholder:text-app-text-grey
:placeholder="`Search in ${filteredProjectsCount} Projects`"
/>
<div
md:flex-2
flex
items-center
gap-16px
> >
Choose category
</h2>
<CategorySelectBox <CategorySelectBox
v-model="selectedCategoryId" v-model="selectedCategoryId"
:options="extendedOptions" :options="categoryOptions"
name="categorySelect"
w-full w-full
@selected="selectedCategoryId === 'all' ? navigateTo(`/`) : navigateTo(`/category/${selectedCategoryId}`)" @selected="selectedCategoryId === 'all' ? navigateTo(`/`) : navigateTo(`/category/${selectedCategoryId}`)"
/> />
</div> <CategorySelectBox
<SearchBox /> v-if="usecases?.length"
</div> v-model="selectedUsecaseId"
<div name="usecaseSelect"
flex :options="usecaseOptions"
gap-28px w-full
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
/> />
<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>
</div> </div>
<slot /> <slot />
</div> </div>

View file

@ -1,7 +1,7 @@
{ {
"name": "develitesse-nuxt", "name": "develitesse-nuxt",
"private": true, "private": true,
"packageManager": "pnpm@9.9.0", "packageManager": "pnpm@9.10.0",
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"cleanup": "nuxt cleanup", "cleanup": "nuxt cleanup",
@ -17,8 +17,14 @@
}, },
"devDependencies": { "devDependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@iconify-json/bi": "^1.2.0",
"@iconify-json/heroicons-outline": "^1.2.0", "@iconify-json/heroicons-outline": "^1.2.0",
"@iconify-json/heroicons-solid": "^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/devtools": "^1.4.1",
"@nuxt/eslint": "0.5.5", "@nuxt/eslint": "0.5.5",
"@nuxt/image": "^1.8.0", "@nuxt/image": "^1.8.0",
@ -46,5 +52,8 @@
}, },
"lint-staged": { "lint-staged": {
"*": "eslint --fix" "*": "eslint --fix"
},
"dependencies": {
"moment": "^2.30.1"
} }
} }

View file

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

View file

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

View file

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

View file

@ -7,16 +7,38 @@ settings:
importers: importers:
.: .:
dependencies:
moment:
specifier: ^2.30.1
version: 2.30.1
devDependencies: devDependencies:
'@formkit/auto-animate': '@formkit/auto-animate':
specifier: ^0.8.2 specifier: ^0.8.2
version: 0.8.2 version: 0.8.2
'@iconify-json/bi':
specifier: ^1.2.0
version: 1.2.0
'@iconify-json/heroicons-outline': '@iconify-json/heroicons-outline':
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
'@iconify-json/heroicons-solid': '@iconify-json/heroicons-solid':
specifier: ^1.2.0 specifier: ^1.2.0
version: 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': '@nuxt/devtools':
specifier: ^1.4.1 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)) 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==} resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'} 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': '@iconify-json/heroicons-outline@1.2.0':
resolution: {integrity: sha512-Qy1sRmQYqih6xRxwCtnX0hXJ4252t83C0CnNWAP3gF0fH0Qmp9RY66LMB0moYGxQxUhsTFIl2nNceSVSBUo8Tg==} resolution: {integrity: sha512-Qy1sRmQYqih6xRxwCtnX0hXJ4252t83C0CnNWAP3gF0fH0Qmp9RY66LMB0moYGxQxUhsTFIl2nNceSVSBUo8Tg==}
'@iconify-json/heroicons-solid@1.2.0': '@iconify-json/heroicons-solid@1.2.0':
resolution: {integrity: sha512-o+PjtMXPr4wk0veDS7Eh6H1BnTJT1vD7HcKl+I7ixdYQC8i1P2zdtk0C2v7C9OjJBMsiwJSCxT4qQ3OzONgyjw==} 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': '@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@ -1671,9 +1711,6 @@ packages:
'@types/eslint-scope@3.7.6': '@types/eslint-scope@3.7.6':
resolution: {integrity: sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==} 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': '@types/eslint@9.6.1':
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
@ -2821,9 +2858,6 @@ packages:
errx@0.1.0: errx@0.1.0:
resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==}
es-module-lexer@1.3.1:
resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==}
es-module-lexer@1.5.4: es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
@ -3827,6 +3861,9 @@ packages:
mlly@1.7.1: mlly@1.7.1:
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
mri@1.2.0: mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -6425,6 +6462,10 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {} '@humanwhocodes/retry@0.3.0': {}
'@iconify-json/bi@1.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/heroicons-outline@1.2.0': '@iconify-json/heroicons-outline@1.2.0':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@ -6433,6 +6474,26 @@ snapshots:
dependencies: dependencies:
'@iconify/types': 2.0.0 '@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/types@2.0.0': {}
'@iconify/utils@2.1.32': '@iconify/utils@2.1.32':
@ -7223,13 +7284,8 @@ snapshots:
'@types/eslint-scope@3.7.6': '@types/eslint-scope@3.7.6':
dependencies: dependencies:
'@types/eslint': 8.44.6 '@types/eslint': 9.6.1
'@types/estree': 1.0.3 '@types/estree': 1.0.5
'@types/eslint@8.44.6':
dependencies:
'@types/estree': 1.0.3
'@types/json-schema': 7.0.14
'@types/eslint@9.6.1': '@types/eslint@9.6.1':
dependencies: dependencies:
@ -7947,9 +8003,9 @@ snapshots:
dependencies: dependencies:
event-target-shim: 5.0.1 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: dependencies:
acorn: 8.11.2 acorn: 8.12.1
acorn-import-attributes@1.9.5(acorn@8.12.1): acorn-import-attributes@1.9.5(acorn@8.12.1):
dependencies: dependencies:
@ -8662,8 +8718,6 @@ snapshots:
errx@0.1.0: {} errx@0.1.0: {}
es-module-lexer@1.3.1: {}
es-module-lexer@1.5.4: {} es-module-lexer@1.5.4: {}
esbuild@0.19.10: esbuild@0.19.10:
@ -9865,6 +9919,8 @@ snapshots:
pkg-types: 1.2.0 pkg-types: 1.2.0
ufo: 1.5.4 ufo: 1.5.4
moment@2.30.1: {}
mri@1.2.0: {} mri@1.2.0: {}
mrmime@2.0.0: {} mrmime@2.0.0: {}
@ -10793,7 +10849,7 @@ snapshots:
schema-utils@3.3.0: schema-utils@3.3.0:
dependencies: dependencies:
'@types/json-schema': 7.0.14 '@types/json-schema': 7.0.15
ajv: 6.12.6 ajv: 6.12.6
ajv-keywords: 3.5.2(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)): terser-webpack-plugin@5.3.9(esbuild@0.23.1)(webpack@5.89.0(esbuild@0.23.1)):
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.20 '@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1 jest-worker: 27.5.1
schema-utils: 3.3.0 schema-utils: 3.3.0
serialize-javascript: 6.0.1 serialize-javascript: 6.0.1
@ -11724,16 +11780,16 @@ snapshots:
webpack@5.89.0(esbuild@0.23.1): webpack@5.89.0(esbuild@0.23.1):
dependencies: dependencies:
'@types/eslint-scope': 3.7.6 '@types/eslint-scope': 3.7.6
'@types/estree': 1.0.3 '@types/estree': 1.0.5
'@webassemblyjs/ast': 1.11.6 '@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.2 acorn: 8.12.1
acorn-import-assertions: 1.9.0(acorn@8.11.2) acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.22.2 browserslist: 4.23.3
chrome-trace-event: 1.0.3 chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0 enhanced-resolve: 5.15.0
es-module-lexer: 1.3.1 es-module-lexer: 1.5.4
eslint-scope: 5.1.1 eslint-scope: 5.1.1
events: 3.3.0 events: 3.3.0
glob-to-regexp: 0.4.1 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": [ "projects": [
{ {
"id": "01-labs", "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": [ "usecases": [
{ {
"id": "wallets", "id": "wallets",
@ -16185,5 +16083,360 @@
"id": "other", "id": "other",
"name": "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 id: string
name: string name: string
projectsCount: number 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 id: string
name: string name: string
categories: string[] categories: string[]
ecosystem?: string usecases?: string[]
ecosystem?: string[]
product_readiness?: string product_readiness?: string
security?: string security?: string
have_token?: boolean have_token?: boolean
token_link?: string token_link?: string
assets_used?: string[]
tokens?: { tokens?: {
name?: string name?: string
symbol: string symbol: string
@ -35,6 +37,7 @@ export interface Project {
telegram?: string telegram?: string
discord?: string discord?: string
blog?: string blog?: string
governance?: string
facebook?: string facebook?: string
block_explorer?: string block_explorer?: string
whitepaper?: string whitepaper?: string
@ -121,6 +124,7 @@ export interface Project {
url?: string url?: string
[k: string]: unknown [k: string]: unknown
}[] }[]
ratings?: ProjectRating[]
} }
export interface ProjectShallow { export interface ProjectShallow {
@ -129,6 +133,10 @@ export interface ProjectShallow {
title1: string title1: string
description: string description: string
percentage: number percentage: number
categories: string[]
usecases?: string[]
ecosystem?: string[]
assets_used?: string []
forum?: string | undefined forum?: string | undefined
github?: string | undefined github?: string | undefined
website?: string | undefined website?: string | undefined
@ -142,8 +150,24 @@ export interface ProjectShallow {
audits?: Audit[] | undefined audits?: Audit[] | undefined
support?: number | undefined support?: number | undefined
anonymity?: boolean | undefined anonymity?: boolean | undefined
ratings?: ProjectRating[]
} }
export interface ProjectIndexable extends Project { export interface ProjectIndexable extends Project {
[key: string]: unknown [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>', 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>', 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>', 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: { bg: {
grey: '#ffffff33', grey: '#ffffff33',
dark_grey: '#161616', 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: { text: {
grey: '#909090', 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')
}