Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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,466 changes: 859 additions & 607 deletions src/frontend/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"npm-run-all2": "^6.2.0",
"prettier": "^3.2.5",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vite": "^7.1.3",
"vue-tsc": "^2.0.21"
}
}
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 = -1;
popupStore.addPopup(PopupType.Success, 'Event Deleted!');
this.closeModal();
} catch (error) {
Expand Down
13 changes: 10 additions & 3 deletions src/frontend/src/components/EventCard.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<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">
Expand All @@ -22,7 +23,8 @@ import { useScreenStore } from '@/stores/screen';

export default defineComponent({
props: {
event: Object as PropType<Event>
event: Object as PropType<Event>,
isInPast: Boolean
},
data() {
let screenStore = useScreenStore();
Expand All @@ -34,6 +36,11 @@ export default defineComponent({
formattedStart() {
let data = this.event?.startTime.toLocaleString();
return data ? format(data, 'MM/dd/yyyy hh:mm a') : 'N/A';
},
eventPath() {
if (this.$props.isInPast) {
return `/history/${this.$props.event?.id}`;
} else return `${this.$props.event?.id}`;
}
}
});
Expand Down
23 changes: 16 additions & 7 deletions src/frontend/src/components/EventDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ 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 />
<ShareEventButton :event-id="id" />
</div>
<div class="mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center">
Expand All @@ -31,7 +32,7 @@ import EditCarButton from './EditCarButton.vue';
<EditCarButton v-else :car="userCar" />
</div>
</div>
<CarTable :eventId="event?.id" :key="event?.id" />
<CarTable :eventId="id" :key="event?.id" />
<h4 class="mr-1">Need a Ride</h4>
<div v-if="event?.needsRide.length === 0">
<p><i>No one needs a ride</i></p>
Expand All @@ -53,26 +54,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 +98,9 @@ export default defineComponent({
?.filter((car) => car.driver.id === authStore.user?.id)
.pop();
}
},
created() {
this.eventStore.setEventId(this.$props.id!);
}
});
</script>
5 changes: 4 additions & 1 deletion src/frontend/src/components/NeedsRideButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,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>
33 changes: 33 additions & 0 deletions src/frontend/src/components/ShareEventButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<button type="button" class="btn btn-primary mx-2 mb-2" @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>
51 changes: 49 additions & 2 deletions src/frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ 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';

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
Expand All @@ -11,7 +15,18 @@ const router = createRouter({
path: '/',
name: 'home',
component: HomeView,
props: { showPast: false }
props: { showPast: false },
children: [
{
path: '',
component: NoEventDetails
},
{
path: ':id',
component: EventDetails,
props: true
}
]
},
{
path: '/login',
Expand All @@ -22,7 +37,39 @@ const router = createRouter({
path: '/history',
name: 'history',
component: HomeView,
props: { showPast: true }
props: { showPast: true },
children: [
{
path: '',
component: NoEventDetails
},
{
path: ':id',
component: EventDetails,
props: true
}
]
},
{
path: '/event/:id',
beforeEnter: async (to, _from) => {
const response = await fetch(`/api/v1/event/${to.params.id}`);

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

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

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

if (isInPast) {
return { path: `/history/${to.params.id}` };
} else {
return { path: `${to.params.id}` };
}
},
component: NoEventDetails // This never gets used but has to be here for the beforeEnter to be called
}
]
});
Expand Down
36 changes: 23 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: -1 as number
}),
getters: {
selectedEventCars: (state) => state.selectedEvent?.cars
selectedEvent(state) {
return state.events.find((event) => {
return event.id == state.id;
});
},
Comment on lines +14 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could argue it's more efficient to store events as a dict of ID to event, but it doesn't really matter

selectedEventCars(): Car[] | undefined {
return this.selectedEvent?.cars;
}
},
actions: {
addEvent(event: Event) {
Expand All @@ -20,29 +27,32 @@ 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 = [];
},
// selectEvent(event: Event) {
// if (this.selectedEvent == event) {
// return;
// }
// this.selectedEvent = event;
// this.selectedEvent.cars = [];
// },
addCar(car: Car) {
this.selectedEvent?.cars?.push(car);
},
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export function validateEvent(title: string, location: string, start: string, en
if (new Date(start) > new Date(end)) {
out.push('Start date cannot be after end.');
}
if (new Date(end) < new Date()) {
out.push('Event cannot be in the past.');
}
// if (new Date(end) < new Date()) {
// out.push('Event cannot be in the past.');
// }
return out;
}
Loading
Loading