<script setup lang="ts"> import * as yup from 'yup' 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 { saveProject, publishProject, saveProjectImage } = useProject() const { project, isPublishing } = storeToRefs(useProject()) const tabs = reactive([ { label: 'Basic Info', value: 'basic_info', component: markRaw(ProjectCreateCategoriesBasicInfo) }, { label: 'Assets', value: 'assets', component: markRaw(ProjectCreateCategoriesAssets) }, { label: 'Links', value: 'links', component: markRaw(ProjectCreateCategoriesLinks) }, { label: 'Technology', value: 'technology', component: markRaw(ProjectCreateCategoriesTechnology) }, { label: 'Privacy', value: 'privacy', component: markRaw(ProjectCreateCategoriesPrivacy) }, { label: 'Security', value: 'security', component: markRaw(ProjectCreateCategoriesSecurity) }, { label: 'Team', value: 'team', component: markRaw(ProjectCreateCategoriesTeam) }, { label: 'Funding', value: 'funding', component: markRaw(ProjectCreateCategoriesFunding) }, { label: 'History', value: 'history', component: markRaw(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') const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create'])) name.value = project.value?.name || 'Untitled' function toggleEdit() { isEditing.value = !isEditing.value if (isEditing.value) { setTimeout(() => { projectNameInput.value?.focus() }, 0) } } return { isEditing, name, nameError, toggleEdit, } } const { isEditing, name, nameError, toggleEdit } = useProjectName() function save() { saveProject({ ...project.value, 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 } 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 } const transitionDone = ref(false) </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 relative > <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';" @blur="toggleEdit()" > <h2 v-else font-700 text-20px leading-28px > {{ name }} </h2> <button v-if="!isEditing" @click="toggleEdit()" > <UnoIcon text-20px class="text-app-white/30" hover:text-app-white i-heroicons-solid-pencil /> </button> <span v-if="nameError" text-nowrap text-app-danger text-12px absolute lg:bottom--24px bottom--16px select-none >Invalid project name</span> </div> </div> </div> <div flex w-full gap-46px lg="mt-24px" > <SelectBox :model-value="selectedTab" label="Choose category" :options="tabs.map(t => ({ label: t.label, value: t.value }))" :border-opacity="30" w-full lg:hidden block mt-16px @update:model-value="$event => jumpTo($event)" /> <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-170px lg="mb-55px" > <ClientOnly> <Transition v-if="!transitionDone" name="fade" mode="out-in" appear @after-enter="transitionDone = true" > <component :is="getCurrentComponent()" ref="currentComponent" :project="project" w-full flex flex-col gap-24px /> </Transition> <component :is="getCurrentComponent()" v-else 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> </ClientOnly> </div> </div> <div flex flex-col gap-16px justify-center text-center fixed 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 @click="navigateTo('/')" > <span px-24px>CANCEL</span> </Button> <Button w-full lg="w-fit" inverted-color @click="publish()" > <UnoIcon v-if="isPublishing" w-108px i-eos-icons-loading text-black text-18px /> <span v-else px-24px >PUBLISH</span> </Button> </div> </div> </div> </template> <style scoped> .fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease-in-out; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style>