mirror of
https://github.com/web3privacy/explorer-app.git
synced 2024-10-15 16:46:26 +02:00
refactor: project store, project save, validations
This commit is contained in:
parent
67660b0972
commit
a94ea73d15
13 changed files with 187 additions and 100 deletions
|
@ -44,7 +44,7 @@ function useTokens(project?: Partial<Project>) {
|
|||
const { tokens, newToken, addToken, removeToken } = useTokens(props.project)
|
||||
const assetsUsed = ref<string[]>(Array.isArray(props.project?.assets_used) ? props.project?.assets_used.map(a => a.toLowerCase()) : [])
|
||||
|
||||
const { useProject, assetsData } = useData()
|
||||
const { assetsData } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -6,7 +6,7 @@ const props = defineProps<{
|
|||
project?: Partial<Project>
|
||||
}>()
|
||||
|
||||
const { useProject, categoriesData, usecasesData, ecosystemsData } = useData()
|
||||
const { categoriesData, usecasesData, ecosystemsData } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
|
@ -31,8 +31,8 @@ const isDead = ref(props.project?.sunset || false)
|
|||
|
||||
resetForm({
|
||||
values: {
|
||||
categories: props.project?.categories?.map(c => c.toLowerCase()) || [],
|
||||
usecases: props.project?.usecases?.map(u => u.toLowerCase()) || [],
|
||||
categories: Array.isArray(props.project?.categories) ? props.project?.categories?.map(c => c.toLowerCase()) : [],
|
||||
usecases: Array.isArray(props.project?.usecases) ? props.project?.usecases?.map(u => u.toLowerCase()) : [],
|
||||
ecosystems: Array.isArray(props.project?.ecosystem) ? props.project?.ecosystem?.map(e => e.toLowerCase()) : [],
|
||||
description: props.project?.description || '',
|
||||
},
|
||||
|
@ -55,7 +55,7 @@ function save() {
|
|||
usecases: usecases.value,
|
||||
ecosystem: ecosystems.value,
|
||||
description: description.value,
|
||||
product_launch_day: new Date(year.value, month.value, day.value).toISOString(),
|
||||
product_launch_day: (year.value && month.value && day.value) ? new Date(year.value, month.value, day.value).toISOString() : undefined,
|
||||
sunset: isDead.value,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ function useFundings(project?: Partial<Project>) {
|
|||
|
||||
const { fundings, newFunding, addFunding, removeFunding } = useFundings(props.project)
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -72,7 +72,6 @@ function formatDate(date: string) {
|
|||
return `${day}/${month}/${year}`
|
||||
}
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -19,7 +19,6 @@ const telegram = ref<string>(props.project?.links?.telegram || '')
|
|||
const lens = ref<string>(props.project?.links?.lens || '')
|
||||
const farcaster = ref<string>(props.project?.links?.farcaster || '')
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -13,7 +13,7 @@ const signRequirements = ref(Array.isArray(props.project?.tracebility?.sign_in_t
|
|||
const trackedData = ref(props.project?.tracebility?.tracked_data || '')
|
||||
const dataUsage = ref(props.project?.privacy_policy?.data_usage || '')
|
||||
|
||||
const { useProject, signInRequirmentsData } = useData()
|
||||
const { signInRequirmentsData } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -73,7 +73,6 @@ const thirdPartyDep = ref(props.project?.third_party_dependency || '')
|
|||
const socialTrust = ref(props.project?.social_trust || '')
|
||||
const spof = ref(props.project?.technical_spof || '')
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -39,7 +39,6 @@ function useMembers(project?: Partial<Project>) {
|
|||
|
||||
const { members, newMember, addMember, removeMember } = useMembers(props.project)
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -13,7 +13,7 @@ const encryption = ref(props.project?.blockchain_features?.encryption || '')
|
|||
const peerToPeer = ref(props.project?.blockchain_features?.p2p)
|
||||
const decentralizedStorage = ref(props.project?.storage?.decentralized)
|
||||
|
||||
const { useProject, projectPhaseData, assetCustodyData } = useData()
|
||||
const { projectPhaseData, assetCustodyData } = useData()
|
||||
const { saveProject } = useProject()
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Buffer } from 'buffer'
|
||||
import type { Category, Project, ProjectShallow } from '~/types'
|
||||
|
||||
export const useData = defineStore('data', () => {
|
||||
|
@ -146,73 +145,6 @@ export const useData = defineStore('data', () => {
|
|||
|
||||
const filteredProjectsCount = computed(() => filteredProjects.value.length)
|
||||
|
||||
function useProject() {
|
||||
const project = ref<Partial<Project>>()
|
||||
const projectImage = ref<File>()
|
||||
const isPublishing = ref(false)
|
||||
|
||||
function setProject(id: string) {
|
||||
project.value = getProjectById(id, { shallow: false }) as Project
|
||||
}
|
||||
|
||||
function clearProject() {
|
||||
project.value = undefined
|
||||
}
|
||||
|
||||
function saveProject(data: Partial<Project>) {
|
||||
project.value = {
|
||||
...project.value,
|
||||
...data,
|
||||
}
|
||||
}
|
||||
|
||||
function saveProjectImage(image: File) {
|
||||
projectImage.value = image
|
||||
}
|
||||
|
||||
async function publishProject() {
|
||||
isPublishing.value = true
|
||||
try {
|
||||
const imageArrayBuffer = await projectImage.value?.arrayBuffer()
|
||||
let imageBuffer: Buffer | undefined
|
||||
let base64Image: string | undefined
|
||||
|
||||
if (imageArrayBuffer) {
|
||||
imageBuffer = Buffer.from(imageArrayBuffer)
|
||||
const base64String = imageBuffer.toString('base64')
|
||||
base64Image = base64String
|
||||
}
|
||||
|
||||
await $fetch(`/api/data`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
project: project.value,
|
||||
image: {
|
||||
type: projectImage.value?.type,
|
||||
data: base64Image,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
finally {
|
||||
isPublishing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
project,
|
||||
isPublishing,
|
||||
setProject,
|
||||
clearProject,
|
||||
saveProject,
|
||||
saveProjectImage,
|
||||
publishProject,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectedCategoryId,
|
||||
filter,
|
||||
|
@ -234,6 +166,5 @@ export const useData = defineStore('data', () => {
|
|||
getProjectsByCategory,
|
||||
filteredProjects,
|
||||
projectToShallow,
|
||||
useProject,
|
||||
}
|
||||
})
|
||||
|
|
71
composables/useProject.ts
Normal file
71
composables/useProject.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { Buffer } from 'buffer'
|
||||
import type { Project } from '~/types'
|
||||
|
||||
export const useProject = defineStore('project', () => {
|
||||
const project = ref<Partial<Project>>()
|
||||
const projectImage = ref<File>()
|
||||
const isPublishing = ref(false)
|
||||
|
||||
const { getProjectById } = useData()
|
||||
|
||||
function setProject(id: string) {
|
||||
project.value = getProjectById(id, { shallow: false }) as Project
|
||||
}
|
||||
|
||||
function clearProject() {
|
||||
project.value = undefined
|
||||
}
|
||||
|
||||
function saveProject(data: Partial<Project>) {
|
||||
project.value = {
|
||||
...project.value,
|
||||
...data,
|
||||
}
|
||||
}
|
||||
|
||||
function saveProjectImage(image: File) {
|
||||
projectImage.value = image
|
||||
}
|
||||
|
||||
async function publishProject() {
|
||||
isPublishing.value = true
|
||||
try {
|
||||
const imageArrayBuffer = await projectImage.value?.arrayBuffer()
|
||||
let imageBuffer: Buffer | undefined
|
||||
let base64Image: string | undefined
|
||||
|
||||
if (imageArrayBuffer) {
|
||||
imageBuffer = Buffer.from(imageArrayBuffer)
|
||||
const base64String = imageBuffer.toString('base64')
|
||||
base64Image = base64String
|
||||
}
|
||||
|
||||
await $fetch(`/api/data`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
project: project.value,
|
||||
image: {
|
||||
type: projectImage.value?.type,
|
||||
data: base64Image,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
finally {
|
||||
isPublishing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
project,
|
||||
isPublishing,
|
||||
setProject,
|
||||
clearProject,
|
||||
saveProject,
|
||||
saveProjectImage,
|
||||
publishProject,
|
||||
}
|
||||
})
|
|
@ -1,4 +1,5 @@
|
|||
<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'
|
||||
|
@ -13,8 +14,9 @@ definePageMeta({
|
|||
layout: 'create',
|
||||
})
|
||||
|
||||
const { useProject, projects } = useData()
|
||||
const { saveProject, setProject, project, publishProject, saveProjectImage, isPublishing } = useProject()
|
||||
const { projects } = useData()
|
||||
const { saveProject, setProject, publishProject, saveProjectImage } = useProject()
|
||||
const { project, isPublishing } = storeToRefs(useProject())
|
||||
|
||||
const route = useRoute()
|
||||
await until(projects).toMatch(p => p?.length > 0)
|
||||
|
@ -70,7 +72,9 @@ onChange((files) => {
|
|||
const projectNameInput = ref<HTMLInputElement | null>(null)
|
||||
function useProjectName() {
|
||||
const isEditing = ref(false)
|
||||
const name = ref('Untitled')
|
||||
// const name = ref('Untitled')
|
||||
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined']))
|
||||
name.value = project.value?.name || 'Untitled'
|
||||
|
||||
function toggleEdit() {
|
||||
isEditing.value = !isEditing.value
|
||||
|
@ -84,11 +88,12 @@ function useProjectName() {
|
|||
return {
|
||||
isEditing,
|
||||
name,
|
||||
nameError,
|
||||
toggleEdit,
|
||||
}
|
||||
}
|
||||
|
||||
const { isEditing, name, toggleEdit } = useProjectName()
|
||||
const { isEditing, name, nameError, toggleEdit } = useProjectName()
|
||||
name.value = project.value?.name || 'Untitled'
|
||||
|
||||
function save() {
|
||||
|
@ -97,7 +102,10 @@ function save() {
|
|||
})
|
||||
}
|
||||
|
||||
function next() {
|
||||
async function next() {
|
||||
if (nameError.value)
|
||||
return
|
||||
|
||||
if (selectedTab.value === 'basic_info') {
|
||||
if (!currentComponent.value.isFormValid())
|
||||
return
|
||||
|
@ -110,6 +118,31 @@ function next() {
|
|||
const nextTab = tabs[currentIndex + 1] || tabs[0]
|
||||
selectedTab.value = nextTab.value
|
||||
}
|
||||
|
||||
async function publish() {
|
||||
if (selectedTab.value === 'basic_info') {
|
||||
if (!currentComponent.value.isFormValid())
|
||||
return
|
||||
}
|
||||
else if (isPublishing) {
|
||||
return
|
||||
}
|
||||
|
||||
await publishProject()
|
||||
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
|
||||
}
|
||||
|
||||
function jumpTo(tab: string) {
|
||||
if (selectedTab.value === 'basic_info') {
|
||||
if (!currentComponent.value.isFormValid())
|
||||
return
|
||||
else save()
|
||||
}
|
||||
|
||||
currentComponent.value.save()
|
||||
|
||||
selectedTab.value = tab
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -178,6 +211,7 @@ function next() {
|
|||
flex
|
||||
items-center
|
||||
gap-12px
|
||||
relative
|
||||
>
|
||||
<input
|
||||
v-if="isEditing"
|
||||
|
@ -216,6 +250,16 @@ function next() {
|
|||
i-heroicons-solid-pencil
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
v-if="nameError"
|
||||
text-nowrap
|
||||
text-app-danger
|
||||
text-12px
|
||||
absolute
|
||||
lg:bottom--24px
|
||||
bottom--16px
|
||||
select-none
|
||||
>Invalid project name</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -226,7 +270,7 @@ function next() {
|
|||
lg="mt-24px"
|
||||
>
|
||||
<SelectBox
|
||||
v-model="selectedTab"
|
||||
:model-value="selectedTab"
|
||||
label="Choose category"
|
||||
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
|
||||
:border-opacity="30"
|
||||
|
@ -234,6 +278,7 @@ function next() {
|
|||
lg:hidden
|
||||
block
|
||||
mt-16px
|
||||
@update:model-value="$event => jumpTo($event)"
|
||||
/>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
|
@ -243,7 +288,7 @@ function next() {
|
|||
pb-8px
|
||||
leading-40px
|
||||
:class="selectedTab === tab.value ? 'font-bold border-b-4 border-app-white' : ''"
|
||||
@click="selectedTab = tab.value"
|
||||
@click="jumpTo(tab.value)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
|
@ -258,7 +303,8 @@ function next() {
|
|||
>
|
||||
<div
|
||||
app-container
|
||||
mb-55px
|
||||
mb-170px
|
||||
lg="mb-55px"
|
||||
>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
|
@ -288,7 +334,7 @@ function next() {
|
|||
gap-16px
|
||||
justify-center
|
||||
text-center
|
||||
absolute
|
||||
fixed
|
||||
bottom-0
|
||||
w-full
|
||||
bg-app-bg-dark_grey
|
||||
|
@ -316,6 +362,7 @@ function next() {
|
|||
w-full
|
||||
lg="w-fit"
|
||||
border
|
||||
@click="navigateTo(`/project/${route.params.id}`)"
|
||||
>
|
||||
<span px-24px>CANCEL</span>
|
||||
</Button>
|
||||
|
@ -323,7 +370,7 @@ function next() {
|
|||
w-full
|
||||
lg="w-fit"
|
||||
inverted-color
|
||||
@click="isPublishing ? () => null : publishProject()"
|
||||
@click="publish()"
|
||||
>
|
||||
<UnoIcon
|
||||
v-if="isPublishing"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<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'
|
||||
|
@ -13,6 +14,9 @@ definePageMeta({
|
|||
layout: 'create',
|
||||
})
|
||||
|
||||
const { saveProject, publishProject, saveProjectImage } = useProject()
|
||||
const { project, isPublishing } = storeToRefs(useProject())
|
||||
|
||||
const tabs = reactive([
|
||||
{ label: 'Basic Info', value: 'basic_info', component: ProjectCreateCategoriesBasicInfo },
|
||||
{ label: 'Assets', value: 'assets', component: ProjectCreateCategoriesAssets },
|
||||
|
@ -55,7 +59,9 @@ onChange((files) => {
|
|||
const projectNameInput = ref<HTMLInputElement | null>(null)
|
||||
function useProjectName() {
|
||||
const isEditing = ref(false)
|
||||
const name = ref('Untitled')
|
||||
// const name = ref('Untitled')
|
||||
const { value: name, errorMessage: nameError } = useField<string>('name', yup.string().required().notOneOf(['Untitled', 'Undefined']))
|
||||
name.value = project.value?.name || 'Untitled'
|
||||
|
||||
function toggleEdit() {
|
||||
isEditing.value = !isEditing.value
|
||||
|
@ -69,17 +75,16 @@ function useProjectName() {
|
|||
return {
|
||||
isEditing,
|
||||
name,
|
||||
nameError,
|
||||
toggleEdit,
|
||||
}
|
||||
}
|
||||
|
||||
const { isEditing, name, toggleEdit } = useProjectName()
|
||||
|
||||
const { useProject } = useData()
|
||||
const { saveProject, publishProject, saveProjectImage, isPublishing } = useProject()
|
||||
const { isEditing, name, nameError, toggleEdit } = useProjectName()
|
||||
|
||||
function save() {
|
||||
saveProject({
|
||||
...project.value,
|
||||
name: name.value,
|
||||
})
|
||||
}
|
||||
|
@ -97,6 +102,31 @@ function next() {
|
|||
const nextTab = tabs[currentIndex + 1] || tabs[0]
|
||||
selectedTab.value = nextTab.value
|
||||
}
|
||||
|
||||
async function publish() {
|
||||
if (selectedTab.value === 'basic_info') {
|
||||
if (!currentComponent.value.isFormValid())
|
||||
return
|
||||
}
|
||||
else if (isPublishing) {
|
||||
return
|
||||
}
|
||||
|
||||
await publishProject()
|
||||
navigateTo(`/project/${project.value?.id || project.value?.name?.toLowerCase().replace(/\s+/g, '-')}`)
|
||||
}
|
||||
|
||||
function jumpTo(tab: string) {
|
||||
if (selectedTab.value === 'basic_info') {
|
||||
if (!currentComponent.value.isFormValid())
|
||||
return
|
||||
else save()
|
||||
}
|
||||
|
||||
currentComponent.value.save()
|
||||
|
||||
selectedTab.value = tab
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -214,7 +244,7 @@ function next() {
|
|||
lg="mt-24px"
|
||||
>
|
||||
<SelectBox
|
||||
v-model="selectedTab"
|
||||
:model-value="selectedTab"
|
||||
label="Choose category"
|
||||
:options="tabs.map(t => ({ label: t.label, value: t.value }))"
|
||||
:border-opacity="30"
|
||||
|
@ -222,6 +252,7 @@ function next() {
|
|||
lg:hidden
|
||||
block
|
||||
mt-16px
|
||||
@update:model-value="$event => jumpTo($event)"
|
||||
/>
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
|
@ -235,6 +266,16 @@ function next() {
|
|||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
<span
|
||||
v-if="nameError"
|
||||
text-nowrap
|
||||
text-app-danger
|
||||
text-12px
|
||||
absolute
|
||||
lg:bottom--24px
|
||||
bottom--16px
|
||||
select-none
|
||||
>Invalid project name</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -246,7 +287,8 @@ function next() {
|
|||
>
|
||||
<div
|
||||
app-container
|
||||
mb-55px
|
||||
mb-170px
|
||||
lg="mb-55px"
|
||||
>
|
||||
<component
|
||||
:is="getCurrentComponent()"
|
||||
|
@ -274,7 +316,7 @@ function next() {
|
|||
gap-16px
|
||||
justify-center
|
||||
text-center
|
||||
absolute
|
||||
fixed
|
||||
bottom-0
|
||||
w-full
|
||||
bg-app-bg-dark_grey
|
||||
|
@ -302,6 +344,7 @@ function next() {
|
|||
w-full
|
||||
lg="w-fit"
|
||||
border
|
||||
@click="navigateTo('/')"
|
||||
>
|
||||
<span px-24px>CANCEL</span>
|
||||
</Button>
|
||||
|
@ -309,7 +352,7 @@ function next() {
|
|||
w-full
|
||||
lg="w-fit"
|
||||
inverted-color
|
||||
@click="isPublishing ? () => null : publishProject()"
|
||||
@click="publish()"
|
||||
>
|
||||
<UnoIcon
|
||||
v-if="isPublishing"
|
||||
|
|
Loading…
Reference in a new issue