Olivier Massot 2 месяцев назад
Родитель
Сommit
ebbbc9484c

+ 2 - 1
i18n/lang/fr/general.json

@@ -827,5 +827,6 @@
   "refresh_needed_message": "La page a besoin d'être actualisée pour afficher les dernières modifications.",
   "refresh_page": "Actualiser la page",
   "helloasso_presentation": "HelloAsso aide les associations à collecter des paiements en ligne et propose ses services gratuitement. Elle prend à sa charge tous les frais de transaction pour que vous puissiez bénéficier de la totalité des sommes versées par vos publics, sans frais. Les contributions volontaires laissées par ces derniers sont leur unique source de revenus.",
-  "connect_to_helloasso": "Connecter à HelloAsso"
+  "connect_to_helloasso": "Connecter à HelloAsso",
+  "your_helloasso_account_is_linked": "Votre compte HelloAsso a bien été lié."
 }

+ 2 - 2
models/Custom/HelloAsso/AuthUrl.ts → models/ApiResources/HelloAsso/AuthUrl.ts

@@ -1,12 +1,12 @@
 import { Uid, Str } from 'pinia-orm/dist/decorators'
-import ApiModel from '~/models/ApiModel'
+import ApiResource from '~/models/ApiResource'
 
 /**
  * AP2i Model : AuthUrl
  *
  * @see https://gitlab.2iopenservice.com/opentalent/ap2i/-/blob/develop/src/ApiResource/HelloAsso/AuthUrl.php
  */
