Merge pull request #49 from web3privacy/dw/bug-fixes

Bug fixes and polishing
This commit is contained in:
MufCZ 2024-10-01 15:53:20 +02:00 committed by GitHub
commit cd476c6367
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 298 additions and 372 deletions

View file

@ -38,8 +38,8 @@ resetForm({
}, },
}) })
function isFormValid() { async function isFormValid() {
validate() await validate()
if (meta.value.valid) { if (meta.value.valid) {
return true return true

View file

@ -11,7 +11,7 @@ type Fundings = {
} }
function useFundings(project?: Partial<Project>) { function useFundings(project?: Partial<Project>) {
const fundings = ref<Fundings[]>(Array.isArray(project?.funding) ? project?.funding as unknown as Fundings[] : [project?.funding as unknown as Fundings]) const fundings = ref(Array.isArray(project?.funding) ? project.funding : (project?.funding ? [project.funding] : []))
const newFunding = reactive<Fundings>({ const newFunding = reactive<Fundings>({
name: '', name: '',
link: '', link: '',
@ -58,7 +58,7 @@ defineExpose({
/> />
<ProjectCreateComponentsItem <ProjectCreateComponentsItem
v-for="funding in fundings" v-for="funding in fundings"
:key="funding.name" :key="funding?.name"
@remove="() => removeFunding(fundings?.indexOf(funding))" @remove="() => removeFunding(fundings?.indexOf(funding))"
> >
<template #label> <template #label>
@ -69,13 +69,13 @@ defineExpose({
<span <span
class="text-app-black text-14px font-700" class="text-app-black text-14px font-700"
lg="text-16px" lg="text-16px"
> {{ funding.name }} > {{ funding?.name }}
</span> </span>
</div> </div>
</template> </template>
<template #desc> <template #desc>
<NuxtLink <NuxtLink
:to="funding.link" :to="funding?.link"
hover:text-app-black hover:text-app-black
class="text-app-black/50 text-16px hidden" class="text-app-black/50 text-16px hidden"
lg="block" lg="block"

View file

@ -76,30 +76,25 @@ const days = computed(() => {
lg="w-1/2" lg="w-1/2"
relative relative
> >
<SelectBox <ProjectCreateComponentsSelect
v-model="day" v-model="day"
w-full class="lg:w-full!"
:options="days.map(day => ({ label: day.toString(), value: day }))" :options="days.map(day => ({ label: day.toString(), value: day }))"
placeholder="Day" placeholder="Day"
:border-opacity="30"
:is-margin-top="false"
/> />
<SelectBox <ProjectCreateComponentsSelect
v-model="month" v-model="month"
w-full class="lg:w-full!"
mx--2px ml--2px
:options="months.map(month => ({ label: month.label, value: month.value }))" :options="months.map(month => ({ label: month.label, value: month.value }))"
placeholder="Month" placeholder="Month"
:border-opacity="30"
:is-margin-top="false"
/> />
<SelectBox <ProjectCreateComponentsSelect
v-model="year" v-model="year"
w-full class="lg:w-full!"
ml--2px
:options="years.map(year => ({ label: year.toString(), value: year }))" :options="years.map(year => ({ label: year.toString(), value: year }))"
placeholder="Year" placeholder="Year"
:border-opacity="30"
:is-margin-top="false"
/> />
</div> </div>
<span <span

View file

@ -45,13 +45,13 @@ const model = defineModel<string>()
lg="flex flex-row gap-24px items-center" lg="flex flex-row gap-24px items-center"
relative relative
> >
<div v-bind="$attrs"> <div v-bind="$attrs" shrink-0>
<textarea <textarea
v-if="textarea" v-if="textarea"
v-model="model" v-model="model"
:rows="textareaRows" :rows="textareaRows"
:placeholder="placeholder" :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="relative w-full p-8px text-left border-2px text-app-white bg-black sm:text-sm sm:leading-6 focus:outline-none focus:ring-0 focus:border-white"
:class="error ? 'border-app-danger/50' : 'border-white/30'" :class="error ? 'border-app-danger/50' : 'border-white/30'"
/> />
<input <input
@ -59,7 +59,7 @@ const model = defineModel<string>()
v-model="model" v-model="model"
:placeholder="placeholder" :placeholder="placeholder"
type="text" 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="relative w-full p-8px text-left border-2px text-app-white bg-black sm:text-sm sm:leading-6 focus:outline-none focus:ring-0 focus:border-white"
:class="error ? 'border-app-danger/50' : 'border-white/30'" :class="error ? 'border-app-danger/50' : 'border-white/30'"
> >
</div> </div>

View file

@ -7,7 +7,7 @@ const emits = defineEmits(['update:modelValue'])
interface SelectProps { interface SelectProps {
options: InputOption[] options: InputOption[]
label?: string label?: string
modelValue: string modelValue: any
placeholder?: string placeholder?: string
required?: boolean required?: boolean
hint?: string hint?: string
@ -24,7 +24,10 @@ const selectedValue = useVModel(props, 'modelValue', emits)
lg="flex flex-row gap-24px" lg="flex flex-row gap-24px"
relative relative
> >
<div lg="w-1/2"> <div
v-bind="$attrs"
lg="w-1/2"
>
<HeadlessListbox <HeadlessListbox
v-model="selectedValue" v-model="selectedValue"
as="div" as="div"
@ -48,11 +51,11 @@ const selectedValue = useVModel(props, 'modelValue', emits)
> >
<HeadlessListboxButton <HeadlessListboxButton
as="div" 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" 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 aria-expanded:border-white aria-expanded:z-10"
> >
<span <span
class="block truncate mr-8px" class="block truncate mr-8px"
:class="[selectedValue ? 'text-app-white' : 'font-400 text-white/50']" :style="[selectedValue ? 'text-app-white' : 'font-400 text-white/50']"
> >
{{ props.options.find(option => option.value === selectedValue)?.label || props.placeholder }} {{ props.options.find(option => option.value === selectedValue)?.label || props.placeholder }}
</span> </span>
@ -71,7 +74,7 @@ const selectedValue = useVModel(props, 'modelValue', emits)
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<HeadlessListboxOptions <HeadlessListboxOptions
class="absolute z-100 max-h-60 w-full divide-y-2px border-2px border-t-0 overflow-auto bg-app-black text-app-white focus:outline-none sm:text-sm border-white/30" 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"
> >
<HeadlessListboxOption <HeadlessListboxOption
v-for="option in props.options" v-for="option in props.options"

View file

@ -97,10 +97,9 @@ function addOption() {
</HeadlessComboboxLabel> </HeadlessComboboxLabel>
<div class="relative font-700 mt-2 bg-app-black"> <div class="relative font-700 mt-2 bg-app-black">
<HeadlessComboboxButton <HeadlessComboboxButton
as="div" 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="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'" :class="error ? 'border-app-danger/50' : 'border-white/30 aria-expanded:border-white'"
> >
<div class="flex flex-wrap gap-8px"> <div class="flex flex-wrap gap-8px">
<span <span
@ -123,8 +122,7 @@ function addOption() {
</button> </button>
</span> </span>
<HeadlessComboboxInput <HeadlessComboboxInput
class="text-14px font-400 leading-20px ml-8px w-fit bg-transparent border-none focus:ring-0 focus:outline-none" class="text-14px lg:focus:text-14px focus:text-16px font-400 leading-20px ml-8px w-fit bg-transparent border-none focus:ring-0 focus:outline-none"
lg="16px"
:placeholder="placeholder" :placeholder="placeholder"
@keyup.enter="addOption" @keyup.enter="addOption"
@change="query = $event.target.value" @change="query = $event.target.value"

View file

@ -9,6 +9,7 @@ export const useProject = defineStore('project', () => {
const { getProjectById } = useData() const { getProjectById } = useData()
function setProject(id: string) { function setProject(id: string) {
clearProject()
project.value = getProjectById(id, { shallow: false }) as Project project.value = getProjectById(id, { shallow: false }) as Project
delete project.value.ratings delete project.value.ratings
} }

View file

@ -0,0 +1,108 @@
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'
export const useProjectForm = defineStore('useProjectForm', () => {
const { saveProject, publishProject } = useProject()
const { project, isPublishing } = storeToRefs(useProject())
const isEditingName = ref(false)
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create']))
name.value = project.value?.name || 'Untitled'
function toggleEditName() {
isEditingName.value = !isEditingName.value
}
function saveName() {
saveProject({
...project.value,
name: name.value,
})
}
const tabs = reactive({
BASIC_INFO: { label: 'Basic Info', value: 'basic_info', component: markRaw(ProjectCreateCategoriesBasicInfo) },
ASSETS: { label: 'Assets', value: 'assets', component: markRaw(ProjectCreateCategoriesAssets) },
LINKS: { label: 'Links', value: 'links', component: markRaw(ProjectCreateCategoriesLinks) },
TECHNOLOGY: { label: 'Technology', value: 'technology', component: markRaw(ProjectCreateCategoriesTechnology) },
PRIVACY: { label: 'Privacy', value: 'privacy', component: markRaw(ProjectCreateCategoriesPrivacy) },
SECURITY: { label: 'Security', value: 'security', component: markRaw(ProjectCreateCategoriesSecurity) },
TEAM: { label: 'Team', value: 'team', component: markRaw(ProjectCreateCategoriesTeam) },
FUNDING: { label: 'Funding', value: 'funding', component: markRaw(ProjectCreateCategoriesFunding) },
HISTORY: { label: 'History', value: 'history', component: markRaw(ProjectCreateCategoriesHistory) },
})
const selectedTab = ref(0)
const tabsArray = computed(() => Object.values(tabs))
const currentComponent = ref()
async function next() {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
}
saveName()
currentComponent.value.save()
if (selectedTab.value === tabsArray.value.length - 1)
return
selectedTab.value = selectedTab.value + 1
}
async function jumpTo(index: number) {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
if (nameError.value)
return
else saveName()
}
saveName()
currentComponent.value.save()
selectedTab.value = index
}
async function publish(isNew?: boolean) {
if (selectedTab.value === 0) {
if (!(await currentComponent.value.isFormValid()) || nameError.value)
return
}
else if (isPublishing) {
return
}
saveName()
currentComponent.value?.save()
await publishProject()
if (isNew)
navigateTo('/')
else
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
}
return {
isEditingName,
name,
nameError,
toggleEditName,
saveName,
selectedTab,
tabsArray,
currentComponent,
next,
publish,
jumpTo,
}
})

View file

@ -1,21 +1,10 @@
<script setup lang="ts"> <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({ definePageMeta({
layout: 'create', layout: 'create',
}) })
const { projects } = useData() const { projects } = useData()
const { saveProject, setProject, publishProject, saveProjectImage } = useProject() const { setProject, saveProjectImage } = useProject()
const { project, isPublishing } = storeToRefs(useProject()) const { project, isPublishing } = storeToRefs(useProject())
const route = useRoute() const route = useRoute()
@ -30,27 +19,6 @@ if (!project.value) {
}) })
} }
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({ const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files accept: 'image/*', // Set to accept only image files
}) })
@ -69,84 +37,19 @@ onChange((files) => {
saveProjectImage(file) saveProjectImage(file)
}) })
const projectNameInput = ref<HTMLInputElement | null>(null) const { next, jumpTo, publish, toggleEditName } = useProjectForm()
function useProjectName() { const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
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()
name.value = project.value?.name || 'Untitled' name.value = project.value?.name || 'Untitled'
function save() { const projectNameInput = ref<HTMLInputElement | null>(null)
saveProject({ watch(isEditingName, () => {
...project.value, if (isEditingName.value) {
name: name.value, setTimeout(() => {
}) projectNameInput.value?.focus()
} }, 0)
async function next() {
if (nameError.value)
return
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 save()
}
else if (isPublishing) {
return
}
currentComponent.value.save()
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) const transitionDone = ref(false)
</script> </script>
@ -219,7 +122,7 @@ const transitionDone = ref(false)
relative relative
> >
<input <input
v-if="isEditing" v-if="isEditingName"
ref="projectNameInput" ref="projectNameInput"
v-model="name" v-model="name"
w-fit w-fit
@ -230,7 +133,7 @@ const transitionDone = ref(false)
leading-28px leading-28px
bg-app-bg-dark_grey bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';" onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()" @blur="toggleEditName()"
> >
<h2 <h2
v-else v-else
@ -241,8 +144,8 @@ const transitionDone = ref(false)
{{ name }} {{ name }}
</h2> </h2>
<button <button
v-if="!isEditing" v-if="!isEditingName"
@click="toggleEdit()" @click="toggleEditName()"
> >
<UnoIcon <UnoIcon
text-20px text-20px
@ -270,26 +173,25 @@ const transitionDone = ref(false)
gap-46px gap-46px
lg="mt-24px" lg="mt-24px"
> >
<SelectBox <ProjectCreateComponentsSelect
:model-value="selectedTab" :model-value="selectedTab"
label="Choose category" label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))" :options="tabsArray.map((t, index) => ({ label: t.label, value: index }))"
:border-opacity="100"
w-full w-full
lg:hidden class="lg:hidden! block!"
block block
mt-16px mt-16px
@update:model-value="$event => jumpTo($event)" @update:model-value="$event => jumpTo($event)"
/> />
<button <button
v-for="tab in tabs" v-for="(tab, index) in tabsArray"
:key="tab.value" :key="tab.value"
lg:block lg:block
hidden hidden
pb-8px pb-8px
leading-40px leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''" :class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="jumpTo(tab.value)" @click="jumpTo(index)"
> >
{{ tab.label }} {{ tab.label }}
</button> </button>
@ -316,7 +218,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true" @after-enter="transitionDone = true"
> >
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
w-full w-full
@ -326,7 +228,7 @@ const transitionDone = ref(false)
/> />
</Transition> </Transition>
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
v-else v-else
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
@ -336,7 +238,7 @@ const transitionDone = ref(false)
gap-8px gap-8px
/> />
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
class="hidden!" class="hidden!"
mt-48px mt-48px
lg="w-fit flex!" lg="w-fit flex!"
@ -348,64 +250,72 @@ const transitionDone = ref(false)
</ClientOnly> </ClientOnly>
</div> </div>
</div> </div>
<div <Transition
flex name="fade"
flex-col mode="out-in"
gap-10px appear
justify-center
text-center
md: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 <div
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="transitionDone"
flex flex
lg="w-fit hidden!" flex-col
border gap-10px
@click="next()" justify-center
> text-center
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
lg="hidden"
block block
text="12px italic app-white/50" lg:fixed
>or you can submit changes by publishing them</span> bottom-0
<div flex> 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 <Button
w-full v-if="selectedTab !== tabsArray.length - 1"
lg="w-fit" flex
lg="w-fit hidden!"
border border
@click="navigateTo(`/project/${route.params.id}`)" @click="next()"
> >
<span px-24px>CANCEL</span> <span px-24px>NEXT SECTION</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> </Button>
<span
v-if="selectedTab !== tabsArray.length - 1"
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(`/project/${route.params.id}`)"
>
<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>
</div> </Transition>
</div> </div>
</template> </template>

View file

@ -1,43 +1,11 @@
<script setup lang="ts"> <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({ definePageMeta({
layout: 'create', layout: 'create',
}) })
const { saveProject, publishProject, saveProjectImage } = useProject() const { saveProjectImage } = useProject()
const { project, isPublishing } = storeToRefs(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({ const { open, onChange } = useFileDialog({
accept: 'image/*', // Set to accept only image files accept: 'image/*', // Set to accept only image files
}) })
@ -56,80 +24,18 @@ onChange((files) => {
saveProjectImage(file) saveProjectImage(file)
}) })
const { next, jumpTo, publish, toggleEditName } = useProjectForm()
const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
const projectNameInput = ref<HTMLInputElement | null>(null) const projectNameInput = ref<HTMLInputElement | null>(null)
function useProjectName() { watch(isEditingName, () => {
const isEditing = ref(false) if (isEditingName.value) {
// const name = ref('Untitled') setTimeout(() => {
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined', 'Create', 'create'])) projectNameInput.value?.focus()
name.value = project.value?.name || 'Untitled' }, 0)
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 save()
}
else if (isPublishing) {
return
}
currentComponent.value?.save()
await publishProject()
navigateTo('/')
}
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) const transitionDone = ref(false)
</script> </script>
@ -203,7 +109,7 @@ const transitionDone = ref(false)
relative relative
> >
<input <input
v-if="isEditing" v-if="isEditingName"
ref="projectNameInput" ref="projectNameInput"
v-model="name" v-model="name"
w-fit w-fit
@ -214,7 +120,7 @@ const transitionDone = ref(false)
leading-28px leading-28px
bg-app-bg-dark_grey bg-app-bg-dark_grey
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';" onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
@blur="toggleEdit()" @blur="toggleEditName()"
> >
<h2 <h2
v-else v-else
@ -225,8 +131,8 @@ const transitionDone = ref(false)
{{ name }} {{ name }}
</h2> </h2>
<button <button
v-if="!isEditing" v-if="!isEditingName"
@click="toggleEdit()" @click="toggleEditName()"
> >
<UnoIcon <UnoIcon
text-20px text-20px
@ -254,26 +160,24 @@ const transitionDone = ref(false)
gap-46px gap-46px
lg="mt-24px" lg="mt-24px"
> >
<SelectBox <ProjectCreateComponentsSelect
:model-value="selectedTab" :model-value="selectedTab"
label="Choose category" label="Choose category"
:options="tabs.map(t => ({ label: t.label, value: t.value }))" :options="tabsArray.map((t, index) => ({ label: t.label, value: index }))"
:border-opacity="30"
w-full w-full
lg:hidden class="lg:hidden! block!"
block
mt-16px mt-16px
@update:model-value="$event => jumpTo($event)" @update:model-value="$event => jumpTo($event)"
/> />
<button <button
v-for="tab in tabs" v-for="(tab, index) in tabsArray"
:key="tab.value" :key="tab.value"
lg:block lg:block
hidden hidden
pb-8px pb-8px
leading-40px leading-40px
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''" :class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
@click="selectedTab = tab.value" @click="jumpTo(index)"
> >
{{ tab.label }} {{ tab.label }}
</button> </button>
@ -288,7 +192,6 @@ const transitionDone = ref(false)
> >
<div <div
app-container app-container
mb-170px
lg="mb-55px" lg="mb-55px"
> >
<ClientOnly> <ClientOnly>
@ -300,7 +203,7 @@ const transitionDone = ref(false)
@after-enter="transitionDone = true" @after-enter="transitionDone = true"
> >
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
w-full w-full
@ -310,7 +213,7 @@ const transitionDone = ref(false)
/> />
</Transition> </Transition>
<component <component
:is="getCurrentComponent()" :is="tabsArray[selectedTab].component"
v-else v-else
ref="currentComponent" ref="currentComponent"
:project="project" :project="project"
@ -320,7 +223,7 @@ const transitionDone = ref(false)
gap-24px gap-24px
/> />
<Button <Button
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="selectedTab !== tabsArray.length - 1"
class="hidden!" class="hidden!"
mt-48px mt-48px
lg="w-fit flex!" lg="w-fit flex!"
@ -332,64 +235,72 @@ const transitionDone = ref(false)
</ClientOnly> </ClientOnly>
</div> </div>
</div> </div>
<div <Transition
flex name="fade"
flex-col mode="out-in"
gap-16px appear
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 <div
v-if="selectedTab !== tabs[tabs.length - 1].value" v-if="transitionDone"
flex flex
lg="w-fit hidden!" flex-col
border gap-16px
@click="next()" justify-center
> text-center
<span px-24px>NEXT SECTION</span>
</Button>
<span
v-if="selectedTab !== tabs[tabs.length - 1].value"
lg="hidden"
block block
text="12px italic app-white/50" lg:fixed
>or you can submit changes by publishing them</span> bottom-0
<div flex> 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 <Button
w-full v-if="selectedTab !== tabsArray.length - 1"
lg="w-fit" flex
lg="w-fit hidden!"
border border
@click="navigateTo('/')" @click="next()"
> >
<span px-24px>CANCEL</span> <span px-24px>NEXT SECTION</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> </Button>
<span
v-if="selectedTab !== tabsArray.length - 1"
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(true)"
>
<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>
</div> </Transition>
</div> </div>
</template> </template>