| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- <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 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-form
- v-if="!trialRequestSent"
- ref="form"
- validate-on="submit lazy"
- @submit.prevent="submit"
- >
- <v-container>
- <div v-if="isDevelopment" 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 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 mail 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>
- <!-- 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>
- </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 mail*"
- required
- type="email"
- />
- </v-col>
- <v-col cols="12" md="6">
- <v-text-field
- v-model="trialRequest.representativePhone"
- :rules="[validateRequired, validatePhone]"
- label="Téléphone*"
- required
- type="tel"
- />
- </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="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
- 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 essai gratuit de 30 jours d'Opentalent Artist Premium
- a bien été activé.
- </p>
- <p>
- Vous allez recevoir un email avec vos identifiants de
- connexion et toutes les informations nécessaires pour
- commencer à utiliser la plateforme.
- </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 color="primary" 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 { parsePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js'
- import { useRuntimeConfig, useAsyncData, useNuxtApp } from '#app'
- import type { TrialRequest } from '~/types/interface'
- import { STRUCTURE_TYPE, LEGAL_STATUS } from '~/types/types'
- import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
- import { 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()
- // Check if we're in a development environment
- const config = useRuntimeConfig()
- const isDevelopment = computed(() => config.public.env === 'dev')
- // 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: '',
- termsAccepted: false,
- legalRepresentative: false,
- newsletterSubscription: false,
- })
- // Function to fill the form with dummy data
- const fillWithDummyData = () => {
- trialRequest.structureName = 'Compagnie Artistique Test'
- 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-' + Math.floor(Math.random() * 1000)
- trialRequest.siren = '123456789'
- trialRequest.representativeFirstName = 'Jean'
- trialRequest.representativeLastName = 'Dupont'
- trialRequest.representativeFunction = 'Directeur Artistique'
- trialRequest.representativeEmail = 'jean.dupont@compagnie-test.fr'
- trialRequest.representativePhone = '0612345678'
- 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 validatePhone = (phone: string) => {
- try {
- // Assume French phone number if no country code is provided
- return isValidPhoneNumber(phone, 'FR') || 'Numéro de téléphone invalide'
- } catch (error) {
- return 'Numéro de téléphone invalide'
- }
- }
- const validateSiren = (siren: string) =>
- !siren || /^\d{9}$/.test(siren) || 'SIREN invalide (9 chiffres)'
- const validateCheckbox = (value: boolean) =>
- value || 'Vous devez accepter cette condition'
- 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"
- )
- }
- // Form state
- const trialRequestSent: Ref<boolean> = ref(false)
- const errorMsg: Ref<string | null> = ref(null)
- // Function to convert phone number to international format
- const convertToInternationalFormat = (phone: string): string => {
- try {
- // Assume French phone number if no country code is provided
- const phoneNumber = parsePhoneNumber(phone, 'FR')
- if (phoneNumber && phoneNumber.isValid()) {
- return phoneNumber.format('E.164') // E.164 format: +33123456789
- }
- return phone
- } catch (error) {
- console.error('Error converting phone number:', error)
- return phone
- }
- }
- // Submit function
- const submit = async (): Promise<void> => {
- const { valid } = await form.value!.validate()
- if (!valid) {
- return
- }
- // Convert phone number to international format before submission
- trialRequest.representativePhone = convertToInternationalFormat(
- 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)
- errorMsg.value =
- "Une erreur s'est produite lors de l'activation de votre essai. Veuillez réessayer plus tard ou nous contacter directement."
- // Try to extract the specific error message from the API response
- if (
- e &&
- typeof e === 'object' &&
- 'data' in e &&
- e.data &&
- typeof e.data === 'object'
- ) {
- const errorData = e.data as { detail?: string }
- if (errorData.detail) {
- const { $i18n } = useNuxtApp()
- // Use translation if available, otherwise use the original message
- errorMsg.value += '\n' + ($i18n.t(errorData.detail) || errorData.detail)
- }
- }
- }
- }
- // Event handler for structureName updates
- const onStructureNameUpdated = (newName: string) => {
- if (!structureIdentifierModified.value && newName) {
- trialRequest.structureIdentifier = slugify(trialRequest.structureName)
- 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 auto 2em;
- text-align: 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>
|