feat: form & github api

This commit is contained in:
DomWane 2024-09-12 18:03:06 +02:00
parent c249e433bc
commit 241776eaf1
43 changed files with 3521 additions and 128 deletions

35
app.config.ts Normal file
View file

@ -0,0 +1,35 @@
export default defineAppConfig({
github: {
appId: 995628,
privateKey: `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwkAjU2VY6eUsWm9VrbYyEGZRtY5aSBjWv2O2LvE71F5qOb1E
BcXAOVLFP3wU3+y4Nu9Qz9eKumALmKKokzd6KGrVyiIvCOjA8YtzKpHKa24a/TqH
ek6z1OvyAH+Zk+PoRGY5KU4YD17Hb81aysO6umI7Hn7NKzuDLHqmE2SmrzXeT7iQ
n0+yfwvJPeT+LyCxF42RgPSAzEWhf1Ng/v1neMyr6dfKPKD93KSJJ3rJd1rVZNlY
vMAfzR8kmfFEoQqBjtyW+OdTURSTQtjm+wsD+qGT4560mU22ZIY1yzNugsiSKdIG
bZR7uLC6Ljg7o0TH+HUCwm3w3N/V8EcWq6AIkQIDAQABAoIBAHR9jjHHB2lM/CpH
tjP0nVVkZv/gHJg7/RzNqUXBeUXEUSPLMu3bR4mY751Axiv7hAW9wtLByfo2+v1Q
HpQaovF2EDumAFrxajPwkuImiCOg5iKSZQieBg5caMaPvwQHmxTZ0NHKeXdOOX/8
wMP0WRjJK/b/x/5zNBkKfGoI5EURlyC7Q6wlmW6DGnFPjy7vC7ZKWj1dKfyNK8ZJ
T3absmwGva7cJd3a8nQ1DKK+Tl3RmYoLQpdXPfWDV9/omiSE+EcR0eIm5/v8vQgE
UMGv9fPAXo9D5pgEaGqV5zekLUKH2W8xt9pvHDnrjoGSEWGjT88xyJG37DRXa+wR
quLZ36ECgYEA648dDFMYyhThxbamtzOFCgBM1FYZGSV4618OIvTeQquuKtWjHnMV
2kvl5iUsmLALCLMJeCc8B7Hc4gZsYcm8VrvhvVsv8P1w6ea+wO/g25Q1hKwf9y2k
xdgDomAZxrJ7hXy57JTTZBvvUFOdok7jgCQAGWQg4zXTzoR1Hf1sInUCgYEA0xtd
xxRiVKh1Ei5sRgdUxE1rixRFhEZCdpu8ha1mC3cDlCCiie3ia4VWAkDN7eU8V0gX
DfD4CrxLThWRWlivfT9uhsncXibza9b+QIc0yluReM4+iswA5bd429cZx6kvivgj
FtYd0QKyHduxyAjUfB5JAtO2jJN4J3IWyd6y0i0CgYEAwwXKw/BsXqqpPZr6LkUu
SVh0Q3xA7UvT7/LT7mcTONmQqhAUK9qWZhRec4ulf4iIqhwoo9y/25MLT+qHgvKV
xq1ouPAtIfWCrXSHy48OeF0dbglfsbKNb+tnHuE3lgUhNSRNfiy9U225VaTUppNv
0SB9IIiAnfxrrtiFal3tUB0CgYEAt4bwFRXMkorNFFxVduACupIP755U/TTTrSeh
Upm3wDncW5evlre/gvgtGNJb9Pi4Z6zdlhoqmbKuyRiJAAFft0RBA19UfGnib5s9
+L1SkKXgpySuTJ4kHZFOudibqe+UzH0CjGTjEQUF8l0/hobeq3Tsxk9mQXonGCvg
UemQjpUCgYAvooI/aHrWyFZTigM63W5py4e6YMRvrvZbCBu4qougpJ4TQDnG2tMf
qW6A1gT6hIC54kGOn6WuO20CVVTZpImEwUYQchsvhsKVAxjp3dq8fw1KIuCPrZAs
u1mh4iVpLYIyyyeazf2RhFXBNU49DIS8xYndO970Q9pHZhz629f+qA==
-----END RSA PRIVATE KEY-----
`,
installationId: 54778330,
},
})

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
defineProps<{
border?: boolean
invertedColor?: boolean
}>()
</script>
@ -8,14 +9,14 @@ defineProps<{
<div
flex
items-center
justify-center
gap-12px
px-12px
py-6px
cursor-pointer
text-app-white
:class="[border ? 'border-2px border-app-white' : 'border-0']"
hover:text-app-black
hover:bg-app-white
:class="[border ? 'border-2px border-app-white' : 'border-0',
invertedColor ? 'bg-app-white text-app-black hover:text-app-white hover:bg-app-black' : 'text-app-white bg-app-black hover:text-app-black hover:bg-app-white',
]"
>
<template v-if="!!$slots.prefix">
<div text-24px>

View file

@ -27,7 +27,7 @@ function onOptionSelected(value: string) {
<HeadlessListboxButton
class="relative w-full cursor-pointer py-8px p-16px text-left border-2px text-app-white 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">{{ isOptionSelected?.label }} <span v-if="isOptionSelected?.count" opacity-50>({{ isOptionSelected?.count }})</span></span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
@ -61,7 +61,7 @@ function onOptionSelected(value: string) {
:class="[selected ? 'font-semibold' : 'font-normal']"
>
{{ option.label }}
<span opacity-50>({{ option.count }})</span>
<span v-if="option.count" opacity-50>({{ option.count }})</span>
</span>
</li>
</HeadlessListboxOption>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
defineProps<{
to: string
to?: string
}>()
</script>

View file

@ -143,7 +143,7 @@ watch(y, (newY, oldY) => {
lg:bg-app-bg-dark_grey
>
<div
v-if="!isProjectRoute"
v-if="!isProjectRoute && !route.fullPath.includes('create') && !route.fullPath.includes('edit')"
relative
app-container
w-full
@ -355,7 +355,10 @@ watch(y, (newY, oldY) => {
:class="[(!showBar && width < 768) ? 'translate-y--128px' : 'translate-y-0px']"
duration-200ms
>
<ProjectNavigation md:mt-16px />
<ProjectNavigation
v-if="!route.fullPath.includes('create') && !route.fullPath.includes('edit')"
md:mt-16px
/>
</div>
<div
v-else

View file

@ -0,0 +1,181 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
type Token = {
name: string
symbol: string
contract_address: string
token_link: string
}
function useTokens(project?: Partial<Project>) {
const tokens = ref<Token[]>(project?.tokens as Token[] || [])
const newToken = reactive<Token>({
symbol: '',
name: '',
contract_address: '',
token_link: '',
})
function addToken() {
tokens.value.push({ ...newToken })
newToken.symbol = ''
newToken.name = ''
newToken.contract_address = ''
newToken.token_link = ''
}
function removeToken(index: number) {
tokens.value.splice(index, 1)
}
return {
tokens,
newToken,
addToken,
removeToken,
}
}
const { tokens, newToken, addToken, removeToken } = useTokens(props.project)
const assetsUsed = ref<string[]>(Array.isArray(props.project?.assets_used) ? props.project?.assets_used.map(a => a.toLowerCase()) : [])
const { useProject, assetsData } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
assets_used: assetsUsed.value,
have_token: tokens.value.length > 0 && tokens.value.some(token => token.contract_address),
tokens: tokens.value,
})
}
defineExpose({
save,
})
</script>
<template>
<div mt-24px>
<ClientOnly>
<ProjectCreateComponentsSelectChips
v-model="assetsUsed"
label="Assets used"
:options="assetsData?.map(asset => ({ label: asset.name, value: asset.id }))"
placeholder="Add assets"
hint="Most used assets user can use to interact with your project"
can-add-new
/>
</ClientOnly>
<ProjectCreateComponentsCategoryDivider
w-full
title="NATIVE TOKENS"
/>
<ProjectCreateComponentsItem
v-for="token in tokens"
:key="token.name"
@remove="() => removeToken(tokens.indexOf(token))"
>
<template #label>
<div
flex
gap-2px
>
<span
class="text-app-black text-14px font-700"
lg="text-16px"
> {{ token.symbol }}
</span>
<span
v-if="token.name"
class="text-app-black text-14px font-400"
lg="text-16px"
> {{ `(${token.name})` }}
</span>
</div>
</template>
<template #desc>
<NuxtLink
target="_blank"
:to="token.token_link"
hover:text-app-black
class="text-app-black/50 text-16px hidden"
lg="block"
>Etherscan
</NuxtLink>
</template>
</ProjectCreateComponentsItem>
<ProjectCreateComponentsItemAdd
button-label="ADD TOKEN"
@add="addToken()"
>
<template #content>
<div
flex
flex-col
gap-16px
lg="gap-24px"
>
<div
flex
flex-col
gap-8px
lg="flex-row gap-24px"
>
<div
class="w-100%"
flex
flex-col
gap-16px
lg="flex-row gap-24px"
>
<ProjectCreateComponentsInput
v-model="newToken.symbol"
w-full
label="Token symbol"
placeholder="Token symbol"
/>
<ProjectCreateComponentsInput
v-model="newToken.name"
w-full
label="Token name"
placeholder="Token name"
/>
</div>
<span
class="w-100%"
lg="mt-32px left-1/2 self-center"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ 'Does your project have native token? Enter Symbol, name and address' }}
</span>
</div>
<ProjectCreateComponentsInput
v-model="newToken.contract_address"
lg="w-1/2"
label="Token contract address"
placeholder="Contract address"
hint="Enter token contract address"
/>
<ProjectCreateComponentsInput
v-model="newToken.token_link"
lg="w-1/2"
label="URL for explorer"
placeholder="Token explorer URL"
hint="Link to any explorer showing data about your native token"
/>
</div>
</template>
</ProjectCreateComponentsItemAdd>
</div>
</template>

View file

@ -0,0 +1,130 @@
<script setup lang="ts">
import * as yup from 'yup'
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
const { useProject, categoriesData, usecasesData, ecosystemsData } = useData()
const { saveProject } = useProject()
const validationSchema = yup.object().shape({
categories: yup.array().of(yup.string()).required().min(1),
usecases: yup.array().of(yup.string()).required().min(1),
ecosystems: yup.array().of(yup.string()).required().min(1),
description: yup.string().required(),
})
const { validate, meta, resetForm } = useForm({
validationSchema,
})
const { value: categories, errorMessage: categoriesError } = useField<string[]>('categories')
const { value: usecases, errorMessage: usecasesError } = useField<string[]>('usecases')
const { value: ecosystems, errorMessage: ecosystemsError } = useField<string[]>('ecosystems')
const { value: description, errorMessage: descriptionError } = useField<string>('description')
const day = ref(new Date(props.project?.product_launch_day || '').getDay())
const month = ref(new Date(props.project?.product_launch_day || '').getMonth())
const year = ref(new Date(props.project?.product_launch_day || '').getFullYear())
const isDead = ref(props.project?.sunset || false)
resetForm({
values: {
categories: props.project?.categories?.map(c => c.toLowerCase()) || [],
usecases: props.project?.usecases?.map(u => u.toLowerCase()) || [],
ecosystems: Array.isArray(props.project?.ecosystem) ? props.project?.ecosystem?.map(e => e.toLowerCase()) : [],
description: props.project?.description || '',
},
})
function isFormValid() {
validate()
if (meta.value.valid) {
return true
}
else {
return false
}
}
function save() {
saveProject({
categories: categories.value,
usecases: usecases.value,
ecosystem: ecosystems.value,
description: description.value,
product_launch_day: new Date(year.value, month.value, day.value).toISOString(),
sunset: isDead.value,
})
}
defineExpose({
isFormValid,
save,
})
</script>
<template>
<div mt-24px>
<ClientOnly>
<ProjectCreateComponentsSelectChips
v-model="categories"
label="Categories"
required
:options="categoriesData?.map(cat => ({ label: cat.name, value: cat.id }))"
placeholder="Add category"
hint="Choose categories that fits your project"
:error="categoriesError"
/>
<ProjectCreateComponentsSelectChips
v-model="usecases"
label="Use-cases"
required
:options="usecasesData?.map(uc => ({ label: uc.name, value: uc.id }))"
placeholder="Add use-case"
hint="What can be your project used for?"
:error="usecasesError"
/>
<ProjectCreateComponentsSelectChips
v-model="ecosystems"
label="Ecosystems"
required
:options="ecosystemsData?.map(ec => ({ label: ec.name, value: ec.id }))"
placeholder="Add ecosystem"
hint="Choose ecosystems that is your project part of"
:error="ecosystemsError"
/>
</ClientOnly>
<ProjectCreateComponentsInput
v-model="description"
lg="w-1/2"
label="Description"
text-area
required
hint="What kind of technology you use, what are your special features
and why should user use your project."
placeholder="Write something about your project"
:error="descriptionError"
/>
<ProjectCreateComponentsDatePicker
v-model:day="day"
v-model:month="month"
v-model:year="year"
label="Project launch date"
hint="Date of project emergence (Optional)"
/>
<span
text-16px
font-400
lg="text-16px"
>Other Information
</span>
<ProjectCreateComponentsToggle
v-model="isDead"
label="Sunset (project is dead)"
hint="Check if project is currently running and working"
/>
</div>
</template>

View file

@ -0,0 +1,116 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
type Fundings = {
name: string
link: string
}
function useFundings(project?: Partial<Project>) {
const fundings = ref<Fundings[]>(project?.funding as Fundings[] || [])
const newFunding = reactive<Fundings>({
name: '',
link: '',
})
function addFunding() {
fundings.value.push({ ...newFunding })
newFunding.name = ''
newFunding.link = ''
}
function removeFunding(index: number) {
fundings.value.splice(index, 1)
}
return {
fundings,
newFunding,
addFunding,
removeFunding,
}
}
const { fundings, newFunding, addFunding, removeFunding } = useFundings(props.project)
const { useProject } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
funding: fundings.value,
})
}
defineExpose({
save,
})
</script>
<template>
<div>
<ProjectCreateComponentsCategoryDivider
w-full
title="FUNDING"
/>
<ProjectCreateComponentsItem
v-for="funding in fundings"
:key="funding.name"
@remove="() => removeFunding(fundings.indexOf(funding))"
>
<template #label>
<div
flex
gap-2px
>
<span
class="text-app-black text-14px font-700"
lg="text-16px"
> {{ funding.name }}
</span>
</div>
</template>
<template #desc>
<NuxtLink
:to="funding.link"
hover:text-app-black
class="text-app-black/50 text-16px hidden"
lg="block"
>Link
</NuxtLink>
</template>
</ProjectCreateComponentsItem>
<ProjectCreateComponentsItemAdd
button-label="ADD FUNDING"
@add="addFunding()"
>
<template #content>
<div
flex
flex-col
gap-16px
lg="gap-24px"
>
<ProjectCreateComponentsInput
v-model="newFunding.name"
lg="w-50%"
label="Name"
placeholder="Investor's name"
hint="Name of entity investing into project"
/>
<ProjectCreateComponentsInput
v-model="newFunding.link"
lg="w-50%"
label="Link confirming investment"
placeholder="Add URL"
hint="Link to Article, PR or other sources confirming funding information"
/>
</div>
</template>
</ProjectCreateComponentsItemAdd>
</div>
</template>

