Fix login bug. Improve reservations

This commit is contained in:
2024-05-21 16:32:31 -04:00
parent 737de91bbc
commit cd692a6f3b
10 changed files with 103 additions and 81 deletions

View File

@@ -21,7 +21,8 @@
"file": "^0.2.2", "file": "^0.2.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "3", "vue": "3",
"vue-router": "4" "vue-router": "4",
"vue3-google-login": "^2.0.26"
}, },
"devDependencies": { "devDependencies": {
"@quasar/app-vite": "^1.9.1", "@quasar/app-vite": "^1.9.1",

View File

@@ -1,5 +1,12 @@
import { boot } from 'quasar/wrappers'; import { boot } from 'quasar/wrappers';
import { Client, Account, Databases, Functions, ID } from 'appwrite'; import {
Client,
Account,
Databases,
Functions,
ID,
AppwriteException,
} from 'appwrite';
import { useAuthStore } from 'src/stores/auth'; import { useAuthStore } from 'src/stores/auth';
import { Dialog, Notify } from 'quasar'; import { Dialog, Notify } from 'quasar';
import type { Router } from 'vue-router'; import type { Router } from 'vue-router';
@@ -90,7 +97,7 @@ async function logout() {
}); });
} }
function login(email: string, password: string) { async function login(email: string, password: string) {
const notification = Notify.create({ const notification = Notify.create({
type: 'primary', type: 'primary',
position: 'top', position: 'top',
@@ -100,31 +107,31 @@ function login(email: string, password: string) {
group: false, group: false,
}); });
const authStore = useAuthStore(); const authStore = useAuthStore();
authStore try {
.login(email, password) await authStore.login(email, password);
.then(() => { notification({
notification({ type: 'positive',
type: 'positive', message: 'Logged in!',
message: 'Logged in!', timeout: 2000,
timeout: 2000, spinner: false,
spinner: false, icon: 'check_circle',
icon: 'check_circle', });
}); console.log('Redirecting to index page');
console.log('Redirecting to index page'); appRouter.replace({ name: 'index' });
appRouter.replace({ name: 'index' }); } catch (error: unknown) {
}) notification({
.catch(function (reason: Error) { type: 'negative',
notification({ message: 'Login failed.',
type: 'negative', timeout: 2000,
message: 'Login failed.', });
timeout: 1, if (error instanceof AppwriteException) {
});
Dialog.create({ Dialog.create({
title: 'Login Error!', title: 'Login Error!',
message: reason.message, message: error.message,
persistent: true, persistent: true,
}); });
}); }
}
} }
export { export {
client, client,

View File

@@ -52,7 +52,8 @@
) )
" "
> >
{{ getUserName(reservation.user) || 'loading...' }} {{ getUserName(reservation.user) || 'loading...' }}<br />
{{ reservation.reason }}
</div> </div>
</div> </div>
</template> </template>

View File

@@ -30,8 +30,8 @@
filled filled
></q-input> ></q-input>
<q-btn <q-btn
type="submit" type="button"
@click="login(email, password)" @click="doLogin"
label="Login" label="Login"
color="primary" color="primary"
></q-btn> ></q-btn>
@@ -44,7 +44,7 @@
></q-btn> --> ></q-btn> -->
</q-form> </q-form>
</q-card-section> </q-card-section>
<q-card-section><GoogleOauthComponent /></q-card-section> <!-- <q-card-section><GoogleOauthComponent /></q-card-section> -->
</q-card> </q-card>
</q-page> </q-page>
</q-page-container> </q-page-container>
@@ -69,8 +69,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { login } from 'boot/appwrite'; import { login } from 'boot/appwrite';
import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue'; // import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
const email = ref(''); const email = ref('');
const password = ref(''); const password = ref('');
const doLogin = async () => {
login(email.value, password.value);
};
</script> </script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<q-page> <q-page>
<q-list> <q-list>
<q-form @submit="onSubmit" @reset="onReset" class="q-gutter-sm"> <q-form @reset="onReset" class="q-gutter-sm">
<q-item> <q-item>
<q-item-section :avatar="true"> <q-item-section :avatar="true">
<q-icon name="person" <q-icon name="person"
@@ -45,52 +45,41 @@
</li> </li>
</ol> </ol>
</q-banner> </q-banner>
<!-- <q-card-section> <q-card-section>
<q-btn <q-btn
color="primary" color="primary"
class="full-width" class="full-width"
icon="keyboard_arrow_down" icon="keyboard_arrow_down"
icon-right="keyboard_arrow_down" icon-right="keyboard_arrow_down"
label="Next: Crew & Passengers" label="Next: Booking Details"
@click="resourceView = false" @click="resourceView = false"
/></q-card-section> --> /></q-card-section>
</q-expansion-item> </q-expansion-item>
<!-- <q-expansion-item <q-expansion-item
expand-separator expand-separator
icon="people" icon="people"
label="Crew and Passengers" label="Booking Details"
default-opened default-opened
><q-banner v-if="bookingForm.boat" >
>Passengers: <div class="row">
{{ bookingForm.members.length + bookingForm.guests.length }} / <div class="col-auto">
{{ bookingForm.boat.maxPassengers }}</q-banner <q-select
> filled
<q-item v-model="bookingForm.reason"
class="q-my-sm" :options="reason_options"
v-for="passenger in [...bookingForm.members, ...bookingForm.guests]" label="Reason for sail"
:key="passenger.name" />
> </div>
<q-item-section avatar> </div>
<q-avatar color="primary" text-color="white" size="sm"> </q-expansion-item>
{{
passenger.name
.split(' ')
.map((i) => i.charAt(0))
.join('')
.toUpperCase()
}}
</q-avatar>
</q-item-section>
<q-item-section>{{ passenger.name }}</q-item-section>
<q-item-section side>
<q-btn color="negative" flat dense round icon="cancel" />
</q-item-section>
</q-item>
<q-separator />
</q-expansion-item> -->
<q-item-section> <q-item-section>
<q-btn label="Submit" type="submit" color="primary" /> <q-btn
label="Submit"
type="submit"
@click="onSubmit"
color="primary"
/>
</q-item-section> </q-form </q-item-section> </q-form
></q-list> ></q-list>
</q-page> </q-page>
@@ -113,22 +102,25 @@ interface BookingForm {
boat?: Boat; boat?: Boat;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
reason: string;
members: { name: string }[]; members: { name: string }[];
guests: { name: string }[]; guests: { name: string }[];
} }
const reason_options = ['Open Sail', 'Private Sail', 'Racing', 'Other'];
const auth = useAuthStore(); const auth = useAuthStore();
const dateFormat = 'MMM D, YYYY h:mm A';
const resourceView = ref(true); const resourceView = ref(true);
const interval = ref<Interval>(); const interval = ref<Interval>();
const bookingForm = ref<BookingForm>({ const bookingForm = ref<BookingForm>({
bookingId: getNewId(), bookingId: getNewId(),
name: auth.currentUser?.name, name: auth.currentUser?.name,
boat: <Boat | undefined>undefined, boat: <Boat | undefined>undefined,
startDate: date.formatDate(new Date(), dateFormat), startDate: '',
endDate: date.formatDate(new Date(), dateFormat), endDate: '',
members: [{ name: 'Karen Henrikso' }, { name: "Rich O'hare" }], reason: 'Open Sail',
guests: [{ name: 'Bob Barker' }, { name: 'Taylor Swift' }], members: [],
guests: [],
}); });
const router = useRouter(); const router = useRouter();
const reservationStore = useReservationStore(); const reservationStore = useReservationStore();
@@ -138,11 +130,8 @@ watch(interval, (new_interval) => {
bookingForm.value.boat = useBoatStore().boats.find( bookingForm.value.boat = useBoatStore().boats.find(
(b) => b.$id === new_interval?.boatId (b) => b.$id === new_interval?.boatId
); );
bookingForm.value.startDate = date.formatDate( bookingForm.value.startDate = new_interval?.start;
new_interval?.start, bookingForm.value.endDate = new_interval?.end;
dateFormat
);
bookingForm.value.endDate = date.formatDate(new_interval?.end, dateFormat);
}); });
const onReset = () => { const onReset = () => {
@@ -150,6 +139,7 @@ const onReset = () => {
}; };
const onSubmit = () => { const onSubmit = () => {
console.log('SUBMIT!');
const booking = bookingForm.value; const booking = bookingForm.value;
if ( if (
!(booking.boat && booking.startDate && booking.endDate && auth.currentUser) !(booking.boat && booking.startDate && booking.endDate && auth.currentUser)
@@ -163,9 +153,11 @@ const onSubmit = () => {
end: booking.endDate, end: booking.endDate,
user: auth.currentUser.$id, user: auth.currentUser.$id,
status: 'confirmed', status: 'confirmed',
reason: booking.reason,
}; };
console.log(reservation);
// TODO: Fix this. It will always look successful // TODO: Fix this. It will always look successful
reservationStore.createReservation(reservation); reservationStore.createReservation(reservation); // Probably should pass the notify as a callback to the reservation creation.
$q.notify({ $q.notify({
color: 'green-4', color: 'green-4',
textColor: 'white', textColor: 'white',
@@ -195,7 +187,9 @@ const bookingSummary = computed(() => {
return bookingForm.value.boat && return bookingForm.value.boat &&
bookingForm.value.startDate && bookingForm.value.startDate &&
bookingForm.value.endDate bookingForm.value.endDate
? `${bookingForm.value.boat.name} @ ${bookingForm.value.startDate} for ${bookingDuration.value}` ? `${bookingForm.value.boat.name} @ ${new Date(
bookingForm.value.startDate
).toLocaleString()} for ${bookingDuration.value}`
: ''; : '';
}); });
</script> </script>

View File

@@ -36,12 +36,13 @@ export default route(function (/* { store, ssrContext } */) {
}); });
Router.beforeEach((to) => { Router.beforeEach((to) => {
const auth = useAuthStore(); const publicPages = routes
.filter((route) => route.meta?.publicRoute)
.map((r) => r.path);
const authRequired = !publicPages.includes(to.path);
if (auth.currentUser) { if (authRequired && !useAuthStore().currentUser) {
return to.meta.accountRoute ? { name: 'index' } : true; return '/login';
} else {
return to.name == 'login' ? true : { name: 'login' };
} }
}); });

View File

@@ -51,6 +51,7 @@ export const useReservationStore = defineStore('reservation', () => {
reservation reservation
); );
reservations.value.set(response.$id, response as Reservation); reservations.value.set(response.$id, response as Reservation);
console.info('Reservation booked: ', response);
} catch (e) { } catch (e) {
console.error('Error creating Reservation: ' + e); console.error('Error creating Reservation: ' + e);
} }

View File

@@ -71,6 +71,7 @@ export function getSampleReservations(): Reservation[] {
end: '10:00', end: '10:00',
boat: '66359729003825946ae1', boat: '66359729003825946ae1',
status: 'confirmed', status: 'confirmed',
reason: 'Open Sail',
}, },
{ {
id: '2', id: '2',
@@ -79,6 +80,7 @@ export function getSampleReservations(): Reservation[] {
end: '19:00', end: '19:00',
boat: '66359729003825946ae1', boat: '66359729003825946ae1',
status: 'confirmed', status: 'confirmed',
reason: 'Open Sail',
}, },
{ {
id: '3', id: '3',
@@ -87,6 +89,7 @@ export function getSampleReservations(): Reservation[] {
end: '13:00', end: '13:00',
boat: '663597030029b71c7a9b', boat: '663597030029b71c7a9b',
status: 'tentative', status: 'tentative',
reason: 'Open Sail',
}, },
{ {
id: '4', id: '4',
@@ -95,6 +98,7 @@ export function getSampleReservations(): Reservation[] {
end: '13:00', end: '13:00',
boat: '663597030029b71c7a9b', boat: '663597030029b71c7a9b',
status: 'pending', status: 'pending',
reason: 'Open Sail',
}, },
{ {
id: '5', id: '5',
@@ -103,6 +107,7 @@ export function getSampleReservations(): Reservation[] {
end: '19:00', end: '19:00',
boat: '663596b9000235ffea55', boat: '663596b9000235ffea55',
status: 'confirmed', status: 'confirmed',
reason: 'Private Sail',
}, },
{ {
id: '6', id: '6',
@@ -110,6 +115,7 @@ export function getSampleReservations(): Reservation[] {
start: '13:00', start: '13:00',
end: '16:00', end: '16:00',
boat: '663596b9000235ffea55', boat: '663596b9000235ffea55',
reason: 'Open Sail',
}, },
]; ];
const boatStore = useBoatStore(); const boatStore = useBoatStore();
@@ -137,6 +143,7 @@ export function getSampleReservations(): Reservation[] {
end: date.adjustDate(now, makeOpts(splitTime(entry.end))).toISOString(), end: date.adjustDate(now, makeOpts(splitTime(entry.end))).toISOString(),
resource: boat.$id, resource: boat.$id,
reservationDate: now, reservationDate: now,
reason: entry.reason,
status: entry.status as StatusTypes, status: entry.status as StatusTypes,
}; };
}); });

View File

@@ -8,6 +8,7 @@ export type Reservation = Partial<Models.Document> & {
end: string; end: string;
resource: string; // Boat ID resource: string; // Boat ID
status?: StatusTypes; status?: StatusTypes;
reason: string;
}; };
// 24 hrs in advance only 2 weekday, and 1 weekend slot // 24 hrs in advance only 2 weekday, and 1 weekend slot

View File

@@ -5278,6 +5278,11 @@ vue-tsc@^1.8.22:
"@vue/language-core" "1.8.27" "@vue/language-core" "1.8.27"
semver "^7.5.4" semver "^7.5.4"
vue3-google-login@^2.0.26:
version "2.0.26"
resolved "https://registry.yarnpkg.com/vue3-google-login/-/vue3-google-login-2.0.26.tgz#0e55dbb3c6cbb78872dee0de800624c749d07882"
integrity sha512-BuTSIeSjINNHNPs+BDF4COnjWvff27IfCBDxK6JPRqvm57lF8iK4B3+zcG8ud6BXfZdyuiDlxletbEDgg4/RFA==
vue@3: vue@3:
version "3.4.25" version "3.4.25"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b" resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"