Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,062 changes: 583 additions & 479 deletions src/frontend/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/frontend/src/components/CreateEventModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export default defineComponent({
};
eventStore.addEvent(newEvent);
eventStore.sortEvents(false);
eventStore.selectEvent(newEvent);
this.$router.push('/' + result);

popupStore.addPopup(PopupType.Success, 'Event Created!');
this.closeModal();
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/components/DeleteEventModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ export default defineComponent({
const popupStore = usePopupStore();
try {
const eventStore = useEventStore();
const response = await fetch(`/api/v1/event/${eventStore.selectedEvent!.id}`, {
const response = await fetch(`/api/v1/event/${eventStore.id}`, {
method: 'DELETE'
});

if (!response.ok) {
popupStore.addPopup(PopupType.Danger, `Failed to Delete Event (${response.status})`);
return;
}
eventStore.removeEvent(eventStore.selectedEvent);
eventStore.selectedEvent = null;
eventStore.removeEvent(eventStore.id);
eventStore.id = null;
popupStore.addPopup(PopupType.Success, 'Event Deleted!');
this.closeModal();
} catch (error) {
Expand Down
15 changes: 12 additions & 3 deletions src/frontend/src/components/EventCard.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<script setup lang="ts">
import CaretRight from './icons/CaretRight.vue';
import { RouterLink } from 'vue-router';
</script>

<template>
<div class="card mb-3">
<RouterLink :to="eventPath" class="card mb-3">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title">{{ event!.name }}</h5>
<h6 class="card-time">{{ formattedStart }}</h6>
</div>
<CaretRight v-if="screenStore.mobile" />
</div>
</div>
</RouterLink>
</template>

<script lang="ts">
import { defineComponent, type PropType } from 'vue';
import { defineComponent, inject, type PropType } from 'vue';
import { type Event } from '@/models';
import { format } from 'date-fns';
import { useScreenStore } from '@/stores/screen';
Expand All @@ -27,13 +28,21 @@ export default defineComponent({
data() {
let screenStore = useScreenStore();
return {
historyMode: inject('historyMode') as Boolean,
screenStore
};
},
computed: {
formattedStart() {
let data = this.event?.startTime.toLocaleString();
return data ? format(data, 'MM/dd/yyyy hh:mm a') : 'N/A';
},
eventPath() {
if (this.historyMode) {
return `/history/${this.$props.event?.id}`;
} else {
return `${this.$props.event?.id}`;
}
}
}
});
Expand Down
25 changes: 18 additions & 7 deletions src/frontend/src/components/EventDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import EditCarButton from './EditCarButton.vue';
<div>
<h3 class="card-title">{{ event?.name }}</h3>
<h5 class="card-text">
<a target="_blank" v-bind:href="'https://maps.google.com/?q=' + event?.location"
><IconPin /> {{ event?.location }}</a
>
<a target="_blank" v-bind:href="'https://maps.google.com/?q=' + event?.location">
<IconPin /> {{ event?.location }}
</a>
</h5>
<div v-if="screenStore.mobile">
<h5><b>Start: </b>{{ startTime }}</h5>
<h5><b>End: </b>{{ endTime }}</h5>
</div>
<h5 class="card-text" v-else>{{ startTime }} - {{ endTime }}</h5>
<NeedsRideButton />
<div class="d-flex flex-wrap gap-2 pb-2">
<NeedsRideButton />
<ShareEventButton :event-id="id" />
</div>
</div>
<div class="mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center">
Expand Down Expand Up @@ -53,26 +56,31 @@ import EditCarButton from './EditCarButton.vue';
</template>

<script lang="ts">
import { defineComponent, inject, type PropType } from 'vue';
import { type Event } from '@/models';
import { defineComponent, inject } from 'vue';
import { format } from 'date-fns';
import { useAuthStore } from '@/stores/auth';
import { useScreenStore } from '@/stores/screen';
import { useEventStore } from '@/stores/events';
import NeedsRideButton from './NeedsRideButton.vue';
import ShareEventButton from './ShareEventButton.vue';

export default defineComponent({
props: {
event: Object as PropType<Event>
id: Number
},
data() {
let screenStore = useScreenStore();
let eventStore = useEventStore();
return {
historyMode: inject('historyMode'),
eventStore,
screenStore
};
},
computed: {
event() {
return this.eventStore.selectedEvent;
},
startTime() {
let data = this.event!.startTime.toLocaleString();
return format(data, this.screenStore.mobile ? 'MM/dd/yy hh:mm a' : 'LLLL dd, yyyy hh:mm a');
Expand All @@ -92,6 +100,9 @@ export default defineComponent({
?.filter((car) => car.driver.id === authStore.user?.id)
.pop();
}
},
created() {
this.eventStore.setEventId(this.$props.id!);
}
});
</script>
15 changes: 6 additions & 9 deletions src/frontend/src/components/NeedsRideButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,12 @@ import BellSlash from './icons/BellSlash.vue';
</script>

<template>
<button type="button" class="btn btn-danger mb-2" @click="sendDataRemove" v-if="userInNeedsRide">
<button type="button" class="btn btn-danger" @click="sendDataRemove" v-if="userInNeedsRide">
<BellSlash style="vertical-align: text-top" /> No Longer Need Ride ({{
eventStore.selectedEvent?.needsRide.length
}})
</button>
<button
type="button"
class="btn btn-primary mb-2"
@click="sendDataAdd"
:disabled="userInCar"
v-else
>
<button type="button" class="btn btn-primary" @click="sendDataAdd" :disabled="userInCar" v-else>
<BellAlert style="vertical-align: text-top" /> Need Ride ({{
eventStore.selectedEvent?.needsRide.length
}})
Expand All @@ -38,8 +32,11 @@ export default defineComponent({
},
computed: {
userInCar() {
let allCars = this.eventStore.selectedEvent?.cars;
let allCars = this.eventStore.selectedEventCars;
let userId = this.authStore.user?.id;

if (!allCars) return false;

return allCars!.some(
(car) => car.riders.some((rider) => rider.id === userId) || car.driver.id === userId
);
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/components/NoEventDetails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<p>Select an Event to see details</p>
</template>
31 changes: 31 additions & 0 deletions src/frontend/src/components/ShareEventButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<button type="button" class="btn btn-primary" @click="copyLink">Copy Link to Event</button>
</template>

<script lang="ts">
import { PopupType } from '@/models';
import { usePopupStore } from '@/stores/popup';
import { defineComponent } from 'vue';

export default defineComponent({
props: {
eventId: Number
},
methods: {
copyLink() {
const popupStore = usePopupStore();
const baseUrl = window.location.host;

const urlToCopy = baseUrl + '/event/' + this.eventId;

try {
navigator.clipboard.writeText(urlToCopy);

popupStore.addPopup(PopupType.Success, 'Copied url for event to clipboard!');
} catch (_err) {
popupStore.addPopup(PopupType.Danger, `Failed to copy url ${urlToCopy} to clipboard`);
}
}
}
});
</script>
81 changes: 76 additions & 5 deletions src/frontend/src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,71 @@
import { createRouter, createWebHistory } from 'vue-router';
import {
createRouter,
createWebHistory,
type RouteLocationNormalizedGeneric,
type RouteLocationNormalizedLoadedGeneric
} from 'vue-router';
import HomeView from '../views/HomeView.vue';
import LoginView from '../views/LoginView.vue';
import { useAuthStore } from '@/stores/auth';
import { type UserData } from '@/models';
import EventDetails from '@/components/EventDetails.vue';
import NoEventDetails from '@/components/NoEventDetails.vue';

import { type Event } from '@/models';

async function handleEventIdRedirects(
to: RouteLocationNormalizedGeneric,
_from: RouteLocationNormalizedLoadedGeneric
) {
const response = await fetch(`/api/v1/event/${to.params.id}`);

if (response.status == 404) {
return {
path: '/'
};
}

if (response.status != 200) {
throw Error('Bad Return Code');
}

const jsonData: Event = await response.json();

const isInPast = new Date(jsonData.startTime).getTime() < Date.now();

const isGoingToPast = to.path.includes('history');

if ((isGoingToPast && isInPast) || (!isGoingToPast && !isInPast)) {
return true;
}

if (isInPast) {
return { path: `/history/${to.params.id}` };
} else {
return { path: `${to.params.id}` };
}
}

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
props: { showPast: false }
props: { showPast: false },
children: [
{
path: '',
name: 'home',
component: NoEventDetails
},
{
path: ':id',
component: EventDetails,
beforeEnter: handleEventIdRedirects,
props: (route) => ({ id: Number(route.params.id) })
}
]
},
{
path: '/login',
Expand All @@ -20,9 +74,26 @@ const router = createRouter({
},
{
path: '/history',
name: 'history',
component: HomeView,
props: { showPast: true }
props: { showPast: true },
children: [
{
path: '',
name: 'history',
component: NoEventDetails
},
{
path: ':id',
component: EventDetails,
beforeEnter: handleEventIdRedirects,
props: (route) => ({ id: Number(route.params.id) })
}
]
},
{
path: '/event/:id',
beforeEnter: handleEventIdRedirects,
component: NoEventDetails // This never gets used but has to be here for the beforeEnter to be called
}
]
});
Expand Down
29 changes: 16 additions & 13 deletions src/frontend/src/stores/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ function sortByStartDate(a: Event, b: Event) {
export const useEventStore = defineStore('events', {
state: () => ({
events: [] as Event[],
selectedEvent: null as Event | null
id: null as number | null
}),
getters: {
selectedEventCars: (state) => state.selectedEvent?.cars
selectedEvent(state) {
return state.events.find((event) => {
return event.id == state.id;
});
},
selectedEventCars(): Car[] | undefined {
return this.selectedEvent?.cars;
}
},
actions: {
addEvent(event: Event) {
Expand All @@ -20,29 +27,25 @@ export const useEventStore = defineStore('events', {
setEvents(events: Event[]) {
this.events = events;
},
setEventId(id: number) {
this.id = id;
},
sortEvents(past: Boolean) {
this.events.sort(sortByStartDate);

if (past) {
this.events.reverse();
this.events.reverse();
}
},
removeEvent(event: Event | null) {
if (event == null) {
removeEvent(id: number | null) {
if (id == null) {
return;
}
const index = this.events.indexOf(event);
const index = this.events.findIndex((event) => event.id == id);
if (index > -1) {
this.events.splice(index, 1);
}
},
selectEvent(event: Event) {
if (this.selectedEvent == event) {
return;
}
this.selectedEvent = event;
this.selectedEvent.cars = [];
},
addCar(car: Car) {
this.selectedEvent?.cars?.push(car);
},
Expand Down
Loading
Loading