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() {
|
||||
validate()
|
||||
async function isFormValid() {
|
||||
await validate()
|
||||
|
||||
if (meta.value.valid) {
|
||||
return true
|
||||
|
|
|
@ -11,7 +11,7 @@ type Fundings = {
|
|||
}
|
||||
|
||||
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>({
|
||||
name: '',
|
||||
link: '',
|
||||
|
@ -58,7 +58,7 @@ defineExpose({
|
|||
/>
|
||||
<ProjectCreateComponentsItem
|
||||
v-for="funding in fundings"
|
||||
:key="funding.name"
|
||||
:key="funding?.name"
|
||||
@remove="() => removeFunding(fundings?.indexOf(funding))"
|
||||
>
|
||||
<template #label>
|
||||
|
@ -69,13 +69,13 @@ defineExpose({
|
|||
<span
|
||||
class="text-app-black text-14px font-700"
|
||||
lg="text-16px"
|
||||
> {{ funding.name }}
|
||||
> {{ funding?.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #desc>
|
||||
<NuxtLink
|
||||
:to="funding.link"
|
||||
:to="funding?.link"
|
||||
hover:text-app-black
|
||||
class="text-app-black/50 text-16px hidden"
|
||||
lg="block"
|
||||
|
|
|
@ -76,30 +76,25 @@ const days = computed(() => {
|
|||
lg="w-1/2"
|
||||
relative
|
||||
>
|
||||
<SelectBox
|
||||
<ProjectCreateComponentsSelect
|
||||
v-model="day"
|
||||
w-full
|
||||
class="lg:w-full!"
|
||||
:options="days.map(day => ({ label: day.toString(), value: day }))"
|
||||
placeholder="Day"
|
||||
:border-opacity="30"
|
||||
:is-margin-top="false"
|
||||
/>
|
||||
<SelectBox
|
||||
<ProjectCreateComponentsSelect
|
||||
v-model="month"
|
||||
w-full
|
||||
mx--2px
|
||||
class="lg:w-full!"
|
||||
ml--2px
|
||||
:options="months.map(month => ({ label: month.label, value: month.value }))"
|
||||
placeholder="Month"
|
||||
:border-opacity="30"
|
||||
:is-margin-top="false"
|
||||
/>
|
||||
<SelectBox
|
||||
<ProjectCreateComponentsSelect
|
||||
v-model="year"
|
||||
w-full
|
||||
class="lg:w-full!"
|
||||
ml--2px
|
||||
:options="years.map(year => ({ label: year.toString(), value: year }))"
|
||||
placeholder="Year"
|
||||
:border-opacity="30"
|
||||
:is-margin-top="false"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
|
|
|
@ -45,13 +45,13 @@ const model = defineModel<string>()
|
|||
lg="flex flex-row gap-24px items-center"
|
||||
relative
|
||||
>
|
||||
<div v-bind="$attrs">
|
||||
<div v-bind="$attrs" shrink-0>
|
||||
<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="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'"
|
||||
/>
|
||||
<input
|
||||
|
@ -59,7 +59,7 @@ const model = defineModel<string>()
|
|||
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="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'"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ const emits = defineEmits(['update:modelValue'])
|
|||
interface SelectProps {
|
||||
options: InputOption[]
|
||||
label?: string
|
||||
modelValue: string
|
||||
modelValue: any
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
hint?: string
|
||||
|
@ -24,7 +24,10 @@ const selectedValue = useVModel(props, 'modelValue', emits)
|
|||
lg="flex flex-row gap-24px"
|
||||
relative
|
||||
>
|
||||
<div lg="w-1/2">
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
lg="w-1/2"
|
||||
>
|
||||
<HeadlessListbox
|
||||
v-model="selectedValue"
|
||||
as="div"
|
||||
|
@ -48,11 +51,11 @@ const selectedValue = useVModel(props, 'modelValue', emits)
|
|||
>
|
||||
<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"
|
||||
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
|
||||
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 }}
|
||||
</span>
|
||||
|
@ -71,7 +74,7 @@ const selectedValue = useVModel(props, 'modelValue', emits)
|
|||
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"
|
||||
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
|
||||
v-for="option in props.options"
|
||||
|
|
|
@ -97,10 +97,9 @@ function addOption() {
|
|||
</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'"
|
||||
:class="error ? 'border-app-danger/50' : 'border-white/30 aria-expanded:border-white'"
|
||||
>
|
||||
<div class="flex flex-wrap gap-8px">
|
||||
<span
|
||||
|
@ -123,8 +122,7 @@ function addOption() {
|
|||
</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"
|
||||
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"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="addOption"
|
||||
@change="query = $event.target.value"
|
||||
|
|
|
@ -9,6 +9,7 @@ export const useProject = defineStore('project', () => {
|
|||
const { getProjectById } = useData()
|
||||
|
||||
function setProject(id: string) {
|
||||
clearProject()
|
||||
project.value = getProjectById(id, { shallow: false }) as Project
|
||||
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">
|
||||
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 { projects } = useData()
|
||||
const { saveProject, setProject, publishProject, saveProjectImage } = useProject()
|
||||
const { setProject, saveProjectImage } = useProject()
|
||||
const { project, isPublishing } = storeToRefs(useProject())
|
||||
|
||||
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({
|
||||
accept: 'image/*', // Set to accept only image files
|
||||
})
|
||||
|
@ -69,84 +37,19 @@ onChange((files) => {
|
|||
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()
|
||||
const { next, jumpTo, publish, toggleEditName } = useProjectForm()
|
||||
const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
|
||||
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()
|
||||
const projectNameInput = ref<HTMLInputElement | null>(null)
|
||||
watch(isEditingName, () => {
|
||||
if (isEditingName.value) {
|
||||
setTimeout(() => {
|
||||
projectNameInput.value?.focus()
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
</script>
|
||||
|
||||
|
@ -219,7 +122,7 @@ const transitionDone = ref(false)
|
|||
relative
|
||||
>
|
||||
<input
|
||||
v-if="isEditing"
|
||||
v-if="isEditingName"
|
||||
ref="projectNameInput"
|
||||
v-model="name"
|
||||
w-fit
|
||||
|
@ -230,7 +133,7 @@ const transitionDone = ref(false)
|
|||
leading-28px
|
||||
bg-app-bg-dark_grey
|
||||
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
|
||||
@blur="toggleEdit()"
|
||||
@blur="toggleEditName()"
|
||||
>
|
||||
<h2
|
||||
v-else
|
||||
|
@ -241,8 +144,8 @@ const transitionDone = ref(false)
|
|||
{{ name }}
|
||||
</h2>
|
||||
<button
|
||||
v-if="!isEditing"
|
||||
@click="toggleEdit()"
|
||||
v-if="!isEditingName"
|
||||
@click="toggleEditName()"
|
||||
>
|
||||
<UnoIcon
|
||||
text-20px
|
||||
|
@ -270,26 +173,25 @@ const transitionDone = ref(false)
|
|||
gap-46px
|
||||
lg="mt-24px"
|
||||
>
|
||||
<SelectBox
|
||||
<ProjectCreateComponentsSelect
|
||||
:model-value="selectedTab"
|
||||
label="Choose category"
|
||||
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
|
||||
:border-opacity="100"
|
||||
:options="tabsArray.map((t, index) => ({ label: t.label, value: index }))"
|
||||
w-full
|
||||
lg:hidden
|
||||
class="lg:hidden! block!"
|
||||
block
|
||||
mt-16px
|
||||
@update:model-value="$event => jumpTo($event)"
|
||||
/>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabsArray"
|
||||
:key="tab.value"
|
||||
lg:block
|
||||
hidden
|
||||
pb-8px
|
||||
leading-40px
|
||||
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
|
||||
@click="jumpTo(tab.value)"
|
||||
:class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
|
||||
@click="jumpTo(index)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
|
@ -316,7 +218,7 @@ const transitionDone = ref(false)
|
|||
@after-enter="transitionDone = true"
|
||||
>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
:is="tabsArray[selectedTab].component"
|
||||
ref="currentComponent"
|
||||
:project="project"
|
||||
w-full
|
||||
|
@ -326,7 +228,7 @@ const transitionDone = ref(false)
|
|||
/>
|
||||
</Transition>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
:is="tabsArray[selectedTab].component"
|
||||
v-else
|
||||
ref="currentComponent"
|
||||
:project="project"
|
||||
|
@ -336,7 +238,7 @@ const transitionDone = ref(false)
|
|||
gap-8px
|
||||
/>
|
||||
<Button
|
||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
||||
v-if="selectedTab !== tabsArray.length - 1"
|
||||
class="hidden!"
|
||||
mt-48px
|
||||
lg="w-fit flex!"
|
||||
|
@ -348,64 +250,72 @@ const transitionDone = ref(false)
|
|||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
flex
|
||||
flex-col
|
||||
gap-10px
|
||||
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
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<Button
|
||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
||||
<div
|
||||
v-if="transitionDone"
|
||||
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"
|
||||
flex-col
|
||||
gap-10px
|
||||
justify-center
|
||||
text-center
|
||||
block
|
||||
text="12px italic app-white/50"
|
||||
>or you can submit changes by publishing them</span>
|
||||
<div flex>
|
||||
lg: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
|
||||
w-full
|
||||
lg="w-fit"
|
||||
v-if="selectedTab !== tabsArray.length - 1"
|
||||
flex
|
||||
lg="w-fit hidden!"
|
||||
border
|
||||
@click="navigateTo(`/project/${route.params.id}`)"
|
||||
@click="next()"
|
||||
>
|
||||
<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>
|
||||
<span px-24px>NEXT SECTION</span>
|
||||
</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>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,43 +1,11 @@
|
|||
<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 { 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
|
||||
})
|
||||
|
@ -56,80 +24,18 @@ onChange((files) => {
|
|||
saveProjectImage(file)
|
||||
})
|
||||
|
||||
const { next, jumpTo, publish, toggleEditName } = useProjectForm()
|
||||
const { currentComponent, selectedTab, tabsArray, isEditingName, name, nameError } = storeToRefs(useProjectForm())
|
||||
|
||||
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)
|
||||
}
|
||||
watch(isEditingName, () => {
|
||||
if (isEditingName.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)
|
||||
</script>
|
||||
|
||||
|
@ -203,7 +109,7 @@ const transitionDone = ref(false)
|
|||
relative
|
||||
>
|
||||
<input
|
||||
v-if="isEditing"
|
||||
v-if="isEditingName"
|
||||
ref="projectNameInput"
|
||||
v-model="name"
|
||||
w-fit
|
||||
|
@ -214,7 +120,7 @@ const transitionDone = ref(false)
|
|||
leading-28px
|
||||
bg-app-bg-dark_grey
|
||||
onfocus="this.style.width = 0; this.style.width = this.scrollWidth + 2 + 'px';"
|
||||
@blur="toggleEdit()"
|
||||
@blur="toggleEditName()"
|
||||
>
|
||||
<h2
|
||||
v-else
|
||||
|
@ -225,8 +131,8 @@ const transitionDone = ref(false)
|
|||
{{ name }}
|
||||
</h2>
|
||||
<button
|
||||
v-if="!isEditing"
|
||||
@click="toggleEdit()"
|
||||
v-if="!isEditingName"
|
||||
@click="toggleEditName()"
|
||||
>
|
||||
<UnoIcon
|
||||
text-20px
|
||||
|
@ -254,26 +160,24 @@ const transitionDone = ref(false)
|
|||
gap-46px
|
||||
lg="mt-24px"
|
||||
>
|
||||
<SelectBox
|
||||
<ProjectCreateComponentsSelect
|
||||
:model-value="selectedTab"
|
||||
label="Choose category"
|
||||
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
|
||||
:border-opacity="30"
|
||||
:options="tabsArray.map((t, index) => ({ label: t.label, value: index }))"
|
||||
w-full
|
||||
lg:hidden
|
||||
block
|
||||
class="lg:hidden! block!"
|
||||
mt-16px
|
||||
@update:model-value="$event => jumpTo($event)"
|
||||
/>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabsArray"
|
||||
: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"
|
||||
:class="selectedTab === index ? 'font-bold border-b-4 border-app-white' : ''"
|
||||
@click="jumpTo(index)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
|
@ -288,7 +192,6 @@ const transitionDone = ref(false)
|
|||
>
|
||||
<div
|
||||
app-container
|
||||
mb-170px
|
||||
lg="mb-55px"
|
||||
>
|
||||
<ClientOnly>
|
||||
|
@ -300,7 +203,7 @@ const transitionDone = ref(false)
|
|||
@after-enter="transitionDone = true"
|
||||
>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
:is="tabsArray[selectedTab].component"
|
||||
ref="currentComponent"
|
||||
:project="project"
|
||||
w-full
|
||||
|
@ -310,7 +213,7 @@ const transitionDone = ref(false)
|
|||
/>
|
||||
</Transition>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
:is="tabsArray[selectedTab].component"
|
||||
v-else
|
||||
ref="currentComponent"
|
||||
:project="project"
|
||||
|
@ -320,7 +223,7 @@ const transitionDone = ref(false)
|
|||
gap-24px
|
||||
/>
|
||||
<Button
|
||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
||||
v-if="selectedTab !== tabsArray.length - 1"
|
||||
class="hidden!"
|
||||
mt-48px
|
||||
lg="w-fit flex!"
|
||||
|
@ -332,64 +235,72 @@ const transitionDone = ref(false)
|
|||
</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
|
||||
<Transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
appear
|
||||
>
|
||||
<Button
|
||||
v-if="selectedTab !== tabs[tabs.length - 1].value"
|
||||
<div
|
||||
v-if="transitionDone"
|
||||
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"
|
||||
flex-col
|
||||
gap-16px
|
||||
justify-center
|
||||
text-center
|
||||
block
|
||||
text="12px italic app-white/50"
|
||||
>or you can submit changes by publishing them</span>
|
||||
<div flex>
|
||||
lg: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
|
||||
w-full
|
||||
lg="w-fit"
|
||||
v-if="selectedTab !== tabsArray.length - 1"
|
||||
flex
|
||||
lg="w-fit hidden!"
|
||||
border
|
||||
@click="navigateTo('/')"
|
||||
@click="next()"
|
||||
>
|
||||
<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>
|
||||
<span px-24px>NEXT SECTION</span>
|
||||
</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>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in a new issue