View file

@ -0,0 +1,166 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
type Event = {
title: string
description: string
link: string
time: string
}
function useEvents(project?: Partial<Project>) {
const events = ref<Event[]>(project?.history as Event[] || [])
const newEvent = reactive<Event>({
title: '',
description: '',
link: '',
time: '',
})
const day = ref<number>()
const month = ref<number>()
const year = ref<number>()
function addEvent() {
events.value.push({ ...newEvent })
newEvent.title = ''
newEvent.description = ''
newEvent.link = ''
newEvent.time = ''
day.value = undefined
month.value = undefined
year.value = undefined
}
function removeEvent(index: number) {
events.value.splice(index, 1)
}
return {
events,
newEvent,
day,
month,
year,
addEvent,
removeEvent,
}
}
const { events, newEvent, addEvent, removeEvent, day, month, year } = useEvents(props.project)
watch([day, month, year], () => {
const yearValue = year.value
const monthValue = month.value
const dayValue = day.value
if (!yearValue || !monthValue || !dayValue) {
return null
}
newEvent.time = new Date(yearValue, monthValue - 1, dayValue).toString()
})
function formatDate(date: string) {
const d = new Date(date)
const day = String(d.getDate()).padStart(2, '0')
const month = String(d.getMonth() + 1).padStart(2, '0')
const year = d.getFullYear()
return `${day}/${month}/${year}`
}
const { useProject } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
history: events.value,
})
}
defineExpose({
save,
})
</script>
<template>
<div mt-24px>
<ProjectCreateComponentsItem
v-for="event in events"
:key="event.title"
@remove="() => removeEvent(events.indexOf(event))"
>
<template #label>
<div
flex
gap-2px
>
<span
class="text-app-black text-14px font-700"
lg="text-16px"
> {{ event.title }}
</span>
</div>
</template>
<template #desc>
<span
class="text-app-black/50 text-16px hidden"
lg="block"
>{{ formatDate(event.time) }}
</span>
<NuxtLink
:to="event.link"
hover:text-app-black
class="text-app-black/50 text-16px hidden"
lg="block"
>Link
</NuxtLink>
</template>
</ProjectCreateComponentsItem>
<ProjectCreateComponentsItemAdd
button-label="ADD EVENT"
@add="addEvent()"
>
<template #content>
<div
flex
flex-col
gap-16px
lg="gap-24px"
>
<ProjectCreateComponentsInput
v-model="newEvent.title"
lg="w-50%"
label="Event title"
placeholder="Enter title of event"
hint="Specific name of events (or what happened)"
/>
<ProjectCreateComponentsInput
v-model="newEvent.description"
lg="w-50%"
label="Event description"
placeholder="Describe this event"
hint="Detailed description of event (max. 1000 glyphs)"
/>
<ProjectCreateComponentsInput
v-model="newEvent.link"
lg="w-50%"
label="Event URL"
placeholder="Enter URL with event details"
hint="Link to Article, PR or other sources confirming event information"
/>
<ProjectCreateComponentsDatePicker
v-model:day="day"
v-model:month="month"
v-model:year="year"
label="Date of event"
placeholder="When this event happened?"
/>
</div>
</template>
</ProjectCreateComponentsItemAdd>
</div>
</template>

View file

@ -0,0 +1,160 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
const web = ref<string>(props.project?.links?.web || '')
const blog = ref<string>(props.project?.links?.blog || '')
const github = ref<string>(props.project?.links?.github || '')
const forum = ref<string>(props.project?.links?.forum || '')
const docs = ref<string>(props.project?.links?.docs || '')
const whitepaper = ref<string>(props.project?.links?.whitepaper || '')
const block_explorer = ref<string>(props.project?.links?.block_explorer || '')
const governance = ref<string>(props.project?.links?.governance || '')
const twitter = ref<string>(props.project?.links?.twitter || '')
const discord = ref<string>(props.project?.links?.discord || '')
const telegram = ref<string>(props.project?.links?.telegram || '')
const lens = ref<string>(props.project?.links?.lens || '')
const farcaster = ref<string>(props.project?.links?.farcaster || '')
const { useProject } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
links: {
web: web.value,
blog: blog.value,
github: github.value,
forum: forum.value,
docs: docs.value,
whitepaper: whitepaper.value,
block_explorer: block_explorer.value,
governance: governance.value,
twitter: twitter.value,
discord: discord.value,
telegram: telegram.value,
lens: lens.value,
farcaster: farcaster.value,
},
})
}
defineExpose({
save,
})
</script>
<template>
<div mt-24px>
<div
flex
flex-col
gap-16px
lg="grid grid-cols-2 gap-24px"
>
<ProjectCreateComponentsInput
v-model="web"
w-full
label="Website"
placeholder="ex. https://www.yourproject.com"
/>
<ProjectCreateComponentsInput
v-model="blog"
w-full
label="Blog"
placeholder="https://blog,yourproject.com"
/>
<ProjectCreateComponentsInput
v-model="github"
w-full
label="Github repository"
placeholder="Github.com/Yourproject"
/>
<ProjectCreateComponentsInput
v-model="forum"
w-full
label="Forum"
placeholder="https://forum,yourproject.com"
/>
</div>
<ProjectCreateComponentsCategoryDivider
w-full
title="TECHNOGY DETAILS"
/>
<div
flex
flex-col
gap-16px
lg="grid grid-cols-2 gap-24px"
>
<ProjectCreateComponentsInput
v-model="docs"
w-full
label="Documentation"
placeholder="https://docs.yourproject.com"
/>
<ProjectCreateComponentsInput
v-model="whitepaper"
w-full
label="Whitepaper"
placeholder="https://www.yourproject.com/whitepaper.pdf"
/>
<ProjectCreateComponentsInput
v-model="block_explorer"
w-full
label="Explorer"
placeholder="https://explorer.yourproject.com"
/>
<ProjectCreateComponentsInput
v-model="governance"
w-full
label="Governance"
placeholder="https://governance.yourproject.com"
/>
</div>
<ProjectCreateComponentsCategoryDivider
w-full
title="SOCIAL NETWORKS"
/>
<div
flex
flex-col
gap-16px
lg="grid grid-cols-2 gap-24px"
>
<ProjectCreateComponentsInput
v-model="twitter"
w-full
label="Twitter"
placeholder="Twitter profile URL"
/>
<ProjectCreateComponentsInput
v-model="discord"
w-full
label="Discord"
placeholder="Discord invite link"
/>
<ProjectCreateComponentsInput
v-model="telegram"
w-full
label="Telegram"
placeholder="Telegram channel URL"
/>
<ProjectCreateComponentsInput
v-model="lens"
w-full
label="Lens"
placeholder="Lens profile URL"
/>
<ProjectCreateComponentsInput
v-model="farcaster"
w-full
label="Farcaster"
placeholder="Farcaster profile URL"
/>
</div>
</div>
</template>

View file

@ -0,0 +1,104 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
const privacyPolicyUrl = ref(props.project?.privacy_policy?.link || '')
const compliance = ref(props.project?.compliance || '')
const kyc = ref(props.project?.tracebility?.kyc || false)
const defaultPrivacy = ref(props.project?.default_privacy || false)
const signRequirements = ref(Array.isArray(props.project?.tracebility?.sign_in_type_requirments) ? props.project?.tracebility?.sign_in_type_requirments?.map(s => s.toLowerCase()) : [])
const trackedData = ref(props.project?.tracebility?.tracked_data || '')
const dataUsage = ref(props.project?.privacy_policy?.data_usage || '')
const { useProject, signInRequirmentsData } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
privacy_policy: {
defined: privacyPolicyUrl.value ? true : false,
link: privacyPolicyUrl.value,
data_usage: dataUsage.value,
},
compliance: compliance.value,
tracebility: {
kyc: kyc.value,
tracked_data: trackedData.value,
},
default_privacy: defaultPrivacy.value,
})
}
defineExpose({
save,
})
</script>
<template>
<div mt-24px>
<div
flex
flex-col
gap-24px
>
<ProjectCreateComponentsInput
v-model="privacyPolicyUrl"
lg="w-50%"
label="Link to projects Privacy Policy"
placeholder="https://policy.yourproject.com"
hint="URL of document defining you privacy policy and data usage"
/>
<ProjectCreateComponentsInput
v-model="compliance"
lg="w-50%"
label="Compliance"
placeholder="Enter compliance that project follow"
hint="Is project Compliant with regulations? (ex. OFAC, Privacy Act)"
/>
<ProjectCreateComponentsToggle
v-model="kyc"
label="KYC (Know your customer)"
hint="Is project requiering KYC for its users?"
/>
<ProjectCreateComponentsToggle
v-model="defaultPrivacy"
label="Defult privacy"
hint="Is maximum privacy turned on by default,"
/>
</div>
<ProjectCreateComponentsCategoryDivider
w-full
title="ADDITIONAL INFO"
/>
<div
flex
flex-col
gap-24px
>
<ProjectCreateComponentsSelectChips
v-model="signRequirements"
label="Sign-in requirements"
:options="signInRequirmentsData.map(s => ({ label: s.name, value: s.id }))"
placeholder="Add requirement"
hint="What do you need to provide to use your project?"
/>
<ProjectCreateComponentsInput
v-model="trackedData"
lg="w-50%"
label="Tracked data"
placeholder="Enter tracked data"
hint="What data does project collect? (ex. IP, Location, E-mail, Wallet,...)"
/>
<ProjectCreateComponentsInput
v-model="dataUsage"
lg="w-50%"
label="Data usage"
placeholder="What are you using data for?"
hint="How does project use user info (resseling, information gathering, none,...)"
/>
</div>
</div>
</template>

View file

