|
|
@@ -0,0 +1,907 @@
|
|
|
+<template>
|
|
|
+ <div class="theme-artist">
|
|
|
+ <CommonMeta
|
|
|
+ title="Essai gratuit Opentalent Artist Premium - 30 jours sans engagement"
|
|
|
+ description="Essayez gratuitement Opentalent Artist Premium pendant 30 jours. Solution complète pour orchestres, chorales, compagnies de théâtre, de danse ou de cirque."
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="background-container">
|
|
|
+ <LayoutContainer class="trial-container">
|
|
|
+ <div id="anchor" />
|
|
|
+
|
|
|
+ <v-card class="form-card">
|
|
|
+ <v-card-text>
|
|
|
+ <h1 class="text-center mb-6">
|
|
|
+ Essayez gratuitement Opentalent Artist Premium pendant 30 jours !
|
|
|
+ </h1>
|
|
|
+
|
|
|
+ <div v-if="!trialRequestSent">
|
|
|
+ <div class="description mb-8">
|
|
|
+ <p>
|
|
|
+ Opentalent Artist Premium est une solution en ligne complète,
|
|
|
+ pensée pour les orchestres, chorales, compagnies de théâtre,
|
|
|
+ de danse ou de cirque. Elle vous aide à gagner du temps dans
|
|
|
+ l'organisation de vos activités, à mieux collaborer avec vos
|
|
|
+ équipes et à renforcer votre visibilité auprès de votre
|
|
|
+ public.
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <p>
|
|
|
+ Pendant 30 jours, profitez de toutes les fonctionnalités
|
|
|
+ d'Opentalent Artist Premium, gratuitement et sans engagement :
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <ul class="benefits-list">
|
|
|
+ <li>
|
|
|
+ <span class="mr-1">✔️</span> Gestion intuitive des membres
|
|
|
+ et des événements
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="mr-1">✔️</span> Planification avancée des
|
|
|
+ répétitions, spectacles et tournées
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="mr-1">✔️</span> Outils de communication
|
|
|
+ intégrés (emails, publipostage, etc.)
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="mr-1">✔️</span> Site web personnalisable pour
|
|
|
+ présenter vos projets et votre structure
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="mr-1">✔️</span> Accès collaboratif pour vos
|
|
|
+ équipes, en temps réel
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+
|
|
|
+ <p>
|
|
|
+ Il vous suffit de remplir le formulaire ci-dessous pour
|
|
|
+ activer votre essai gratuit.
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <p>
|
|
|
+ Lancez-vous dès aujourd'hui et découvrez comment Opentalent
|
|
|
+ peut transformer votre organisation artistique !
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <v-alert
|
|
|
+ type="info"
|
|
|
+ variant="tonal"
|
|
|
+ border="start"
|
|
|
+ class="mb-4"
|
|
|
+ density="comfortable"
|
|
|
+ >
|
|
|
+ <template #title>
|
|
|
+ Vous êtes adhérents à la Confédération Musicale de France ?
|
|
|
+ <br />Et si on vous disait que vous l'aviez déjà...
|
|
|
+ </template>
|
|
|
+ Dans le cadre de votre adhésion, vous bénéficiez de la version
|
|
|
+ Opentalent Artist Standard, et de conditions privilégiées pour
|
|
|
+ la version Artist Premium. Contactez votre fédération pour
|
|
|
+ obtenir vos codes d'accès.
|
|
|
+ <div class="mt-2">
|
|
|
+ <a href="https://www.cmf-musique.org/contact/" target="_blank"
|
|
|
+ >Je souhaite obtenir mon code d'accès</a
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </v-alert>
|
|
|
+
|
|
|
+ <v-form
|
|
|
+ ref="form"
|
|
|
+ validate-on="submit lazy"
|
|
|
+ @submit.prevent="submit"
|
|
|
+ >
|
|
|
+ <v-container>
|
|
|
+ <div v-if="isDevelopmentOrTest" class="dev-tools-container">
|
|
|
+ <v-btn
|
|
|
+ color="info"
|
|
|
+ size="small"
|
|
|
+ prepend-icon="fa fa-magic"
|
|
|
+ @click="fillWithDummyData"
|
|
|
+ >
|
|
|
+ Remplir avec des données de test
|
|
|
+ </v-btn>
|
|
|
+ </div>
|
|
|
+ <i
|
|
|
+ >Les champs dont le nom est suivi d'un astérisque (*) sont
|
|
|
+ obligatoires.</i
|
|
|
+ >
|
|
|
+
|
|
|
+ <h2 class="section-title">Coordonnées de la structure</h2>
|
|
|
+
|
|
|
+ <!-- Structure name -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.structureName"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Nom de la structure*"
|
|
|
+ required
|
|
|
+ @input="onStructureNameUpdated"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Structure address -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.address"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Adresse du siège social de la structure*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.addressComplement"
|
|
|
+ label="Adresse (suite)"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.postalCode"
|
|
|
+ :rules="[validateRequired, validatePostalCode]"
|
|
|
+ label="Code postal*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.city"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Ville*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Structure email and SIREN -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.structureEmail"
|
|
|
+ :rules="[validateRequired, validateEmail]"
|
|
|
+ label="Adresse email de la structure*"
|
|
|
+ required
|
|
|
+ type="email"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.siren"
|
|
|
+ :rules="[validateSiren]"
|
|
|
+ label="SIREN (optionnel)"
|
|
|
+ hint="Numéro à 9 chiffres"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Structure type and legal status -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-select
|
|
|
+ v-model="trialRequest.structureType"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Type de la structure*"
|
|
|
+ :items="structureTypes"
|
|
|
+ item-value="value"
|
|
|
+ item-title="title"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-select
|
|
|
+ v-model="trialRequest.legalStatus"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Statut juridique*"
|
|
|
+ :items="legalStatuses"
|
|
|
+ item-value="value"
|
|
|
+ item-title="title"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <h2 class="section-title">Représentée par</h2>
|
|
|
+
|
|
|
+ <!-- Representative function -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.representativeFunction"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Fonction*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Representative name -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.representativeFirstName"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Prénom*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.representativeLastName"
|
|
|
+ :rules="[validateRequired]"
|
|
|
+ label="Nom*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Representative contact -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.representativeEmail"
|
|
|
+ :rules="[validateRequired, validateEmail]"
|
|
|
+ label="Adresse email*"
|
|
|
+ required
|
|
|
+ type="email"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+
|
|
|
+ <v-col cols="12" md="6">
|
|
|
+ <CommonPhoneInput
|
|
|
+ ref="phoneInput"
|
|
|
+ v-model="trialRequest.representativePhone"
|
|
|
+ label="Téléphone*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <h2 class="section-title mb-6">Informations de connexion</h2>
|
|
|
+
|
|
|
+ <!-- Structure identifier -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6" class="mx-auto">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.structureIdentifier"
|
|
|
+ :rules="[
|
|
|
+ validateRequired,
|
|
|
+ validateSubdomain,
|
|
|
+ validateSubdomainAvailability,
|
|
|
+ ]"
|
|
|
+ label="Identifiant de la structure*"
|
|
|
+ required
|
|
|
+ class="text-center"
|
|
|
+ @input="onStructureIdentifierUpdated"
|
|
|
+ />
|
|
|
+ <div class="validationMessage">
|
|
|
+ <span v-if="validationPending">
|
|
|
+ <v-progress-circular size="16" indeterminate />
|
|
|
+ <i class="ml-2">Vérification en cours</i>
|
|
|
+ </span>
|
|
|
+ <span
|
|
|
+ v-else-if="subdomainAvailable === true"
|
|
|
+ class="text-success"
|
|
|
+ >
|
|
|
+ <v-icon>fa fa-check</v-icon>
|
|
|
+ <i class="ml-2"> Cet identifiant est disponible</i>
|
|
|
+ </span>
|
|
|
+ <span
|
|
|
+ v-else-if="subdomainAvailable === false"
|
|
|
+ class="text-error"
|
|
|
+ >
|
|
|
+ <v-icon>fa fa-x</v-icon>
|
|
|
+ <i class="ml-2"
|
|
|
+ >Cet identifiant n'est pas disponible</i
|
|
|
+ >
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="mt-2">
|
|
|
+ <i v-if="trialRequest.structureIdentifier">
|
|
|
+ Le compte administrateur de la structure sera
|
|
|
+ <strong>
|
|
|
+ admin{{ trialRequest.structureIdentifier }}
|
|
|
+ </strong>
|
|
|
+ </i>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <i>
|
|
|
+ Veuillez renseigner un mot de passe pour ce compte :
|
|
|
+ </i>
|
|
|
+ </div>
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <!-- Password field -->
|
|
|
+ <v-row>
|
|
|
+ <v-col cols="12" md="6" class="mx-auto">
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.password"
|
|
|
+ :rules="[validateRequired, validatePassword]"
|
|
|
+ label="Mot de passe*"
|
|
|
+ required
|
|
|
+ :type="showPassword ? 'text' : 'password'"
|
|
|
+ :append-inner-icon="
|
|
|
+ showPassword ? 'fa fa-eye-slash' : 'fa fa-eye'
|
|
|
+ "
|
|
|
+ @click:append-inner="showPassword = !showPassword"
|
|
|
+ />
|
|
|
+ <div class="mt-1">
|
|
|
+ <i>
|
|
|
+ Le mot de passe doit contenir au moins 8 caractères,
|
|
|
+ une minuscule, une majuscule, un chiffre et un
|
|
|
+ caractère spécial.
|
|
|
+ </i>
|
|
|
+ </div>
|
|
|
+ <v-text-field
|
|
|
+ v-model="trialRequest.confirmPassword"
|
|
|
+ :rules="[
|
|
|
+ showPassword ? () => true : validateRequired,
|
|
|
+ showPassword ? () => true : validatePasswordMatch,
|
|
|
+ ]"
|
|
|
+ label="Confirmer le mot de passe*"
|
|
|
+ :required="!showPassword"
|
|
|
+ :disabled="showPassword"
|
|
|
+ :type="showPassword ? 'text' : 'password'"
|
|
|
+ />
|
|
|
+ </v-col>
|
|
|
+ </v-row>
|
|
|
+
|
|
|
+ <h2 class="section-title">Accord de termes et conditions</h2>
|
|
|
+
|
|
|
+ <!-- Terms checkboxes -->
|
|
|
+ <v-checkbox
|
|
|
+ v-model="trialRequest.termsAccepted"
|
|
|
+ :rules="[validateCheckbox]"
|
|
|
+ required
|
|
|
+ >
|
|
|
+ <template #label>
|
|
|
+ Mon organisme accepte les
|
|
|
+ <a
|
|
|
+ href="https://maestro.opentalent.fr/uploads/share/Documents_juridique/CGU.pdf"
|
|
|
+ target="_blank"
|
|
|
+ >
|
|
|
+ conditions générales d'utilisation </a
|
|
|
+ >.*
|
|
|
+ </template>
|
|
|
+ </v-checkbox>
|
|
|
+
|
|
|
+ <v-checkbox
|
|
|
+ v-model="trialRequest.legalRepresentative"
|
|
|
+ :rules="[validateCheckbox]"
|
|
|
+ label="J'agis en tant que représentant légal de l'association ou de la structure.*"
|
|
|
+ required
|
|
|
+ />
|
|
|
+
|
|
|
+ <v-checkbox
|
|
|
+ v-model="trialRequest.newsletterSubscription"
|
|
|
+ label="J'accepte de recevoir la lettre d'information culturelle afin de découvrir des idées de sorties adaptées à ma région."
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="d-flex flex-row justify-center">
|
|
|
+ <LayoutCaptcha />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Submit Button -->
|
|
|
+ <div class="d-flex flex-row justify-center my-10">
|
|
|
+ <v-btn
|
|
|
+ type="submit"
|
|
|
+ color="secondary"
|
|
|
+ size="large"
|
|
|
+ class="submit-btn"
|
|
|
+ >
|
|
|
+ COMMENCER MON ESSAI DE 30 JOURS
|
|
|
+ </v-btn>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="text-center no-credit-card">
|
|
|
+ Aucune carte de crédit requise. En cliquant sur "Commencer
|
|
|
+ mon essai de 30 jours", vous acceptez de démarrer votre
|
|
|
+ période d'essai gratuit.
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <div v-if="validationError" class="error">
|
|
|
+ Des champs du formulaire sont invalides ou manquants.
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="errorMsg" class="error">
|
|
|
+ {{ errorMsg }}
|
|
|
+ </div>
|
|
|
+ </v-container>
|
|
|
+
|
|
|
+ <div class="legal">
|
|
|
+ Les données recueillies par Opentalent sont utilisées pour le
|
|
|
+ traitement de votre demande et pour vous informer sur nos
|
|
|
+ offres. Elles sont destinées aux services Opentalent et à ses
|
|
|
+ sous-traitants pour l'exécution des contrats. Conformément à
|
|
|
+ la loi "Informatique et Libertés du 6 Janvier 1978", vous
|
|
|
+ disposez d'un droit d'accès, de modifications, de
|
|
|
+ rectification et de suppression des données vous concernant.
|
|
|
+ Pour toute demande, adressez-vous à : OPENTALENT, 265 rue de
|
|
|
+ la Grange 74950 SCIONZIER - FRANCE, opentalent.fr s'engage à
|
|
|
+ la confidentialité et à la protection de vos données."
|
|
|
+ </div>
|
|
|
+ </v-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="confirmation-message d-flex flex-row justify-center"
|
|
|
+ >
|
|
|
+ <v-card>
|
|
|
+ <v-card-title class="text-center">
|
|
|
+ <v-icon
|
|
|
+ icon="fas fa-check mr-2"
|
|
|
+ color="success"
|
|
|
+ max-height="48"
|
|
|
+ />
|
|
|
+ Félicitations !
|
|
|
+ </v-card-title>
|
|
|
+ <v-card-text class="text-center">
|
|
|
+ <p>
|
|
|
+ Votre demande d'essai gratuit de 30 jours d'Opentalent
|
|
|
+ Artist Premium a bien été enregistrée, mais nécessite une
|
|
|
+ validation de votre part.
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ Vous allez recevoir un email permettant de valider cette
|
|
|
+ demande, à la suite de quoi votre compte sera créé.
|
|
|
+ Attention, la durée de validité du lien d'activation est de
|
|
|
+ 60 minutes.
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ Notre équipe reste à votre disposition pour vous accompagner
|
|
|
+ durant cette période d'essai.
|
|
|
+ </p>
|
|
|
+ </v-card-text>
|
|
|
+ <v-card-actions class="justify-center">
|
|
|
+ <v-btn
|
|
|
+ variant="elevated"
|
|
|
+ prepend-icon="fas fa-arrow-left"
|
|
|
+ color="secondary"
|
|
|
+ to="/opentalent-artist"
|
|
|
+ >
|
|
|
+ Retour à la page Opentalent Artist
|
|
|
+ </v-btn>
|
|
|
+ </v-card-actions>
|
|
|
+ </v-card>
|
|
|
+ </div>
|
|
|
+ </v-card-text>
|
|
|
+ </v-card>
|
|
|
+ </LayoutContainer>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+import type { Ref } from 'vue'
|
|
|
+import { reactive } from 'vue'
|
|
|
+import _ from 'lodash'
|
|
|
+import { useRuntimeConfig, useAsyncData } from '#app'
|
|
|
+import type { TrialRequest } from '~/types/interface'
|
|
|
+import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
|
|
|
+import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
|
|
|
+import { useAp2iErrorHandler } from '~/composables/utils/useAp2iErrorHandler'
|
|
|
+import {
|
|
|
+ convertPhoneNumberToInternationalFormat,
|
|
|
+ slugify,
|
|
|
+} from '~/services/utils/stringUtils'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const form: Ref<HTMLElement | null> = ref(null)
|
|
|
+
|
|
|
+// Structure types and legal statuses
|
|
|
+const structureTypes = Object.values(STRUCTURE_TYPE)
|
|
|
+ .map((item) => ({
|
|
|
+ value: item.key,
|
|
|
+ title: item.label,
|
|
|
+ }))
|
|
|
+ .sort((a, b) => (a.title > b.title ? 1 : -1))
|
|
|
+
|
|
|
+const legalStatuses = Object.values(LEGAL_STATUS)
|
|
|
+ .map((item) => ({
|
|
|
+ value: item.key,
|
|
|
+ title: item.label,
|
|
|
+ }))
|
|
|
+ .sort((a, b) => (a.title > b.title ? 1 : -1))
|
|
|
+
|
|
|
+// Get apiRequestService for subdomain availability check
|
|
|
+const { ap2iRequestService } = useAp2iRequestService()
|
|
|
+const { processApiError } = useAp2iErrorHandler()
|
|
|
+
|
|
|
+// Check if we're in a development environment
|
|
|
+const config = useRuntimeConfig()
|
|
|
+const isDevelopmentOrTest = computed(
|
|
|
+ () => config.public.env === 'dev' || config.public.env === 'test'
|
|
|
+)
|
|
|
+
|
|
|
+// Trial request data
|
|
|
+const trialRequest = reactive<TrialRequest>({
|
|
|
+ structureName: '',
|
|
|
+ address: '',
|
|
|
+ addressComplement: '',
|
|
|
+ postalCode: '',
|
|
|
+ city: '',
|
|
|
+ structureEmail: '',
|
|
|
+ structureType: 'ARTISTIC_PRACTICE_ONLY',
|
|
|
+ legalStatus: 'ASSOCIATION_LAW_1901',
|
|
|
+ structureIdentifier: '',
|
|
|
+ siren: '',
|
|
|
+ representativeFirstName: '',
|
|
|
+ representativeLastName: '',
|
|
|
+ representativeFunction: '',
|
|
|
+ representativeEmail: '',
|
|
|
+ representativePhone: '',
|
|
|
+ password: '',
|
|
|
+ confirmPassword: '',
|
|
|
+ termsAccepted: false,
|
|
|
+ legalRepresentative: false,
|
|
|
+ newsletterSubscription: false,
|
|
|
+ createWebsite: false,
|
|
|
+})
|
|
|
+
|
|
|
+// Function to fill the form with dummy data
|
|
|
+const fillWithDummyData = () => {
|
|
|
+ // Generate a short timestamp (unix timestamp in seconds)
|
|
|
+ const shortTimestamp = Math.floor(Date.now() / 1000).toString()
|
|
|
+
|
|
|
+ trialRequest.structureName = `Compagnie Artistique Test ${shortTimestamp}`
|
|
|
+ trialRequest.address = '123 Rue des Arts'
|
|
|
+ trialRequest.addressComplement = 'Bâtiment B'
|
|
|
+ trialRequest.postalCode = '75001'
|
|
|
+ trialRequest.city = 'Paris'
|
|
|
+ trialRequest.structureEmail = 'contact@compagnie-test.fr'
|
|
|
+ trialRequest.structureType = 'ARTISTIC_PRACTICE_ONLY'
|
|
|
+ trialRequest.legalStatus = 'ASSOCIATION_LAW_1901'
|
|
|
+ trialRequest.structureIdentifier = `compagnie-test-${shortTimestamp}`
|
|
|
+ trialRequest.siren = '123456789'
|
|
|
+ trialRequest.representativeFirstName = 'Jean'
|
|
|
+ trialRequest.representativeLastName = 'Dupont'
|
|
|
+ trialRequest.representativeFunction = 'Directeur Artistique'
|
|
|
+ trialRequest.representativeEmail = 'jean.dupont@compagnie-test.fr'
|
|
|
+ trialRequest.representativePhone = '0612345678'
|
|
|
+ trialRequest.password = 'Test1234!'
|
|
|
+ trialRequest.confirmPassword = 'Test1234!'
|
|
|
+ trialRequest.termsAccepted = true
|
|
|
+ trialRequest.legalRepresentative = true
|
|
|
+ trialRequest.newsletterSubscription = true
|
|
|
+
|
|
|
+ // Trigger subdomain availability check
|
|
|
+ checkSubdomainAvailabilityDebounced()
|
|
|
+}
|
|
|
+
|
|
|
+// Track if structure identifier has been manually modified
|
|
|
+const structureIdentifierModified = ref(false)
|
|
|
+
|
|
|
+// Variables for subdomain validation
|
|
|
+const validationPending = ref(false)
|
|
|
+const subdomainAvailable = ref<boolean | null>(null)
|
|
|
+
|
|
|
+const checkSubdomainAvailability = async (subdomain: string) => {
|
|
|
+ if (!subdomain || validateSubdomain(subdomain) !== true) {
|
|
|
+ subdomainAvailable.value = null
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ validationPending.value = true
|
|
|
+ try {
|
|
|
+ const subdomainAvailability = await ap2iRequestService.get(
|
|
|
+ '/api/public/subdomains/is_available',
|
|
|
+ { subdomain }
|
|
|
+ )
|
|
|
+
|
|
|
+ subdomainAvailable.value =
|
|
|
+ subdomainAvailability && subdomainAvailability.available === true
|
|
|
+ validationPending.value = false
|
|
|
+ return subdomainAvailable.value
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error checking subdomain availability:', error)
|
|
|
+ subdomainAvailable.value = false
|
|
|
+ validationPending.value = false
|
|
|
+ return false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Version debounced de la fonction checkAvailability
|
|
|
+ * @see https://docs-lodash.com/v4/debounce/
|
|
|
+ */
|
|
|
+const checkSubdomainAvailabilityDebounced: _.DebouncedFunc<() => void> =
|
|
|
+ _.debounce(
|
|
|
+ async () =>
|
|
|
+ await checkSubdomainAvailability(trialRequest.structureIdentifier),
|
|
|
+ 600
|
|
|
+ )
|
|
|
+
|
|
|
+// Validation rules
|
|
|
+const validateRequired = (value: string) =>
|
|
|
+ !!value || 'Ce champ est obligatoire'
|
|
|
+
|
|
|
+const validateEmail = (email: string) =>
|
|
|
+ /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || 'Adresse email invalide'
|
|
|
+
|
|
|
+const validatePostalCode = (postalCode: string) =>
|
|
|
+ /^\d{5}$/.test(postalCode) || 'Code postal invalide (5 chiffres)'
|
|
|
+
|
|
|
+const validateSiren = (siren: string) =>
|
|
|
+ !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
|
|
|
+
|
|
|
+const validateCheckbox = (value: boolean) =>
|
|
|
+ value || 'Vous devez accepter cette condition'
|
|
|
+
|
|
|
+const validatePassword = (password: string) => {
|
|
|
+ if (!password) return 'Ce champ est obligatoire'
|
|
|
+
|
|
|
+ const minLength = password.length >= 8
|
|
|
+ const hasLowercase = /[a-z]/.test(password)
|
|
|
+ const hasUppercase = /[A-Z]/.test(password)
|
|
|
+ const hasNumber = /[0-9]/.test(password)
|
|
|
+ const hasSpecial = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)
|
|
|
+
|
|
|
+ if (!minLength) {
|
|
|
+ return 'Le mot de passe doit contenir au moins 8 caractères'
|
|
|
+ }
|
|
|
+ if (!hasLowercase) {
|
|
|
+ return 'Le mot de passe doit contenir au moins une lettre minuscule'
|
|
|
+ }
|
|
|
+ if (!hasUppercase) {
|
|
|
+ return 'Le mot de passe doit contenir au moins une lettre majuscule'
|
|
|
+ }
|
|
|
+ if (!hasNumber) {
|
|
|
+ return 'Le mot de passe doit contenir au moins un chiffre'
|
|
|
+ }
|
|
|
+ if (!hasSpecial) {
|
|
|
+ return 'Le mot de passe doit contenir au moins un caractère spécial'
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const validatePasswordMatch = () => {
|
|
|
+ if (!trialRequest.confirmPassword) return 'Ce champ est obligatoire'
|
|
|
+
|
|
|
+ return (
|
|
|
+ trialRequest.password === trialRequest.confirmPassword ||
|
|
|
+ 'Les mots de passe ne correspondent pas'
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const validateSubdomain = (value: string) => {
|
|
|
+ if (!value) return 'Ce champ est obligatoire'
|
|
|
+
|
|
|
+ const regex = /^[a-z0-9][a-z0-9-]{0,28}[a-z0-9]$/
|
|
|
+
|
|
|
+ return (
|
|
|
+ regex.test(value) ||
|
|
|
+ 'Format invalide. Utilisez uniquement des lettres minuscules, des chiffres et des tirets. Doit commencer et finir par une lettre ou un chiffre. Maximum 30 caractères.'
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const validateSubdomainAvailability = (value: string) => {
|
|
|
+ if (!value) return ''
|
|
|
+
|
|
|
+ return (
|
|
|
+ subdomainAvailable.value === true || "Cet identifiant n'est pas disponible"
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// Password visibility toggle
|
|
|
+const showPassword = ref(false)
|
|
|
+
|
|
|
+// Form state
|
|
|
+const trialRequestSent: Ref<boolean> = ref(false)
|
|
|
+const errorMsg: Ref<string | null> = ref(null)
|
|
|
+const validationError: Ref<boolean> = ref(false)
|
|
|
+// Reference to the phone input component
|
|
|
+const phoneInput = ref(null)
|
|
|
+
|
|
|
+// Submit function
|
|
|
+const submit = async (): Promise<void> => {
|
|
|
+ const { valid } = await form.value!.validate()
|
|
|
+
|
|
|
+ if (!valid) {
|
|
|
+ validationError.value = true
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ validationError.value = false
|
|
|
+
|
|
|
+ // Convert phone number to international format before submission
|
|
|
+ if (phoneInput.value) {
|
|
|
+ trialRequest.representativePhone = convertPhoneNumberToInternationalFormat(
|
|
|
+ trialRequest.representativePhone
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const { data, error } = await useAsyncData('submit-trial-request', () =>
|
|
|
+ ap2iRequestService.post(
|
|
|
+ '/api/public/shop/new-structure-artist-premium-trial-request',
|
|
|
+ trialRequest
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ if (error.value) {
|
|
|
+ throw error.value
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('Trial request submitted successfully:', data.value)
|
|
|
+
|
|
|
+ trialRequestSent.value = true
|
|
|
+ errorMsg.value = null
|
|
|
+
|
|
|
+ // Scroll to top to show confirmation message
|
|
|
+ setTimeout(() => router.push({ path: '', hash: '#anchor' }), 30)
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Error submitting trial request:', e)
|
|
|
+
|
|
|
+ // Process the error message using the common error handler
|
|
|
+ const processedError = processApiError(e)
|
|
|
+
|
|
|
+ // Set the error message directly from the processed error
|
|
|
+ // The processApiError function now returns either a translated message
|
|
|
+ // or a generic error message as appropriate
|
|
|
+ errorMsg.value = processedError
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Event handler for structureName updates
|
|
|
+const onStructureNameUpdated = (newName: string) => {
|
|
|
+ if (!structureIdentifierModified.value && newName) {
|
|
|
+ trialRequest.structureIdentifier = slugify(
|
|
|
+ trialRequest.structureName
|
|
|
+ ).replace(/[-_]/g, '')
|
|
|
+ checkSubdomainAvailabilityDebounced()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Event handler for structureIdentifier updates
|
|
|
+const onStructureIdentifierUpdated = () => {
|
|
|
+ structureIdentifierModified.value = true
|
|
|
+ checkSubdomainAvailabilityDebounced()
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.background-container {
|
|
|
+ background-image: url('/images/logos/opentalent/Logo_Opentalent_Griffe.png');
|
|
|
+ background-size: 700px;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+.trial-container {
|
|
|
+ max-width: 1200px;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+.form-card {
|
|
|
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
|
|
|
+ max-width: 90%;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+h1 {
|
|
|
+ font-size: 2.5rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--primary-color);
|
|
|
+ text-decoration: underline var(--artist-color) 3px solid;
|
|
|
+ margin-bottom: 2rem;
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ font-size: 1.8rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.description {
|
|
|
+ font-size: 1.1rem;
|
|
|
+ line-height: 1.6;
|
|
|
+ margin-bottom: 2rem;
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.benefits-list {
|
|
|
+ list-style: none;
|
|
|
+ padding-left: 1rem;
|
|
|
+ margin: 1.5rem 0;
|
|
|
+
|
|
|
+ li {
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ margin-top: 2rem;
|
|
|
+ font-size: 1.5rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--primary-color);
|
|
|
+ text-decoration: underline var(--artist-color) 3px solid;
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.05em;
|
|
|
+}
|
|
|
+
|
|
|
+.v-form {
|
|
|
+ max-width: 1200px;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ .submit-btn {
|
|
|
+ font-weight: 600;
|
|
|
+ letter-spacing: 0.05em;
|
|
|
+ padding: 0 2rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .error {
|
|
|
+ color: var(--warning-color);
|
|
|
+ width: 80%;
|
|
|
+ margin: 0.7em auto 2em;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 1.05rem;
|
|
|
+ font-weight: 600;
|
|
|
+ border: 2px solid var(--warning-color);
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 0.5rem;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .legal {
|
|
|
+ opacity: 0.7;
|
|
|
+ font-size: 14px;
|
|
|
+ font-style: italic;
|
|
|
+ margin: 2rem auto;
|
|
|
+ max-width: 80%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.no-credit-card {
|
|
|
+ font-size: 0.9rem;
|
|
|
+ opacity: 0.8;
|
|
|
+ margin-top: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+.confirmation-message {
|
|
|
+ .v-card {
|
|
|
+ max-width: 800px;
|
|
|
+ padding: 2rem;
|
|
|
+ margin: 4rem 0;
|
|
|
+
|
|
|
+ .v-card-title {
|
|
|
+ font-size: 1.8rem;
|
|
|
+ font-weight: 700;
|
|
|
+ color: var(--primary-color);
|
|
|
+ }
|
|
|
+
|
|
|
+ .v-card-text {
|
|
|
+ font-size: 1.1rem;
|
|
|
+ line-height: 1.6;
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin-bottom: 1rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.dev-tools-container {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 10px;
|
|
|
+ right: 10px;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+</style>
|