-export default class AuthUrl extends ApiModel {
+export default class AuthUrl extends ApiResource {
   static override entity = 'helloasso/auth-url'
 
   @Uid()

+ 2 - 2
models/Custom/HelloAsso/ConnectionRequest.ts → models/ApiResources/HelloAsso/ConnectionRequest.ts

@@ -1,14 +1,14 @@
 import { Uid, Str } from 'pinia-orm/dist/decorators'
-import ApiModel from '~/models/ApiModel'
 import { IdField } from '~/models/decorators'
 import { Num } from 'pinia-orm/decorators'
+import ApiResource from '~/models/ApiResource'
 
 /**
  * AP2i Model : ConnectionRequest
  *
  * @see https://gitlab.2iopenservice.com/opentalent/ap2i/-/blob/develop/src/ApiResource/HelloAsso/ConnectionRequest.php
  */
-export default class ConnectionRequest extends ApiModel {
+export default class ConnectionRequest extends ApiResource {
   static override entity = 'helloasso/connect'
 
   @Uid()

+ 20 - 0
models/ApiResources/HelloAsso/HelloAssoProfile.ts

@@ -0,0 +1,20 @@
+import { Uid, Str } from 'pinia-orm/dist/decorators'
+import ApiResource from '~/models/ApiResource'
+
+/**
+ * AP2i Resource : HelloAssoProfile
+ *
+ * @see https://gitlab.2iopenservice.com/opentalent/ap2i/-/blob/develop/src/ApiResource/HelloAsso/HelloAssoProfile.php
+ */
+export default class HelloAssoProfile extends ApiResource {
+  static override entity = 'helloasso/profile'
+
+  @Uid()
+  declare id: number | string
+
+  @Str('')
+  declare token: string
+
+  @Str('')
+  declare organizationSlug: string
+}

+ 7 - 4
models/Organization/HelloAsso.ts

@@ -8,7 +8,7 @@ import { Str } from 'pinia-orm/decorators'
  *
  * @see https://gitlab.2iopenservice.com/opentalent/ap2i/-/blob/develop/src/ApiResources/Mobyt/MobytUserStatus.php
  */
-export default class MobytUserStatus extends ApiResource {
+export default class HelloAsso extends ApiResource {
   static override entity = 'helloasso'
 
   @Uid()
@@ -19,11 +19,14 @@ export default class MobytUserStatus extends ApiResource {
   declare organizationId: number
 
   @Str(null)
-  declare token: boolean
+  declare challengeVerifier: string
 
   @Str(null)
-  declare refreshToken: number
+  declare token: string
 
   @Str(null)
-  declare organizationSlug: number
+  declare refreshToken: string
+
+  @Str(null)
+  declare organizationSlug: string
 }

+ 0 - 95
pages/helloasso.vue

@@ -1,95 +0,0 @@
-<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">
-          <UiButtonHelloAssoConnect
-            :disabled="status !== 'success'"
-            @click="onHelloAssoConnectClicked"
-          />
-        </v-col>
-      </v-row>
-    </v-card>
-
-    <!--    <v-dialog-->
-    <!--      v-model="showDialog"-->
-    <!--      :width="900"-->
-    <!--      class="authDialog"-->
-    <!--    >-->
-    <!--      <v-card>-->
-    <!--        <iframe-->
-    <!--          :src="authUrl!.authUrl"-->
-    <!--          height="600"-->
-    <!--          frameborder="0"-->
-    <!--        />-->
-    <!--      </v-card>-->
-    <!--    </v-dialog>-->
-  </LayoutContainer>
-</template>
-
-<script setup lang="ts">
-import { useEntityFetch } from '~/composables/data/useEntityFetch'
-import AuthUrl from '~/models/Custom/HelloAsso/AuthUrl'
-
-const route: RouteLocationNormalizedLoaded = useRoute()
-
-const { fetch } = useEntityFetch()
-
-const { data: authUrl, status } = fetch(AuthUrl)
-
-const showDialog: Ref<boolean> = ref(true)
-
-const onHelloAssoConnectClicked = () => {
-  if (status.value !== 'success') {
-    console.log('still pending')
-  } else {
-    showDialog.value = true
-    navigateTo(authUrl.value!.authUrl, { external: true })
-  }
-}
-
-const authorizationCode: Ref<string | null> = ref(null)
-
-if (route.query.code) {
-  authorizationCode.value = route.query.code as string
-}
-
-
-
-</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>

+ 63 - 0
pages/helloasso/callback.vue

@@ -0,0 +1,63 @@
+<template>
+  <NuxtLayout name="blank">
+    <v-app>
+      <div class="d-flex flex-column align-center justify-center fill-height">
+        <v-progress-circular indeterminate size="64" />
+        <span class="mt-3">{{ $t('please_wait') }}</span>
+      </div>
+    </v-app>
+  </NuxtLayout>
+</template>
+
+<script setup lang="ts">
+/**
+ * Disable the default layout, the page will use the layout defined with <NuxtLayout />
+ * @see https://nuxt.com/docs/guide/directory-structure/layouts#overriding-a-layout-on-a-per-page-basis
+ */
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import ConnectionRequest from '~/models/ApiResources/HelloAsso/ConnectionRequest'
+import { useOrganizationProfileStore } from '~/stores/organizationProfile'
+
+definePageMeta({
+  name: 'helloasso_callback_page',
+  layout: false,
+})
+
+const organizationProfile = useOrganizationProfileStore()
+
+const { em } = useEntityManager()
+
+const route: RouteLocationNormalizedLoaded = useRoute()
+
+if (!route.query.code) {
+  throw new Error('Missing parameter')
+}
+
+const authorizationCode: Ref<string> = ref(route.query.code as string)
+
+const connectionRequest: ConnectionRequest = em.newInstance(
+  ConnectionRequest,
+  {
+    organizationId: organizationProfile.id,
+    authorizationCode: authorizationCode.value,
+  },
+)
+
+onMounted(async () => {
+  await em.persist(connectionRequest)
+  console.log('Connection request created')
+
+  // Send a event to the parent window to notify the connection request has been created (in case SSE is not available)
+  window.opener?.postMessage(
+    { code: authorizationCode.value },
+    window.location.origin,
+  )
+
+  // Close the popup
+  window.close()
+})
+
+</script>
+
+<style scoped lang="scss"></style>

+ 127 - 0
pages/helloasso/index.vue

@@ -0,0 +1,127 @@
+<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">
+          <UiButtonHelloAssoConnect
+            v-if="!alreadyConnected"
+            @click="onHelloAssoConnectClicked"
+          />
+
+          <div v-else>
+            <v-icon icon="fas fa-check" color="success" />
+            {{ $t('your_helloasso_account_is_linked') }}
+          </div>
+        </v-col>
+      </v-row>
+    </v-card>
+
+    <!--    <v-dialog-->
+    <!--      v-model="showDialog"-->
+    <!--      :width="900"-->
+    <!--      class="authDialog"-->
+    <!--    >-->
+    <!--      <v-card>-->
+    <!--        <iframe-->
+    <!--          :src="authUrl!.authUrl"-->
+    <!--          height="600"-->
+    <!--          frameborder="0"-->
+    <!--        />-->
+    <!--      </v-card>-->
+    <!--    </v-dialog>-->
+  </LayoutContainer>
+</template>
+
+<script setup lang="ts">
+import AuthUrl from '~/models/ApiResources/HelloAsso/AuthUrl'
+import { useHelloAssoStore } from '~/stores/helloasso'
+import { useEntityManager } from '~/composables/data/useEntityManager'
+import { FETCHING_STATUS } from '~/types/enum/data'
+
+const helloassoStore = useHelloAssoStore()
+
+const { em } = useEntityManager()
+
+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,
+      },
+    },
+  })
+}
+
+const alreadyConnected = computed(() => helloassoStore.authorizationCode)
+
+onMounted(() => {
+  window.addEventListener('message', (event) => {
+    if (event.origin !== window.location.origin) {
+      return
+    }
+    if (!event.data || !event.data.code) {
+      return
+    }
+    onHelloAssoConnected()
+  })
+})
+
+const helloAssoToken: Ref<string | null> = ref(null)
+
+const onHelloAssoConnected = async () => {
+  // const { data: helloAsso, status: helloAssoStatus } = fetch(HelloAsso)
+
+  console.log('helloasso connected')
+
+  // const helloAsso = await em.fetch(HelloAsso)
+  //
+  // helloAssoToken.value = helloAsso.token
+}
+
+
+</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>

+ 20 - 0
stores/helloasso.ts

@@ -0,0 +1,20 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import type { Ref } from 'vue'
+import type { MenuGroup, MenuItem } from '~/types/layout'
+
+export const useHelloAssoStore = defineStore('helloasso', () => {
+  /**
+   * Le code d'autorisation Helloasso obtenu via la mire d'autorisation
+   */
+  const authorizationCode: Ref<string | null> = ref(null)
+
+  const setAuthorizationCode = (code: string | null) => {
+    authorizationCode.value = code
+  }
+
+  return {
+    authorizationCode,
+    setAuthorizationCode
+  }
+})