@ -0,0 +1,199 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
type Audit = {
name: string
url: string
time: string
}
function useAudits(project?: Partial<Project>) {
const audits = ref<Audit[]>(project?.audits as Audit[] || [])
const newAudit = reactive<Audit>({
name: '',
url: '',
time: '',
})
const day = ref<number>()
const month = ref<number>()
const year = ref<number>()
function addAudit() {
audits.value.push({ ...newAudit })
newAudit.name = ''
newAudit.url = ''
newAudit.time = ''
day.value = undefined
month.value = undefined
year.value = undefined
}
function removeAudit(index: number) {
audits.value.splice(index, 1)
}
return {
audits,
newAudit,
day,
month,
year,
addAudit,
removeAudit,
}
}
const { audits, newAudit, addAudit, removeAudit, day, month, year } = useAudits(props.project)
watch([day, month, year], () => {
const yearValue = year.value
const monthValue = month.value
const dayValue = day.value
if (!yearValue || !monthValue || !dayValue) {
return null
}
newAudit.time = new Date(yearValue, monthValue - 1, dayValue).toString()
})
function formatDate(date: string) {
const d = new Date(date)
const day = String(d.getDate()).padStart(2, '0')
const month = String(d.getMonth() + 1).padStart(2, '0')
const year = d.getFullYear()
return `${day}/${month}/${year}`
}
const thirdPartyDep = ref(props.project?.third_party_dependency || '')
const socialTrust = ref(props.project?.social_trust || '')
const spof = ref(props.project?.technical_spof || '')
const { useProject } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
audits: audits.value,
third_party_dependency: thirdPartyDep.value,
social_trust: socialTrust.value,
technical_spof: spof.value,
})
}
defineExpose({
save,
})
</script>
<template>
<div>
<ProjectCreateComponentsCategoryDivider
w-full
title="AUDITS"
/>
<ProjectCreateComponentsItem
v-for="audit in audits"
:key="audit.name"
@remove="() => removeAudit(audits.indexOf(audit))"
>
<template #label>
<div
flex
gap-2px
>
<span
class="text-app-black text-14px font-700"
lg="text-16px"
> {{ audit.name }}
</span>
</div>
</template>
<template #desc>
<span
class="text-app-black/50 text-16px hidden"
lg="block"
>{{ formatDate(audit.time) }}
</span>
<NuxtLink
target="_blank"
:to="audit.url"
hover:text-app-black
class="text-app-black/50 text-16px hidden"
lg="block"
>Link
</NuxtLink>
</template>
</ProjectCreateComponentsItem>
<ProjectCreateComponentsItemAdd
button-label="ADD AUDIT"
@add="addAudit()"
>
<template #content>
<div
flex
flex-col
gap-16px
lg="gap-24px"
>
<ProjectCreateComponentsInput
v-model="newAudit.name"
lg="w-50%"
label="Audit name"
placeholder="Audit name"
hint="Title and Name of audit company"
/>
<ProjectCreateComponentsInput
v-model="newAudit.url"
lg="w-50%"
label="URL of audit"
placeholder="URL of audit"
hint="Enter URL of audit"
/>
<ProjectCreateComponentsDatePicker
v-model:day="day"
v-model:month="month"
v-model:year="year"
label="Date of audit"
placeholder="When has been project audited?"
/>
</div>
</template>
</ProjectCreateComponentsItemAdd>
<ProjectCreateComponentsCategoryDivider
w-full
title="ADDITIONAL INFO"
/>
<ProjectCreateComponentsInput
v-model="thirdPartyDep"
textarea
:textarea-rows="3"
lg="w-50%"
label="Third party dependency"
placeholder="Write about dependencies"
hint="Is your project dependend on third service like Uniswap pools, Network or third party contract, Chainlink oracle,...?"
/>
<ProjectCreateComponentsInput
v-model="socialTrust"
textarea
:textarea-rows="3"
lg="w-50%"
label="Social trust"
placeholder="Who does project trust with treasury and security?"
hint="Who is governing project and how? DAO, Multisig wallet of X,"
/>
<ProjectCreateComponentsInput
v-model="spof"
textarea
:textarea-rows="3"
lg="w-50%"
label="Single point of failure"
placeholder="What is single point of failture for project"
hint="What have to happen to shutdown, hack project or leak data? ex. Hack of SHA256, Stolen admin keys,..."
/>
</div>
</template>

View file

@ -0,0 +1,127 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
const isAnonymousTeam = ref<boolean>(props.project?.team?.anonymous || false)
type Member = {
name: string
link: string
}
function useMembers(project?: Partial<Project>) {
const members = ref<Member[]>(project?.team?.teammembers as Member[] || [])
const newMember = reactive<Member>({
name: '',
link: '',
})
function addMember() {
members.value.push({ ...newMember })
newMember.name = ''
newMember.link = ''
}
function removeMember(index: number) {
members.value.splice(index, 1)
}
return {
members,
newMember,
addMember,
removeMember,
}
}
const { members, newMember, addMember, removeMember } = useMembers(props.project)
const { useProject } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
team: {
anonymous: isAnonymousTeam.value,
teammembers: members.value,
},
})
}
defineExpose({
save,
})
</script>
<template>
<div mt-24px>
<ProjectCreateComponentsToggle
v-model="isAnonymousTeam"
label="Anonymous team"
hint="Are developers able to upgrade and change deployed contract?"
/>
<ProjectCreateComponentsCategoryDivider
w-full
title="TEAM MEMBERS"
/>
<ProjectCreateComponentsItem
v-for="member in members"
:key="member.name"
@remove="() => removeMember(members.indexOf(member))"
>
<template #label>
<div
flex
gap-2px
>
<span
class="text-app-black text-14px font-700"
lg="text-16px"
> {{ member.name }}
</span>
</div>
</template>
<template #desc>
<NuxtLink
target="_blank"
:to="member.link"
hover:text-app-black
class="text-app-black/50 text-16px hidden"
lg="block"
>Link
</NuxtLink>
</template>
</ProjectCreateComponentsItem>
<ProjectCreateComponentsItemAdd
button-label="ADD MEMBER"
@add="addMember()"
>
<template #content>
<div
flex
flex-col
gap-16px
lg="gap-24px"
>
<ProjectCreateComponentsInput
v-model="newMember.name"
lg="w-50%"
label="Team member name"
placeholder="Enter member name"
hint="Full member's name"
/>
<ProjectCreateComponentsInput
v-model="newMember.link"
lg="w-50%"
label="Profile URL"
placeholder="Linkedin, Farcaster, Twitter,..."
hint="Link to members social profile (ex. Twitter, Linkedin, Lens,...)"
/>
</div>
</template>
</ProjectCreateComponentsItemAdd>
</div>
</template>

View file

@ -0,0 +1,107 @@
<script setup lang="ts">
import type { Project } from '~/types'
const props = defineProps<{
project?: Partial<Project>
}>()
const version = ref(props.project?.project_phase?.toLowerCase() || undefined)
const openSource = ref(props.project?.blockchain_features?.opensource || false)
const upgradability = ref(props.project?.blockchain_features?.upgradability?.enabled || false)
const assetType = ref(props.project?.blockchain_features?.asset_custody_type?.toLowerCase() || '')
const encryption = ref(props.project?.blockchain_features?.encryption || '')
const peerToPeer = ref(props.project?.blockchain_features?.p2p)
const decentralizedStorage = ref(props.project?.storage?.decentralized)
const { useProject, projectPhaseData, assetCustodyData } = useData()
const { saveProject } = useProject()
function save() {
saveProject({
project_phase: version.value,
blockchain_features: {
opensource: openSource.value,
upgradability: {
enabled: upgradability.value,
},
asset_custody_type: assetType.value,
encryption: encryption.value,
p2p: peerToPeer.value,
},
storage: {
decentralized: decentralizedStorage.value,
},
})
}
defineExpose({
save,
})
</script>
<template>
<div>
<ProjectCreateComponentsCategoryDivider
w-full
title="VERSION"
/>
<ProjectCreateComponentsRadio
v-model="version"
:options="projectPhaseData.map(p => ({ label: p.name, value: p.id }))"
/>
<ProjectCreateComponentsCategoryDivider
w-full
title="FEATURES"
/>
<div
flex
flex-col
gap-24px
>
<ProjectCreateComponentsToggle
v-model="openSource"
label="Open source"
hint="Check when projects source code is openly available and editable"
/>
<ProjectCreateComponentsToggle
v-model="upgradability"
label="Upgradability"
hint="Are developers able to upgrade and change deployed contract?"
/>
</div>
<ProjectCreateComponentsSelect
v-model="assetType"
:options="assetCustodyData.map(c => ({ label: c.name, value: c.id }))"
label="Asset custody type"
placeholder="Select custody type"
hint="How are users fund handled? (non-custody, multisig, pool,...)"
/>
<ProjectCreateComponentsCategoryDivider
w-full
title="ADDITIONAL INFO"
/>
<div
flex
flex-col
gap-24px
>
<ProjectCreateComponentsInput
v-model="encryption"
lg="w-1/2"
label="Technology type / Encryption"
hint="Define technologies your project uses for privacy ex. Zero-Knowledge (ZK), SHA256,"
placeholder="Technology name"
/>
<ProjectCreateComponentsToggle
v-model="peerToPeer"
label="Peer to Peer (P2P)"
hint="Check when you transfer / communicate withou intermediaries"
/>
<ProjectCreateComponentsToggle
v-model="decentralizedStorage"
label="Decentralized storage"
hint="Is your data hosted on IPFS, Filecoin or other decentralized storage?"
/>
</div>
</div>
</template>

View file

@ -0,0 +1,38 @@
<script setup lang="ts">
defineProps<{
title: string
}>()
</script>
<template>
<div
w-full
flex
justify-between
gap-8px
my-40px
lg="gap-32px"
>
<div
flex
items-center
gap-8px
text-16px
font-700
class="text-app-white/50"
>
<span text-nowrap>{{ title }}</span>
</div>
<div
w-full
flex
items-center
>
<hr
border="t-2px white/50"
w-full
shrink-1
>
</div>
</div>
</template>

View file

@ -0,0 +1,119 @@
<script setup lang="ts">
export interface SelectProps {
label?: string
hint?: string
placeholder?: string
required?: boolean
modelValue?: any
}
defineProps<SelectProps>()
const months = [
{ label: 'January', value: 1 },
{ label: 'February', value: 2 },
{ label: 'March', value: 3 },
{ label: 'April', value: 4 },
{ label: 'May', value: 5 },
{ label: 'June', value: 6 },
{ label: 'July', value: 7 },
{ label: 'August', value: 8 },
{ label: 'September', value: 9 },
{ label: 'October', value: 10 },
{ label: 'November', value: 11 },
{ label: 'December', value: 12 },
]
const years = Array.from({ length: 101 }, (_, i) => new Date().getFullYear() - i)
const day = defineModel<number>('day')
const month = defineModel<number>('month')
const year = defineModel<number>('year')
const daysInMonth = (month: number, year: number) => {
const monthObj = months.find(m => m.value === month)
if (!monthObj) {
throw new Error(`Invalid month value: ${month}`)
}
const monthIndex = months.indexOf(monthObj)
return new Date(year, monthIndex + 1, 0).getDate()
}
const days = computed(() => {
if (month.value && year.value) {
return Array.from({ length: daysInMonth(month.value, year.value) }, (_, i) => i + 1)
}
return Array.from({ length: 31 }, (_, i) => i + 1)
})
</script>
<template>
<div
flex
flex-col
gap-8px
>
<label
v-if="label"
font-400
text-14px
lg:text-16px
text-app-white
>
{{ label }}
<span
v-if="required"
text-app-danger
text-16px
>*</span>
</label>
<div
class="flex flex-col"
gap-8px
lg="flex flex-row gap-24px"
>
<div
class="flex flex-row"
lg="w-1/2"
relative
>
<SelectBox
v-model="day"
w-full
:options="days.map(day => ({ label: day.toString(), value: day }))"
placeholder="Day"
:border-opacity="30"
:is-margin-top="false"
/>
<SelectBox
v-model="month"
w-full
mx--2px
:options="months.map(month => ({ label: month.label, value: month.value }))"
placeholder="Month"
:border-opacity="30"
:is-margin-top="false"
/>
<SelectBox
v-model="year"
w-full
:options="years.map(year => ({ label: year.toString(), value: year }))"
placeholder="Year"
:border-opacity="30"
:is-margin-top="false"
/>
</div>
<span
v-if="hint"
lg="left-1/2 self-center"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ hint }}
</span>
</div>
</div>
</template>

