243 lines
7.6 KiB
Vue
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>
|