Rabbi_Gerzi/frontend/rabbi_gerzi/src/stores/elements.ts

231 lines
5.9 KiB
TypeScript

import { ref } from 'vue'
import { defineStore } from 'pinia'
export interface ChildElement {
id: number
title: string
description: string
}
export interface Element extends ChildElement {
iconImageUrl: string | null
richText: string
pdfPath: string | null
youtubeUrl: string | null
}
interface ElementResponse {
element: Element
childElements: ChildElement[]
}
export interface UpdateElementInput {
title: string
description: string
richText: string
youtubeUrl: string
}
interface UpdateElementResponse {
element: Element
}
interface ElementPatchInput {
title?: string
description?: string
iconImageUrl?: string
richText?: string
pdfPath?: string
youtubeUrl?: string
}
interface ErrorResponse {
error?: string
}
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string
const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update`
export const useElementsStore = defineStore('elements', () => {
const element = ref<Element | null>(null)
const childElements = ref<ChildElement[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const isSaving = ref(false)
const isUploadingIconImage = ref(false)
const isUploadingPdf = ref(false)
const saveError = ref<string | null>(null)
async function fetchElement(elementId: string): Promise<void> {
element.value = null
childElements.value = []
error.value = null
saveError.value = null
isLoading.value = true
try {
const encodedElementId = encodeURIComponent(elementId)
const elementUrl = `${API_BASE_URL}/api/elements/${encodedElementId}`
const response = await fetch(elementUrl)
if (response.status === 404) {
error.value = 'Element not found'
return
}
if (!response.ok) {
error.value = 'Could not load element'
return
}
const data: ElementResponse = await response.json()
element.value = data.element
childElements.value = data.childElements
} catch {
childElements.value = []
error.value = 'Network error - could not load element'
} finally {
isLoading.value = false
}
}
async function updateElement(elementId: string, input: UpdateElementInput): Promise<boolean> {
return await saveElementPatch(elementId, input, 'Could not save element')
}
async function clearElementIconImage(elementId: string): Promise<boolean> {
return await saveElementPatch(elementId, { iconImageUrl: '' }, 'Could not remove icon image')
}
async function clearElementPdf(elementId: string): Promise<boolean> {
return await saveElementPatch(elementId, { pdfPath: '' }, 'Could not remove PDF')
}
async function saveElementPatch(
elementId: string,
input: ElementPatchInput,
failureMessage: string,
): Promise<boolean> {
saveError.value = null
isSaving.value = true
try {
const response = await fetch(ELEMENT_UPDATE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ elementId, ...input }),
})
return await handleElementResponse(response, failureMessage)
} catch {
saveError.value = `Network error - ${failureMessage.toLowerCase()}`
return false
} finally {
isSaving.value = false
}
}
async function uploadElementIconImage(elementId: string, file: File): Promise<boolean> {
saveError.value = null
isUploadingIconImage.value = true
try {
const formData = new FormData()
formData.append('elementId', elementId)
formData.append('iconImage', file)
const response = await fetch(ELEMENT_UPDATE_URL, {
method: 'POST',
headers: { Accept: 'application/json' },
credentials: 'include',
body: formData,
})
return await handleElementResponse(response, 'Could not upload icon image')
} catch {
saveError.value = 'Network error - could not upload icon image'
return false
} finally {
isUploadingIconImage.value = false
}
}
async function uploadElementPdf(elementId: string, file: File): Promise<boolean> {
saveError.value = null
isUploadingPdf.value = true
try {
const formData = new FormData()
formData.append('elementId', elementId)
formData.append('pdf', file)
const response = await fetch(ELEMENT_UPDATE_URL, {
method: 'POST',
headers: { Accept: 'application/json' },
credentials: 'include',
body: formData,
})
return await handleElementResponse(response, 'Could not upload PDF')
} catch {
saveError.value = 'Network error - could not upload PDF'
return false
} finally {
isUploadingPdf.value = false
}
}
async function handleElementResponse(
response: Response,
failureMessage: string,
): Promise<boolean> {
if (response.status === 401) {
saveError.value = 'Please log in again'
return false
}
if (response.status === 404) {
saveError.value = 'Element not found'
return false
}
if (response.status === 400) {
saveError.value = await errorMessage(response, failureMessage)
return false
}
if (!response.ok) {
saveError.value = failureMessage
return false
}
const data: UpdateElementResponse = await response.json()
element.value = data.element
return true
}
async function errorMessage(response: Response, fallbackMessage: string): Promise<string> {
try {
const data: ErrorResponse = await response.json()
return data.error ?? fallbackMessage
} catch {
return fallbackMessage
}
}
return {
element,
childElements,
isLoading,
error,
isSaving,
isUploadingIconImage,
isUploadingPdf,
saveError,
fetchElement,
updateElement,
uploadElementIconImage,
uploadElementPdf,
clearElementIconImage,
clearElementPdf,
}
})