Added connection error message, added max height for tags field, added optional google maps link, added price options.

main
Eloi Zalczer 2024-06-19 17:31:04 +02:00
parent 04fb307b7a
commit d73cb4dbb8
7 changed files with 59 additions and 15 deletions

View File

@ -18,6 +18,10 @@ const { errors, handleSubmit, defineField, resetForm } = useForm({
name: yup.string().required("This field is required."), name: yup.string().required("This field is required."),
tags: yup.array().of(yup.string()), tags: yup.array().of(yup.string()),
price: yup.string().required("This field is required."), price: yup.string().required("This field is required."),
googleMapsLink: yup
.string()
.trim()
.matches(/^$|https:\/\/maps\.app\.goo\.gl\/[a-zA-Z0-9]+/, "Not a valid Google Maps link."),
}) })
), ),
}); });
@ -39,6 +43,7 @@ const submit = handleSubmit((values) => {
tags: tags, tags: tags,
latitude: position.value?.lat, latitude: position.value?.lat,
longitude: position.value?.lng, longitude: position.value?.lng,
googleMapsLink: values.googleMapsLink,
}); });
emit("succeeded", `Restaurant ${values.name} created successfully!`); emit("succeeded", `Restaurant ${values.name} created successfully!`);
@ -65,6 +70,7 @@ onMounted(async () => {
const [name, nameAttrs] = defineField("name"); const [name, nameAttrs] = defineField("name");
const [tags, tagsAttrs] = defineField("tags"); const [tags, tagsAttrs] = defineField("tags");
const [price, priceAttrs] = defineField("price"); const [price, priceAttrs] = defineField("price");
const [googleMapsLink, googleMapsLinkAttrs] = defineField("googleMapsLink");
const emit = defineEmits({ const emit = defineEmits({
succeeded: (message: string) => true, succeeded: (message: string) => true,
@ -138,12 +144,25 @@ defineExpose({ show, hide, setRestaurantPosition });
v-bind="priceAttrs" v-bind="priceAttrs"
> >
<option value="€">1-10</option> <option value="€">1-10</option>
<option value="€€">10-20</option> <option value="€€">10-15</option>
<option value="€€€">20-30</option> <option value="€€€">15-20</option>
<option value="€€€€">>20</option>
</select> </select>
<label class="text-red-500">{{ errors.price }}</label> <label class="text-red-500">{{ errors.price }}</label>
</div> </div>
</div> </div>
<div class="flex flex-row flex-nowrap w-96">
<div class="mx-5 my-4 w-full">
<label class="form-label"> Google Maps Link (Optional) </label>
<input
:class="errors.googleMapsLink === undefined ? 'form-field' : 'form-field-error'"
v-model="googleMapsLink"
v-bind="googleMapsLinkAttrs"
type="text"
/>
<label class="text-red-500">{{ errors.googleMapsLink }}</label>
</div>
</div>
</div> </div>
</ModalComponent> </ModalComponent>
</template> </template>

View File

@ -23,6 +23,7 @@ const columns = [
_: "[, ]", _: "[, ]",
}, },
}, },
{ data: "price", title: "Price" },
{ data: "average_rating", title: "" }, { data: "average_rating", title: "" },
]; ];
@ -39,7 +40,7 @@ function deselectAll() {
} }
const emit = defineEmits({ const emit = defineEmits({
restaurantSelected: (restaurant: Object) => true, restaurantSelected: (restaurant: Restaurant) => true,
restaurantHovered: (restaurant: Object | null) => true, restaurantHovered: (restaurant: Object | null) => true,
}); });
@ -92,7 +93,7 @@ defineExpose({ deselectAll });
}" }"
@select="onRowSelected" @select="onRowSelected"
> >
<template #column-2="props"> <template #column-3="props">
<RatingField <RatingField
v-if="props.cellData !== null" v-if="props.cellData !== null"
:disabled="true" :disabled="true"

View File

