init backend, added admin page in front
Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
parent
5c6e641fbd
commit
ddc6c64f0f
89 changed files with 5083 additions and 9 deletions
278
summer2024-frontend/src/views/CreatePostView.vue
Normal file
278
summer2024-frontend/src/views/CreatePostView.vue
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
<script setup>
|
||||
import { Button } from '@/components/ui/button/index.js'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form/index.js'
|
||||
import { Textarea } from '@/components/ui/textarea/index.js'
|
||||
import { Input } from '@/components/ui/input/index.js'
|
||||
import { Label } from '@/components/ui/label/index.js'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { z } from 'zod'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { AlertCircle, ArrowLeft, ArrowRight, CircleCheckBig, CirclePlus, RefreshCcw } from 'lucide-vue-next'
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious
|
||||
} from '@/components/ui/carousel/index.js'
|
||||
import { Card, CardContent } from '@/components/ui/card/index.js'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert/index.js'
|
||||
import { useAuthStore } from '@/stores/auth.js'
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
description: z.string().min(1),
|
||||
latitude: z.number(),
|
||||
longitude: z.number(),
|
||||
city: z.string().min(1),
|
||||
country: z.string().min(1)
|
||||
}))
|
||||
const form = useForm({
|
||||
validationSchema: formSchema
|
||||
})
|
||||
|
||||
const formContainer = ref(null)
|
||||
const latitudeInput = ref(null)
|
||||
const longitudeInput = ref(null)
|
||||
const selectedFiles = ref([])
|
||||
|
||||
const formStatus = ref({
|
||||
sending: false,
|
||||
sent: false,
|
||||
error: false,
|
||||
errorMsg: ''
|
||||
})
|
||||
|
||||
const onSubmit = form.handleSubmit(async (values) => {
|
||||
if (!authStore.isAuth)
|
||||
return
|
||||
console.log('Envoi du post...')
|
||||
if (selectedFiles.value.length === 0)
|
||||
return
|
||||
formContainer.value.classList.add('invisible')
|
||||
formStatus.value.sending = true
|
||||
|
||||
const assets = []
|
||||
for (const file of selectedFiles.value) {
|
||||
console.log('Contact API asset')
|
||||
const response = await fetch('http://localhost:8080/admin/assets', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"X-admin-token": authStore.adminToken
|
||||
},
|
||||
body: JSON.stringify({ filename: file.file.name })
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.log('Contact API asset failed: ' + response.statusText + '\n\n' + response.body)
|
||||
formStatus.value.sending = false
|
||||
formStatus.value.error = true
|
||||
formStatus.value.errorMsg = 'Une erreur est survenue à la préparation de l\'envoi d\'un média : ' + response.statusText
|
||||
formContainer.value.classList.remove('invisible')
|
||||
return
|
||||
}
|
||||
const responseBody = await response.json()
|
||||
const mediaUploadFormData = new FormData()
|
||||
for (const [key, value] of Object.entries(responseBody.formData)) {
|
||||
mediaUploadFormData.append(key, value)
|
||||
}
|
||||
mediaUploadFormData.append('key', '${filename}')
|
||||
mediaUploadFormData.append('Content-Type', file.file.type)
|
||||
mediaUploadFormData.append('file', file.file, responseBody.filename)
|
||||
|
||||
console.log('Envoi image sur s3')
|
||||
const s3Response = await fetch('http://localhost:32795/assets/', {
|
||||
method: 'POST',
|
||||
body: mediaUploadFormData
|
||||
})
|
||||
if (!s3Response.ok) {
|
||||
console.log('Envoi media S3 failed: ' + s3Response.statusText + '\n\n' + s3Response.body)
|
||||
formStatus.value.sending = false
|
||||
formStatus.value.error = true
|
||||
formStatus.value.errorMsg = 'Une erreur est survenue pendant l\'envoi d\'un média : ' + s3Response.statusText
|
||||
formContainer.value.classList.remove('invisible')
|
||||
return
|
||||
}
|
||||
|
||||
assets.push(responseBody.id)
|
||||
}
|
||||
|
||||
const response = await fetch('http://localhost:8080/admin/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"X-admin-token": authStore.adminToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
description: values.description,
|
||||
latitude: values.latitude,
|
||||
longitude: values.longitude,
|
||||
city: values.city,
|
||||
country: values.country,
|
||||
assets: assets
|
||||
})
|
||||
})
|
||||
if (!response.ok) {
|
||||
console.log('POST post API failed: ' + response.statusText + '\n\n' + response.body)
|
||||
formStatus.value.sending = false
|
||||
formStatus.value.error = true
|
||||
formStatus.value.errorMsg = 'Une erreur est survenue lors de l\'envoi du poste : ' + response.statusText
|
||||
formContainer.value.classList.remove('invisible')
|
||||
return
|
||||
}
|
||||
|
||||
formStatus.value.sending = false
|
||||
formStatus.value.sent = true
|
||||
})
|
||||
|
||||
let geoWatchId = null
|
||||
|
||||
onMounted(() => {
|
||||
geoWatchId = navigator.geolocation.watchPosition((position) => {
|
||||
console.log(position)
|
||||
form.setFieldValue('latitude', position.coords.latitude)
|
||||
form.setFieldValue('longitude', position.coords.longitude)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (geoWatchId != null) {
|
||||
navigator.geolocation.clearWatch(geoWatchId)
|
||||
}
|
||||
})
|
||||
|
||||
function onFilesSelected(event) {
|
||||
console.log(event.target.files)
|
||||
selectedFiles.value = []
|
||||
for (const file of event.target.files) {
|
||||
selectedFiles.value.push({ displayUrl: URL.createObjectURL(file), file: file })
|
||||
}
|
||||
}
|
||||
|
||||
function mediaReorder(index, diff) {
|
||||
const tmp = selectedFiles.value[index]
|
||||
selectedFiles.value[index] = selectedFiles.value[index + diff]
|
||||
selectedFiles.value[index + diff] = tmp
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-3 grid-rows-1 mt-20 w-full justify-center items-center gap-4">
|
||||
<div class="col-span-3 col-start-1 lg:col-span-1 lg:col-start-2 mx-5 lg:mx-0">
|
||||
<h1 class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl mb-6">
|
||||
Ajouter un post
|
||||
</h1>
|
||||
<RouterLink to="/admin">
|
||||
<Button class="mb-6">
|
||||
<ArrowLeft class="mr-2" />
|
||||
Retour
|
||||
</Button>
|
||||
</RouterLink>
|
||||
<Alert variant="destructive" class="mb-6" v-if="formStatus.error">
|
||||
<AlertCircle class="w-4 h-4"></AlertCircle>
|
||||
<AlertTitle>Erreur...</AlertTitle>
|
||||
<AlertDescription>{{ formStatus.errorMsg }}</AlertDescription>
|
||||
</Alert>
|
||||
<Alert class="mb-6" v-if="formStatus.sent">
|
||||
<CircleCheckBig class="w-4 h-4"></CircleCheckBig>
|
||||
<AlertTitle>Post créé !</AlertTitle>
|
||||
</Alert>
|
||||
<p v-if="formStatus.sending">Sending...</p>
|
||||
<div id="form-container" ref="formContainer">
|
||||
<form id="post-form" class="space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="description">
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea v-bind="componentField"></Textarea>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<FormField ref="latitudeInput" v-slot="{ componentField }" name="latitude">
|
||||
<FormItem>
|
||||
<FormLabel>Latitude</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled v-bind="componentField"></Input>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField ref="longitudeInput" v-slot="{ componentField }" name="longitude">
|
||||
<FormItem>
|
||||
<FormLabel>Longitude</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled v-bind="componentField"></Input>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
<div class="grid grid-cols-9 gap-4">
|
||||
<FormField v-slot="{ componentField }" name="city">
|
||||
<FormItem class="col-span-4">
|
||||
<FormLabel>Ville</FormLabel>
|
||||
<FormControl>
|
||||
<Input v-bind="componentField"></Input>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="country">
|
||||
<FormItem class="col-span-4">
|
||||
<FormLabel>Pays</FormLabel>
|
||||
<FormControl>
|
||||
<Input v-bind="componentField"></Input>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<Button class="place-self-end">
|
||||
<RefreshCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<form id="assets-form" class="mt-6">
|
||||
<Label for="medias">Medias</Label>
|
||||
<Input id="medias" type="file" accept="image/*,video/*" multiple @change="onFilesSelected" />
|
||||
</form>
|
||||
<Carousel
|
||||
class="w-full mt-10"
|
||||
:opts="{
|
||||
align: 'start',
|
||||
}"
|
||||
v-if="selectedFiles.length > 0"
|
||||
>
|
||||
<CarouselContent>
|
||||
<CarouselItem v-for="(file, index) in selectedFiles" :key="index" class="md:basis-1/2 lg:basis-1/3">
|
||||
<div class="flex flex-col">
|
||||
<Card>
|
||||
<CardContent class="flex aspect-square items-center justify-center p-6">
|
||||
<img :src="file.displayUrl" v-if="file.file.type.startsWith('image/')" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div class="grid grid-cols-2 justify-items-center mt-2">
|
||||
<Button v-if="index > 0" @click="mediaReorder(index, -1)">
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<div v-else></div>
|
||||
<Button v-if="index < selectedFiles.length - 1" @click="mediaReorder(index, 1)">
|
||||
<ArrowRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
<Button class="mt-6 w-full" type="submit" form="post-form">
|
||||
<CirclePlus />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue