mirror of
https://github.com/web3privacy/explorer-app.git
synced 2024-10-15 16:46:26 +02:00
Merge pull request #49 from web3privacy/dw/bug-fixes
Bug fixes and polishing
This commit is contained in:
commit
cd476c6367
10 changed files with 298 additions and 372 deletions
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
108
composables/useProjectForm.ts
Normal file
108
composables/useProjectForm.ts
Normal 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,
|
||||||
|
}
|
||||||
|
})
|
|
@ -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'
|
name.value = project.value?.name || 'Untitled'
|
||||||
|
|
||||||
function toggleEdit() {
|
const projectNameInput = ref<HTMLInputElement | null>(null)
|
||||||
isEditing.value = !isEditing.value
|
watch(isEditingName, () => {
|
||||||
if (isEditing.value) {
|
if (isEditingName.value) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
projectNameInput.value?.focus()
|
projectNameInput.value?.focus()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isEditing,
|
|
||||||
name,
|
|
||||||
nameError,
|
|
||||||
toggleEdit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isEditing, name, nameError, toggleEdit } = useProjectName()
|
|
||||||
name.value = project.value?.name || 'Untitled'
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
saveProject({
|
|
||||||
...project.value,
|
|
||||||
name: name.value,
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
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,13 +250,20 @@ const transitionDone = ref(false)
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Transition
|
||||||
|
name="fade"
|
||||||
|
mode="out-in"
|
||||||
|
appear
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
|
v-if="transitionDone"
|
||||||
flex
|
flex
|
||||||
flex-col
|
flex-col
|
||||||
gap-10px
|
gap-10px
|
||||||
justify-center
|
justify-center
|
||||||
text-center
|
text-center
|
||||||
md:fixed
|
block
|
||||||
|
lg:fixed
|
||||||
bottom-0
|
bottom-0
|
||||||
w-full
|
w-full
|
||||||
bg-app-bg-dark_grey
|
bg-app-bg-dark_grey
|
||||||
|
@ -363,7 +272,7 @@ const transitionDone = ref(false)
|
||||||
p-12px
|
p-12px
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
v-if="selectedTab !== tabsArray.length - 1"
|
||||||
flex
|
flex
|
||||||
lg="w-fit hidden!"
|
lg="w-fit hidden!"
|
||||||
border
|
border
|
||||||
|
@ -372,7 +281,7 @@ const transitionDone = ref(false)
|
||||||
<span px-24px>NEXT SECTION</span>
|
<span px-24px>NEXT SECTION</span>
|
||||||
</Button>
|
</Button>
|
||||||
<span
|
<span
|
||||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
v-if="selectedTab !== tabsArray.length - 1"
|
||||||
lg="hidden"
|
lg="hidden"
|
||||||
block
|
block
|
||||||
text="12px italic app-white/50"
|
text="12px italic app-white/50"
|
||||||
|
@ -406,6 +315,7 @@ const transitionDone = ref(false)
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -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 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() {
|
const projectNameInput = ref<HTMLInputElement | null>(null)
|
||||||
isEditing.value = !isEditing.value
|
watch(isEditingName, () => {
|
||||||
if (isEditing.value) {
|
if (isEditingName.value) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
projectNameInput.value?.focus()
|
projectNameInput.value?.focus()
|
||||||
}, 0)
|
}, 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,13 +235,20 @@ const transitionDone = ref(false)
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Transition
|
||||||
|
name="fade"
|
||||||
|
mode="out-in"
|
||||||
|
appear
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
|
v-if="transitionDone"
|
||||||
flex
|
flex
|
||||||
flex-col
|
flex-col
|
||||||
gap-16px
|
gap-16px
|
||||||
justify-center
|
justify-center
|
||||||
text-center
|
text-center
|
||||||
fixed
|
block
|
||||||
|
lg:fixed
|
||||||
bottom-0
|
bottom-0
|
||||||
w-full
|
w-full
|
||||||
bg-app-bg-dark_grey
|
bg-app-bg-dark_grey
|
||||||
|
@ -347,7 +257,7 @@ const transitionDone = ref(false)
|
||||||
p-12px
|
p-12px
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
v-if="selectedTab !== tabsArray.length - 1"
|
||||||
flex
|
flex
|
||||||
lg="w-fit hidden!"
|
lg="w-fit hidden!"
|
||||||
border
|
border
|
||||||
|
@ -356,7 +266,7 @@ const transitionDone = ref(false)
|
||||||
<span px-24px>NEXT SECTION</span>
|
<span px-24px>NEXT SECTION</span>
|
||||||
</Button>
|
</Button>
|
||||||
<span
|
<span
|
||||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
v-if="selectedTab !== tabsArray.length - 1"
|
||||||
lg="hidden"
|
lg="hidden"
|
||||||
block
|
block
|
||||||
text="12px italic app-white/50"
|
text="12px italic app-white/50"
|
||||||
|
@ -374,7 +284,7 @@ const transitionDone = ref(false)
|
||||||
w-full
|
w-full
|
||||||
lg="w-fit"
|
lg="w-fit"
|
||||||
inverted-color
|
inverted-color
|
||||||
@click="publish()"
|
@click="publish(true)"
|
||||||
>
|
>
|
||||||
<UnoIcon
|
<UnoIcon
|
||||||
v-if="isPublishing"
|
v-if="isPublishing"
|
||||||
|
@ -390,6 +300,7 @@ const transitionDone = ref(false)
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue