Migrate to Vue 3 and Vite

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-07-12 10:55:28 +02:00
parent 8f4099ee33
commit ee20e03cc2
464 changed files with 31515 additions and 32758 deletions

View File

@@ -1,48 +1,48 @@
<template>
<section class="section container">
<section class="container mx-auto">
<h1 class="title" v-if="loading">
{{ $t("Your participation request is being validated") }}
</h1>
<div v-else>
<div v-if="failed && participation === undefined">
<b-message
<o-notification
:title="$t('Error while validating participation request')"
type="is-danger"
variant="danger"
>
{{
$t(
"Either the participation request has already been validated, either the validation token is incorrect."
)
}}
</b-message>
</o-notification>
</div>
<div v-else>
<h1 class="title">
{{ $t("Your participation request has been validated") }}
</h1>
<p
class="content"
v-if="participation.event.joinOptions == EventJoinOptions.RESTRICTED"
class="prose dark:prose-invert"
v-if="participation?.event.joinOptions == EventJoinOptions.RESTRICTED"
>
{{
$t("Your participation still has to be approved by the organisers.")
}}
</p>
<div v-if="failed">
<b-message
<o-notification
:title="
$t(
'Error while updating participation status inside this browser'
)
"
type="is-warning"
variant="warning"
>
{{
$t(
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue."
)
}}
</b-message>
</o-notification>
</div>
<div class="columns has-text-centered">
<div class="column">
@@ -52,7 +52,7 @@
class="button is-primary is-large"
:to="{
name: RouteName.EVENT,
params: { uuid: this.participation.event.uuid },
params: { uuid: participation?.event.uuid },
}"
>{{ $t("Go to the event page") }}</router-link
>
@@ -63,60 +63,50 @@
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
<script lang="ts" setup>
import { confirmLocalAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
import { EventJoinOptions } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import RouteName from "../../router/name";
import { CONFIRM_PARTICIPATION } from "../../graphql/event";
import { computed, ref } from "vue";
import { useMutation } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { useHead } from "@vueuse/head";
@Component({
metaInfo() {
return {
title: this.$t("Confirm participation") as string,
};
},
})
export default class ConfirmParticipation extends Vue {
@Prop({ type: String, required: true }) token!: string;
const { t } = useI18n({ useScope: "global" });
loading = true;
useHead({
title: computed(() => t("Confirm participation")),
});
failed = false;
const props = defineProps<{
token: string;
}>();
participation!: IParticipant;
const loading = ref(true);
const failed = ref(false);
const participation = ref<IParticipant | null | undefined>(null);
EventJoinOptions = EventJoinOptions;
const { onDone, onError, mutate } = useMutation<{
confirmParticipation: IParticipant;
}>(CONFIRM_PARTICIPATION);
RouteName = RouteName;
mutate({
token: props.token,
});
async created(): Promise<void> {
await this.validateAction();
onDone(async ({ data }) => {
participation.value = data?.confirmParticipation;
if (participation.value) {
await confirmLocalAnonymousParticipation(participation.value?.event.uuid);
}
loading.value = false;
});
async validateAction(): Promise<void> {
try {
const { data } = await this.$apollo.mutate<{
confirmParticipation: IParticipant;
}>({
mutation: CONFIRM_PARTICIPATION,
variables: {
token: this.token,
},
});
if (data) {
const { confirmParticipation: participation } = data;
this.participation = participation;
await confirmLocalAnonymousParticipation(this.participation.event.uuid);
}
} catch (err) {
console.error(err);
this.failed = true;
} finally {
this.loading = false;
}
}
}
onError((err) => {
console.error(err);
failed.value = true;
loading.value = false;
});
</script>

View File

@@ -1,14 +1,12 @@
<template>
<div>
<div
class="event-participation has-text-right"
v-if="isEventNotAlreadyPassed"
>
<div class="event-participation" v-if="isEventNotAlreadyPassed">
<participation-button
v-if="shouldShowParticipationButton"
:participation="participation"
:event="event"
:current-actor="currentActor"
:identities="identities"
@join-event="(actor) => $emit('join-event', actor)"
@join-modal="$emit('join-modal')"
@join-event-with-confirmation="
@@ -16,223 +14,207 @@
"
@confirm-leave="$emit('confirm-leave')"
/>
<b-button
type="is-text"
<o-button
variant="text"
v-if="!actorIsParticipant && anonymousParticipation !== null"
@click="$emit('cancel-anonymous-participation')"
>{{ $t("Cancel anonymous participation") }}</b-button
>{{ t("Cancel anonymous participation") }}</o-button
>
<small v-if="!actorIsParticipant && anonymousParticipation">
{{ $t("You are participating in this event anonymously") }}
<b-tooltip :label="$t('Click for more information')">
{{ t("You are participating in this event anonymously") }}
<VTooltip>
<template #popper>
{{ t("Click for more information") }}
</template>
<span
class="is-clickable"
@click="isAnonymousParticipationModalOpen = true"
>
<b-icon size="is-small" icon="information-outline" />
<InformationOutline :size="16" />
</span>
</b-tooltip>
</VTooltip>
</small>
<small
v-else-if="!actorIsParticipant && anonymousParticipation === false"
>
{{
$t(
t(
"You are participating in this event anonymously but didn't confirm participation"
)
}}
<b-tooltip
:label="
$t(
'This information is saved only on your computer. Click for details'
)
"
>
<VTooltip>
<template #popper>
{{
t(
"This information is saved only on your computer. Click for details"
)
}}
</template>
<router-link :to="{ name: RouteName.TERMS }">
<b-icon size="is-small" icon="help-circle-outline" />
<HelpCircleOutline :size="16" />
</router-link>
</b-tooltip>
</VTooltip>
</small>
</div>
<div v-else>
<button class="button is-primary" type="button" slot="trigger" disabled>
<template>
<span>{{ $t("Event already passed") }}</span>
</template>
<b-icon icon="menu-down" />
</button>
<o-button variant="primary" disabled icon-left="menu-down">
{{ t("Event already passed") }}
</o-button>
</div>
<b-modal
:active.sync="isAnonymousParticipationModalOpen"
<o-modal
v-model:active="isAnonymousParticipationModalOpen"
has-modal-card
:close-button-aria-label="$t('Close')"
:close-button-aria-label="t('Close')"
ref="anonymous-participation-modal"
>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
{{ $t("About anonymous participation") }}
{{ t("About anonymous participation") }}
</p>
</header>
<section class="modal-card-body">
<b-notification
type="is-primary"
<o-notification
variant="primary"
:closable="false"
v-if="event.joinOptions === EventJoinOptions.RESTRICTED"
>
{{
$t(
t(
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted."
)
}}
</b-notification>
</o-notification>
<p>
{{
$t(
t(
"Your participation status is saved only on this device and will be deleted one month after the event's passed."
)
}}
</p>
<p v-if="isSecureContext">
<p v-if="isSecureContext()">
{{
$t(
t(
"You may clear all participation information for this device with the buttons below."
)
}}
</p>
<div class="buttons" v-if="isSecureContext">
<b-button
<div class="buttons" v-if="isSecureContext()">
<o-button
type="is-danger is-outlined"
@click="clearEventParticipationData"
>
{{ $t("Clear participation data for this event") }}
</b-button>
<b-button type="is-danger" @click="clearAllParticipationData">
{{ $t("Clear participation data for all events") }}
</b-button>
{{ t("Clear participation data for this event") }}
</o-button>
<o-button variant="danger" @click="clearAllParticipationData">
{{ t("Clear participation data for all events") }}
</o-button>
</div>
</section>
</div>
</b-modal>
</o-modal>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { EventJoinOptions, EventStatus, ParticipantRole } from "@/types/enums";
import { IParticipant } from "@/types/participant.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import RouteName from "@/router/name";
import { IEvent } from "@/types/event.model";
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { IPerson } from "@/types/actor";
import { IConfig } from "@/types/config.model";
import { CONFIG } from "@/graphql/config";
import {
removeAllAnonymousParticipations,
removeAnonymousParticipation,
} from "@/services/AnonymousParticipationStorage";
import ParticipationButton from "../Event/ParticipationButton.vue";
import { computed, ref } from "vue";
import InformationOutline from "vue-material-design-icons/InformationOutline.vue";
import HelpCircleOutline from "vue-material-design-icons/HelpCircleOutline.vue";
import { useI18n } from "vue-i18n";
import { IPerson } from "@/types/actor";
import { IAnonymousParticipationConfig } from "@/types/config.model";
@Component({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
config: CONFIG,
},
components: {
ParticipationButton,
},
})
export default class ParticipationSection extends Vue {
@Prop({ required: true }) participation!: IParticipant;
const { t } = useI18n({ useScope: "global" });
@Prop({ required: true }) event!: IEvent;
@Prop({ required: true, default: null }) anonymousParticipation!:
| boolean
| null;
currentActor!: IPerson;
config!: IConfig;
RouteName = RouteName;
EventJoinOptions = EventJoinOptions;
isAnonymousParticipationModalOpen = false;
get actorIsParticipant(): boolean {
if (this.actorIsOrganizer) return true;
return (
this.participation &&
this.participation.role === ParticipantRole.PARTICIPANT
);
const props = withDefaults(
defineProps<{
participation: IParticipant | undefined;
event: IEvent;
anonymousParticipation?: boolean | null;
currentActor: IPerson;
identities: IPerson[];
anonymousParticipationConfig: IAnonymousParticipationConfig;
}>(),
{
anonymousParticipation: null,
}
);
get actorIsOrganizer(): boolean {
return (
this.participation && this.participation.role === ParticipantRole.CREATOR
);
}
const isAnonymousParticipationModalOpen = ref(false);
get shouldShowParticipationButton(): boolean {
// If we have an anonymous participation, don't show the participation button
if (
this.config &&
this.config.anonymous.participation.allowed &&
this.anonymousParticipation
) {
return false;
}
const actorIsParticipant = computed((): boolean => {
if (actorIsOrganizer.value) return true;
// So that people can cancel their participation
if (this.actorIsParticipant) return true;
return props.participation?.role === ParticipantRole.PARTICIPANT;
});
// You can participate to draft or cancelled events
if (this.event.draft || this.event.status === EventStatus.CANCELLED)
return false;
const actorIsOrganizer = computed((): boolean => {
return props.participation?.role === ParticipantRole.CREATOR;
});
// If capacity is OK
if (this.eventCapacityOK) return true;
// Else
const shouldShowParticipationButton = computed((): boolean => {
// If we have an anonymous participation, don't show the participation button
if (
props.anonymousParticipationConfig?.allowed &&
props.anonymousParticipation
) {
return false;
}
get eventCapacityOK(): boolean {
if (this.event.draft) return true;
if (!this.event.options.maximumAttendeeCapacity) return true;
return (
this.event.options.maximumAttendeeCapacity >
this.event.participantStats.participant
);
}
// So that people can cancel their participation
if (actorIsParticipant.value) return true;
get isEventNotAlreadyPassed(): boolean {
return new Date(this.endDate) > new Date();
}
// You can participate to draft or cancelled events
if (props.event.draft || props.event.status === EventStatus.CANCELLED)
return false;
get endDate(): Date {
return this.event.endsOn !== null && this.event.endsOn > this.event.beginsOn
? this.event.endsOn
: this.event.beginsOn;
}
// If capacity is OK
if (eventCapacityOK.value) return true;
// eslint-disable-next-line class-methods-use-this
get isSecureContext(): boolean {
return window.isSecureContext;
}
// Else
return false;
});
async clearEventParticipationData(): Promise<void> {
await removeAnonymousParticipation(this.event.uuid);
window.location.reload();
}
const eventCapacityOK = computed((): boolean => {
if (props.event.draft) return true;
if (!props.event.options.maximumAttendeeCapacity) return true;
return (
props.event.options.maximumAttendeeCapacity >
props.event.participantStats.participant
);
});
// eslint-disable-next-line class-methods-use-this
clearAllParticipationData(): void {
removeAllAnonymousParticipations();
window.location.reload();
}
}
const isEventNotAlreadyPassed = computed((): boolean => {
return new Date(endDate.value) > new Date();
});
const endDate = computed((): Date => {
return props.event.endsOn !== null &&
props.event.endsOn > props.event.beginsOn
? props.event.endsOn
: props.event.beginsOn;
});
const isSecureContext = (): boolean => {
return window.isSecureContext;
};
const clearEventParticipationData = async (): Promise<void> => {
await removeAnonymousParticipation(props.event.uuid);
window.location.reload();
};
const clearAllParticipationData = (): void => {
removeAllAnonymousParticipations();
window.location.reload();
};
</script>

View File

@@ -6,46 +6,31 @@
:sentence="sentence"
/>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
<script lang="ts" setup>
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { FETCH_EVENT } from "@/graphql/event";
import { IEvent } from "@/types/event.model";
import { useFetchEvent } from "@/composition/apollo/event";
import { useHead } from "@vueuse/head";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
@Component({
components: { RedirectWithAccount },
apollo: {
event: {
query: FETCH_EVENT,
fetchPolicy: "cache-and-network",
variables() {
return {
uuid: this.uuid,
};
},
skip() {
return !this.uuid;
},
},
},
metaInfo() {
return {
title: this.$t("Participation with account") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class ParticipationWithAccount extends Vue {
@Prop({ type: String, required: true }) uuid!: string;
const props = defineProps<{
uuid: string;
}>();
event!: IEvent;
const { event } = useFetchEvent(props.uuid);
get uri(): string | undefined {
return this.event?.url;
}
const { t } = useI18n({ useScope: "global" });
sentence = this.$t(
"We will redirect you to your instance in order to interact with this event"
);
}
useHead({
title: computed(() => t("Participation with account")),
meta: [{ name: "robots", content: "noindex" }],
});
const uri = computed((): string | undefined => {
return event.value?.url;
});
const sentence = t(
"We will redirect you to your instance in order to interact with this event"
);
</script>

View File

@@ -1,118 +1,128 @@
<template>
<section class="container section hero is-fullheight">
<div class="hero-body" v-if="event">
<div class="container">
<form @submit.prevent="joinEvent" v-if="!formSent">
<p>
{{
$t(
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation."
)
}}
</p>
<b-message type="is-info">
{{
$t(
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer."
)
}}
</b-message>
<b-message type="is-danger" v-if="error">{{ error }}</b-message>
<b-field :label="$t('Email address')">
<b-input
type="email"
v-model="anonymousParticipation.email"
:placeholder="$t('Your email')"
required
></b-input>
</b-field>
<p v-if="event.joinOptions === EventJoinOptions.RESTRICTED">
{{
$t(
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event."
)
}}
</p>
<p v-else>
{{
$t(
"If you want, you may send a message to the event organizer here."
)
}}
</p>
<b-field :label="$t('Message')">
<b-input
type="textarea"
v-model="anonymousParticipation.message"
minlength="10"
:required="event.joinOptions === EventJoinOptions.RESTRICTED"
></b-input>
</b-field>
<b-field>
<b-checkbox v-model="anonymousParticipation.saveParticipation">
<b>{{ $t("Remember my participation in this browser") }}</b>
<p>
{{
$t(
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device."
)
}}
</p>
</b-checkbox>
</b-field>
<b-button
:disabled="sendingForm"
type="is-primary"
native-type="submit"
>{{ $t("Send email") }}</b-button
>
<div class="has-text-centered">
<b-button
native-type="button"
tag="a"
type="is-text"
@click="$router.go(-1)"
>{{ $t("Back to previous page") }}</b-button
>
</div>
</form>
<div v-else>
<h1 class="title">
{{ $t("Request for participation confirmation sent") }}
</h1>
<p class="content">
<span>{{
$t("Check your inbox (and your junk mail folder).")
}}</span>
<span
class="details"
v-if="event.joinOptions === EventJoinOptions.RESTRICTED"
>
<section class="container mx-auto">
<div class="" v-if="event">
<form @submit.prevent="joinEvent" v-if="!formSent">
<p>
{{
$t(
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation."
)
}}
</p>
<o-notification variant="info">
{{
$t(
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer."
)
}}
</o-notification>
<o-notification variant="danger" v-if="error">{{
error
}}</o-notification>
<o-field
:label="$t('Email address')"
labelFor="anonymousParticipationEmail"
>
<o-input
type="email"
id="anonymousParticipationEmail"
v-model="anonymousParticipation.email"
:placeholder="$t('Your email')"
required
/>
</o-field>
<p v-if="event.joinOptions === EventJoinOptions.RESTRICTED">
{{
$t(
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event."
)
}}
</p>
<p v-else>
{{
$t(
"If you want, you may send a message to the event organizer here."
)
}}
</p>
<o-field
:label="$t('Message')"
labelFor="anonymousParticipationMessage"
>
<o-input
id="anonymousParticipationMessage"
type="textarea"
v-model="anonymousParticipation.message"
minlength="10"
:required="event.joinOptions === EventJoinOptions.RESTRICTED"
/>
</o-field>
<o-field>
<o-checkbox v-model="anonymousParticipation.saveParticipation">
<b>{{ $t("Remember my participation in this browser") }}</b>
<p>
{{
$t(
"Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation."
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device."
)
}} </span
><span class="details" v-else>{{
}}
</p>
</o-checkbox>
</o-field>
<div class="flex gap-2 my-2">
<o-button
:disabled="sendingForm"
variant="primary"
native-type="submit"
>{{ $t("Send email") }}</o-button
>
<o-button
native-type="button"
variant="text"
@click="$router.go(-1)"
>{{ $t("Back to previous page") }}</o-button
>
</div>
</form>
<div v-else>
<h1 class="title">
{{ $t("Request for participation confirmation sent") }}
</h1>
<p class="prose dark:prose-invert">
<span>{{ $t("Check your inbox (and your junk mail folder).") }}</span>
<span
class="details"
v-if="event.joinOptions === EventJoinOptions.RESTRICTED"
>
{{
$t(
"Your participation will be validated once you click the confirmation link into the email."
"Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation."
)
}}</span>
</p>
<b-message type="is-warning" v-if="error">{{ error }}</b-message>
<p class="content">
<i18n path="You may now close this window, or {return_to_event}.">
}} </span
><span class="details" v-else>{{
$t(
"Your participation will be validated once you click the confirmation link into the email."
)
}}</span>
</p>
<o-notification variant="warning" v-if="error">{{
error
}}</o-notification>
<p class="prose dark:prose-invert">
<i18n-t
keypath="You may now close this window, or {return_to_event}."
>
<template #return_to_event>
<router-link
slot="return_to_event"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
>{{ $t("return to the event's page") }}</router-link
>
</i18n>
</p>
</div>
</template>
</i18n-t>
</p>
</div>
</div>
<b-message type="is-danger" v-else-if="!$apollo.loading"
<o-notification variant="danger" v-else-if="!loading"
>{{
$t(
"Unable to load event for participation. The error details are provided below:"
@@ -121,162 +131,147 @@
<details>
<pre>{{ error }}</pre>
</details>
</b-message>
</o-notification>
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { EventModel, IEvent } from "@/types/event.model";
<script lang="ts" setup>
import { IEvent } from "@/types/event.model";
import { FETCH_EVENT_BASIC, JOIN_EVENT } from "@/graphql/event";
import { IConfig } from "@/types/config.model";
import { CONFIG } from "@/graphql/config";
import { addLocalUnconfirmedAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
import RouteName from "@/router/name";
import { IParticipant } from "../../types/participant.model";
import { ApolloCache, FetchResult } from "@apollo/client/core";
import { useFetchEventBasic } from "@/composition/apollo/event";
import { useAnonymousActorId } from "@/composition/apollo/config";
import { computed, reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useHead } from "@vueuse/head";
import { useMutation } from "@vue/apollo-composable";
@Component({
apollo: {
event: {
const error = ref<boolean | string>(false);
const { anonymousActorId } = useAnonymousActorId();
const props = defineProps<{
uuid: string;
}>();
const { event, loading } = useFetchEventBasic(props.uuid);
const { t, locale } = useI18n({ useScope: "global" });
useHead({
title: computed(() => t("Participation without account")),
meta: [{ name: "robots", content: "noindex" }],
});
const anonymousParticipation = reactive<{
email: string;
message: string;
saveParticipation: boolean;
}>({
email: "",
message: "",
saveParticipation: true,
});
const formSent = ref(false);
const sendingForm = ref(false);
const {
mutate: joinEventMutation,
onDone: joinEventDone,
onError: joinEventError,
} = useMutation<{
joinEvent: IParticipant;
}>(JOIN_EVENT, () => ({
update: (
store: ApolloCache<{ joinEvent: IParticipant }>,
{ data: updateData }: FetchResult
) => {
if (updateData == null) {
console.error(
"Cannot update event participant cache, because of data null value."
);
return;
}
const cachedData = store.readQuery<{ event: IEvent }>({
query: FETCH_EVENT_BASIC,
variables() {
return {
uuid: this.uuid,
};
variables: { uuid: event.value?.uuid },
});
if (cachedData == null) {
console.error(
"Cannot update event participant cache, because of cached null value."
);
return;
}
const participantStats = { ...cachedData.event.participantStats };
if (updateData.joinEvent.role === ParticipantRole.NOT_CONFIRMED) {
participantStats.notConfirmed += 1;
} else {
participantStats.going += 1;
participantStats.participant += 1;
}
store.writeQuery({
query: FETCH_EVENT_BASIC,
variables: { uuid: event.value?.uuid },
data: {
event: {
...cachedData.event,
participantStats,
},
},
error(e) {
this.error = e;
},
skip() {
return !this.uuid;
},
update: (data) => new EventModel(data.event),
},
config: CONFIG,
});
},
metaInfo() {
return {
title: this.$t("Participation without account") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class ParticipationWithoutAccount extends Vue {
@Prop({ type: String, required: true }) uuid!: string;
}));
anonymousParticipation: {
email: string;
message: string;
saveParticipation: boolean;
} = {
email: "",
message: "",
saveParticipation: true,
};
event!: IEvent;
config!: IConfig;
error: string | boolean = false;
formSent = false;
sendingForm = false;
EventJoinOptions = EventJoinOptions;
RouteName = RouteName;
async joinEvent(): Promise<void> {
this.error = false;
this.sendingForm = true;
joinEventDone(async ({ data }) => {
sendingForm.value = false;
formSent.value = true;
if (
data?.joinEvent.metadata.cancellationToken &&
anonymousParticipation.saveParticipation &&
event.value
) {
try {
const { data } = await this.$apollo.mutate<{ joinEvent: IParticipant }>({
mutation: JOIN_EVENT,
variables: {
eventId: this.event.id,
actorId: this.config.anonymous.actorId,
email: this.anonymousParticipation.email,
message: this.anonymousParticipation.message,
locale: this.$i18n.locale,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
},
update: (
store: ApolloCache<{ joinEvent: IParticipant }>,
{ data: updateData }: FetchResult
) => {
if (updateData == null) {
console.error(
"Cannot update event participant cache, because of data null value."
);
return;
}
const cachedData = store.readQuery<{ event: IEvent }>({
query: FETCH_EVENT_BASIC,
variables: { uuid: this.event.uuid },
});
if (cachedData == null) {
console.error(
"Cannot update event participant cache, because of cached null value."
);
return;
}
const participantStats = { ...cachedData.event.participantStats };
if (updateData.joinEvent.role === ParticipantRole.NOT_CONFIRMED) {
participantStats.notConfirmed += 1;
} else {
participantStats.going += 1;
participantStats.participant += 1;
}
store.writeQuery({
query: FETCH_EVENT_BASIC,
variables: { uuid: this.event.uuid },
data: {
event: {
...cachedData.event,
participantStats,
},
},
});
},
});
this.formSent = true;
if (
data &&
data.joinEvent.metadata.cancellationToken &&
this.anonymousParticipation.saveParticipation
) {
await addLocalUnconfirmedAnonymousParticipation(
this.event,
data.joinEvent.metadata.cancellationToken
);
}
await addLocalUnconfirmedAnonymousParticipation(
event.value,
data.joinEvent.metadata.cancellationToken
);
} catch (e: any) {
if (
["TextEncoder is not defined", "crypto.subtle is undefined"].includes(
e.message
)
) {
this.error = this.$t(
"Unable to save your participation in this browser."
) as string;
} else if (e.graphQLErrors && e.graphQLErrors.length > 0) {
this.error = e.graphQLErrors[0].message;
} else if (e.networkError) {
this.error = e.networkError.message;
error.value = t("Unable to save your participation in this browser.");
}
}
this.sendingForm = false;
}
}
});
joinEventError((e) => {
if (e.graphQLErrors && e.graphQLErrors.length > 0) {
error.value = e.graphQLErrors[0].message;
} else if (e.networkError) {
error.value = e.networkError.message;
}
});
const joinEvent = async (): Promise<void> => {
error.value = false;
sendingForm.value = true;
joinEventMutation({
eventId: event.value?.id,
actorId: anonymousActorId.value,
email: anonymousParticipation.email,
message: anonymousParticipation.message,
locale: locale.value,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
};
</script>
<style lang="scss" scoped>
section.container.section {
background: $white;
}
</style>

View File

@@ -1,10 +1,10 @@
<template>
<section class="section container hero">
<section class="container mx-auto hero">
<div class="hero-body" v-if="event">
<div class="container">
<subtitle>{{
$t("You wish to participate to the following event")
}}</subtitle>
<div class="container mx-auto">
<h2 class="text-2xl">
{{ $t("You wish to participate to the following event") }}
</h2>
<EventListViewCard v-if="event" :event="event" />
<div class="columns has-text-centered">
<div class="column">
@@ -17,9 +17,9 @@
alt="Profile illustration"
/>
</figure>
<b-button type="is-primary">{{
<o-button variant="primary">{{
$t("I have a Mobilizon account")
}}</b-button>
}}</o-button>
</router-link>
<p>
<small>
@@ -32,7 +32,7 @@
)
}}
</small>
<b-tooltip
<o-tooltip
type="is-dark"
:label="
$t(
@@ -40,8 +40,8 @@
)
"
>
<b-icon size="is-small" icon="help-circle-outline" />
</b-tooltip>
<o-icon size="small" icon="help-circle-outline" />
</o-tooltip>
</p>
</div>
<vertical-divider
@@ -65,9 +65,9 @@
alt="Privacy illustration"
/>
</figure>
<b-button type="is-primary">{{
<o-button variant="primary">{{
$t("I don't have a Mobilizon account")
}}</b-button>
}}</o-button>
</router-link>
<a :href="`${event.url}/participate/without-account`" v-else>
<figure class="image is-128x128">
@@ -76,9 +76,9 @@
alt="Privacy illustration"
/>
</figure>
<b-button type="is-primary">{{
<o-button variant="primary">{{
$t("I don't have a Mobilizon account")
}}</b-button>
}}</o-button>
</a>
<p>
<small>{{ $t("Participate using your email address") }}</small>
@@ -90,78 +90,56 @@
</div>
</div>
<div class="has-text-centered">
<b-button tag="a" type="is-text" @click="$router.go(-1)">{{
<o-button tag="a" type="is-text" @click="router.go(-1)">{{
$t("Back to previous page")
}}</b-button>
}}</o-button>
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { FETCH_EVENT } from "@/graphql/event";
<script lang="ts" setup>
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
import { EventModel, IEvent } from "@/types/event.model";
import VerticalDivider from "@/components/Utils/VerticalDivider.vue";
import { CONFIG } from "@/graphql/config";
import { IConfig } from "@/types/config.model";
import Subtitle from "@/components/Utils/Subtitle.vue";
import RouteName from "../../router/name";
import { useFetchEvent } from "@/composition/apollo/event";
import { useAnonymousParticipationConfig } from "@/composition/apollo/config";
import { computed } from "vue";
import { useRouter } from "vue-router";
import { useHead } from "@vueuse/head";
import { useI18n } from "vue-i18n";
@Component({
components: {
VerticalDivider,
EventListViewCard,
Subtitle,
},
apollo: {
event: {
query: FETCH_EVENT,
variables() {
return {
uuid: this.uuid,
};
},
skip() {
return !this.uuid;
},
update: (data) => new EventModel(data.event),
},
config: CONFIG,
},
metaInfo() {
return {
title: this.$t("Unlogged participation") as string,
meta: [{ name: "robots", content: "noindex" }],
};
},
})
export default class UnloggedParticipation extends Vue {
@Prop({ type: String, required: true }) uuid!: string;
const props = defineProps<{ uuid: string }>();
RouteName = RouteName;
const { event } = useFetchEvent(props.uuid);
event!: IEvent;
const { anonymousParticipationConfig } = useAnonymousParticipationConfig();
config!: IConfig;
const router = useRouter();
// eslint-disable-next-line class-methods-use-this
get host(): string {
return window.location.hostname;
}
const { t } = useI18n({ useScope: "global" });
get anonymousParticipationAllowed(): boolean {
return this.event.options.anonymousParticipation;
}
useHead({
title: computed(() => t("Unlogged participation")),
meta: [{ name: "robots", content: "noindex" }],
});
get hasAnonymousEmailParticipationMethod(): boolean {
const host = computed((): string => {
return window.location.hostname;
});
const anonymousParticipationAllowed = computed((): boolean | undefined => {
return event.value?.options.anonymousParticipation;
});
const hasAnonymousEmailParticipationMethod = computed(
(): boolean | undefined => {
return (
this.config.anonymous.participation.allowed &&
this.config.anonymous.participation.validation.email.enabled
anonymousParticipationConfig.value?.allowed &&
anonymousParticipationConfig.value?.validation.email.enabled
);
}
}
);
</script>
<style lang="scss" scoped>
.column > a {