refactor: project store, project save, validations

This commit is contained in:
DomWane 2024-09-16 16:43:29 +02:00
parent 67660b0972
commit a94ea73d15
13 changed files with 187 additions and 100 deletions

View file

@ -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() {

View file

@ -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,
})
}

View file

@ -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() {

View file

@ -72,7 +72,6 @@ function formatDate(date: string) {
return `${day}/${month}/${year}`
}
const { useProject } = useData()
const { saveProject } = useProject()
function save() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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() {

View file

@ -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
View 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,
}
})

View file

@ -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"

View file

@ -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"