travel-steps/summer2024-frontend/src/views/CreatePostView.vue
Nicolas Froger efde8738a8
refactor, add post edit and delete, merge APIs, add not found view
Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
2024-07-27 02:20:08 +02:00

243 lines
7.6 KiB
Vue

<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 { getCityAndCountry } from '@/lib/utils.js'
import { useAdminPostsStore } from '@/stores/adminPosts.js'
const adminPostStore = useAdminPostsStore()
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) => {
formContainer.value.classList.add('hidden')
formStatus.value.sending = true
try {
await adminPostStore.createPost(values, selectedFiles.value)
} catch (e) {
formStatus.value.sending = false
formStatus.value.error = true
formStatus.value.errorMsg = e.message
formContainer.value.classList.remove('invisible')
throw e
}
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
}
function updateCityAndCountry() {
const lat = latitudeInput.value.value
const lon = longitudeInput.value.value
if (!lat || !lon) return
getCityAndCountry(lat, lon).then((cityAndCountry) => {
form.setFieldValue('city', cityAndCountry[0])
form.setFieldValue('country', cityAndCountry[1])
})
}
</script>
<template>
<div class="grid grid-cols-3 grid-rows-1 mt-28 sm: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" @click="updateCityAndCountry">
<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>