|
|
@@ -0,0 +1,185 @@
|
|
|
+<!--
|
|
|
+Administration de la connexion Opentalent / HelloAsso
|
|
|
+-->
|
|
|
+<template>
|
|
|
+ <LayoutContainer>
|
|
|
+ <v-card>
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="4" class="d-flex justify-center">
|
|
|
+ <v-img src="/images/logos/Logo-HelloAsso.svg" class="logo" />
|
|
|
+ </v-col>
|
|
|
+
|
|
|
+ <v-col cols="12" md="8" class="presentation">
|
|
|
+ {{ $t('helloasso_presentation') }}
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" class="d-flex justify-center align-center w-100 mt-6">
|
|
|
+ <v-progress-circular
|
|
|
+ v-if="
|
|
|
+ statusHelloAssoProfile === FETCHING_STATUS.PENDING ||
|
|
|
+ unlinkingPending
|
|
|
+ "
|
|
|
+ indeterminate
|
|
|
+ size="32"
|
|
|
+ />
|
|
|
+
|
|
|
+ <UiButtonHelloAssoConnect
|
|
|
+ v-else-if="!helloAssoProfile || !helloAssoProfile.token"
|
|
|
+ @click="onHelloAssoConnectClicked"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div v-else class="d-flex flex-column align-center">
|
|
|
+ <v-row>
|
|
|
+ <v-icon icon="fas fa-check" color="success" class="mr-3" />
|
|
|
+ {{ $t('your_helloasso_account_is_linked') }}
|
|
|
+ </v-row>
|
|
|
+ <v-row>
|
|
|
+ <v-btn class="theme-warning mt-4" @click="onUnlinkAccountClick">
|
|
|
+ {{ $t('unlink_your_helloasso_account') }}
|
|
|
+ </v-btn>
|
|
|
+ </v-row>
|
|
|
+ </div>
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+ </v-card>
|
|
|
+
|
|
|
+ <v-snackbar v-model="connectedSnackbar" color="success">
|
|
|
+ {{ $t('your_helloasso_account_was_successfully_connected') }}
|
|
|
+
|
|
|
+ <template v-slot:actions>
|
|
|
+ <v-btn variant="text" @click="connectedSnackbar = false">
|
|
|
+ {{ $t('close') }}
|
|
|
+ </v-btn>
|
|
|
+ </template>
|
|
|
+ </v-snackbar>
|
|
|
+
|
|
|
+ <v-snackbar v-model="unlinkedSnackbar" color="success">
|
|
|
+ {{ $t('your_helloasso_account_was_successfully_unlinked') }}
|
|
|
+
|
|
|
+ <template v-slot:actions>
|
|
|
+ <v-btn variant="text" @click="unlinkedSnackbar = false">
|
|
|
+ {{ $t('close') }}
|
|
|
+ </v-btn>
|
|
|
+ </template>
|
|
|
+ </v-snackbar>
|
|
|
+ </LayoutContainer>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import AuthUrl from '~/models/HelloAsso/AuthUrl'
|
|
|
+import HelloAssoProfile from '~/models/HelloAsso/HelloAssoProfile'
|
|
|
+import { useEntityManager } from '~/composables/data/useEntityManager'
|
|
|
+import { useEntityFetch } from '~/composables/data/useEntityFetch'
|
|
|
+import { FETCHING_STATUS } from '~/types/enum/data'
|
|
|
+import UnlinkRequest from '~/models/HelloAsso/UnlinkRequest'
|
|
|
+
|
|
|
+const { em } = useEntityManager()
|
|
|
+
|
|
|
+const organizationProfile = useOrganizationProfileStore()
|
|
|
+
|
|
|
+const connectedSnackbar: Ref<boolean> = ref(false)
|
|
|
+const unlinkedSnackbar: Ref<boolean> = ref(false)
|
|
|
+
|
|
|
+const onHelloAssoConnectClicked = async () => {
|
|
|
+ // Important de régénérer une URL avec un nouveau challenge à chaque
|
|
|
+ // essai (entre autres pour supporter le HMR pendant les tests en local,
|
|
|
+ // ou en cas d'erreur et de ré-essai)
|
|
|
+ const authUrl = await em.fetch(AuthUrl)
|
|
|
+
|
|
|
+ navigateTo(authUrl.authUrl, {
|
|
|
+ external: true,
|
|
|
+ open: {
|
|
|
+ target: '_blank',
|
|
|
+ windowFeatures: {
|
|
|
+ popup: true,
|
|
|
+ width: 900,
|
|
|
+ height: 600,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ window.addEventListener('message', (event) => {
|
|
|
+ if (event.origin !== window.location.origin) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!event.data || !event.data.code) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ onHelloAssoConnected()
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+const { fetch } = useEntityFetch()
|
|
|
+
|
|
|
+const { status: statusHelloAssoProfile, refresh: refreshHelloAssoProfile } =
|
|
|
+ await fetch(HelloAssoProfile)
|
|
|
+
|
|
|
+const helloAssoProfile: ComputedRef<HelloAssoProfile | null> = computed(() => {
|
|
|
+ if (statusHelloAssoProfile.value !== FETCHING_STATUS.SUCCESS) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return em.find(HelloAssoProfile, 1)
|
|
|
+})
|
|
|
+
|
|
|
+const onHelloAssoConnected = async () => {
|
|
|
+ // On attend 200ms pour laisser en attente du message SSE
|
|
|
+ await new Promise((r) => setTimeout(r, 200))
|
|
|
+
|
|
|
+ if (!helloAssoProfile.value || !helloAssoProfile.value.token) {
|
|
|
+ // Fallback en cas de défaut de fonctionnement du SSE
|
|
|
+ console.log('Helloasso connected (fallback SSE)')
|
|
|
+ await refreshHelloAssoProfile()
|
|
|
+ }
|
|
|
+
|
|
|
+ connectedSnackbar.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const unlinkingPending: Ref<boolean> = ref(false)
|
|
|
+
|
|
|
+const onUnlinkAccountClick = async () => {
|
|
|
+ const unlinkRequest = em.newInstance(UnlinkRequest, {
|
|
|
+ organizationId: organizationProfile.id,
|
|
|
+ })
|
|
|
+
|
|
|
+ unlinkingPending.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ await em.persist(unlinkRequest)
|
|
|
+ await refreshHelloAssoProfile()
|
|
|
+
|
|
|
+ unlinkedSnackbar.value = true
|
|
|
+ } finally {
|
|
|
+ unlinkingPending.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.v-card {
|
|
|
+ padding: 48px;
|
|
|
+ max-width: 70%;
|
|
|
+ margin: 36px auto;
|
|
|
+
|
|
|
+ @media (max-width: 600px) {
|
|
|
+ max-width: 90%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.logo {
|
|
|
+ max-width: 80%;
|
|
|
+}
|
|
|
+
|
|
|
+.presentation {
|
|
|
+ border-left: 3px solid rgb(var(--v-theme-info));
|
|
|
+ padding: 0 24px;
|
|
|
+ color: rgb(var(--v-theme-on-neutral));
|
|
|
+}
|
|
|
+
|
|
|
+.authDialog {
|
|
|
+ max-width: 90%;
|
|
|
+}
|
|
|
+</style>
|