View file

@ -0,0 +1,81 @@
<script setup lang="ts">
export interface SelectProps {
label?: string
hint?: string
placeholder?: string
required?: boolean
modelValue?: any
textarea?: boolean
textareaRows?: number
lgWidth?: string
error?: string
}
defineProps<SelectProps>()
const model = defineModel<string>()
</script>
<template>
<div
class="w-full!"
flex
flex-col
gap-8px
>
<label
v-if="label"
font-400
text-14px
lg:text-16px
text-app-white
>
{{ label }}
<span
v-if="required"
text-app-danger
text-16px
>*</span>
</label>
<div
w-full
flex
flex-col
gap-8px
lg="flex flex-row gap-24px"
relative
>
<div v-bind="$attrs">
<textarea
v-if="textarea"
v-model="model"
:rows="textareaRows"
:placeholder="placeholder"
class="relative w-full p-8px text-left border-2px text-app-white bg-black sm:text-sm sm:leading-6 focus:ring-0 focus:outline-none"
:class="error ? 'border-app-danger/50' : 'border-white/30'"
/>
<input
v-else
v-model="model"
:placeholder="placeholder"
type="text"
class="relative w-full p-8px text-left border-2px text-app-white bg-black sm:text-sm sm:leading-6 focus:ring-0 focus:outline-none"
:class="error ? 'border-app-danger/50' : 'border-white/30'"
>
</div>
<span
v-if="hint"
lg="left-1/2 self-center"
v-bind="$attrs"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ hint }}
</span>
</div>
</div>
</template>

View file

@ -0,0 +1,33 @@
<script setup lang="ts">
const emits = defineEmits(['remove'])
</script>
<template>
<div
flex
justify-between
items-center
py-12px
px-16px
bg-app-white
>
<slot name="label" />
<div
flex
items-center
gap-16px
>
<slot
class="text-app-black/50"
name="desc"
/>
<button @click="emits('remove')">
<UnoIcon
i-heroicons-solid-x
text-12px
text-app-black
/>
</button>
</div>
</div>
</template>

View file

@ -0,0 +1,31 @@
<script setup lang="ts">
defineProps<{
buttonLabel: string
}>()
const emits = defineEmits(['add'])
</script>
<template>
<div
flex
flex-col
w-full
border-2
border-app-white
p-24px
>
<slot name="content" />
<Button
mt-16px
lg="w-fit mt-24px"
border
inverted-color
@click="emits('add')"
>
<span
px-24px
>{{ buttonLabel }}</span>
</Button>
</div>
</template>

View file

@ -0,0 +1,61 @@
<script setup lang="ts">
interface OptionItem<T> {
label: string
value: T
}
export interface SelectProps {
options: OptionItem<string>[]
}
defineProps<SelectProps>()
const selected = defineModel<string>()
</script>
<template>
<div w-full>
<div w-full>
<HeadlessRadioGroup v-model="selected">
<div class="flex flex-wrap gap-20px">
<HeadlessRadioGroupOption
v-for="option in options"
:key="option.label"
v-slot="{ checked }"
as="template"
:value="option.value"
>
<div class="cursor-pointer w-49% flex items-center gap-22px">
<div
rounded-full
p-6px
flex
items-center
justify-centerě
:class="checked ? 'bg-app-white text-app-black' : 'outline outline-2px outline-offset--2 outline-app-white text-app-white'"
>
<UnoIcon
v-if="checked"
i-heroicons-solid-check
text-20px
/>
<div
v-else
w-20px
h-20px
/>
</div>
<HeadlessRadioGroupLabel
as="p"
:class="checked ? 'text-app-white' : 'text-app-white/50'"
class="font-medium"
>
{{ option.label }}
</HeadlessRadioGroupLabel>
</div>
</HeadlessRadioGroupOption>
</div>
</HeadlessRadioGroup>
</div>
</div>
</template>

View file

@ -0,0 +1,107 @@
<script setup lang="ts">
import type { InputOption } from '~/types'
const props = defineProps<SelectProps>()
const emits = defineEmits(['update:modelValue'])
interface SelectProps {
options: InputOption[]
label?: string
modelValue: string
placeholder?: string
required?: boolean
hint?: string
}
const selectedValue = useVModel(props, 'modelValue', emits)
</script>
<template>
<div
flex
flex-col
gap-8px
lg="flex flex-row gap-24px"
relative
>
<div lg="w-1/2">
<HeadlessListbox
v-model="selectedValue"
as="div"
>
<HeadlessListboxLabel
v-if="label"
font-400
text-14px
lg:text-16px
text-app-white
>
{{ label }}
<span
v-if="required"
text-app-danger
text-16px
>*</span>
</HeadlessListboxLabel>
<div
class="relative font-700 mt-2 bg-app-black"
>
<HeadlessListboxButton
as="div"
class="relative w-full cursor-pointer p-8px text-left border-2px text-app-white bg-black border-white/30 sm:text-sm sm:leading-6"
>
<span
class="block truncate mr-8px"
:class="[selectedValue ? 'text-app-white' : 'font-400 text-white/50']"
>
{{ props.options.find(option => option.value === selectedValue)?.label || props.placeholder }}
</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
class="text-app-white"
aria-hidden="true"
/>
</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 border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm border-white/30"
>
<HeadlessListboxOption
v-for="option in props.options"
:key="option.value"
v-slot="{ selected }"
as="template"
:value="option.value"
class="w-full relative cursor-pointer select-none py-8px p-16px border-white/30"
>
<span
class="block truncate"
:class="[selected ? 'font-semibold' : 'font-normal']"
>{{ option.label }}</span>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</transition>
</div>
</HeadlessListbox>
</div>
<span
v-if="hint"
lg="mt-28px left-1/2 self-center"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ hint }}
</span>
</div>
</template>

View file

@ -0,0 +1,213 @@
<script setup lang="ts">
interface OptionItem<T> {
label: string
value: T
}
export interface SelectProps {
options: OptionItem<any>[]
label?: string
hint?: string
placeholder?: string
required?: boolean
multiple?: boolean
canAddNew?: boolean
modelValue?: any
error?: string
}
const props = withDefaults(defineProps<SelectProps>(), {
multiple: true,
})
const emits = defineEmits(['update:modelValue'])
const modelValue = useVModel(props, 'modelValue', emits)
const query = ref('')
const newOptions = ref<OptionItem<any>[]>([])
const options = computed(() => {
return [...props.options, ...newOptions.value]
})
const filteredOptions = computed(() =>
query.value === ''
? options.value
: options.value.filter(o => o.label.toLowerCase().includes(query.value.toLowerCase())),
)
// const modelValue = ref(props.modelValue || (props.multiple ? [] : null))
const selectedOptions = computed(() => props.multiple ? options.value.filter(o => modelValue.value?.includes(o.value)) : [])
function deleteOption(value: string) {
const index = modelValue.value.indexOf(value)
if (index !== -1)
modelValue.value.splice(index, 1)
newOptions.value = newOptions.value.filter((o: any) => o.value !== value)
}
function addOption() {
if (query.value === '' || !props.canAddNew) return
if (!options.value.some(o => o.value === query.value)) {
newOptions.value.push({ label: query.value, value: query.value })
}
if (props.multiple) {
if (!modelValue.value) modelValue.value = []
modelValue.value.push(query.value)
}
else {
modelValue.value = query.value
}
query.value = ''
}
</script>
<template>
<div
flex
flex-col
gap-8px
lg="flex flex-row gap-24px"
relative
>
<div lg="w-1/2">
<HeadlessCombobox
v-model="modelValue"
:multiple="props.multiple"
>
<HeadlessComboboxLabel
v-if="label"
font-400
text-14px
lg:text-16px
text-app-white
>
{{ label }}
<span
v-if="required"
text-app-danger
text-16px
>*</span>
</HeadlessComboboxLabel>
<div class="relative font-700 mt-2 bg-app-black">
<HeadlessComboboxButton
as="div"
class="relative w-full cursor-pointer p-8px text-left border-2px text-app-white bg-black sm:text-sm sm:leading-6"
:class="error ? 'border-app-danger/50' : 'border-white/30'"
>
<div class="flex flex-wrap gap-8px">
<span
v-for="option in selectedOptions"
:key="option.value"
class="font-700 text-14px leading-20px flex gap-8px items-center bg-app-white text-black px-8px py-4px"
lg="text-16px"
>
{{ option.label }}
<button
type="button"
class="ml-1 text-black"
@click.stop="deleteOption(option.value)"
>
<UnoIcon
i-heroicons-solid-x
text-16px
text-black
/>
</button>
</span>
<HeadlessComboboxInput
class="text-14px font-400 leading-20px ml-8px w-fit bg-transparent border-none focus:ring-0 focus:outline-none"
lg="16px"
:placeholder="placeholder"
@keyup.enter="addOption"
@change="query = $event.target.value"
/>
</div>
<span class="absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
class="text-app-white"
aria-hidden="true"
/>
</span>
</HeadlessComboboxButton>
</div>
<Transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
@after-leave="query = ''"
>
<HeadlessComboboxOptions
lg="w-1/2"
class="w-full absolute z-100 max-h-60 divide-y-2px border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm border-white/30"
>
<div
v-if="filteredOptions.length === 0 && query !== '' && props.canAddNew"
class="gap-4px flex items-center relative cursor-default select-none px-4 py-2 text-app-white"
@click="addOption"
>
<UnoIcon
i-heroicons-solid-plus
class="text-app-white"
/>
<span>Add</span>
</div>
<div
v-else-if="filteredOptions.length === 0 && query !== '' && !props.canAddNew"
class="relative cursor-default select-none px-4 py-2 text-app-white opacity-50"
>
Nothing found.
</div>
<HeadlessComboboxOption
v-for="option in filteredOptions"
:key="option.value"
v-slot="{ selected, active }"
class="border-white/30"
as="template"
:value="option.value"
>
<li
class="w-full relative cursor-pointer select-none py-8px p-16px"
:class="[active ? 'bg-#ffffff1a' : 'text-white']"
>
<span
class="block truncate"
:class="[selected ? 'font-semibold' : 'font-normal']"
>{{ option.label }}</span>
<span
v-if="selected"
class="absolute inset-y-0 right-0 flex items-center pr-3 text-white"
>
<UnoIcon
i-heroicons-solid-check
text-20px
aria-hidden="true"
/>
</span>
</li>
</HeadlessComboboxOption>
</HeadlessComboboxOptions>
</Transition>
</HeadlessCombobox>
</div>
<span
v-if="hint"
lg="mt-28px left-1/2 self-center"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ hint }}
</span>
</div>
</template>

View file

@ -0,0 +1,81 @@
<script setup lang="ts">
defineProps<{
label?: string
hint?: string
}>()
const enabled = defineModel<boolean>()
</script>
<template>
<div
flex
flex-col
gap-8px
lg="flex flex-row gap-24px"
relative
>
<div
flex
items-center
gap-24px
lg="w-1/2"
>
<HeadlessSwitch
v-model="enabled"
:class="enabled ? 'bg-white/10' : 'bg-white/10'"
class="relative inline-flex h-[28px] w-[68px] shrink-0 cursor-pointer transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"
>
<span
aria-hidden="true"
:class="enabled ? 'translate-x-9 bg-app-white' : 'translate-x-0 bg-app-black border-2 border-app-white'"
flex
items-center
class="pointer-events-none inline-block h-[28px] w-[32px] transform shadow-lg ring-0 transition duration-200 ease-in-out"
>
<UnoIcon
v-if="enabled"
transition
duration-200
ease-in-out
i-heroicons-solid-check
w-full
text-20px
text-app-black
/>
<UnoIcon
v-else
transition
duration-200
ease-in-out
i-heroicons-solid-x
w-full
text-20px
text-app-white
/>
</span>
</HeadlessSwitch>
<span
v-if="label"
text-14px
font-400
lg="text-16px"
:class="enabled ? 'text-app-white' : 'text-app-white/50'"
>
{{ label }}
</span>
</div>
<span
v-if="hint"
lg="left-1/2 self-center"
font-400
italic
lg:text-14px
text-12px
text-app-white
opacity-50
>
{{ hint }}
</span>
</div>
</template>

View file

