index.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <!--
  2. Administration de la connexion Opentalent / HelloAsso
  3. -->
  4. <template>
  5. <LayoutContainer>
  6. <v-card>
  7. <v-row>
  8. <v-col cols="12" md="4" class="d-flex justify-center">
  9. <v-img src="/images/logos/Logo-HelloAsso.svg" class="logo" />
  10. </v-col>
  11. <v-col cols="12" md="8" class="presentation">
  12. {{ $t('helloasso_presentation') }}
  13. </v-col>
  14. </v-row>
  15. <v-row>
  16. <v-col cols="12" class="d-flex justify-center align-center w-100 mt-6">
  17. <v-progress-circular
  18. v-if="
  19. statusHelloAssoProfile === FETCHING_STATUS.PENDING ||
  20. unlinkingPending
  21. "
  22. indeterminate
  23. size="32"
  24. />
  25. <UiButtonHelloAssoConnect
  26. v-else-if="!helloAssoProfile || !helloAssoProfile.token"
  27. @click="onHelloAssoConnectClicked"
  28. />
  29. <div v-else class="d-flex flex-column align-center">
  30. <v-row>
  31. <v-icon icon="fas fa-check" color="success" class="mr-3" />
  32. {{ $t('your_helloasso_account_is_linked') }}
  33. </v-row>
  34. <v-row>
  35. <v-btn class="theme-warning mt-4" @click="onUnlinkAccountClick">
  36. {{ $t('unlink_your_helloasso_account') }}
  37. </v-btn>
  38. </v-row>
  39. </div>
  40. </v-col>
  41. </v-row>
  42. </v-card>
  43. <v-snackbar v-model="connectedSnackbar" color="success">
  44. {{ $t('your_helloasso_account_was_successfully_connected') }}
  45. <template v-slot:actions>
  46. <v-btn variant="text" @click="connectedSnackbar = false">
  47. {{ $t('close') }}
  48. </v-btn>
  49. </template>
  50. </v-snackbar>
  51. <v-snackbar v-model="unlinkedSnackbar" color="success">
  52. {{ $t('your_helloasso_account_was_successfully_unlinked') }}
  53. <template v-slot:actions>
  54. <v-btn variant="text" @click="unlinkedSnackbar = false">
  55. {{ $t('close') }}
  56. </v-btn>
  57. </template>
  58. </v-snackbar>
  59. </LayoutContainer>
  60. </template>
  61. <script setup lang="ts">
  62. import AuthUrl from '~/models/HelloAsso/AuthUrl'
  63. import HelloAssoProfile from '~/models/HelloAsso/HelloAssoProfile'
  64. import { useEntityManager } from '~/composables/data/useEntityManager'
  65. import { useEntityFetch } from '~/composables/data/useEntityFetch'
  66. import { FETCHING_STATUS } from '~/types/enum/data'
  67. import UnlinkRequest from '~/models/HelloAsso/UnlinkRequest'
  68. const { em } = useEntityManager()
  69. const organizationProfile = useOrganizationProfileStore()
  70. const connectedSnackbar: Ref<boolean> = ref(false)
  71. const unlinkedSnackbar: Ref<boolean> = ref(false)
  72. const onHelloAssoConnectClicked = async () => {
  73. // Important de régénérer une URL avec un nouveau challenge à chaque
  74. // essai (entre autres pour supporter le HMR pendant les tests en local,
  75. // ou en cas d'erreur et de ré-essai)
  76. const authUrl = await em.fetch(AuthUrl)
  77. navigateTo(authUrl.authUrl, {
  78. external: true,
  79. open: {
  80. target: '_blank',
  81. windowFeatures: {
  82. popup: true,
  83. width: 900,
  84. height: 600,
  85. },
  86. },
  87. })
  88. }
  89. onMounted(() => {
  90. window.addEventListener('message', (event) => {
  91. if (event.origin !== window.location.origin) {
  92. return
  93. }
  94. if (!event.data || !event.data.code) {
  95. return
  96. }
  97. onHelloAssoConnected()
  98. })
  99. })
  100. const { fetch } = useEntityFetch()
  101. const { status: statusHelloAssoProfile, refresh: refreshHelloAssoProfile } =
  102. await fetch(HelloAssoProfile)
  103. const helloAssoProfile: ComputedRef<HelloAssoProfile | null> = computed(() => {
  104. if (statusHelloAssoProfile.value !== FETCHING_STATUS.SUCCESS) {
  105. return null
  106. }
  107. return em.find(HelloAssoProfile, 1)
  108. })
  109. const onHelloAssoConnected = async () => {
  110. // On attend 200ms pour laisser en attente du message SSE
  111. await new Promise((r) => setTimeout(r, 200))
  112. if (!helloAssoProfile.value || !helloAssoProfile.value.token) {
  113. // Fallback en cas de défaut de fonctionnement du SSE
  114. console.log('Helloasso connected (fallback SSE)')
  115. await refreshHelloAssoProfile()
  116. }
  117. connectedSnackbar.value = true
  118. }
  119. const unlinkingPending: Ref<boolean> = ref(false)
  120. const onUnlinkAccountClick = async () => {
  121. const unlinkRequest = em.newInstance(UnlinkRequest, {
  122. organizationId: organizationProfile.id,
  123. })
  124. unlinkingPending.value = true
  125. try {
  126. await em.persist(unlinkRequest)
  127. await refreshHelloAssoProfile()
  128. unlinkedSnackbar.value = true
  129. } finally {
  130. unlinkingPending.value = false
  131. }
  132. }
  133. </script>
  134. <style scoped lang="scss">
  135. .v-card {
  136. padding: 48px;
  137. max-width: 70%;
  138. margin: 36px auto;
  139. @media (max-width: 600px) {
  140. max-width: 90%;
  141. }
  142. }
  143. .logo {
  144. max-width: 80%;
  145. }
  146. .presentation {
  147. border-left: 3px solid rgb(var(--v-theme-info));
  148. padding: 0 24px;
  149. color: rgb(var(--v-theme-on-neutral));
  150. }
  151. .authDialog {
  152. max-width: 90%;
  153. }
  154. </style>