subscription.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. <!--
  2. Page 'Mon abonnement'
  3. @see https://ressources.opentalent.fr/display/SPEC/Mon+abonnement
  4. -->
  5. <template>
  6. <LayoutContainer>
  7. <v-col cols="12" sm="12" md="12">
  8. <v-expansion-panels v-model="openedPanels" :multiple="true">
  9. <UiExpansionPanel title="informations" icon="fas fa-info">
  10. <v-container fluid class="container">
  11. <v-row>
  12. <v-table>
  13. <tbody>
  14. <tr>
  15. <td v-if="smAndUp">{{ $t('client_id') }}</td>
  16. <td class="py-2">
  17. <h5
  18. v-if="!smAndUp"
  19. class="text-decoration-underline py-2"
  20. >
  21. {{ $t('client_id') }}
  22. </h5>
  23. <span>{{
  24. dolibarrAccount ? dolibarrAccount.clientNumber : '-'
  25. }}</span>
  26. </td>
  27. </tr>
  28. <tr>
  29. <td v-if="smAndUp">{{ $t('version') }}</td>
  30. <td class="py-2">
  31. <h5
  32. v-if="!smAndUp"
  33. class="text-decoration-underline py-2"
  34. >
  35. {{ $t('version') }}
  36. </h5>
  37. <span>{{
  38. dolibarrAccount ? $t(dolibarrAccount.product) : '-'
  39. }}</span>
  40. </td>
  41. </tr>
  42. <tr v-if="dolibarrAccount && dolibarrAccount.contract">
  43. <td v-if="smAndUp">{{ $t('services') }}</td>
  44. <td class="py-2">
  45. <h5
  46. v-if="!smAndUp"
  47. class="text-decoration-underline py-2"
  48. >
  49. {{ $t('services') }}
  50. </h5>
  51. <div
  52. v-for="line in dolibarrAccount.contract.lines"
  53. :key="line.id"
  54. >
  55. {{ line.serviceLabel }} - {{ $t('until') }} :
  56. {{ $d(line.dateEnd) }}
  57. </div>
  58. </td>
  59. </tr>
  60. <tr v-if="ability.can('manage', 'texto')">
  61. <td v-if="smAndUp">{{ $t('remaining_sms_credit') }}</td>
  62. <td class="py-2">
  63. <h5
  64. v-if="!smAndUp"
  65. class="text-decoration-underline py-2"
  66. >
  67. {{ $t('remaining_sms_credit') }}
  68. </h5>
  69. <span
  70. v-if="
  71. !mobytPending &&
  72. mobytStatus !== null &&
  73. mobytStatus.active
  74. "
  75. >
  76. {{
  77. mobytStatus.money.toLocaleString($i18n.locale, {
  78. style: 'currency',
  79. currency: 'EUR',
  80. })
  81. }}
  82. {{
  83. i18n.t('convert_price_to_sms', {
  84. nb_sms: mobytStatus.amount,
  85. })
  86. }}
  87. </span>
  88. </td>
  89. </tr>
  90. </tbody>
  91. </v-table>
  92. </v-row>
  93. </v-container>
  94. </UiExpansionPanel>
  95. <UiExpansionPanel
  96. v-if="showDolibarrPanel"
  97. title="bills"
  98. icon="fas fa-file"
  99. >
  100. <v-container :fluid="true" class="container">
  101. <v-row>
  102. <v-table v-if="dolibarrAccount !== null">
  103. <thead>
  104. <tr>
  105. <th>{{ $t('reference') }}</th>
  106. <th>{{ $t('date') }}</th>
  107. <th>{{ $t('taxExcludedAmount') }}</th>
  108. <th>{{ $t('status') }}</th>
  109. <th></th>
  110. </tr>
  111. </thead>
  112. <tbody>
  113. <tr v-for="bill in dolibarrAccount.bills" :key="bill.id">
  114. <td>{{ bill.ref }}</td>
  115. <td>{{ $d(bill.date) }}</td>
  116. <td>
  117. {{
  118. bill.taxExcludedAmount.toLocaleString($i18n.locale, {
  119. style: 'currency',
  120. currency: 'EUR',
  121. })
  122. }}
  123. </td>
  124. <td>
  125. {{ bill.paid === true ? $t('paid') : $t('unpaid') }}
  126. </td>
  127. <td>
  128. <a @click="downloadDolibarrBill(bill.ref)" class="clickable">
  129. {{ $t('download') }}
  130. </a>
  131. </td>
  132. </tr>
  133. </tbody>
  134. </v-table>
  135. </v-row>
  136. </v-container>
  137. </UiExpansionPanel>
  138. <UiExpansionPanel title="more_features" icon="fas fa-plus">
  139. <v-container id="products-section" :fluid="true" class="container">
  140. <v-row>
  141. <!-- Opentalent Artist Premium -->
  142. <v-col
  143. v-if="organizationProfile.isArtistProduct"
  144. :cols="colWidth"
  145. >
  146. <v-row>
  147. {{ $t('PRODUCT_ARTIST_PREMIUM') }}
  148. </v-row>
  149. <v-row class="align-end">
  150. <nuxt-img src="/images/Opentalent_Artist.png" />
  151. </v-row>
  152. <v-row>
  153. <p>
  154. {{ $t('get_more_functionalities_with_version') }}
  155. <b>{{ $t('PRODUCT_ARTIST_PREMIUM') }}</b>
  156. </p>
  157. <!-- Cmf member -->
  158. <div v-if="organizationProfile.isCmf">
  159. <p>
  160. <b
  161. >{{
  162. i18n.t('for_only_x_eur_ttc_by_month', {
  163. price: formatCurrency(7.5, 'EUR'),
  164. })
  165. }}&nbsp;*</b
  166. >
  167. </p>
  168. <div>
  169. <i
  170. >*&nbsp;{{
  171. i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
  172. price: formatCurrency(90.0, 'EUR'),
  173. })
  174. }}</i
  175. >
  176. </div>
  177. <div>
  178. <i
  179. >{{ $t('only_for_cmf_members') }} ({{
  180. i18n.t('public_price_x_ttc_a_year', {
  181. price: formatCurrency(216.0, 'EUR'),
  182. })
  183. }})</i
  184. >
  185. </div>
  186. </div>
  187. <!-- Not a cmf member -->
  188. <div v-else>
  189. <p>
  190. <b
  191. >{{
  192. i18n.t('for_only_x_eur_ttc_by_month', {
  193. price: formatCurrency(18.0, 'EUR'),
  194. })
  195. }}&nbsp;*</b
  196. >
  197. </p>
  198. <p>
  199. <i>
  200. *&nbsp;{{
  201. i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
  202. price: formatCurrency(216.0, 'EUR'),
  203. })
  204. }}
  205. </i>
  206. </p>
  207. </div>
  208. <p class="mt-3">
  209. <a
  210. :href="
  211. runtimeConfig.public.fileStorageBaseUrl +
  212. '/Fiche_produit/Fiche_produit_Opentalent_Artist.pdf'
  213. "
  214. target="_blank"
  215. >
  216. {{ $t('product_sheet') }}
  217. {{ $t('PRODUCT_ARTIST_PREMIUM') }}
  218. </a>
  219. </p>
  220. <p v-if="organizationProfile.isCmf" class="mt-3">
  221. <a
  222. :href="
  223. runtimeConfig.public.fileStorageBaseUrl +
  224. '/Bon_de_commande/Opentalent_Artist_CMF.pdf'
  225. "
  226. target="_blank"
  227. >
  228. <b>{{ $t('download_cmf_order_form') }}</b>
  229. </a>
  230. </p>
  231. <p v-else class="mt-3">
  232. <a
  233. :href="
  234. runtimeConfig.public.fileStorageBaseUrl +
  235. '/Bon_de_commande/Opentalent_Artist_Public.pdf'
  236. "
  237. target="_blank"
  238. >
  239. <b>{{ $t('download_order_form') }}</b>
  240. </a>
  241. </p>
  242. </v-row>
  243. </v-col>
  244. <!-- Opentalent School Premium -->
  245. <v-col v-if="organizationProfile.isArtist" :cols="colWidth">
  246. <v-row>
  247. {{ $t('PRODUCT_SCHOOL') }}
  248. </v-row>
  249. <v-row class="align-end">
  250. <nuxt-img src="/images/Opentalent_School.png" />
  251. </v-row>
  252. <v-row>
  253. <p>
  254. {{ $t('switch_to_version') }}
  255. <b>{{ $t('PRODUCT_SCHOOL_PREMIUM') }}</b>
  256. {{ $t('and_benefit') }} :
  257. </p>
  258. <ul class="mb-2">
  259. <li>{{ $t('of_accounts_for_teachers_and_students') }}</li>
  260. <li>{{ $t('of_a_complete_website') }}</li>
  261. </ul>
  262. <!-- Cmf member -->
  263. <div v-if="organizationProfile.isCmf">
  264. <p>
  265. <b
  266. >{{
  267. i18n.t('starting_from_x_eur_ttc_per_month', {
  268. price: formatCurrency(30.5, 'EUR'),
  269. })
  270. }}&nbsp;*</b
  271. >
  272. </p>
  273. <div>
  274. <i
  275. >*
  276. {{
  277. i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
  278. price: formatCurrency(366.0, 'EUR'),
  279. })
  280. }}</i
  281. >
  282. </div>
  283. <div>
  284. <i>{{
  285. i18n.t('version_x_up_to_x_students', {
  286. product: $t('PRODUCT_SCHOOL_PREMIUM'),
  287. max_students: '69',
  288. })
  289. }}</i>
  290. </div>
  291. <div>
  292. <i>{{ $t('excluding_license_and_training_fees') }}.</i>
  293. </div>
  294. <div>
  295. <i
  296. >{{ $t('only_for_cmf_members') }} ({{
  297. i18n.t('public_price_x_ttc_a_year', {
  298. price: formatCurrency(607.2, 'EUR'),
  299. })
  300. }})</i
  301. >
  302. </div>
  303. </div>
  304. <!-- Not cmf member -->
  305. <div v-else>
  306. <p>
  307. {{
  308. i18n.t('starting_from_x_eur_ttc_per_month', {
  309. price: formatCurrency(50.6, 'EUR'),
  310. })
  311. }}&nbsp;*
  312. </p>
  313. <div>
  314. <i
  315. >*&nbsp;{{
  316. i18n.t('yearly_paid_giving_x_eur_ttc_per_year', {
  317. price: formatCurrency(607.2, 'EUR'),
  318. })
  319. }}</i
  320. >
  321. </div>
  322. <div>
  323. <i>{{
  324. i18n.t('version_x_up_to_x_students', {
  325. product: $t('PRODUCT_SCHOOL_PREMIUM'),
  326. max_students: '69',
  327. })
  328. }}</i>
  329. </div>
  330. <div>
  331. <i>{{ $t('excluding_license_and_training_fees') }}.</i>
  332. </div>
  333. </div>
  334. <p class="mt-4">
  335. <a
  336. :href="
  337. runtimeConfig.public.fileStorageBaseUrl +
  338. '/Fiche_produit/Fiche_produit_Opentalent_School.pdf'
  339. "
  340. target="_blank"
  341. >
  342. {{ $t('product_sheet') }} {{ $t('PRODUCT_SCHOOL') }}
  343. </a>
  344. </p>
  345. <p>
  346. {{ $t('contact_us_at') }}
  347. <a href="tel:+33972126017">0 972 126 017</a>,
  348. {{ $t('or_by_mail_at') }}
  349. <a href="mailto:contact@opentalent.fr"
  350. >contact@opentalent.fr</a
  351. >
  352. </p>
  353. </v-row>
  354. </v-col>
  355. <!-- SMS -->
  356. <v-col :cols="colWidth">
  357. <v-row>
  358. {{ $t('sms') }}
  359. </v-row>
  360. <v-row class="align-end">
  361. <nuxt-img src="/images/sms.png" :height="140" :width="175" />
  362. </v-row>
  363. <v-row>
  364. <p>
  365. <b
  366. >{{ $t('send_sms') }}
  367. {{ $t('to_your_members_from_app') }}</b
  368. >
  369. </p>
  370. <!-- Cmf member -->
  371. <div v-if="organizationProfile.isCmf">
  372. <p>
  373. <b
  374. >{{
  375. i18n.t('starting_from_x_eur_ttc_per_sms', {
  376. price: formatCurrency(0.11, 'EUR'),
  377. })
  378. }}&nbsp;*</b
  379. >
  380. </p>
  381. <p>
  382. <i
  383. >*&nbsp;{{ i18n.t('for_x_sms', { amount: '5000' }) }}</i
  384. >
  385. </p>
  386. <p>
  387. <b>
  388. <a
  389. :href="
  390. runtimeConfig.public.fileStorageBaseUrl +
  391. '/Bon_de_commande/Achat_SMS_CMF.pdf'
  392. "
  393. target="_blank"
  394. >
  395. {{ i18n.t('download_cmf_order_form') }}
  396. </a>
  397. </b>
  398. </p>
  399. </div>
  400. <!-- Not cmf member -->
  401. <div v-else>
  402. <p>
  403. <b
  404. >{{
  405. i18n.t('starting_from_x_eur_ttc_per_sms', {
  406. price: formatCurrency(0.13, 'EUR'),
  407. })
  408. }}&nbsp;*</b
  409. >
  410. </p>
  411. <p>
  412. <i
  413. >*&nbsp;{{ i18n.t('for_x_sms', { amount: '5000' }) }}</i
  414. >
  415. </p>
  416. <p>
  417. <a
  418. :href="
  419. runtimeConfig.public.fileStorageBaseUrl +
  420. '/Bon_de_commande/Achat_SMS_Public.pdf'
  421. "
  422. target="_blank"
  423. >
  424. <b>{{ $t('download_order_form') }}</b>
  425. </a>
  426. </p>
  427. </div>
  428. </v-row>
  429. </v-col>
  430. <!-- Custom domain -->
  431. <v-col :cols="colWidth">
  432. <v-row>
  433. {{ $t('website') }}
  434. </v-row>
  435. <v-row class="align-end">
  436. <nuxt-img src="/images/nom_de_domaine.png" :height="160" />
  437. </v-row>
  438. <v-row>
  439. <v-col>
  440. <p>
  441. <b>{{
  442. i18n.t(
  443. 'get_your_own_domain_and_up_to_five_emails_for_only_x_eur_ttc_per_year',
  444. { price: formatCurrency(46.8, 'EUR') },
  445. )
  446. }}</b>
  447. </p>
  448. <p>{{ $t('example') }} :</p>
  449. <table>
  450. <tbody>
  451. <tr>
  452. <td class="pa-2" style="width: 190px">
  453. {{ $t('domain_name') }}&nbsp;:
  454. </td>
  455. <td>
  456. <i>{{ $t('dummy_domain_name') }}</i>
  457. </td>
  458. </tr>
  459. <tr>
  460. <td class="pa-2">
  461. {{ $t('associated_mail_address') }}&nbsp;:
  462. </td>
  463. <td>
  464. <i>{{ $t('dummy_email_address') }}</i>
  465. </td>
  466. </tr>
  467. </tbody>
  468. </table>
  469. <p>
  470. <a
  471. :href="
  472. runtimeConfig.public.fileStorageBaseUrl +
  473. '/Bon_de_commande/Nom_de_domaine.pdf'
  474. "
  475. target="_blank"
  476. >
  477. <b>{{ $t('download_order_form') }}</b>
  478. </a>
  479. </p>
  480. </v-col>
  481. </v-row>
  482. </v-col>
  483. </v-row>
  484. </v-container>
  485. </UiExpansionPanel>
  486. </v-expansion-panels>
  487. </v-col>
  488. </LayoutContainer>
  489. </template>
  490. <script setup lang="ts">
  491. import { useAbility } from '@casl/vue'
  492. import type { Ref } from 'vue'
  493. import { useDisplay } from 'vuetify'
  494. import type { AsyncData } from '#app'
  495. import { useOrganizationProfileStore } from '~/stores/organizationProfile'
  496. import { useEntityFetch } from '~/composables/data/useEntityFetch'
  497. import DolibarrAccount from '~/models/Organization/DolibarrAccount'
  498. import MobytUserStatus from '~/models/Organization/MobytUserStatus'
  499. import UrlUtils from '~/services/utils/urlUtils';
  500. import {useDownloadFromRoute} from '~/composables/utils/useDownloadFromRoute';
  501. const ability = useAbility()
  502. const runtimeConfig = useRuntimeConfig()
  503. definePageMeta({
  504. name: 'subscription_page',
  505. })
  506. const showDolibarrPanel = computed(
  507. () =>
  508. !dolibarrPending.value &&
  509. dolibarrAccount.value &&
  510. dolibarrAccount.value.bills.length > 0,
  511. )
  512. const { smAndUp } = useDisplay()
  513. // On déplie les expansion panels dans le onMounted en attendant la résolution du bug : https://github.com/vuetifyjs/vuetify/issues/16427#issuecomment-1380927133
  514. // TODO: quand le bug ci dessus est résolu, remplacer par `const openedPanels: Ref<Array<string>> = ref(['informations', 'bills', 'more_features'])`
  515. const openedPanels: Ref<Array<string>> = ref([])
  516. onMounted(() => {
  517. openedPanels.value = ['informations', 'bills', 'more_features']
  518. })
  519. const i18n = useI18n()
  520. const organizationProfile = useOrganizationProfileStore()
  521. if (organizationProfile.id === null) {
  522. throw new Error("Missing organization's id")
  523. }
  524. const { fetch } = useEntityFetch()
  525. const { data: dolibarrAccount, pending: dolibarrPending } = fetch(
  526. DolibarrAccount,
  527. organizationProfile.id,
  528. )
  529. let mobytStatus: Ref<MobytUserStatus | null>
  530. let mobytPending: Ref<boolean>
  531. if (ability.can('manage', 'texto')) {
  532. const fetchMobytStatus = () => {
  533. const { data, pending } = fetch(
  534. MobytUserStatus,
  535. organizationProfile!.id!,
  536. ) as AsyncData<MobytUserStatus | null, Error | null>
  537. mobytStatus = data
  538. mobytPending = pending
  539. }
  540. fetchMobytStatus()
  541. } else {
  542. mobytStatus = ref(null)
  543. mobytPending = ref(false)
  544. }
  545. const formatCurrency = (value: number, currency: string): string => {
  546. return value.toLocaleString(i18n.locale.value, {
  547. style: 'currency',
  548. currency,
  549. })
  550. }
  551. const downloadDolibarrBill = (ref: string): void => {
  552. const route = UrlUtils.join('api/dolibarr/download/invoice', ref)
  553. useDownloadFromRoute(route, `${ref}.pdf`)
  554. }
  555. // Compute the number of columns of the more_features pannel
  556. const nbCols =
  557. 2 +
  558. (organizationProfile.isArtist ? 1 : 0) +
  559. (organizationProfile.isArtistProduct ? 1 : 0)
  560. const colWidth = 12 / nbCols
  561. </script>
  562. <style scoped lang="scss">
  563. .clickable {
  564. cursor: pointer;
  565. text-decoration: underline;
  566. }
  567. #products-section {
  568. width: 100%;
  569. .v-col {
  570. min-width: 260px;
  571. border: solid 1px rgb(var(--v-theme-on-primary));
  572. .v-row:nth-child(1) {
  573. //background: rgb(var(--v-theme-neutral-soft));
  574. height: 64px;
  575. color: rgb(var(--v-theme-on-neutral-soft));
  576. //font-size: 15px;
  577. font-weight: bold;
  578. border-bottom: solid 1px rgb(var(--v-theme-neutral));
  579. }
  580. .v-row:nth-child(2) {
  581. height: 230px;
  582. display: flex;
  583. justify-content: center;
  584. border-bottom: solid 1px rgb(var(--v-theme-neutral));
  585. }
  586. .v-row:nth-child(3) {
  587. }
  588. }
  589. .v-col:not(:first-child) {
  590. border-left: none;
  591. }
  592. img {
  593. max-height: 90%;
  594. max-width: 90%;
  595. }
  596. .v-row {
  597. padding: 12px 18px;
  598. vertical-align: top;
  599. border-bottom: solid 1px rgb(var(--v-theme-on-primary));
  600. }
  601. .v-row:last-child {
  602. border: none;
  603. }
  604. p {
  605. margin-bottom: 12px;
  606. }
  607. ul {
  608. padding-left: 24px;
  609. }
  610. }
  611. </style>