@ -41,8 +41,6 @@ const calculateScore = computed(() => {
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

View file

@ -3,9 +3,9 @@ import { useRoute } from 'vue-router'
const route = useRoute()
const githubProjectUrl = computed(() => {
return `https://github.com/web3privacy/explorer-data/blob/main/src/projects/${route.params.id}/index.yaml`
})
// const githubProjectUrl = computed(() => {
// return `https://github.com/web3privacy/explorer-data/blob/main/src/projects/${route.params.id}/index.yaml`
// })
</script>
<template>
@ -20,7 +20,7 @@ const githubProjectUrl = computed(() => {
>
<NavigationButton
w-230px
@click="$router.back()"
@click="$router.push('/')"
>
<span
block
@ -46,9 +46,8 @@ const githubProjectUrl = computed(() => {
<EditButton
px-16px
py-8px
hover:bg-white
hover:text-black
:to="githubProjectUrl"
hover="cursor-pointer bg-white text-black"
@click="$router.push('/project/' + route.params.id + '/edit')"
>
<span
text-16px

View file

@ -4,14 +4,18 @@ import type { InputOption } from '~/types'
const props = withDefaults(defineProps<SelectProps>(), {
isMarginTop: true,
blackAndWhite: true,
borderOpacity: 100,
})
const emits = defineEmits(['update:modelValue'])
interface SelectProps {
options: InputOption[]
modelValue: string
label?: string
modelValue: any
isMarginTop?: boolean
blackAndWhite?: boolean
borderOpacity?: number
placeholder?: string
}
const selectedValue = useVModel(props, 'modelValue', emits)
@ -28,13 +32,18 @@ const selectedValue = useVModel(props, 'modelValue', emits)
>
<HeadlessListboxButton
class="relative w-full cursor-pointer py-8px p-16px text-left border-2px sm:text-sm sm:leading-6"
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
:class="[blackAndWhite ? 'text-app-white' : 'text-app-black', `border-white/${borderOpacity}`]"
>
<span class="block truncate mr-8px">{{ props.options.find(option => option.value === selectedValue)?.label }}</span>
<span
class="block truncate mr-8px"
:class="[selectedValue ? 'text-app-white' : 'font-400 text-white/50']"
>
{{ props.options.find(option => option.value === selectedValue)?.label || props.placeholder }}
</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<UnoIcon
i-heroicons-solid-chevron-down
:class="[blackAndWhite ? ' text-app-white' : 'text-app-black']"
:class="[blackAndWhite ? 'text-app-white' : 'text-app-black']"
/>
</span>
</HeadlessListboxButton>
@ -45,24 +54,22 @@ const selectedValue = useVModel(props, 'modelValue', emits)
leave-to-class="opacity-0"
>
<HeadlessListboxOptions
:class="`border-white/${borderOpacity}`"
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"
>
<HeadlessListboxOption
v-for="option in props.options"
:key="option.value"
v-slot="{ active, selected }"
v-slot="{ selected }"
as="template"
:value="option.value"
>
<li
class="w-full relative cursor-pointer select-none py-8px p-16px"
:class="[active ? 'bg-#ffffff1a' : 'text-white']"
class="py-8px p-16px cursor-pointer"
:class="`border-white/${borderOpacity}`"
>
<span
class="block truncate"
:class="[selected ? 'font-semibold' : 'font-normal']"
>{{ option.label }}</span>
</li>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</transition>

View file

@ -1,6 +1,16 @@
import { Buffer } from 'buffer'
import type { Category, Project, ProjectShallow } from '~/types'
export const useData = defineStore('data', () => {
const assetsData = useState<{ id: string, name: string }[]>('assetsData')
const categoriesData = useState<{ id: string, name: string }[]>('categoriesData')
const featuresData = useState<{ id: string, name: string }[]>('featuresData')
const ecosystemsData = useState<{ id: string, name: string }[]>('ecosystemsData')
const usecasesData = useState<{ id: string, name: string }[]>('usecasesData')
const projectPhaseData = useState<{ id: string, name: string }[]>('projectPhaseData')
const assetCustodyData = useState<{ id: string, name: string }[]>('assetCustodyData')
const signInRequirmentsData = useState<{ id: string, name: string }[]>('signInRequirmenetsData')
const categories = useState<Category[]>('categories')
const projects = useState<Project[]>('projects')
const selectedCategoryId = useState(() => 'defi')
@ -25,6 +35,13 @@ export const useData = defineStore('data', () => {
const data = await $fetch<{
categories: Category[]
projects: Project[]
assets: { id: string, name: string }[]
features: { id: string, name: string }[]
ecosystems: { id: string, name: string }[]
usecases: { id: string, name: string }[]
project_phase: { id: string, name: string }[]
asset_custody_type: { id: string, name: string }[]
sign_in_type_requirments: { id: string, name: string }[]
}>('/api/data')
projects.value = data.projects.filter(p => p.name)
categories.value = data.categories.map((c) => {
@ -33,6 +50,15 @@ export const useData = defineStore('data', () => {
).length
return c
}).filter(c => c.projectsCount > 0)
assetsData.value = data.assets.map(a => ({ id: a.id.toLowerCase(), name: a.name }))
categoriesData.value = data.categories.map(c => ({ id: c.id.toLowerCase(), name: c.name }))
featuresData.value = data.features.map(f => ({ id: f.id.toLowerCase(), name: f.name }))
ecosystemsData.value = data.ecosystems.map(e => ({ id: e.id.toLowerCase(), name: e.name }))
usecasesData.value = data.usecases.map(u => ({ id: u.id.toLowerCase(), name: u.name }))
projectPhaseData.value = data.project_phase.map(p => ({ id: p.id.toLowerCase(), name: p.name }))
assetCustodyData.value = data.asset_custody_type.map(a => ({ id: a.id.toLowerCase(), name: a.name }))
signInRequirmentsData.value = data.sign_in_type_requirments.map(s => ({ id: s.id.toLowerCase(), name: s.name }))
}
catch (e) {
console.error(e)
@ -120,11 +146,74 @@ export const useData = defineStore('data', () => {
const filteredProjectsCount = computed(() => filteredProjects.value.length)
function useProject() {
const project = ref<Partial<Project>>()
const projectImage = ref<File>()
function setProject(id: string) {
project.value = getProjectById(id, { shallow: false }) as Project
}
function clearProject() {
project.value = undefined
}
function saveProject(data: Partial<Project>) {
project.value = {
...project.value,
...data,
}
}
function saveProjectImage(image: File) {
projectImage.value = image
}
async function publishProject() {
try {
const imageArrayBuffer = await projectImage.value?.arrayBuffer()
let imageBuffer: Buffer | undefined
if (imageArrayBuffer)
imageBuffer = Buffer.from(imageArrayBuffer)
await $fetch(`/api/data`, {
method: 'POST',
body: {
project: project.value,
image: {
type: projectImage.value?.type,
data: imageBuffer?.toString('base64'),
},
},
})
}
catch (e) {
console.error(e)
}
}
return {
project,
setProject,
clearProject,
saveProject,
saveProjectImage,
publishProject,
}
}
return {
selectedCategoryId,
filter,
switcher,
categories,
assetsData,
categoriesData,
featuresData,
ecosystemsData,
usecasesData,
projectPhaseData,
assetCustodyData,
signInRequirmentsData,
projects,
shallowProjects,
filteredProjectsCount,
@ -133,5 +222,6 @@ export const useData = defineStore('data', () => {
getProjectsByCategory,
filteredProjects,
projectToShallow,
useProject,
}
})

6
layouts/create.vue Normal file
View file

@ -0,0 +1,6 @@
<template>
<div h-full w-full>
<Navigation />
<slot />
</div>
</template>

View file

@ -10,6 +10,7 @@ export default defineNuxtConfig({
'nuxt-lodash',
'nuxt-headlessui',
'@nuxt/image',
'@vee-validate/nuxt',
],
sourcemap: {
server: true,

View file

@ -26,16 +26,20 @@
"@nuxtjs/color-mode": "^3.4.4",
"@pinia/nuxt": "^0.5.4",
"@unocss/nuxt": "^0.62.3",
"@vee-validate/nuxt": "^4.13.2",
"@vueuse/nuxt": "^11.0.3",
"eslint": "^9.9.1",
"nuxt": "^3.13.0",
"nuxt-headlessui": "^1.2.0",
"nuxt-lodash": "^2.5.3",
"octokit": "^4.0.2",
"pinia": "^2.2.2",
"taze": "^0.16.7",
"typescript": "^5.5.4",
"vitest": "^2.0.5",
"vue-tsc": "^2.1.4"
"vue-tsc": "^2.1.4",
"yaml": "^2.5.1",
"yup": "^1.4.0"
},
"overrides": {
"vue": "latest"

333
pages/project/[id]/edit.vue Normal file
View file

@ -0,0 +1,333 @@
<script setup lang="ts">
import ProjectCreateCategoriesBasicInfo from '~/components/Project/Create/Categories/BasicInfo.vue'
import ProjectCreateCategoriesAssets from '~/components/Project/Create/Categories/Assets.vue'
import ProjectCreateCategoriesLinks from '~/components/Project/Create/Categories/Links.vue'
import ProjectCreateCategoriesTechnology from '~/components/Project/Create/Categories/Technology.vue'
import ProjectCreateCategoriesPrivacy from '~/components/Project/Create/Categories/Privacy.vue'
import ProjectCreateCategoriesSecurity from '~/components/Project/Create/Categories/Security.vue'
import ProjectCreateCategoriesTeam from '~/components/Project/Create/Categories/Team.vue'
import ProjectCreateCategoriesFunding from '~/components/Project/Create/Categories/Funding.vue'
import ProjectCreateCategoriesHistory from '~/components/Project/Create/Categories/History.vue'
definePageMeta({
layout: 'create',
})
const { useProject, projects } = useData()
const { saveProject, setProject, project, publishProject, saveProjectImage } = useProject()
const route = useRoute()
await until(projects).toMatch(p => p?.length > 0)
setProject(route.params.id as string)
if (!project.value) {
throw createError({
statusCode: 404,
message: 'Project not found',
fatal: true,
})
}
const tabs = reactive([
{ label: 'Basic Info', value: 'basic_info', component: ProjectCreateCategoriesBasicInfo },
{ label: 'Assets', value: 'assets', component: ProjectCreateCategoriesAssets },
{ label: 'Links', value: 'links', component: ProjectCreateCategoriesLinks },
{ label: 'Technology', value: 'technology', component: ProjectCreateCategoriesTechnology },
{ label: 'Privacy', value: 'privacy', component: ProjectCreateCategoriesPrivacy },
{ label: 'Security', value: 'security', component: ProjectCreateCategoriesSecurity },
{ label: 'Team', value: 'team', component: ProjectCreateCategoriesTeam },
{ label: 'Funding', value: 'funding', component: ProjectCreateCategoriesFunding },
{ label: 'History', value: 'history', component: ProjectCreateCategoriesHistory },
])
const selectedTab = ref(tabs[0].value)
function getCurrentComponent() {
const tab = tabs.find(t => t.value === selectedTab.value) || tabs[0]
return tab.component || null
}
const currentComponent = ref()
const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files
})
const logoSrc = ref(project.value?.logos?.[0].url || '/no-image-1-1.svg')
onChange((files) => {
if (!files?.[0]) return
const file = files[0]
const reader = new FileReader()
reader.onload = (e) => {
logoSrc.value = e.target?.result as string
}
reader.readAsDataURL(file)
saveProjectImage(file)
})
const projectNameInput = ref<HTMLInputElement | null>(null)
function useProjectName() {
const isEditing = ref(false)
const name = ref('Untitled')
function toggleEdit() {
isEditing.value = !isEditing.value
if (isEditing.value) {
setTimeout(() => {
projectNameInput.value?.focus()
}, 0)
}
}
return {
isEditing,
name,
toggleEdit,
}
}
const { isEditing, name, toggleEdit } = useProjectName()
name.value = project.value?.name || 'Untitled'
function save() {
saveProject({
name: name.value,
})
}
function next() {
if (selectedTab.value === 'basic_info') {
if (!currentComponent.value.isFormValid())
return
else save()
}
currentComponent.value.save()
const currentIndex = tabs.findIndex(tab => tab.value === selectedTab.value)
const nextTab = tabs[currentIndex + 1] || tabs[0]
selectedTab.value = nextTab.value
}
</script>
<template>
<div w-full>
<div
bg-app-bg-dark_grey
px-16px
py-24px
lg="pb-0px mb--1px"
>
<div app-container>
<div
flex
items-center
gap-16px
>
<div
relative
class="parent"
>
<NuxtImg
lg="w-100px h-100px"
w-64px
h-64px
bg-app-bg-grey
object-cover
border-2
class="border-app-white/30"
:src="logoSrc ?? '/no-image-1-1.svg'"
/>
<button
h-24px
hidden
parent-hover:flex
absolute
bottom-0
w-full
border-t-0
border-2
border-black
border-opacity-50
h-fit
justify-center
text="12px"
font-700
bg-app-white
text-app-black
@click="open()"
>
Upload Logo
</button>
</div>
<div
flex
flex-col
gap-8px
>
<h3
font-400
text="14px app-white/50"
leading-20px
>
{{ 'Project Name' }}
</h3>
<div
flex
items-center
gap-12px
>
<input
v-if="isEditing"
ref="projectNameInput"
v-model="name"
w-fit
onkeydown="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
type="text"
font-700
text-20px
leading-28px
bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
>
<h2
v-else
font-700
text-20px
leading-28px
>
{{ name }}
</h2>
<button @click="toggleEdit()">
<UnoIcon
v-if="isEditing"
text-24px
class="text-app-white/30"
hover:text-app-white
i-heroicons-solid-check
/>
<UnoIcon
v-else
text-20px
class="text-app-white/30"
hover:text-app-white
i-heroicons-solid-pencil
/>
</button>
</div>
</div>
</div>
<div
flex
w-full
gap-46px
lg="mt-24px"
>
<SelectBox
v-model="selectedTab"
label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
:border-opacity="30"
w-full
lg:hidden
block
mt-16px
/>
<button
v-for="tab in tabs"
:key="tab.value"
lg:block
hidden
pb-8px
leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
@click="selectedTab = tab.value"
>
{{ tab.label }}
</button>
</div>
</div>
</div>
<div
border-t-2
class="border-app-white/30"
px-16px
py-24px
>
<div
app-container
mb-55px
>
<component
:is="getCurrentComponent()"
v-if="project"
ref="currentComponent"
:project="project"
w-full
flex
flex-col
gap-24px
/>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
class="hidden!"
mt-48px
lg="w-fit flex!"
border
@click="next()"
>
<span px-24px>NEXT SECTION</span>
</Button>
</div>
</div>
<div
flex
flex-col
gap-16px
justify-center
text-center
absolute
bottom-0
w-full
bg-app-bg-dark_grey
class="border-app-white/30"
lg="bg-app-black w-fit border-l-2 border-t-2 right-0 border-app-white"
p-12px
>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
flex
lg="w-fit hidden!"
border
@click="next()"
>
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
lg="hidden"
block
text="12px italic app-white/50"
>or you can submit changes by publishing them</span>
<div flex>
<Button
w-full
lg="w-fit"
border
>
<span px-24px>CANCEL</span>
</Button>
<Button
w-full
lg="w-fit"
inverted-color
@click="publishProject()"
>
<span px-24px>PUBLISH</span>
</Button>
</div>
</div>
</div>
</template>

319
pages/project/create.vue Normal file
View file

@ -0,0 +1,319 @@
<script setup lang="ts">
import ProjectCreateCategoriesBasicInfo from '~/components/Project/Create/Categories/BasicInfo.vue'
import ProjectCreateCategoriesAssets from '~/components/Project/Create/Categories/Assets.vue'
import ProjectCreateCategoriesLinks from '~/components/Project/Create/Categories/Links.vue'
import ProjectCreateCategoriesTechnology from '~/components/Project/Create/Categories/Technology.vue'
import ProjectCreateCategoriesPrivacy from '~/components/Project/Create/Categories/Privacy.vue'
import ProjectCreateCategoriesSecurity from '~/components/Project/Create/Categories/Security.vue'
import ProjectCreateCategoriesTeam from '~/components/Project/Create/Categories/Team.vue'
import ProjectCreateCategoriesFunding from '~/components/Project/Create/Categories/Funding.vue'
import ProjectCreateCategoriesHistory from '~/components/Project/Create/Categories/History.vue'
definePageMeta({
layout: 'create',
})
const tabs = reactive([
{ label: 'Basic Info', value: 'basic_info', component: ProjectCreateCategoriesBasicInfo },
{ label: 'Assets', value: 'assets', component: ProjectCreateCategoriesAssets },
{ label: 'Links', value: 'links', component: ProjectCreateCategoriesLinks },
{ label: 'Technology', value: 'technology', component: ProjectCreateCategoriesTechnology },
{ label: 'Privacy', value: 'privacy', component: ProjectCreateCategoriesPrivacy },
{ label: 'Security', value: 'security', component: ProjectCreateCategoriesSecurity },
{ label: 'Team', value: 'team', component: ProjectCreateCategoriesTeam },
{ label: 'Funding', value: 'funding', component: ProjectCreateCategoriesFunding },
{ label: 'History', value: 'history', component: ProjectCreateCategoriesHistory },
])
const selectedTab = ref(tabs[0].value)
function getCurrentComponent() {
const tab = tabs.find(t => t.value === selectedTab.value) || tabs[0]
return tab.component || null
}
const currentComponent = ref()
const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files
})
const logoSrc = ref('/no-image-1-1.svg')
onChange((files) => {
if (!files?.[0]) return
const file = files[0]
const reader = new FileReader()
reader.onload = (e) => {
logoSrc.value = e.target?.result as string
}
reader.readAsDataURL(file)
saveProjectImage(file)
})
const projectNameInput = ref<HTMLInputElement | null>(null)
function useProjectName() {
const isEditing = ref(false)
const name = ref('Untitled')
function toggleEdit() {
isEditing.value = !isEditing.value
if (isEditing.value) {
setTimeout(() => {
projectNameInput.value?.focus()
}, 0)
}
}
return {
isEditing,
name,
toggleEdit,
}
}
const { isEditing, name, toggleEdit } = useProjectName()
const { useProject } = useData()
const { saveProject, publishProject, saveProjectImage } = useProject()
function save() {
saveProject({
name: name.value,
})
}
function next() {
if (selectedTab.value === 'basic_info') {
if (!currentComponent.value.isFormValid())
return
else save()
}
currentComponent.value.save()
const currentIndex = tabs.findIndex(tab => tab.value === selectedTab.value)
const nextTab = tabs[currentIndex + 1] || tabs[0]
selectedTab.value = nextTab.value
}
</script>
<template>
<div w-full>
<div
bg-app-bg-dark_grey
px-16px
py-24px
lg="pb-0px mb--1px"
>
<div app-container>
<div
flex
items-center
gap-16px
>
<div
relative
class="parent"
>
<NuxtImg
lg="w-100px h-100px"
w-64px
h-64px
bg-app-bg-grey
object-cover
border-2
class="border-app-white/30"
opacity-30
:src="logoSrc ?? '/no-image-1-1.svg'"
/>
<button
h-24px
hidden
parent-hover:flex
absolute
bottom-0
w-full
border-t-0
border-2
border-black
border-opacity-80
h-fit
justify-center
text="12px"
font-700
bg-app-white
text-app-black
@click="open()"
>
Upload Logo
</button>
</div>
<div
flex
flex-col
gap-8px
>
<h3
font-400
text="14px app-white/50"
leading-20px
>
{{ 'Project Name' }}
</h3>
<div
flex
items-center
gap-12px
>
<input
v-if="isEditing"
ref="projectNameInput"
v-model="name"
w-fit
onkeydown="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
type="text"
font-700
text-20px
leading-28px
bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
>
<h2
v-else
font-700
text-20px
leading-28px
>
{{ name }}
</h2>
<button @click="toggleEdit()">
<UnoIcon
v-if="isEditing"
text-24px
class="text-app-white/30"
hover:text-app-white
i-heroicons-solid-check
/>
<UnoIcon
v-else
text-20px
class="text-app-white/30"
hover:text-app-white
i-heroicons-solid-pencil
/>
</button>
</div>
</div>
</div>
<div
flex
w-full
gap-46px
lg="mt-24px"
>
<SelectBox
v-model="selectedTab"
label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
:border-opacity="30"
w-full
lg:hidden
block
mt-16px
/>
<button
v-for="tab in tabs"
:key="tab.value"
lg:block
hidden
pb-8px
leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
@click="selectedTab = tab.value"
>
{{ tab.label }}
</button>
</div>
</div>
</div>
<div
border-t-2
class="border-app-white/30"
px-16px
py-24px
>
<div
app-container
mb-55px
>
<component
:is="getCurrentComponent()"
ref="currentComponent"
w-full
flex
flex-col
gap-24px
/>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
class="hidden!"
mt-48px
lg="w-fit flex!"
border
@click="next()"
>
<span px-24px>NEXT SECTION</span>
</Button>
</div>
</div>
<div
flex
flex-col
gap-16px
justify-center
text-center
absolute
bottom-0
w-full
bg-app-bg-dark_grey
class="border-app-white/30"
lg="bg-app-black w-fit border-l-2 border-t-2 right-0 border-app-white"
p-12px
>
<Button
v-if="selectedTab !== tabs[tabs.length - 1].value"
flex
lg="w-fit hidden!"
border
@click="next()"
>
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
lg="hidden"
block
text="12px italic app-white/50"
>or you can submit changes by publishing them</span>
<div flex>
<Button
w-full
lg="w-fit"
border
>
<span px-24px>CANCEL</span>
</Button>
<Button
w-full
lg="w-fit"
inverted-color
@click="publishProject()"
>
<span px-24px>PUBLISH</span>
</Button>
</div>
</div>
</div>
</template>

16
plugins/githubApi.ts Normal file
View file

@ -0,0 +1,16 @@
export default defineNuxtPlugin(() => {
const appConfig = useAppConfig()
const githubApi = $fetch.create({
baseURL: 'https://api.github.com',
headers: {
Authorization: `Bearer ${appConfig.github.accessToken}`,
},
})
return {
provide: {
githubApi,
},
}
})

View file

@ -38,6 +38,9 @@ importers:
'@unocss/nuxt':
specifier: ^0.62.3
version: 0.62.3(magicast@0.3.5)(postcss@8.4.44)(rollup@4.21.2)(vite@5.4.2(@types/node@20.8.7)(terser@5.22.0))(webpack@5.89.0(esbuild@0.23.1))
'@vee-validate/nuxt':
specifier: ^4.13.2
version: 4.13.2(magicast@0.3.5)(rollup@4.21.2)(vue@3.4.38(typescript@5.5.4))
'@vueuse/nuxt':
specifier: ^11.0.3
version: 11.0.3(magicast@0.3.5)(nuxt@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)))(rollup@4.21.2)(vue@3.4.38(typescript@5.5.4))
@ -53,6 +56,9 @@ importers:
nuxt-lodash:
specifier: ^2.5.3
version: 2.5.3(rollup@4.21.2)
octokit:
specifier: ^4.0.2
version: 4.0.2
pinia:
specifier: ^2.2.2
version: 2.2.2(typescript@5.5.4)(vue@3.4.38(typescript@5.5.4))
@ -68,6 +74,12 @@ importers:
vue-tsc:
specifier: ^2.1.4
version: 2.1.4(typescript@5.5.4)
yaml:
specifier: ^2.5.1
version: 2.5.1
yup:
specifier: ^1.4.0
version: 1.4.0
packages:
@ -1320,6 +1332,113 @@ packages:
'@nuxtjs/color-mode@3.4.4':
resolution: {integrity: sha512-VSNJVGnRIjiGmfbMa0cN+rwNRowDRTL/wku/z5MpKSanVo3khIRitBNqNviso1l3T+LW0pLHeXBNp6L8g/l1EA==}
'@octokit/app@15.1.0':
resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==}
engines: {node: '>= 18'}
'@octokit/auth-app@7.1.1':
resolution: {integrity: sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg==}
engines: {node: '>= 18'}
'@octokit/auth-oauth-app@8.1.1':
resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==}
engines: {node: '>= 18'}
'@octokit/auth-oauth-device@7.1.1':
resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==}
engines: {node: '>= 18'}
'@octokit/auth-oauth-user@5.1.1':
resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==}
engines: {node: '>= 18'}
'@octokit/auth-token@5.1.1':
resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==}
engines: {node: '>= 18'}
'@octokit/auth-unauthenticated@6.1.0':
resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==}
engines: {node: '>= 18'}
'@octokit/core@6.1.2':
resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==}
engines: {node: '>= 18'}
'@octokit/endpoint@10.1.1':
resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==}
engines: {node: '>= 18'}
'@octokit/graphql@8.1.1':
resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==}
engines: {node: '>= 18'}
'@octokit/oauth-app@7.1.3':
resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==}
engines: {node: '>= 18'}
'@octokit/oauth-authorization-url@7.1.1':
resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==}
engines: {node: '>= 18'}
'@octokit/oauth-methods@5.1.2':
resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==}
engines: {node: '>= 18'}
'@octokit/openapi-types@22.2.0':
resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==}
'@octokit/openapi-webhooks-types@8.3.0':
resolution: {integrity: sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg==}
'@octokit/plugin-paginate-graphql@5.2.2':
resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-paginate-rest@11.3.3':
resolution: {integrity: sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-rest-endpoint-methods@13.2.4':
resolution: {integrity: sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-retry@7.1.1':
resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': '>=6'
'@octokit/plugin-throttling@9.3.1':
resolution: {integrity: sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==}
engines: {node: '>= 18'}
peerDependencies:
'@octokit/core': ^6.0.0
'@octokit/request-error@6.1.4':
resolution: {integrity: sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==}
engines: {node: '>= 18'}
'@octokit/request@9.1.3':
resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==}
engines: {node: '>= 18'}
'@octokit/types@13.5.0':
resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==}
'@octokit/webhooks-methods@5.1.0':
resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==}
engines: {node: '>= 18'}
'@octokit/webhooks@13.3.0':
resolution: {integrity: sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg==}
engines: {node: '>= 18'}
'@parcel/watcher-android-arm64@2.4.1':
resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==}
engines: {node: '>= 10.0.0'}
@ -1668,12 +1787,12 @@ packages:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
'@types/aws-lambda@8.10.145':
resolution: {integrity: sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw==}
'@types/eslint-scope@3.7.6':
resolution: {integrity: sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==}
'@types/eslint@8.44.6':
resolution: {integrity: sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==}
'@types/eslint@9.6.1':
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
@ -1878,6 +1997,9 @@ packages:
peerDependencies:
webpack: ^4 || ^5
'@vee-validate/nuxt@4.13.2':
resolution: {integrity: sha512-rpYYO2isCrgHRdJnKRXGPn8aqhyZvCPUJgNubftPum1txbngkO/LNiFsZdFDO7auGprDAQf7axpszRfeKoCGVg==}
'@vercel/nft@0.26.5':
resolution: {integrity: sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==}
engines: {node: '>=16'}
@ -2235,6 +2357,9 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
before-after-hook@3.0.2:
resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==}
binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
@ -2251,6 +2376,9 @@ packages:
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@ -2821,9 +2949,6 @@ packages:
errx@0.1.0:
resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==}
es-module-lexer@1.3.1:
resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==}
es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
@ -3560,9 +3685,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
jsonc-parser@3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@ -3821,9 +3943,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
mlly@1.4.2:
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
mlly@1.7.1:
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
@ -3994,6 +4113,10 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
octokit@4.0.2:
resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==}
engines: {node: '>= 18'}
ofetch@1.3.3:
resolution: {integrity: sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg==}
@ -4168,9 +4291,6 @@ packages:
typescript:
optional: true
pkg-types@1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
pkg-types@1.2.0:
resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==}
@ -4394,6 +4514,9 @@ packages:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
property-expr@2.0.6:
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
protocols@2.0.1:
resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==}
@ -4883,6 +5006,9 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
tiny-invariant@1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
@ -4920,6 +5046,9 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
toposort@2.0.2:
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@ -4964,10 +5093,18 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
type-fest@3.13.1:
resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==}
engines: {node: '>=14.16'}
type-fest@4.26.1:
resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==}
engines: {node: '>=16'}
typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'}
@ -5022,6 +5159,12 @@ packages:
resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
universal-github-app-jwt@2.2.0:
resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==}
universal-user-agent@7.0.2:
resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==}
universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
@ -5184,6 +5327,11 @@ packages:
resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
vee-validate@4.13.2:
resolution: {integrity: sha512-HlpR/6MJ92TW9f135umMZKUqdd/tFQTxLNSf2ImbU4Y/MlLVAUpF1l64VdjTOhbClAqPjCb5p/SqHDxLpUHXrw==}
peerDependencies:
vue: ^3.4.26
vite-hot-client@0.2.3:
resolution: {integrity: sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==}
peerDependencies:
@ -5486,8 +5634,8 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.5.0:
resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==}
yaml@2.5.1:
resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==}
engines: {node: '>= 14'}
hasBin: true
@ -5507,6 +5655,9 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
yup@1.4.0:
resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==}
zhead@2.2.4:
resolution: {integrity: sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==}
@ -6771,9 +6922,9 @@ snapshots:
ignore: 5.2.4
jiti: 1.20.0
knitwork: 1.0.0
mlly: 1.4.2
mlly: 1.7.1
pathe: 1.1.1
pkg-types: 1.0.3
pkg-types: 1.2.0
scule: 1.0.0
semver: 7.5.4
ufo: 1.3.1
@ -6809,7 +6960,7 @@ snapshots:
defu: 6.1.2
hookable: 5.5.3
pathe: 1.1.1
pkg-types: 1.0.3
pkg-types: 1.2.0
postcss-import-resolver: 2.0.0
std-env: 3.4.3
ufo: 1.3.1
@ -6952,6 +7103,152 @@ snapshots:
- rollup
- supports-color
'@octokit/app@15.1.0':
dependencies:
'@octokit/auth-app': 7.1.1
'@octokit/auth-unauthenticated': 6.1.0
'@octokit/core': 6.1.2
'@octokit/oauth-app': 7.1.3
'@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2)
'@octokit/types': 13.5.0
'@octokit/webhooks': 13.3.0
'@octokit/auth-app@7.1.1':
dependencies:
'@octokit/auth-oauth-app': 8.1.1
'@octokit/auth-oauth-user': 5.1.1
'@octokit/request': 9.1.3
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
lru-cache: 10.4.3
universal-github-app-jwt: 2.2.0
universal-user-agent: 7.0.2
'@octokit/auth-oauth-app@8.1.1':
dependencies:
'@octokit/auth-oauth-device': 7.1.1
'@octokit/auth-oauth-user': 5.1.1
'@octokit/request': 9.1.3
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/auth-oauth-device@7.1.1':
dependencies:
'@octokit/oauth-methods': 5.1.2
'@octokit/request': 9.1.3
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/auth-oauth-user@5.1.1':
dependencies:
'@octokit/auth-oauth-device': 7.1.1
'@octokit/oauth-methods': 5.1.2
'@octokit/request': 9.1.3
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/auth-token@5.1.1': {}
'@octokit/auth-unauthenticated@6.1.0':
dependencies:
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
'@octokit/core@6.1.2':
dependencies:
'@octokit/auth-token': 5.1.1
'@octokit/graphql': 8.1.1
'@octokit/request': 9.1.3
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
before-after-hook: 3.0.2
universal-user-agent: 7.0.2
'@octokit/endpoint@10.1.1':
dependencies:
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/graphql@8.1.1':
dependencies:
'@octokit/request': 9.1.3
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/oauth-app@7.1.3':
dependencies:
'@octokit/auth-oauth-app': 8.1.1
'@octokit/auth-oauth-user': 5.1.1
'@octokit/auth-unauthenticated': 6.1.0
'@octokit/core': 6.1.2
'@octokit/oauth-authorization-url': 7.1.1
'@octokit/oauth-methods': 5.1.2
'@types/aws-lambda': 8.10.145
universal-user-agent: 7.0.2
'@octokit/oauth-authorization-url@7.1.1': {}
'@octokit/oauth-methods@5.1.2':
dependencies:
'@octokit/oauth-authorization-url': 7.1.1
'@octokit/request': 9.1.3
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
'@octokit/openapi-types@22.2.0': {}
'@octokit/openapi-webhooks-types@8.3.0': {}
'@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)':
dependencies:
'@octokit/core': 6.1.2
'@octokit/plugin-paginate-rest@11.3.3(@octokit/core@6.1.2)':
dependencies:
'@octokit/core': 6.1.2
'@octokit/types': 13.5.0
'@octokit/plugin-rest-endpoint-methods@13.2.4(@octokit/core@6.1.2)':
dependencies:
'@octokit/core': 6.1.2
'@octokit/types': 13.5.0
'@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)':
dependencies:
'@octokit/core': 6.1.2
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
bottleneck: 2.19.5
'@octokit/plugin-throttling@9.3.1(@octokit/core@6.1.2)':
dependencies:
'@octokit/core': 6.1.2
'@octokit/types': 13.5.0
bottleneck: 2.19.5
'@octokit/request-error@6.1.4':
dependencies:
'@octokit/types': 13.5.0
'@octokit/request@9.1.3':
dependencies:
'@octokit/endpoint': 10.1.1
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
universal-user-agent: 7.0.2
'@octokit/types@13.5.0':
dependencies:
'@octokit/openapi-types': 22.2.0
'@octokit/webhooks-methods@5.1.0': {}
'@octokit/webhooks@13.3.0':
dependencies:
'@octokit/openapi-webhooks-types': 8.3.0
'@octokit/request-error': 6.1.4
'@octokit/webhooks-methods': 5.1.0
'@parcel/watcher-android-arm64@2.4.1':
optional: true
@ -7221,15 +7518,12 @@ snapshots:
'@trysound/sax@0.2.0': {}
'@types/aws-lambda@8.10.145': {}
'@types/eslint-scope@3.7.6':
dependencies:
'@types/eslint': 8.44.6
'@types/estree': 1.0.3
'@types/eslint@8.44.6':
dependencies:
'@types/estree': 1.0.3
'@types/json-schema': 7.0.14
'@types/eslint': 9.6.1
'@types/estree': 1.0.5
'@types/eslint@9.6.1':
dependencies:
@ -7572,6 +7866,17 @@ snapshots:
- rollup
- supports-color
'@vee-validate/nuxt@4.13.2(magicast@0.3.5)(rollup@4.21.2)(vue@3.4.38(typescript@5.5.4))':
dependencies:
'@nuxt/kit': 3.13.0(magicast@0.3.5)(rollup@4.21.2)
local-pkg: 0.5.0
vee-validate: 4.13.2(vue@3.4.38(typescript@5.5.4))
transitivePeerDependencies:
- magicast
- rollup
- supports-color
- vue
'@vercel/nft@0.26.5(encoding@0.1.13)':
dependencies:
'@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
@ -7947,9 +8252,9 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
acorn-import-assertions@1.9.0(acorn@8.11.2):
acorn-import-assertions@1.9.0(acorn@8.12.1):
dependencies:
acorn: 8.11.2
acorn: 8.12.1
acorn-import-attributes@1.9.5(acorn@8.12.1):
dependencies:
@ -8085,6 +8390,8 @@ snapshots:
base64-js@1.5.1: {}
before-after-hook@3.0.2: {}
binary-extensions@2.2.0: {}
bindings@1.5.0:
@ -8102,6 +8409,8 @@ snapshots:
boolbase@1.0.0: {}
bottleneck@2.19.5: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@ -8195,11 +8504,11 @@ snapshots:
dotenv: 16.3.1
giget: 1.1.3
jiti: 1.20.0
mlly: 1.4.2
mlly: 1.7.1
ohash: 1.1.3
pathe: 1.1.1
perfect-debounce: 1.0.0
pkg-types: 1.0.3
pkg-types: 1.2.0
rc9: 2.1.1
transitivePeerDependencies:
- supports-color
@ -8662,8 +8971,6 @@ snapshots:
errx@0.1.0: {}
es-module-lexer@1.3.1: {}
es-module-lexer@1.5.4: {}
esbuild@0.19.10:
@ -9595,8 +9902,6 @@ snapshots:
json5@2.2.3: {}
jsonc-parser@3.2.0: {}
jsonfile@6.1.0:
dependencies:
universalify: 2.0.0
@ -9668,7 +9973,7 @@ snapshots:
local-pkg@0.5.0:
dependencies:
mlly: 1.4.2
mlly: 1.7.1
pkg-types: 1.2.0
locate-path@5.0.0:
@ -9851,13 +10156,6 @@ snapshots:
mkdirp@1.0.4: {}
mlly@1.4.2:
dependencies:
acorn: 8.10.0
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.3.1
mlly@1.7.1:
dependencies:
acorn: 8.12.1
@ -10205,6 +10503,19 @@ snapshots:
object-assign@4.1.1: {}
octokit@4.0.2:
dependencies:
'@octokit/app': 15.1.0
'@octokit/core': 6.1.2
'@octokit/oauth-app': 7.1.3
'@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2)
'@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2)
'@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2)
'@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2)
'@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2)
'@octokit/request-error': 6.1.4
'@octokit/types': 13.5.0
ofetch@1.3.3:
dependencies:
destr: 2.0.2
@ -10373,12 +10684,6 @@ snapshots:
optionalDependencies:
typescript: 5.5.4
pkg-types@1.0.3:
dependencies:
jsonc-parser: 3.2.0
mlly: 1.4.2
pathe: 1.1.1
pkg-types@1.2.0:
dependencies:
confbox: 0.1.7
@ -10597,6 +10902,8 @@ snapshots:
kleur: 3.0.3
sisteransi: 1.0.5
property-expr@2.0.6: {}
protocols@2.0.1: {}
prr@1.0.1: {}
@ -10793,7 +11100,7 @@ snapshots:
schema-utils@3.3.0:
dependencies:
'@types/json-schema': 7.0.14
'@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
@ -11141,7 +11448,7 @@ snapshots:
terser-webpack-plugin@5.3.9(esbuild@0.23.1)(webpack@5.89.0(esbuild@0.23.1)):
dependencies:
'@jridgewell/trace-mapping': 0.3.20
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
@ -11159,6 +11466,8 @@ snapshots:
text-table@0.2.0: {}
tiny-case@1.0.3: {}
tiny-invariant@1.3.1: {}
tinybench@2.9.0: {}
@ -11184,6 +11493,8 @@ snapshots:
toidentifier@1.0.1: {}
toposort@2.0.2: {}
totalist@3.0.1: {}
tr46@0.0.3: {}
@ -11218,8 +11529,12 @@ snapshots:
type-fest@0.8.1: {}
type-fest@2.19.0: {}
type-fest@3.13.1: {}
type-fest@4.26.1: {}
typescript@5.5.4: {}
ufo@1.3.1: {}
@ -11293,9 +11608,9 @@ snapshots:
fast-glob: 3.3.1
local-pkg: 0.4.3
magic-string: 0.30.5
mlly: 1.4.2
mlly: 1.7.1
pathe: 1.1.1
pkg-types: 1.0.3
pkg-types: 1.2.0
scule: 1.0.0
strip-literal: 1.3.0
unplugin: 1.5.0
@ -11310,6 +11625,10 @@ snapshots:
dependencies:
imurmurhash: 0.1.4
universal-github-app-jwt@2.2.0: {}
universal-user-agent@7.0.2: {}
universalify@2.0.0: {}
unocss@0.62.3(@unocss/webpack@0.62.3(rollup@4.21.2)(webpack@5.89.0(esbuild@0.23.1)))(postcss@8.4.44)(rollup@4.21.2)(vite@5.4.2(@types/node@20.8.7)(terser@5.22.0)):
@ -11357,7 +11676,7 @@ snapshots:
pathe: 1.1.2
scule: 1.3.0
unplugin: 1.12.3
yaml: 2.5.0
yaml: 2.5.1
optionalDependencies:
vue-router: 4.4.3(vue@3.4.38(typescript@5.5.4))
transitivePeerDependencies:
@ -11488,6 +11807,12 @@ snapshots:
dependencies:
builtins: 5.0.1
vee-validate@4.13.2(vue@3.4.38(typescript@5.5.4)):
dependencies:
'@vue/devtools-api': 6.6.3
type-fest: 4.26.1
vue: 3.4.38(typescript@5.5.4)
vite-hot-client@0.2.3(vite@5.4.2(@types/node@20.8.7)(terser@5.22.0)):
dependencies:
vite: 5.4.2(@types/node@20.8.7)(terser@5.22.0)
@ -11724,16 +12049,16 @@ snapshots:
webpack@5.89.0(esbuild@0.23.1):
dependencies:
'@types/eslint-scope': 3.7.6
'@types/estree': 1.0.3
'@types/estree': 1.0.5
'@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.2
acorn-import-assertions: 1.9.0(acorn@8.11.2)
browserslist: 4.22.2
acorn: 8.12.1
acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.3.1
es-module-lexer: 1.5.4
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
@ -11804,7 +12129,7 @@ snapshots:
yallist@4.0.0: {}
yaml@2.5.0: {}
yaml@2.5.1: {}
yargs-parser@21.1.1: {}
@ -11822,6 +12147,13 @@ snapshots:
yocto-queue@1.0.0: {}
yup@1.4.0:
dependencies:
property-expr: 2.0.6
tiny-case: 1.0.3
toposort: 2.0.2
type-fest: 2.19.0
zhead@2.2.4: {}
zip-stream@6.0.1:

