mirror of
https://github.com/web3privacy/explorer-app.git
synced 2024-10-15 16:46:26 +02:00
feat(mobile): update rating to support mobile and some more mobile adjustments
This commit is contained in:
parent
6a8a305ce7
commit
6830e499cd
15 changed files with 265 additions and 97 deletions
|
@ -4,7 +4,9 @@ import type { ProjectRating, ProjectShallow } from '~/types'
|
|||
const props = defineProps<{
|
||||
project: ProjectShallow
|
||||
}>()
|
||||
const { switcher, ecosystems } = storeToRefs(useData())
|
||||
const { switcher, ecosystems, filter } = storeToRefs(useData())
|
||||
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
||||
|
||||
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' }
|
||||
|
@ -60,17 +62,24 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
flex
|
||||
items-center
|
||||
gap-8px
|
||||
|
||||
@click.prevent="navigateTo(project.website, { external: true, open: { target: '_blank' } })"
|
||||
>
|
||||
<h1
|
||||
flex
|
||||
items-center
|
||||
text="14px app-white"
|
||||
font-700
|
||||
line-clamp-1
|
||||
hover:underline
|
||||
underline-offset-3
|
||||
leading="20px lg:32px"
|
||||
class="group relative inline-block"
|
||||
>
|
||||
{{ project.title1 }}
|
||||
<div
|
||||
class=" i-bx-link-external ml-4px text-14px opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
<p
|
||||
|
@ -107,10 +116,11 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
gap-16px
|
||||
>
|
||||
<NuxtLink
|
||||
v-if="projectItem.label[1]"
|
||||
:to="projectItem.label[1]"
|
||||
v-if="projectItem.label[0]"
|
||||
:to="projectItem.label[0]"
|
||||
external
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
<UnoIcon
|
||||
i-ic-baseline-language
|
||||
|
@ -118,10 +128,11 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
/>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="projectItem.label[2]"
|
||||
:to="projectItem.label[2]"
|
||||
v-if="projectItem.label[1]"
|
||||
:to="projectItem.label[1]"
|
||||
external
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
<UnoIcon
|
||||
i-mdi-github
|
||||
|
@ -130,10 +141,11 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
/>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="projectItem.label[0]"
|
||||
:to="projectItem.label[0]"
|
||||
v-if="projectItem.label[2]"
|
||||
:to="projectItem.label[2]"
|
||||
external
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
<UnoIcon
|
||||
i-bi-twitter-x
|
||||
|
@ -171,13 +183,23 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
w-full
|
||||
gap-16px
|
||||
>
|
||||
<UnoIcon
|
||||
<NuxtLink
|
||||
v-if="project.website"
|
||||
block
|
||||
lg:hidden
|
||||
:to="project.website"
|
||||
external
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
<UnoIcon
|
||||
|
||||
i-iconoir-internet
|
||||
text="24px"
|
||||
/>
|
||||
</NuxtLink>
|
||||
<div
|
||||
v-if="filter.sortby === 'score' || filter.sortby === 'title' || isLargeScreen"
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
|
@ -192,6 +214,12 @@ const projectItems: { label: string | string[], type: string, rating?: ProjectRa
|
|||
>
|
||||
{{ project.percentage }} %
|
||||
</div>
|
||||
<ProjectRating
|
||||
v-if="(filter.sortby === 'openess' || filter.sortby === 'technology' || filter.sortby === 'privacy') && project.ratings?.find((r) => r.type === filter.sortby) && !isLargeScreen"
|
||||
:percentage="project.ratings.find((r) => r.type === filter.sortby)!.points"
|
||||
:rating="project.ratings.find((r) => r.type === filter.sortby)!"
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@ function onOptionSelected(value: string | number) {
|
|||
>
|
||||
<div class="relative font-700 font-24px">
|
||||
<HeadlessListboxButton
|
||||
class="relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black sm:text-sm sm:leading-6"
|
||||
class="relative cursor-pointer py-6px px-14px text-left border-2px bg-app-white text-app-black text-xs sm:text-sm sm:leading-6"
|
||||
>
|
||||
<span class="block truncate mr-8px font">{{ isOptionSelected?.label }} <span
|
||||
v-if="titleShowCount"
|
||||
|
|
|
@ -93,6 +93,8 @@ const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[
|
|||
Project name
|
||||
</p>
|
||||
<button
|
||||
hidden
|
||||
lg:block
|
||||
type="button"
|
||||
:class="['title' === filter.sortby ? filter.sortDirection === 'desc' ? 'i-ic-baseline-arrow-drop-up'
|
||||
: 'i-ic-baseline-arrow-drop-down'
|
||||
|
@ -107,23 +109,9 @@ const cardTitles = ref< { label: string, sortKey: string, togglable?: boolean }[
|
|||
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"
|
||||
<SortSelectBox
|
||||
v-model="filter.sortby"
|
||||
:options="['title', 'openess', 'technology', 'privacy', 'score'].map((o) => ({ label: capitalize(o), value: o }))"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -1,26 +1,17 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Project } from '~/types'
|
||||
import type { Project, ProjectRating } from '~/types'
|
||||
|
||||
const props = defineProps<{
|
||||
project: Project
|
||||
}>()
|
||||
|
||||
// const availableSupport = computed(() => {
|
||||
// const filteredKeys = ['forum', 'discord', 'twitter', 'lens', 'farcaster', 'telegram']
|
||||
// if (typeof props.project.links === 'object' && (props.project.links !== null || props.project.links !== undefined))
|
||||
// return Object.keys(props.project.links).filter(key => filteredKeys.includes(key)).length
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
||||
|
||||
// return 0
|
||||
// })
|
||||
const selectedMobileRating = ref<ProjectRating>()
|
||||
|
||||
/**
|
||||
* From data points
|
||||
- product readiness
|
||||
- docs (yes/no)
|
||||
- github (yes/no)
|
||||
- team: anon / public
|
||||
- audit: yes / no
|
||||
*/
|
||||
function onSelectMobileRating(rating: ProjectRating) {
|
||||
selectedMobileRating.value = selectedMobileRating.value?.type === rating.type ? undefined : rating
|
||||
}
|
||||
|
||||
const logo = props.project?.logos?.at(0)?.url
|
||||
</script>
|
||||
|
@ -172,6 +163,7 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
flex
|
||||
flex-col
|
||||
lg:flex-row
|
||||
gap-y-4px
|
||||
items-center
|
||||
>
|
||||
<p
|
||||
|
@ -183,7 +175,10 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
<ProjectRating
|
||||
:rating="rating"
|
||||
:percentage="rating.points"
|
||||
:disable-popover="!isLargeScreen"
|
||||
compact
|
||||
:selected="rating.type === selectedMobileRating?.type && !isLargeScreen"
|
||||
@selected="onSelectMobileRating(rating)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -218,9 +213,18 @@ const logo = props.project?.logos?.at(0)?.url
|
|||
py="2px lg:8px"
|
||||
lg:py-4px
|
||||
>
|
||||
{{ calculateScore(project) }} %
|
||||
{{ project.percentage }} %
|
||||
</div>
|
||||
</div>
|
||||
<div col-span-4 flex items-center justify-center w-full v-if="selectedMobileRating && !isLargeScreen">
|
||||
<ProjectRating
|
||||
:rating="selectedMobileRating"
|
||||
:percentage="selectedMobileRating.points"
|
||||
:disable-popover="!isLargeScreen"
|
||||
compact
|
||||
show-only-popover
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -104,6 +104,9 @@ function tooltipMouseOut() {
|
|||
</template>
|
||||
<div
|
||||
v-else
|
||||
flex
|
||||
items-center
|
||||
gap-4px
|
||||
:color="color ?? '#FFF'"
|
||||
text-14px
|
||||
:class="`sm:text-${textSize ?? '16px'} font-${bold ? '700' : '400'} opacity-${opacity}`"
|
||||
|
|
|
@ -54,7 +54,7 @@ defineProps<{
|
|||
>
|
||||
<UnoIcon
|
||||
i-ic-twotone-open-in-new
|
||||
text="22px app-text-grey"
|
||||
text="20px app-text-grey"
|
||||
/>
|
||||
</NuxtLink>
|
||||
</ProjectInfoItem>
|
||||
|
|
|
@ -6,8 +6,13 @@ const props = defineProps<{
|
|||
rating: ProjectRating
|
||||
percentage: number
|
||||
compact?: boolean
|
||||
disablePopover?: boolean
|
||||
showOnlyPopover?: boolean
|
||||
selected?: boolean
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(['selected'])
|
||||
|
||||
const colors = [
|
||||
'#ff0000', // 0-10%
|
||||
'#ff4500', // 11-20%
|
||||
|
@ -26,11 +31,24 @@ const backgroundColorByScore = computed(() => {
|
|||
const colorIndex = Math.floor(normalizedPercentage / 10)
|
||||
return colors[colorIndex]
|
||||
})
|
||||
const isLargeScreen = useMediaQuery('(min-width: 1024px)')
|
||||
|
||||
function onClick() {
|
||||
if (isLargeScreen.value)
|
||||
return
|
||||
if (props.disablePopover)
|
||||
emits('selected')
|
||||
else
|
||||
isPopoverVisible.value = !isPopoverVisible.value
|
||||
}
|
||||
|
||||
const isPopoverVisible = ref(false)
|
||||
|
||||
let hideTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
const showPopover = () => {
|
||||
if (!isLargeScreen.value)
|
||||
return
|
||||
console.log('show')
|
||||
if (hideTimeout) {
|
||||
clearTimeout(hideTimeout)
|
||||
hideTimeout = null
|
||||
|
@ -39,6 +57,8 @@ const showPopover = () => {
|
|||
}
|
||||
|
||||
const hidePopover = () => {
|
||||
if (!isLargeScreen.value)
|
||||
return
|
||||
hideTimeout = setTimeout(() => {
|
||||
isPopoverVisible.value = false
|
||||
}, 100) // Delay of 200ms before hiding
|
||||
|
@ -46,17 +66,24 @@ const hidePopover = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div
|
||||
class="relative"
|
||||
:class="{ 'w-full': showOnlyPopover }"
|
||||
>
|
||||
<!-- Main div that shows rating and triggers the popover on hover -->
|
||||
<div
|
||||
v-if="!showOnlyPopover"
|
||||
flex
|
||||
items-center
|
||||
p-12px
|
||||
gap-4px
|
||||
hover:bg-app-bg-rating-hover
|
||||
:class="{ 'bg-app-bg-rating-hover rounded-8px': selected }"
|
||||
hover:rounded-8px
|
||||
cursor-pointer
|
||||
@mouseenter="showPopover"
|
||||
@mouseleave="hidePopover"
|
||||
@click.prevent="onClick()"
|
||||
>
|
||||
<div
|
||||
v-for="point of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
|
||||
|
@ -68,6 +95,7 @@ const hidePopover = () => {
|
|||
|
||||
<!-- Popover panel that appears on hover -->
|
||||
<transition
|
||||
v-if="!showOnlyPopover"
|
||||
enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
|
@ -76,10 +104,10 @@ const hidePopover = () => {
|
|||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="isPopoverVisible"
|
||||
v-if="(isPopoverVisible && !disablePopover)"
|
||||
class="absolute mt-2 p-2 bg-app-bg-rating-hover w-240px shadow-lg rounded"
|
||||
z-100
|
||||
style="left: 50%; transform: translateX(-50%);"
|
||||
left="-250% lg:50%"
|
||||
flex
|
||||
flex-col
|
||||
gap-14px
|
||||
|
@ -88,44 +116,13 @@ const hidePopover = () => {
|
|||
@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>
|
||||
<ProjectRatingInfo :items="rating.items" />
|
||||
</div>
|
||||
</transition>
|
||||
<ProjectRatingInfo
|
||||
v-if="showOnlyPopover"
|
||||
:items="rating.items"
|
||||
:compact="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
55
components/Project/ProjectRatingInfo.vue
Normal file
55
components/Project/ProjectRatingInfo.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts" setup>
|
||||
import type { ProjectRatingItem } from '~/types'
|
||||
|
||||
withDefaults(defineProps<{ items: ProjectRatingItem[], compact?: boolean }>(), {
|
||||
compact: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
flex
|
||||
flex-col
|
||||
gap-8px
|
||||
:class="{ 'mt-16px': !compact }"
|
||||
>
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.label"
|
||||
flex
|
||||
justify-between
|
||||
items-center
|
||||
text-12px
|
||||
font-700
|
||||
leading-20px
|
||||
w-full
|
||||
: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>
|
||||
</template>
|
78
components/SortSelectBox.vue
Normal file
78
components/SortSelectBox.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<script setup lang="ts">
|
||||
import type { InputOption } from '~/types'
|
||||
|
||||
const props = withDefaults(defineProps<SelectProps>(), {
|
||||
isMarginTop: true,
|
||||
blackAndWhite: true,
|
||||
})
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
|
||||
interface SelectProps {
|
||||
options: InputOption[]
|
||||
modelValue: string
|
||||
isMarginTop?: boolean
|
||||
blackAndWhite?: boolean
|
||||
}
|
||||
|
||||
const selectedValue = useVModel(props, 'modelValue', emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HeadlessListbox
|
||||
v-model="selectedValue"
|
||||
as="div"
|
||||
>
|
||||
<div
|
||||
class="relative font-700"
|
||||
:class="[isMarginTop ? 'mt-2' : 'mt-0', blackAndWhite ? 'bg-app-black' : 'bg-app-white']"
|
||||
>
|
||||
<HeadlessListboxButton
|
||||
class="relative w-full cursor-pointer py-8px p-16px text-left text-xs sm:text-sm sm:leading-6 flex items-center gap-4px"
|
||||
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
|
||||
>
|
||||
<span
|
||||
block
|
||||
text="12px lg:14px app-text-grey"
|
||||
leading="16px lg:24px"
|
||||
>
|
||||
Sort by:
|
||||
</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">
|
||||
<UnoIcon
|
||||
i-ic-baseline-arrow-drop-down
|
||||
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
|
||||
/>
|
||||
</span>
|
||||
</HeadlessListboxButton>
|
||||
|
||||
<transition
|
||||
leave-active-class="transition ease-in duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<HeadlessListboxOptions
|
||||
class="absolute z-100 max-h-60 w-full divide-y-2px border-2px overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm"
|
||||
>
|
||||
<HeadlessListboxOption
|
||||
v-for="option in props.options"
|
||||
:key="option.value"
|
||||
v-slot="{ active, selected }"
|
||||
as="template"
|
||||
:value="option.value"
|
||||
>
|
||||
<li
|
||||
class="w-full relative cursor-pointer select-none py-8px p-16px text-xs sm:text-sm"
|
||||
:class="[active ? 'bg-#ffffff1a' : 'text-white']"
|
||||
>
|
||||
<span
|
||||
class="block truncate"
|
||||
:class="[selected ? 'font-semibold' : 'font-normal']"
|
||||
>{{ option.label }}</span>
|
||||
</li>
|
||||
</HeadlessListboxOption>
|
||||
</HeadlessListboxOptions>
|
||||
</transition>
|
||||
</div>
|
||||
</HeadlessListbox>
|
||||
</template>
|
|
@ -63,6 +63,7 @@ export const useData = defineStore('data', () => {
|
|||
projects.value = data.projects.map(project => ({
|
||||
...project,
|
||||
ratings: generateProjectRating(project),
|
||||
percentage: Math.round((project.ratings?.reduce((a, b) => a + b.points, 0) || 0) / 1.5),
|
||||
})).filter(p => p.name)
|
||||
|
||||
categories.value = data.categories
|
||||
|
@ -98,7 +99,7 @@ export const useData = defineStore('data', () => {
|
|||
id: project.id,
|
||||
title1: project.name,
|
||||
description: project.description ?? 'N/A',
|
||||
percentage: Math.round((project.ratings?.reduce((a, b) => a + b.points, 0) || 0) / 1.5),
|
||||
percentage: project.percentage,
|
||||
forum: project.links?.forum,
|
||||
explorer: project.links?.block_explorer,
|
||||
twitter: project.links?.twitter,
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
"devDependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@iconify-json/bi": "^1.2.0",
|
||||
"@iconify-json/bx": "^1.2.0",
|
||||
"@iconify-json/eos-icons": "^1.1.10",
|
||||
"@iconify-json/heroicons-outline": "^1.2.0",
|
||||
"@iconify-json/heroicons-solid": "^1.2.0",
|
||||
"@iconify-json/eos-icons": "^1.1.10",
|
||||
"@iconify-json/ic": "^1.2.0",
|
||||
"@iconify-json/iconoir": "^1.2.0",
|
||||
"@iconify-json/material-symbols": "^1.2.1",
|
||||
|
@ -46,7 +47,8 @@
|
|||
"vitest": "^2.0.5",
|
||||
"vue-tsc": "^2.1.4",
|
||||
"yaml": "^2.5.1",
|
||||
"yup": "^1.4.0"
|
||||
"yup": "^1.4.0",
|
||||
"moment": "^2.30.1"
|
||||
},
|
||||
"overrides": {
|
||||
"vue": "latest"
|
||||
|
@ -57,8 +59,5 @@
|
|||
},
|
||||
"lint-staged": {
|
||||
"*": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.30.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,6 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
moment:
|
||||
specifier: ^2.30.1
|
||||
version: 2.30.1
|
||||
devDependencies:
|
||||
'@formkit/auto-animate':
|
||||
specifier: ^0.8.2
|
||||
|
@ -18,6 +14,9 @@ importers:
|
|||
'@iconify-json/bi':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
'@iconify-json/bx':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
'@iconify-json/eos-icons':
|
||||
specifier: ^1.1.10
|
||||
version: 1.2.0
|
||||
|
@ -72,6 +71,9 @@ importers:
|
|||
eslint:
|
||||
specifier: ^9.9.1
|
||||
version: 9.9.1(jiti@1.21.6)
|
||||
moment:
|
||||
specifier: ^2.30.1
|
||||
version: 2.30.1
|
||||
nuxt:
|
||||
specifier: ^3.13.0
|
||||
version: 3.13.0(@parcel/watcher@2.4.1)(@types/node@20.8.7)(encoding@0.1.13)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.3)(rollup@4.21.2)(terser@5.22.0)(typescript@5.5.4)(vite@5.4.2(@types/node@20.8.7)(terser@5.22.0))(vue-tsc@2.1.4(typescript@5.5.4))
|
||||
|
@ -1128,6 +1130,9 @@ packages:
|
|||
'@iconify-json/bi@1.2.0':
|
||||
resolution: {integrity: sha512-kaBV87cQlyeMkBBiMqsf3b43Nsxdk/rYKvR29dnktht57WUyHCnBAuH+ca/bscX856CzRpVX+sYs7arjrJD0qA==}
|
||||
|
||||
'@iconify-json/bx@1.2.0':
|
||||
resolution: {integrity: sha512-UWTC5BwAYXAPdzpubCH9DDW0Q1SAQNA4CpN4GfsvqKm+SN4I/ppafhxH2EFWSep3liKfkUo0TkzDNh/iKIxyZw==}
|
||||
|
||||
'@iconify-json/eos-icons@1.2.0':
|
||||
resolution: {integrity: sha512-grdfoS20Z4gWAzNPza7ytguNBWeTOkx4Y6aZHs149t2Z6AhW7zG3VWkkq6M+YuL2G8ugHnBw7ZxgazZ6oiMnIQ==}
|
||||
|
||||
|
@ -6629,6 +6634,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
|
||||
'@iconify-json/bx@1.2.0':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
|
||||
'@iconify-json/eos-icons@1.2.0':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
|
|
|
@ -16289,7 +16289,7 @@
|
|||
{
|
||||
"field": "links.telegram",
|
||||
"label": {
|
||||
"name": "Twitter",
|
||||
"name": "Telegram",
|
||||
"positive": "Link",
|
||||
"negative": "Not available"
|
||||
},
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface Project {
|
|||
token_link?: string
|
||||
[k: string]: unknown
|
||||
}[]
|
||||
percentage: number
|
||||
description?: string
|
||||
project_type?: string
|
||||
product_launch_day?: string
|
||||
|
|
|
@ -3,3 +3,8 @@ import moment from 'moment'
|
|||
export function formatDate(date: Date | string) {
|
||||
return moment(date).format('MM / YYYY')
|
||||
}
|
||||
|
||||
export function capitalize(word: string): string {
|
||||
if (!word) return word
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue