2024-09-12 18:03:06 +02:00
|
|
|
<script setup lang="ts">
|
2024-09-16 16:43:29 +02:00
|
|
|
import * as yup from 'yup'
|
2024-09-12 18:03:06 +02:00
|
|
|
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',
|
|
|
|
})
|
|
|
|
|
2024-09-16 16:43:29 +02:00
|
|
|
const { projects } = useData()
|
|
|
|
const { saveProject, setProject, publishProject, saveProjectImage } = useProject()
|
|
|
|
const { project, isPublishing } = storeToRefs(useProject())
|
2024-09-12 18:03:06 +02:00
|
|
|
|
|
|
|
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)
|
2024-09-16 16:43:29 +02:00
|
|
|
// const name = ref('Untitled')
|
|
|
|
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined']))
|
|
|
|
name.value = project.value?.name || 'Untitled'
|
2024-09-12 18:03:06 +02:00
|
|
|
|
|
|
|
function toggleEdit() {
|
|
|
|
isEditing.value = !isEditing.value
|
|
|
|
if (isEditing.value) {
|
|
|
|
setTimeout(() => {
|
|
|
|
projectNameInput.value?.focus()
|
|
|
|
}, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
isEditing,
|
|
|
|
name,
|
2024-09-16 16:43:29 +02:00
|
|
|
nameError,
|
2024-09-12 18:03:06 +02:00
|
|
|
toggleEdit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-16 16:43:29 +02:00
|
|
|
const { isEditing, name, nameError, toggleEdit } = useProjectName()
|
2024-09-12 18:03:06 +02:00
|
|
|
name.value = project.value?.name || 'Untitled'
|
|
|
|
|
|
|
|
function save() {
|
|
|
|
saveProject({
|
|
|
|
name: name.value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-16 16:43:29 +02:00
|
|
|
async function next() {
|
|
|
|
if (nameError.value)
|
|
|
|
return
|
|
|
|
|
2024-09-12 18:03:06 +02:00
|
|
|
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
|
|
|
|
}
|
2024-09-16 16:43:29 +02:00
|
|
|
|
|
|
|
async function publish() {
|
|
|
|
if (selectedTab.value === 'basic_info') {
|
|
|
|
if (!currentComponent.value.isFormValid())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
else if (isPublishing) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
await publishProject()
|
|
|
|
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
function jumpTo(tab: string) {
|
|
|
|
if (selectedTab.value === 'basic_info') {
|
|
|
|
if (!currentComponent.value.isFormValid())
|
|
|
|
return
|
|
|
|
else save()
|
|
|
|
}
|
|
|
|
|
|
|
|
currentComponent.value.save()
|
|
|
|
|
|
|
|
selectedTab.value = tab
|
|
|
|
}
|
2024-09-12 18:03:06 +02:00
|
|
|
</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
|
2024-09-16 16:43:29 +02:00
|
|
|
relative
|
2024-09-12 18:03:06 +02:00
|
|
|
>
|
|
|
|
<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>
|
2024-09-16 16:43:29 +02:00
|
|
|
<span
|
|
|
|
v-if="nameError"
|
|
|
|
text-nowrap
|
|
|
|
text-app-danger
|
|
|
|
text-12px
|
|
|
|
absolute
|
|
|
|
lg:bottom--24px
|
|
|
|
bottom--16px
|
|
|
|
select-none
|
|
|
|
>Invalid project name</span>
|
2024-09-12 18:03:06 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
flex
|
|
|
|
w-full
|
|
|
|
gap-46px
|
|
|
|
lg="mt-24px"
|
|
|
|
>
|
|
|
|
<SelectBox
|
2024-09-16 16:43:29 +02:00
|
|
|
:model-value="selectedTab"
|
2024-09-12 18:03:06 +02:00
|
|
|
label="Choose category"
|
|
|
|
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
|
|
|
|
:border-opacity="30"
|
|
|
|
w-full
|
|
|
|
lg:hidden
|
|
|
|
block
|
|
|
|
mt-16px
|
2024-09-16 16:43:29 +02:00
|
|
|
@update:model-value="$event => jumpTo($event)"
|
2024-09-12 18:03:06 +02:00
|
|
|
/>
|
|
|
|
<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' : ''"
|
2024-09-16 16:43:29 +02:00
|
|
|
@click="jumpTo(tab.value)"
|
2024-09-12 18:03:06 +02:00
|
|
|
>
|
|
|
|
{{ tab.label }}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
border-t-2
|
|
|
|
class="border-app-white/30"
|
|
|
|
px-16px
|
|
|
|
py-24px
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
app-container
|
2024-09-16 16:43:29 +02:00
|
|
|
mb-170px
|
|
|
|
lg="mb-55px"
|
2024-09-12 18:03:06 +02:00
|
|
|
>
|
|
|
|
<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
|
2024-09-16 16:43:29 +02:00
|
|
|
fixed
|
2024-09-12 18:03:06 +02:00
|
|
|
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
|
2024-09-16 16:43:29 +02:00
|
|
|
@click="navigateTo(`/project/${route.params.id}`)"
|
2024-09-12 18:03:06 +02:00
|
|
|
>
|
|
|
|
<span px-24px>CANCEL</span>
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
w-full
|
|
|
|
lg="w-fit"
|
|
|
|
inverted-color
|
2024-09-16 16:43:29 +02:00
|
|
|
@click="publish()"
|
2024-09-12 18:03:06 +02:00
|
|
|
>
|
2024-09-16 11:10:41 +02:00
|
|
|
<UnoIcon
|
|
|
|
v-if="isPublishing"
|
|
|
|
w-108px
|
|
|
|
i-eos-icons-loading
|
|
|
|
text-black
|
|
|
|
text-18px
|
|
|
|
/>
|
|
|
|
<span
|
|
|
|
v-else
|
|
|
|
px-24px
|
|
|
|
>PUBLISH</span>
|
2024-09-12 18:03:06 +02:00
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|