From ffa4ec92099d7f072cfcfba455b73d80ee9567bd Mon Sep 17 00:00:00 2001
From: Thomas Citharel
Date: Wed, 18 Sep 2019 17:32:37 +0200
Subject: [PATCH] Work on dashboard
Signed-off-by: Thomas Citharel
---
js/public/index.html | 2 +-
js/src/App.vue | 34 +--
js/src/components/Event/DateCalendarIcon.vue | 4 +-
js/src/components/Event/DateTimePicker.vue | 17 +-
js/src/components/Event/EventListCard.vue | 185 ++++++++++++++++
js/src/components/NavBar.vue | 14 +-
js/src/graphql/actor.ts | 62 ++++--
js/src/i18n/en_US.json | 17 +-
js/src/i18n/fr_FR.json | 17 +-
js/src/mixins/actor.ts | 12 ++
js/src/mixins/event.ts | 61 ++++++
js/src/router/event.ts | 8 +
js/src/types/actor/actor.model.ts | 2 +
js/src/types/current-user.model.ts | 3 +
js/src/types/event.model.ts | 15 ++
js/src/utils/auth.ts | 40 +++-
.../views/Account/children/EditIdentity.vue | 1 +
js/src/views/Event/Event.vue | 63 +-----
js/src/views/Event/MyEvents.vue | 201 ++++++++++++++++++
js/src/views/Home.vue | 159 ++++++++++----
js/src/views/User/Login.vue | 3 +-
js/src/views/User/PasswordReset.vue | 4 +-
js/src/views/User/Register.vue | 4 +-
js/src/vue-apollo.ts | 14 +-
lib/mobilizon/events/events.ex | 60 ++++++
lib/mobilizon_web/resolvers/event.ex | 10 +-
lib/mobilizon_web/resolvers/user.ex | 20 +-
lib/mobilizon_web/schema/user.ex | 10 +
lib/mobilizon_web/views/error_view.ex | 23 +-
schema.graphql | 5 +-
test/mobilizon/events/events_test.exs | 16 +-
.../resolvers/event_resolver_test.exs | 46 ++--
test/mobilizon_web/views/error_view_test.exs | 3 +-
33 files changed, 931 insertions(+), 204 deletions(-)
create mode 100644 js/src/components/Event/EventListCard.vue
create mode 100644 js/src/mixins/actor.ts
create mode 100644 js/src/mixins/event.ts
create mode 100644 js/src/views/Event/MyEvents.vue
diff --git a/js/public/index.html b/js/public/index.html
index b7f91bf39..08101498c 100644
--- a/js/public/index.html
+++ b/js/public/index.html
@@ -6,7 +6,7 @@
-
+
mobilizon
diff --git a/js/src/App.vue b/js/src/App.vue
index c9fbf8be8..81b2635fa 100644
--- a/js/src/App.vue
+++ b/js/src/App.vue
@@ -24,7 +24,7 @@ import Footer from '@/components/Footer.vue';
import Logo from '@/components/Logo.vue';
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
import { IPerson } from '@/types/actor';
-import { changeIdentity, saveActorData } from '@/utils/auth';
+import { changeIdentity, initializeCurrentActor, saveActorData } from '@/utils/auth';
@Component({
apollo: {
@@ -40,18 +40,19 @@ import { changeIdentity, saveActorData } from '@/utils/auth';
})
export default class App extends Vue {
async created() {
- await this.initializeCurrentUser();
- await this.initializeCurrentActor();
+ if (await this.initializeCurrentUser()) {
+ await initializeCurrentActor(this.$apollo.provider.defaultClient);
+ }
}
- private initializeCurrentUser() {
+ private async initializeCurrentUser() {
const userId = localStorage.getItem(AUTH_USER_ID);
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
const role = localStorage.getItem(AUTH_USER_ROLE);
if (userId && userEmail && accessToken && role) {
- return this.$apollo.mutate({
+ return await this.$apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,
variables: {
id: userId,
@@ -61,26 +62,7 @@ export default class App extends Vue {
},
});
}
- }
-
- /**
- * We fetch from localStorage the latest actor ID used,
- * then fetch the current identities to set in cache
- * the current identity used
- */
- private async initializeCurrentActor() {
- const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
-
- const result = await this.$apollo.query({
- query: IDENTITIES,
- });
- const identities = result.data.identities;
- if (identities.length < 1) return;
- const activeIdentity = identities.find(identity => identity.id === actorId) || identities[0] as IPerson;
-
- if (activeIdentity) {
- return await changeIdentity(this.$apollo.provider.defaultClient, activeIdentity);
- }
+ return false;
}
}
@@ -107,6 +89,7 @@ export default class App extends Vue {
@import "~bulma/sass/elements/icon.sass";
@import "~bulma/sass/elements/image.sass";
@import "~bulma/sass/elements/other.sass";
+@import "~bulma/sass/elements/progress.sass";
@import "~bulma/sass/elements/tag.sass";
@import "~bulma/sass/elements/title.sass";
@import "~bulma/sass/elements/notification";
@@ -122,6 +105,7 @@ export default class App extends Vue {
@import "~buefy/src/scss/components/autocomplete";
@import "~buefy/src/scss/components/form";
@import "~buefy/src/scss/components/modal";
+@import "~buefy/src/scss/components/progress";
@import "~buefy/src/scss/components/tag";
@import "~buefy/src/scss/components/taginput";
@import "~buefy/src/scss/components/upload";
diff --git a/js/src/components/Event/DateCalendarIcon.vue b/js/src/components/Event/DateCalendarIcon.vue
index 72baa994d..62f5b5cb5 100644
--- a/js/src/components/Event/DateCalendarIcon.vue
+++ b/js/src/components/Event/DateCalendarIcon.vue
@@ -1,5 +1,5 @@
-
-
+
{{ $t('Delete') }}
@@ -111,7 +111,7 @@
+ :alt="event.organizerActor.avatar.alt" />
@@ -262,6 +262,7 @@ import ReportModal from '@/components/Report/ReportModal.vue';
import ParticipationModal from '@/components/Event/ParticipationModal.vue';
import { IReport } from '@/types/report.model';
import { CREATE_REPORT } from '@/graphql/report';
+import EventMixin from '@/mixins/event';
@Component({
components: {
@@ -290,7 +291,7 @@ import { CREATE_REPORT } from '@/graphql/report';
},
},
})
-export default class Event extends Vue {
+export default class Event extends EventMixin {
@Prop({ type: String, required: true }) uuid!: string;
event!: IEvent;
@@ -302,31 +303,12 @@ export default class Event extends Vue {
EventVisibility = EventVisibility;
- async openDeleteEventModal () {
- const participantsLength = this.event.participants.length;
- const prefix = participantsLength
- ? this.$tc('There are {participants} participants.', this.event.participants.length, {
- participants: this.event.participants.length,
- })
- : '';
-
- this.$buefy.dialog.prompt({
- type: 'is-danger',
- title: this.$t('Delete event') as string,
- message: `${prefix}
- ${this.$t('Are you sure you want to delete this event? This action cannot be reverted.')}
-
- ${this.$t('To confirm, type your event title "{eventTitle}"', { eventTitle: this.event.title })}`,
- confirmText: this.$t(
- 'Delete {eventTitle}',
- { eventTitle: this.event.title },
- ) as string,
- inputAttrs: {
- placeholder: this.event.title,
- pattern: this.event.title,
- },
- onConfirm: () => this.deleteEvent(),
- });
+ /**
+ * Delete the event, then redirect to home.
+ */
+ async openDeleteEventModalWrapper() {
+ await this.openDeleteEventModal(this.event, this.currentActor);
+ await this.$router.push({ name: RouteName.HOME });
}
async reportEvent(content: string, forward: boolean) {
@@ -464,31 +446,6 @@ export default class Event extends Vue {
return `mailto:?to=&body=${this.event.url}${encodeURIComponent('\n\n')}${this.event.description}&subject=${this.event.title}`;
}
- private async deleteEvent() {
- const router = this.$router;
- const eventTitle = this.event.title;
-
- try {
- await this.$apollo.mutate({
- mutation: DELETE_EVENT,
- variables: {
- eventId: this.event.id,
- actorId: this.currentActor.id,
- },
- });
-
- await router.push({ name: RouteName.HOME });
- this.$buefy.notification.open({
- message: this.$t('Event {eventTitle} deleted', { eventTitle }) as string,
- type: 'is-success',
- position: 'is-bottom-right',
- duration: 5000,
- });
- } catch (error) {
- console.error(error);
- }
- }
-
}
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index ac9a8db84..e38b06ad3 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -1,8 +1,8 @@
-
+
-
+
{{ config.name }}
{{ config.description }}
@@ -16,7 +16,7 @@
- {{ $t('Welcome back {username}', {username: loggedPerson.preferredUsername}) }}
+ {{ $t('Welcome back {username}', {username: `@${currentActor.preferredUsername}`}) }}
@@ -24,7 +24,7 @@
{{ $t('Create') }}
-
+.organizerActor.id
{{ $t('Event') }}
@@ -32,14 +32,14 @@
{{ $t('Group') }}
-
-
- {{ $t("Events you're going at") }}
-
+
+
+ {{ $t("Upcoming") }}
+
-
-
-
+
+
+
{{ $tc('You have one event today.', row[1].length, {count: row[1].length}) }}
@@ -49,24 +49,42 @@
{{ $tc('You have one event tomorrow.', row[1].length, {count: row[1].length}) }}
+ v-else-if="isInLessThanSevenDays(row[0])">
{{ $tc('You have one event in {days} days.', row[1].length, {count: row[1].length, days: calculateDiffDays(row[0])}) }}
-
{{ $t("You're not going to any event yet") }}
+
+ {{ $t('View everything')}} >>
+
-
+
+
+ {{ $t("Last week") }}
+
+
+
+
+
+
+
{{ $t('Events nearby you') }}
@@ -87,16 +105,18 @@
import ngeohash from 'ngeohash';
import { FETCH_EVENTS } from '@/graphql/event';
import { Component, Vue } from 'vue-property-decorator';
+import EventListCard from '@/components/Event/EventListCard.vue';
import EventCard from '@/components/Event/EventCard.vue';
-import { LOGGED_PERSON_WITH_GOING_TO_EVENTS } from '@/graphql/actor';
+import { CURRENT_ACTOR_CLIENT, LOGGED_USER_PARTICIPATIONS } from '@/graphql/actor';
import { IPerson, Person } from '@/types/actor';
import { ICurrentUser } from '@/types/current-user.model';
import { CURRENT_USER_CLIENT } from '@/graphql/user';
import { RouteName } from '@/router';
-import { IEvent } from '@/types/event.model';
+import { EventModel, IEvent, IParticipant, Participant } from '@/types/event.model';
import DateComponent from '@/components/Event/DateCalendarIcon.vue';
import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model';
+import { EventRouteName } from '@/router/event';
@Component({
apollo: {
@@ -104,8 +124,8 @@ import { IConfig } from '@/types/config.model';
query: FETCH_EVENTS,
fetchPolicy: 'no-cache', // Debug me: https://github.com/apollographql/apollo-client/issues/3030
},
- loggedPerson: {
- query: LOGGED_PERSON_WITH_GOING_TO_EVENTS,
+ currentActor: {
+ query: CURRENT_ACTOR_CLIENT,
},
currentUser: {
query: CURRENT_USER_CLIENT,
@@ -116,6 +136,7 @@ import { IConfig } from '@/types/config.model';
},
components: {
DateComponent,
+ EventListCard,
EventCard,
},
})
@@ -124,10 +145,12 @@ export default class Home extends Vue {
locations = [];
city = { name: null };
country = { name: null };
- loggedPerson: IPerson = new Person();
+ currentUserParticipations: IParticipant[] = [];
currentUser!: ICurrentUser;
+ currentActor!: IPerson;
config: IConfig = { description: '', name: '', registrationsOpen: false };
RouteName = RouteName;
+ EventRouteName = EventRouteName;
// get displayed_name() {
// return this.loggedPerson && this.loggedPerson.name === null
@@ -135,7 +158,23 @@ export default class Home extends Vue {
// : this.loggedPerson.name;
// }
- isToday(date: string) {
+ async mounted() {
+ const lastWeek = new Date();
+ lastWeek.setDate(new Date().getDate() - 7);
+
+ const { data } = await this.$apollo.query({
+ query: LOGGED_USER_PARTICIPATIONS,
+ variables: {
+ afterDateTime: lastWeek.toISOString(),
+ },
+ });
+
+ if (data) {
+ this.currentUserParticipations = data.loggedUser.participations.map(participation => new Participant(participation));
+ }
+ }
+
+ isToday(date: Date) {
return (new Date(date)).toDateString() === (new Date()).toDateString();
}
@@ -148,35 +187,43 @@ export default class Home extends Vue {
}
isBefore(date: string, nbDays: number) :boolean {
- return this.calculateDiffDays(date) > nbDays;
+ return this.calculateDiffDays(date) < nbDays;
}
- // FIXME: Use me
isInLessThanSevenDays(date: string): boolean {
- return this.isInDays(date, 7);
+ return this.isBefore(date, 7);
}
calculateDiffDays(date: string): number {
- const dateObj = new Date(date);
- return Math.ceil((dateObj.getTime() - (new Date()).getTime()) / 1000 / 60 / 60 / 24);
+ return Math.ceil(((new Date(date)).getTime() - (new Date()).getTime()) / 1000 / 60 / 60 / 24);
}
- get goingToEvents(): Map {
- const res = this.$data.loggedPerson.goingToEvents.filter((event) => {
- return event.beginsOn != null && this.isBefore(event.beginsOn, 0);
+ get goingToEvents(): Map> {
+ const res = this.currentUserParticipations.filter(({ event }) => {
+ return event.beginsOn != null && !this.isBefore(event.beginsOn.toDateString(), 0);
});
res.sort(
- (a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn),
+ (a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
);
- return res.reduce((acc: Map, event: IEvent) => {
- const day = (new Date(event.beginsOn)).toDateString();
- const events: IEvent[] = acc.get(day) || [];
- events.push(event);
- acc.set(day, events);
+ return res.reduce((acc: Map>, participation: IParticipant) => {
+ const day = (new Date(participation.event.beginsOn)).toDateString();
+ const participations: Map = acc.get(day) || new Map();
+ participations.set(participation.event.uuid, participation);
+ acc.set(day, participations);
return acc;
}, new Map());
}
+ get lastWeekEvents() {
+ const res = this.currentUserParticipations.filter(({ event }) => {
+ return event.beginsOn != null && this.isBefore(event.beginsOn.toDateString(), 0);
+ });
+ res.sort(
+ (a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
+ );
+ return res;
+ }
+
geoLocalize() {
const router = this.$router;
const sessionCity = sessionStorage.getItem('City');
@@ -226,7 +273,7 @@ export default class Home extends Vue {
-
diff --git a/js/src/views/User/Login.vue b/js/src/views/User/Login.vue
index a062c6d72..3b1cbadee 100644
--- a/js/src/views/User/Login.vue
+++ b/js/src/views/User/Login.vue
@@ -65,7 +65,7 @@
import { Component, Prop, Vue } from 'vue-property-decorator';
import { LOGIN } from '@/graphql/auth';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
-import { saveUserData } from '@/utils/auth';
+import { initializeCurrentActor, saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogin } from '@/vue-apollo';
@@ -146,6 +146,7 @@ export default class Login extends Vue {
role: data.login.user.role,
},
});
+ await initializeCurrentActor(this.$apollo.provider.defaultClient);
onLogin(this.$apollo);
diff --git a/js/src/views/User/PasswordReset.vue b/js/src/views/User/PasswordReset.vue
index f3f61d1e2..6ab6a4c78 100644
--- a/js/src/views/User/PasswordReset.vue
+++ b/js/src/views/User/PasswordReset.vue
@@ -6,7 +6,7 @@
{{ error }}