@ -3,9 +3,10 @@ import { ref } from "vue";
import ReviewsList from "./ReviewsList.vue"; import ReviewsList from "./ReviewsList.vue";
import ReviewField from "./ReviewField.vue"; import ReviewField from "./ReviewField.vue";
import { currentUser, pb } from "../pocketbase"; import { currentUser, pb } from "../pocketbase";
import Restaurant from "../models/restaurant";
const props = defineProps({ const props = defineProps({
restaurant: { type: Object }, restaurant: { type: Object as () => Restaurant },
}); });
const opened = ref<boolean>(false); const opened = ref<boolean>(false);
@ -49,11 +50,22 @@ defineExpose({ open, close });
<div class="drawer-side w-full"> <div class="drawer-side w-full">
<label @click.prevent="close" for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label> <label @click.prevent="close" for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<div class="menu p-4 space-y-4 w-full lg:w-2/6 min-h-full bg-base-200 text-base-content"> <div class="menu p-4 space-y-4 w-full lg:w-2/6 min-h-full bg-base-200 text-base-content">
<font-awesome-icon <div class="flex flex-row w-full">
@click="close" <font-awesome-icon
class="btn btn-circle btn-sm text-gray-500 bg-transparent" @click="close"
icon="fa-solid fa-xmark" class="btn btn-circle btn-sm text-gray-500 bg-transparent"
/> icon="fa-solid fa-xmark"
/>
<div class="flex-1"></div>
<a
v-if="props.restaurant?.googleMapsLink"
:href="props.restaurant.googleMapsLink"
class="btn btn-ghost btn-link btn-sm"
target="_blank"
rel="noopener noreferrer"
>View on Google Maps</a
>
</div>
<ReviewField ref="reviewField" @publish="onPublishReview" /> <ReviewField ref="reviewField" @publish="onPublishReview" />
<ReviewsList ref="reviewsList" :restaurant="props.restaurant" /> <ReviewsList ref="reviewsList" :restaurant="props.restaurant" />

View File

@ -85,7 +85,9 @@ function filteredOptions() {
<font-awesome-icon icon="xmark" /> <font-awesome-icon icon="xmark" />
</button> </button>
</div> </div>
<ul class="menu dropdown-content z-10 w-full rounded-lg border border-neutral-content bg-base-100 p-0 shadow"> <ul
class="menu flex-nowrap dropdown-content overflow-y-auto z-10 max-h-96 w-full rounded-lg border border-neutral-content bg-base-100 p-0 shadow"
>
<li><button @click="onCreateNewTag">Create new tag...</button></li> <li><button @click="onCreateNewTag">Create new tag...</button></li>
<li v-for="option in filteredOptions()" :key="option" role="option" class="m-0 p-0"> <li v-for="option in filteredOptions()" :key="option" role="option" class="m-0 p-0">
<button :class="{ active: model?.includes(option) }" @click="onOptionClicked(option)"> <button :class="{ active: model?.includes(option) }" @click="onOptionClicked(option)">

View File

@ -6,4 +6,5 @@ export default interface Restaurant {
averageRating: number; averageRating: number;
latitude: number; latitude: number;
longitude: number; longitude: number;
googleMapsLink?: string;
} }

View File

@ -5,16 +5,17 @@ import RestaurantsMap from "../components/RestaurantsMap.vue";
import ReviewsDrawer from "../components/ReviewsDrawer.vue"; import ReviewsDrawer from "../components/ReviewsDrawer.vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useRestaurantsStore } from "../stores/restaurants"; import { useRestaurantsStore } from "../stores/restaurants";
import Restaurant from "../models/restaurant";
const drawer = ref<typeof ReviewsDrawer>(); const drawer = ref<typeof ReviewsDrawer>();
const table = ref<typeof RestaurantsTable>(); const table = ref<typeof RestaurantsTable>();
const selectedRestaurant = ref<Object>(); const selectedRestaurant = ref<Restaurant>();
const highlightedRestaurant = ref<string>(); const highlightedRestaurant = ref<string>();
const { restaurants } = storeToRefs(useRestaurantsStore()); const { restaurants } = storeToRefs(useRestaurantsStore());
function onRestaurantSelected(restaurant: Object) { function onRestaurantSelected(restaurant: Restaurant) {
selectedRestaurant.value = restaurant; selectedRestaurant.value = restaurant;
drawer.value?.open(); drawer.value?.open();
} }

View File

@ -10,12 +10,18 @@ const valid = computed(() => {
return username.value !== undefined && password.value !== undefined; return username.value !== undefined && password.value !== undefined;
}); });
const errorMessage = ref<string>();
async function login() { async function login() {
if (!valid.value) { if (!valid.value) {
return; return;
} }
await pb.collection("users").authWithPassword(username.value!, password.value!); try {
await pb.collection("users").authWithPassword(username.value!, password.value!);
} catch (err) {
errorMessage.value = "Incorrect username or password.";
}
} }
</script> </script>
@ -35,7 +41,9 @@ async function login() {
</div> </div>
</div> </div>
<button @click="login" :disabled="!valid">Log In</button> <label v-if="errorMessage" class="text-red-500">{{ errorMessage }}</label>
<button @click="login" :disabled="!valid" class="my-3 btn btn-outline">Log In</button>
<p>New to DOXFOOD ? <RouterLink to="/signup">Create an account !</RouterLink></p> <p>New to DOXFOOD ? <RouterLink to="/signup">Create an account !</RouterLink></p>
</form> </form>
</template> </template>