127
server/api/data.post.ts Normal file
View file

@ -0,0 +1,127 @@
import { App } from 'octokit'
import yaml from 'yaml'
import type { Project } from '~/types'
export default defineEventHandler(async (event) => {
const body = await readBody<{ project: Project, image?: { type: string, data: string } }>(event)
const yamlProject = yaml.stringify(body.project)
const { appId, privateKey, installationId } = useAppConfig().github
const id = body.project.id || body.project.name.toLowerCase().replace(/\s+/g, '-')
const app = new App({
appId,
privateKey,
})
await app.octokit.rest.apps.getAuthenticated()
const octokit = await app.getInstallationOctokit(installationId)
const owner = 'develit-io'
const repo = 'test-repo'
const baseBranch = 'main'
const newBranchName = `${id}-project-update`
const commitMessage = `${body.project.id ? `Updating the project: ${body.project.name}` : `Initiating the creation of project: ${body.project.name}`}`
const files = [
{
path: `src/projects/${id}/index.yaml`,
content: yamlProject,
encoding: 'utf-8',
},
]
if (body.image?.data && body.image?.type) {
files.push(
{
path: `src/projects/${id}/logo.${body.image.type.split('/')[1]}`,
content: body.image.data,
encoding: 'base64',
},
)
}
async function createBranch(owner: string, repo: string, newBranchName: string, baseBranch: string) {
const { data: baseBranchData } = await octokit.rest.git.getRef({
owner,
repo,
ref: `heads/${baseBranch}`,
})
await octokit.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${newBranchName}`,
sha: baseBranchData.object.sha,
})
}
async function commitChangesToNewBranch(owner: string, repo: string, newBranch: string, message: string, files: { path: string, content: string }[]) {
const { data: latestCommit } = await octokit.rest.repos.getCommit({
owner,
repo,
ref: newBranch,
})
const { data: baseTree } = await octokit.rest.git.getTree({
owner,
repo,
tree_sha: latestCommit.commit.tree.sha,
})
const tree = files.map(file => ({
path: file.path,
mode: '100644' as const,
type: 'blob' as const,
content: file.content,
}))
const { data: newTree } = await octokit.rest.git.createTree({
owner,
repo,
base_tree: baseTree.sha,
tree,
})
const { data: newCommit } = await octokit.rest.git.createCommit({
owner,
repo,
message,
tree: newTree.sha,
parents: [latestCommit.sha],
})
await octokit.rest.git.updateRef({
owner,
repo,
ref: `heads/${newBranch}`,
sha: newCommit.sha,
})
}
async function createPullRequest(owner: string, repo: string, head: string, base: string, title: string, body: string) {
const { data: pullRequest } = await octokit.rest.pulls.create({
owner,
repo,
title,
head,
base,
body,
})
return pullRequest
}
try {
await createBranch(owner, repo, newBranchName, baseBranch)
console.log(`Branch ${newBranchName} created successfully!`)
await commitChangesToNewBranch(owner, repo, newBranchName, commitMessage, files)
console.log(`Changes committed to branch ${newBranchName} successfully!`)
const pullRequestData = await createPullRequest(owner, repo, newBranchName, baseBranch, `${body.project.id ? `Update project: ${body.project.name}` : `Create project: ${body.project.name}`}`, `${body.project.id ? `Updating the project: ${body.project.name}` : `Initiating the creation of project: ${body.project.name}`}`)
console.log('Pull request created:', pullRequestData)
}
catch (error) {
console.error('Error during GitHub operations:', error)
}
})

View file

@ -16185,5 +16185,47 @@
"id": "other",
"name": "Other"
}
],
"project_phase": [
{
"id": "mainnet",
"name": "Mainnet"
},
{
"id": "testnet",
"name": "Testnet"
},
{
"id": "alpha",
"name": "Alpha"
},
{
"id": "beta",
"name": "Beta"
},
{
"id": "mvp",
"name": "MVP"
}
],
"asset_custody_type": [
{
"id": "non-custody",
"name": "Non-custody"
}
],
"sign_in_type_requirments": [
{
"id": "wallet",
"name": "Wallet"
},
{
"id": "email",
"name": "Email"
},
{
"id": "seed",
"name": "Seed"
}
]
}

View file

@ -1,5 +1,6 @@
export interface Category {
id: string
name: string
usecases: string[]
projectsCount: number
}

View file

@ -1,5 +1,5 @@
export interface InputOption {
label: string
value: string
value: string | number
count?: number
}

View file

@ -9,7 +9,7 @@ export interface Member {
role?: string
link?: string
[k: string]: unknown
}
}[]
company?: {
name?: string
link?: string

View file

@ -1,4 +1,4 @@
import type { Member } from './member'
import type { Team } from './team'
import type { Fund } from './fund'
import type { ClientDiversability } from './clientDiversability'
import type { Audit } from './audit'
@ -7,22 +7,25 @@ export interface Project {
id: string
name: string
categories: string[]
ecosystem?: string
usecases: string[]
ecosystem?: string[]
product_readiness?: string
security?: string
have_token?: boolean
token_link?: string
tokens?: {
name?: string
symbol: string
symbol?: string
network?: string
contract_address?: string
link?: string
token_link?: string
[k: string]: unknown
}[]
description?: string
project_type?: string
product_launch_day?: string
project_phase?: string
sunset: boolean
technology?: {
type: string
name?: string
@ -41,6 +44,7 @@ export interface Project {
github?: string
docs?: string
changelog?: string
governance?: string
forum?: string
snapshot?: string
lens?: string
@ -62,17 +66,17 @@ export interface Project {
}
[k: string]: unknown
opensource: boolean
viewing_key: boolean
dissapearing_tx: boolean
frontend_anonymity: string
identity_integration: null
connected_tx: boolean
revealed_recipient: boolean
revealed_sender: boolean
revealed_ammount: boolean
reversability_condition: string
data_masking: string
asset_custody_type: string
viewing_key?: boolean
dissapearing_tx?: boolean
frontend_anonymity?: string
identity_integration?: null
connected_tx?: boolean
revealed_recipient?: boolean
revealed_sender?: boolean
revealed_ammount?: boolean
reversability_condition?: string
data_masking?: string
asset_custody_type?: string
}
licences?: string
privacy_policy?: {
@ -81,7 +85,7 @@ export interface Project {
data_usage?: string
[k: string]: unknown
}
team?: Member[]
team?: Team
storage?: {
decentralized?: boolean
[k: string]: unknown
@ -89,7 +93,7 @@ export interface Project {
tracebility?: {
tracked_data?: string
kyc?: boolean
sign_in_type_requirments?: string
sign_in_type_requirments?: string[]
[k: string]: unknown
}
third_party_dependency?: string
@ -97,6 +101,7 @@ export interface Project {
audits?: Audit[]
social_trust?: string
technical_spof?: string
assets_used?: string[]
history?: {
title?: string
event_type?: string
@ -104,7 +109,7 @@ export interface Project {
time?: string
link?: string
[k: string]: unknown
}
}[]
client_diversability?: ClientDiversability[]
default_privacy?: boolean
funding?: Fund[]
@ -129,19 +134,19 @@ export interface ProjectShallow {
title1: string
description: string
percentage: number
forum?: string | undefined
github?: string | undefined
website?: string | undefined
twitter?: string | undefined
coingecko?: string | undefined
explorer?: string | undefined
newsletter?: string | undefined
readyness?: string | undefined
team: Member[] | undefined
docs: string | undefined
audits?: Audit[] | undefined
support?: number | undefined
anonymity?: boolean | undefined
forum?: string
github?: string
website?: string
twitter?: string
coingecko?: string
explorer?: string
newsletter?: string
readyness?: string
team?: Team
docs?: string
audits?: Audit[]
support?: number
anonymity?: boolean
}
export interface ProjectIndexable extends Project {

19
types/team.ts Normal file
View file

@ -0,0 +1,19 @@
export interface Team {
name?: string
role?: string
link?: string
avatar?: string
anonymous?: boolean
teammembers?: {
name?: string
role?: string
link?: string
[k: string]: unknown
}[]
company?: {
name?: string
link?: string
contacts?: string
[k: string]: unknown
}
}

View file

@ -47,6 +47,7 @@ export default defineConfig({
black: '#000',
green: '#B5E26B',
red: '#FF5252',
danger: '#FF0000',
bg: {
grey: '#ffffff33',
dark_grey: '#161616',