Formulaire academie

This commit is contained in:
2026-06-07 10:16:15 +02:00
parent 0711bd6996
commit 9dfce9bc58
21 changed files with 1666 additions and 854 deletions

View File

@@ -34,6 +34,7 @@
professionnels: "Professionnels",
inscriptions: "Inscriptions",
projet_lycee: "Inscription projet lycée",
projet_academie: "Inscription Académie d'Orchestre",
actus: "Actualités"
}

View File

@@ -17,7 +17,7 @@
<slot />
<div class="decalage_button_wp">
<div v-if="resolvedLienCta" class="decalage_button">
<DsButton :to="resolvedLienCta" variant="white" textColor="default" borderColor="none">Candidater à nos projets</DsButton>
<DsButton :to="resolvedLienCta" variant="white" textColor="default" borderColor="none">{{ textCta }}</DsButton>
</div>
<div v-if="ensavoirplusTarget" class="decalage_button">
<DsButton :textColor="buttonTone" :borderColor="buttonTone" @click="toggleTarget(ensavoirplusTarget, ensavoirplusGroup)">En savoir +</DsButton>
@@ -40,6 +40,7 @@
position: { type: String, default: 'left' },
cta: { type: String, default: '' },
lienCta: { type: String, default: '' },
textCta: { type: String, default: 'Candidater à nos projets' },
ensavoirplusTarget: { type: String, default: '' },
ensavoirplusGroup: { type: String, default: '' },
})

View File

@@ -0,0 +1,896 @@
<template>
<div class="inscription-page">
<!-- ================== -->
<!-- FILS D'ARIANE -->
<!-- ================== -->
<PageSection tone="" content-size="default" class="breadcrum_wp">
<Breadcrumb/>
</PageSection>
<!-- ================== -->
<!-- EN-TÊTE -->
<!-- ================== -->
<PageSection tone="jaune" content-size="default" class="theme_bandeau--grid">
<SectionContent pad="xs" class="theme_bandeau--grid--left">
<SectionTitle tone="invert" pad="">
CANDIDATER À L'ACADÉMIE D'ORCHESTRE
</SectionTitle>
<DsHeading as="h3" tone="invert">
</DsHeading>
</SectionContent>
<SectionContent pad="xs" class="theme_bandeau--grid--right">
<DsText tone="invert" size="lg" class="theme_bandeau--grid--right--text">
Renseignez ce formulaire pour soumettre votre candidature à l'Académie d'orchestre de l'Orchestre national d'Île-de-France.
</DsText>
</SectionContent>
</PageSection>
<!-- ================== -->
<!-- FORMULAIRE -->
<!-- ================== -->
<div class="programmer-orchestre-page text-on-surface">
<form class="space-y-6" @submit.prevent="submitQuoteRequest">
<div class="px-4 md:px-8 lg:px-16 py-8 max-w-7xl mx-auto">
<FormSection title="Identité">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldInput
id="academie-last-name"
v-model.trim="quoteForm.lastName"
label="Nom"
required
:error="quoteFormErrors.lastName"
@input="quoteForm.lastName = quoteForm.lastName.toUpperCase()"
/>
<FieldInput
id="academie-first-name"
v-model.trim="quoteForm.firstName"
label="Prénom"
required
:error="quoteFormErrors.firstName"
/>
<FieldSelect
id="academie-gender"
v-model="quoteForm.gender"
label="Genre"
placeholder="Choisir un genre"
required
:options="genderOptions"
:error="quoteFormErrors.gender"
/>
<FieldInput
id="academie-birth-date"
v-model="quoteForm.birthDate"
label="Date de naissance"
type="date"
required
:error="quoteFormErrors.birthDate"
/>
<FieldInput
id="academie-birth-place"
v-model.trim="quoteForm.birthPlace"
label="Lieu de naissance"
required
:error="quoteFormErrors.birthPlace"
/>
<FieldInput
id="academie-nationality"
v-model.trim="quoteForm.nationality"
label="Nationalité"
required
:error="quoteFormErrors.nationality"
/>
</div>
</FormSection>
<FormSection title="Coordonnées">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldInput
id="academie-email"
v-model.trim="quoteForm.email"
label="Adresse mail"
type="email"
required
:error="quoteFormErrors.email"
/>
<FieldInput
id="academie-phone"
v-model.trim="quoteForm.phone"
label="Téléphone"
type="tel"
required
:error="quoteFormErrors.phone"
/>
<FieldInput
id="academie-address"
v-model.trim="quoteForm.address"
label="Adresse postale"
required
:error="quoteFormErrors.address"
class="md:col-span-2"
/>
<FieldInput
id="academie-city"
v-model.trim="quoteForm.city"
label="Ville"
required
:error="quoteFormErrors.city"
/>
<FieldInput
id="academie-postal-code"
v-model.trim="quoteForm.postalCode"
label="Code postal"
required
:error="quoteFormErrors.postalCode"
/>
<FieldInput
id="academie-country"
v-model.trim="quoteForm.country"
label="Pays de résidence"
required
:error="quoteFormErrors.country"
/>
</div>
</FormSection>
<FormSection title="Parcours musical">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldSelect
id="academie-instrument"
v-model="quoteForm.instrument"
label="Instrument"
placeholder="Choisir un instrument"
required
:options="instrumentOptions"
:error="quoteFormErrors.instrument"
class="md:col-span-2"
>
<template #help>
Pour les pupitres à instruments doubles (ex : flûte/piccolo, basson/contrebasson, etc.), merci de sélectionner l'option correspondant aux deux instruments si vous souhaitez les présenter tous les deux. Si vous ne cochez quun seul instrument, vous ne pourrez en présenter quun seul le jour de laudition.
</template>
</FieldSelect>
<FieldSelect
id="academie-previous-participation"
v-model="quoteForm.previousAcademyParticipation"
label="Avez-vous déjà participé à lacadémie ?"
placeholder="Choisir une réponse"
required
:options="yesNoOptions"
:error="quoteFormErrors.previousAcademyParticipation"
/>
<FieldInput
v-if="quoteForm.previousAcademyParticipation === 'oui'"
id="academie-previous-participation-count"
v-model.trim="quoteForm.previousAcademyParticipationCount"
label="Combien de fois ?"
type="number"
min="1"
required
:error="quoteFormErrors.previousAcademyParticipationCount"
/>
</div>
</FormSection>
<FormSection title="Formation">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldSelect
id="academie-training-level"
v-model="quoteForm.trainingLevel"
label="Niveau - cycle actuel année scolaire 2026-2027"
placeholder="Choisir un niveau"
required
:options="trainingLevelOptions"
:error="quoteFormErrors.trainingLevel"
class="md:col-span-2"
>
<template #help>
Pour rappel, vous devez être inscrit.e en 3e cycle, cycle spécialisé ou équivalent à la rentrée 2025-2026 dans un conservatoire dÎle-de-France (hors CNSMD et Pôles denseignement supérieur).
</template>
</FieldSelect>
<FieldInput
v-if="quoteForm.trainingLevel === 'Autre'"
id="academie-other-training-level"
v-model.trim="quoteForm.otherTrainingLevel"
label="Autre formation"
required
:error="quoteFormErrors.otherTrainingLevel"
class="md:col-span-2"
/>
<FieldSelect
id="academie-conservatory"
v-model="quoteForm.conservatory"
label="Conservatoire année scolaire 2026-2027"
placeholder="Choisir un conservatoire"
required
:options="conservatoryOptions"
:error="quoteFormErrors.conservatory"
class="md:col-span-2"
>
<template #help>
Si votre conservatoire ne figure pas dans la liste mais quil est bien situé en Île-de-France, merci de lajouter manuellement en respectant le même format que les propositions déjà indiquées.
</template>
</FieldSelect>
<FieldInput
v-if="quoteForm.conservatory === 'Autre'"
id="academie-other-conservatory"
v-model.trim="quoteForm.otherConservatory"
label="Autre conservatoire (Ville + Nom)"
required
:error="quoteFormErrors.otherConservatory"
/>
<FieldInput
id="academie-teacher-name"
v-model.trim="quoteForm.teacherName"
label="Nom du professeur dinstrument (NOM prénom)"
required
:error="quoteFormErrors.teacherName"
/>
</div>
</FormSection>
<FormSection title="3 derniers diplômes musicaux obtenus">
<div class="space-y-8">
<div
v-for="(diploma, index) in quoteForm.diplomas"
:key="`diploma-${index}`"
class="rounded-xl border border-outline-variant/30 p-5"
>
<h4 class="mb-4 text-lg font-bold uppercase">Diplôme {{ index + 1 }}</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldSelect
:id="`academie-diploma-${index}-type`"
v-model="diploma.type"
label="Type du diplôme"
placeholder="Choisir un type"
:options="diplomaTypeOptions"
:error="quoteFormErrors.diplomas[index].type"
/>
<FieldInput
v-if="diploma.type === 'Autre'"
:id="`academie-diploma-${index}-other-type`"
v-model.trim="diploma.otherType"
label="Autre type de diplôme"
:error="quoteFormErrors.diplomas[index].otherType"
/>
<FieldInput
:id="`academie-diploma-${index}-discipline`"
v-model.trim="diploma.discipline"
label="Discipline"
:error="quoteFormErrors.diplomas[index].discipline"
/>
<FieldInput
:id="`academie-diploma-${index}-year`"
v-model.trim="diploma.year"
label="Année"
type="number"
min="1900"
max="2027"
:error="quoteFormErrors.diplomas[index].year"
/>
<FieldInput
:id="`academie-diploma-${index}-establishment`"
v-model.trim="diploma.establishment"
label="Établissement"
:error="quoteFormErrors.diplomas[index].establishment"
class="md:col-span-2"
/>
</div>
</div>
</div>
</FormSection>
<FormSection title="Contact durgence">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<FieldInput
id="academie-emergency-last-name"
v-model.trim="quoteForm.emergencyLastName"
label="Nom du contact"
required
:error="quoteFormErrors.emergencyLastName"
/>
<FieldInput
id="academie-emergency-first-name"
v-model.trim="quoteForm.emergencyFirstName"
label="Prénom du contact"
required
:error="quoteFormErrors.emergencyFirstName"
/>
<FieldSelect
id="academie-emergency-relation"
v-model="quoteForm.emergencyRelation"
label="Lien avec le participant"
placeholder="Choisir un lien"
required
:options="emergencyRelationOptions"
:error="quoteFormErrors.emergencyRelation"
/>
<FieldInput
id="academie-emergency-phone"
v-model.trim="quoteForm.emergencyPhone"
label="Téléphone"
type="tel"
required
:error="quoteFormErrors.emergencyPhone"
/>
<FieldInput
id="academie-emergency-email"
v-model.trim="quoteForm.emergencyEmail"
label="Mail"
type="email"
required
:error="quoteFormErrors.emergencyEmail"
/>
</div>
</FormSection>
<FormSection title="Règlement">
<div class="space-y-4">
<a
:href="regulationFileUrl"
target="_blank"
download
class="inline-flex items-center justify-center rounded-full border border-primary px-6 py-3 text-sm font-bold text-primary transition-colors hover:bg-primary hover:text-on-primary"
>
Télécharger le règlement
</a>
<label class="flex items-start gap-3 text-sm font-medium text-on-surface">
<input
v-model="quoteForm.acceptRules"
type="checkbox"
class="mt-1 rounded border-outline-variant text-primary focus:ring-primary"
>
<span>Accepter le règlement *</span>
</label>
<p v-if="quoteFormErrors.acceptRules" class="text-xs text-error">{{ quoteFormErrors.acceptRules }}</p>
</div>
</FormSection>
<!-- ENVOYER LE FORMULAIRE -->
<section class="mb-12 mt-12">
<div v-if="quoteSubmitError" class="mb-8 rounded-xl border border-error/20 bg-error-container/20 px-4 py-3 text-sm text-error">
{{ quoteSubmitError }}
</div>
<div v-if="quoteSubmitSuccess" class="mb-8 rounded-xl border border-primary/20 bg-primary-container/40 px-4 py-3 text-sm text-on-surface">
<span v-if="quoteEmailsSent">Votre candidature a bien été envoyée. Un email de confirmation vous a été adressé.</span>
<span v-else>Votre candidature a bien été enregistrée et transmise à léquipe de lAcadémie dorchestre.</span>
</div>
<div v-if="quoteSubmitting" class="mb-8 rounded-xl border border-outline-variant/30 bg-white px-4 py-3 text-sm text-on-surface">
Envoi de votre candidature en cours...
</div>
<div class="flex items-center gap-4">
<button
type="submit"
class="inline-flex items-center justify-center rounded-full bg-primary px-6 py-3 text-sm font-bold text-on-primary transition-colors hover:bg-primary-dim disabled:cursor-not-allowed disabled:opacity-60"
:disabled="quoteSubmitting"
>
{{ quoteSubmitting ? 'Envoi en cours...' : 'Envoyer la candidature' }}
</button>
<p class="text-xs text-on-surface-variant">
En soumettant ce formulaire, jaccepte que les informations saisies soient utilisées dans le cadre de cette candidature.
</p>
</div>
</section>
</div>
</form>
</div>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
const inputClass = "w-full rounded-xl border border-outline-variant/30 bg-white px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary"
const FormSection = defineComponent({
props: {
title: {
type: String,
required: true,
},
},
setup(props, { slots }) {
return () => h("section", { class: "mb-10" }, [
h("div", { class: "flex items-center gap-4 mb-8" }, [
h("h3", { class: "text-2xl font-bold tracking-tight uppercase" }, props.title),
h("div", { class: "h-[2px] flex-1 bg-surface-container" }),
]),
slots.default?.(),
])
},
})
const FieldInput = defineComponent({
props: {
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
modelValue: {
type: [String, Number],
default: "",
},
type: {
type: String,
default: "text",
},
required: {
type: Boolean,
default: false,
},
error: {
type: String,
default: "",
},
min: {
type: [String, Number],
default: undefined,
},
max: {
type: [String, Number],
default: undefined,
},
},
emits: ["update:modelValue", "input"],
setup(props, { emit, slots }) {
return () => h("div", { class: "space-y-2" }, [
h("label", { for: props.id, class: "block text-sm font-medium text-on-surface pl-[2px]" }, `${props.label}${props.required ? " *" : ""}`),
h("input", {
id: props.id,
value: props.modelValue,
type: props.type,
min: props.min,
max: props.max,
class: inputClass,
onInput: (event) => {
emit("update:modelValue", event.target.value)
emit("input", event)
},
}),
slots.help?.(),
props.error ? h("p", { class: "text-xs text-error" }, props.error) : null,
])
},
})
const FieldSelect = defineComponent({
props: {
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
modelValue: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "Choisir une option",
},
required: {
type: Boolean,
default: false,
},
options: {
type: Array,
required: true,
},
error: {
type: String,
default: "",
},
},
emits: ["update:modelValue"],
setup(props, { emit, slots }) {
return () => h("div", { class: "space-y-2" }, [
h("label", { for: props.id, class: "block text-sm font-medium text-on-surface pl-[2px]" }, `${props.label}${props.required ? " *" : ""}`),
h("select", {
id: props.id,
value: props.modelValue,
class: inputClass,
onChange: (event) => emit("update:modelValue", event.target.value),
}, [
h("option", { value: "" }, props.placeholder),
...props.options.map((option) => h("option", { value: option }, option)),
]),
slots.help ? h("p", { class: "text-xs leading-relaxed text-on-surface-variant" }, slots.help()) : null,
props.error ? h("p", { class: "text-xs text-error" }, props.error) : null,
])
},
})
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: '',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap',
},
],
})
const genderOptions = ["Masculin", "Féminin", "Autre"]
const yesNoOptions = ["oui", "non"]
const instrumentOptions = [
"Violon",
"Alto",
"Violoncelle",
"Contrebasse",
"Flûte",
"Piccolo",
"Flûte + piccolo",
"Hautbois",
"Cor anglais jouant le 2e hautbois",
"Clarinette",
"Basson",
"Contrebasson",
"Basson + Contrebasson",
"Cor",
"Trompette",
"Trombone 2",
"Trombone basse ou trombone 3",
"Tuba",
"Harpe",
"Percussions",
"Timbales",
"Percussions + timbales",
]
const trainingLevelOptions = [
"Cycle 3 - 1ère année",
"Cycle 3 - 2ème année",
"Cycle 3 - 3ème année",
"Cycle spécialisé (CPES)",
"DEM",
"Autre",
]
const conservatoryOptions = [
"75 - Paris - Conservatoire à rayonnement régional de Paris - Ida Rubinstein",
"75 - Paris 5 - Conservatoire municipal Gabriel Fauré",
"75 - Paris 11 - Conservatoire municipal Charles Münch",
"75 - Paris 12 - Conservatoire municipal Paul Dukas",
"75 - Paris 13 - Conservatoire municipal Maurice Ravel",
"75 - Paris 17 - Conservatoire municipal Claude Debussy",
"78 - Versailles - Conservatoire à rayonnement régional de Versailles Grand Parc",
"92 - Rueil-Malmaison - Conservatoire à rayonnement régional",
"92 - Boulogne-Billancourt - Conservatoire à rayonnement régional",
"92 - Clamart - Conservatoire à rayonnement départemental Henri Dutilleux",
"92 - Gennevilliers - Conservatoire Edgar Varèse",
"92 - Meudon - Conservatoire Marcel Dupré",
"93 - Aubervilliers - Conservatoire à rayonnement régional",
"93 - Aulnay-sous-Bois - Conservatoire à rayonnement départemental",
"93 - Drancy - Conservatoire",
"93 - Bobigny - Conservatoire Jean Wiener",
"94 - Créteil - Conservatoire à rayonnement régional Marcel Dadi",
"94 - Saint-Maur-des-Fossés - Conservatoire à rayonnement régional",
"94 - Cachan - Conservatoire à rayonnement départemental du Val-de-Bièvre",
"94 - Villeneuve-Saint-Georges - Conservatoire",
"95 - Cergy-Pontoise - Conservatoire à rayonnement régional",
"Autre",
]
const diplomaTypeOptions = ["fin de cycle 2", "DEM", "CEM", "Autre"]
const emergencyRelationOptions = ["Parent", "Frère - sœur", "Conjoint.e", "Ami", "Autre"]
//const regulationFileUrl = "/contenus/reglement-academie-orchestre.pdf"
const regulationFileUrl = "https://media.orchestre-ile.com/uploads/25_26_ONDIF_reglement_academie_57934f522a.pdf"
function createDiploma() {
return {
type: "",
otherType: "",
discipline: "",
year: "",
establishment: "",
}
}
function createDiplomaErrors() {
return {
type: "",
otherType: "",
discipline: "",
year: "",
establishment: "",
}
}
const quoteForm = reactive({
lastName: "",
firstName: "",
gender: "",
birthDate: "",
birthPlace: "",
nationality: "",
email: "",
phone: "",
address: "",
city: "",
postalCode: "",
country: "",
instrument: "",
previousAcademyParticipation: "",
previousAcademyParticipationCount: "",
trainingLevel: "",
otherTrainingLevel: "",
conservatory: "",
otherConservatory: "",
teacherName: "",
diplomas: [createDiploma(), createDiploma(), createDiploma()],
emergencyLastName: "",
emergencyFirstName: "",
emergencyRelation: "",
emergencyPhone: "",
emergencyEmail: "",
acceptRules: false,
})
const quoteFormErrors = reactive({
lastName: "",
firstName: "",
gender: "",
birthDate: "",
birthPlace: "",
nationality: "",
email: "",
phone: "",
address: "",
city: "",
postalCode: "",
country: "",
instrument: "",
previousAcademyParticipation: "",
previousAcademyParticipationCount: "",
trainingLevel: "",
otherTrainingLevel: "",
conservatory: "",
otherConservatory: "",
teacherName: "",
diplomas: [createDiplomaErrors(), createDiplomaErrors(), createDiplomaErrors()],
emergencyLastName: "",
emergencyFirstName: "",
emergencyRelation: "",
emergencyPhone: "",
emergencyEmail: "",
acceptRules: "",
})
const quoteSubmitting = ref(false)
const quoteSubmitSuccess = ref(false)
const quoteSubmitError = ref("")
const quoteEmailsSent = ref(false)
const requiredFieldLabels = {
lastName: "Le nom est obligatoire.",
firstName: "Le prénom est obligatoire.",
gender: "Le genre est obligatoire.",
birthDate: "La date de naissance est obligatoire.",
birthPlace: "Le lieu de naissance est obligatoire.",
nationality: "La nationalité est obligatoire.",
email: "Ladresse mail est obligatoire.",
phone: "Le téléphone est obligatoire.",
address: "Ladresse est obligatoire.",
city: "La ville est obligatoire.",
postalCode: "Le code postal est obligatoire.",
country: "Le pays de résidence est obligatoire.",
instrument: "Linstrument est obligatoire.",
previousAcademyParticipation: "La participation précédente est obligatoire.",
trainingLevel: "Le niveau de formation est obligatoire.",
conservatory: "Le conservatoire est obligatoire.",
teacherName: "Le nom du professeur dinstrument est obligatoire.",
emergencyLastName: "Le nom du contact durgence est obligatoire.",
emergencyFirstName: "Le prénom du contact durgence est obligatoire.",
emergencyRelation: "Le lien avec le participant est obligatoire.",
emergencyPhone: "Le téléphone du contact durgence est obligatoire.",
emergencyEmail: "Le mail du contact durgence est obligatoire.",
}
function resetQuoteFormErrors() {
Object.keys(quoteFormErrors).forEach((key) => {
if (key === "diplomas") {
quoteFormErrors.diplomas = [createDiplomaErrors(), createDiplomaErrors(), createDiplomaErrors()]
} else {
quoteFormErrors[key] = ""
}
})
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
function validateQuoteForm() {
resetQuoteFormErrors()
let isValid = true
Object.entries(requiredFieldLabels).forEach(([field, message]) => {
if (!quoteForm[field]) {
quoteFormErrors[field] = message
isValid = false
}
})
if (quoteForm.email && !isValidEmail(quoteForm.email)) {
quoteFormErrors.email = "Ladresse mail nest pas valide."
isValid = false
}
if (quoteForm.emergencyEmail && !isValidEmail(quoteForm.emergencyEmail)) {
quoteFormErrors.emergencyEmail = "Le mail du contact durgence nest pas valide."
isValid = false
}
if (quoteForm.previousAcademyParticipation === "oui") {
const count = Number(quoteForm.previousAcademyParticipationCount)
if (!quoteForm.previousAcademyParticipationCount) {
quoteFormErrors.previousAcademyParticipationCount = "Le nombre de participations est obligatoire."
isValid = false
} else if (!Number.isInteger(count) || count <= 0) {
quoteFormErrors.previousAcademyParticipationCount = "Le nombre de participations doit être supérieur à 0."
isValid = false
}
}
if (quoteForm.trainingLevel === "Autre" && !quoteForm.otherTrainingLevel) {
quoteFormErrors.otherTrainingLevel = "Lautre formation est obligatoire."
isValid = false
}
if (quoteForm.conservatory === "Autre" && !quoteForm.otherConservatory) {
quoteFormErrors.otherConservatory = "Lautre conservatoire est obligatoire."
isValid = false
}
quoteForm.diplomas.forEach((diploma, index) => {
const hasDiplomaData = Boolean(diploma.type || diploma.otherType || diploma.discipline || diploma.year || diploma.establishment)
if (!hasDiplomaData) {
return
}
if (!diploma.type) {
quoteFormErrors.diplomas[index].type = "Le type du diplôme est obligatoire."
isValid = false
}
if (diploma.type === "Autre" && !diploma.otherType) {
quoteFormErrors.diplomas[index].otherType = "Lautre type de diplôme est obligatoire."
isValid = false
}
if (!diploma.discipline) {
quoteFormErrors.diplomas[index].discipline = "La discipline est obligatoire."
isValid = false
}
if (!diploma.year) {
quoteFormErrors.diplomas[index].year = "Lannée est obligatoire."
isValid = false
}
if (!diploma.establishment) {
quoteFormErrors.diplomas[index].establishment = "Létablissement est obligatoire."
isValid = false
}
})
if (!quoteForm.acceptRules) {
quoteFormErrors.acceptRules = "Vous devez accepter le règlement."
isValid = false
}
return isValid
}
function resetQuoteForm() {
Object.assign(quoteForm, {
lastName: "",
firstName: "",
gender: "",
birthDate: "",
birthPlace: "",
nationality: "",
email: "",
phone: "",
address: "",
city: "",
postalCode: "",
country: "",
instrument: "",
previousAcademyParticipation: "",
previousAcademyParticipationCount: "",
trainingLevel: "",
otherTrainingLevel: "",
conservatory: "",
otherConservatory: "",
teacherName: "",
diplomas: [createDiploma(), createDiploma(), createDiploma()],
emergencyLastName: "",
emergencyFirstName: "",
emergencyRelation: "",
emergencyPhone: "",
emergencyEmail: "",
acceptRules: false,
})
}
async function submitQuoteRequest() {
quoteSubmitSuccess.value = false
quoteSubmitError.value = ""
quoteEmailsSent.value = false
if (!validateQuoteForm()) {
return
}
quoteSubmitting.value = true
try {
const response = await $fetch("/api/projet-academie", {
method: "POST",
body: {
...quoteForm,
diplomas: quoteForm.diplomas.map((diploma) => ({ ...diploma })),
},
})
quoteSubmitSuccess.value = true
quoteEmailsSent.value = Boolean(response?.emailsSent)
resetQuoteForm()
resetQuoteFormErrors()
} catch (error) {
quoteSubmitError.value = error?.data?.statusMessage || "Lenvoi de la candidature a échoué."
} finally {
quoteSubmitting.value = false
}
}
</script>
<style lang="scss">
.breadcrum_wp {
padding-top: 30px;
}
// =======================
// SPÉCIFIQUE À CETTE PAGE
// =======================
.inscription-page {
.fiche_description {
display: flex;
justify-content: center;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 640px;
display: flex;
flex-direction: column;
}
}
.contact_spe_wp {
background-color: var(--c-background-jaune-clair);
margin-top: 50px;
margin-bottom: 20px;
}
}
</style>

View File

@@ -33,7 +33,7 @@
<div class="programmer-orchestre-page text-on-surface">
<!-- FORM -->
<form class="space-y-6" @submit.prevent="submitQuoteRequest">
<div class="px-12 py-8 max-w-7xl mx-auto">
<div class="px-4 md:px-8 lg:px-16 py-8 max-w-7xl mx-auto">
<!-- SOUS-TITRE -->
<section class="mb-8">
<div class="flex items-center gap-4 mb-8">

View File

@@ -70,6 +70,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'entreprise-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -197,6 +199,8 @@
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'particulier-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -223,6 +225,8 @@
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -70,6 +70,8 @@
button-tone="invert"
:ensavoirplus-target="`texte_cache_${index + 3}`"
ensavoirplus-group="projets-details"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -198,6 +200,8 @@ et de la programmation jeune public"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -64,6 +64,8 @@
button-tone="invert"
:ensavoirplus-target="`texte_cache_${index + 3}`"
ensavoirplus-group="valeurs-details"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -170,6 +172,8 @@
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'academie-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -169,6 +171,8 @@
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -0,0 +1,244 @@
<template>
<div class="academie--page">
<!-- ================== -->
<!-- FILS D'ARIANE -->
<!-- ================== -->
<PageSection tone="" content-size="default" class="breadcrum_wp">
<Breadcrumb/>
</PageSection>
<!-- ================== -->
<!-- EN-TêTE -->
<!-- ================== -->
<section class="fiche_header_simple_wp">
<div class="fiche_header_wp_gauche"></div>
<div class="fiche_header_inner">
<div class="fiche_header_titres">
<div>
<DsHeading as="h1" tone="default" textcase="uppercase" class="concert-card__title">
{{academie_donnees_servies.header_titre}}
</DsHeading>
</div>
<DsText as="p" align="justify">
{{academie_donnees_servies.header_text}}
</DsText>
</div>
<div class="fiche_header_img">
<DsMedia
v-if="academie_donnees_servies?.header_illustration?.url"
:src="academie_donnees_servies.header_illustration.url"
:alt="academie_donnees_servies.header_illustration.alternativeText || ''"
fit="cover"
ratio="square"
/>
<div v-else class="img_placeholder" aria-hidden="true" />
</div>
</div>
<div class="fiche_header_wp_droite"></div>
</section>
<!-- ================== -->
<!-- AFFICHAGE DES PARTIE EN DÉCALLAGE AVEC LE CONTENU PROVENANT DE STRAPI -->
<!-- ================== -->
<template v-for="(t, index) in tiroirs" :key="index">
<PageSection :content="false" :class="{ 'decalage-section--spaced': index > 0 }">
<Decalage
:tone="t.decalage_couleur"
title-tone="invert"
:position="t.decalage_sens"
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'academie-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
</template>
<DsText as="p" tone="invert" :align="t.decalage_sens">
{{ t.decalage_texte }}
</DsText>
</Decalage>
</PageSection>
<PageSection
:id="`texte_cache_${index + 3}`"
:data-ensavoirplus-group="t.tiroir_ouvert ? undefined : 'academie-details'"
tone=""
content-size="default"
padded_size=""
:class="{ 'decalage_ensavoirplus--hidden': !t.tiroir_ouvert }"
>
<section v-if="t.tiroir_galerie.length" class="img-gallery_wp">
<div class="img-gallery">
<DsMedia
v-for="img in t.tiroir_galerie"
:key="img.id || img.url"
:src="img.url"
:alt="img.alternativeText"
/>
</div>
</section>
<SectionContent class="fiche_description">
<StrapiBlocksConvert :blocks="t.tiroir_description" />
</SectionContent>
<section v-if="t.tiroir_videos.length" class="youtube_wp">
<div class="youtube-list">
<div v-for="v in t.tiroir_videos" :key="v.id" class="youtube-item">
<iframe
:src="v.lien_youtube"
title="Vidéo YouTube"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
/>
</div>
</div>
</section>
</PageSection>
</template>
<!-- ================== -->
<!-- CONTACT -->
<!-- ================== -->
<PageSection tone="" content-size="default" padded_size="md" class="contact_spe_wp">
<ContactSpecifique
titre="Renseignements Académie d'Orchestre 2026"
nom="Zoë Crampon"
poste="Service action culturelle"
numero="06 82 73 65 01"
mail="zoe.crampon@orchestre-ile.com"
adresse="Orchestre national dÎle-de-France, Service action culturelle, 19, rue des Écoles, 94140 Alfortville"
/>
</PageSection>
</div>
</template>
<script setup>
import DsHeading from '@root/design-system/primitives/DsHeading.vue'
import DsText from '@root/design-system/primitives/DsText.vue'
import DsMedia from '@root/design-system/primitives/DsMedia.vue'
import { computed } from 'vue'
// ======================================
// RÉCUPÉRATION DES DONNÉES DANS STRAPI
// ======================================
const endpoint = "/api/__strapi__/academie"
const populate = {
header_illustration: true,
tiroirs_academie: {
decalage_parametres: true,
tiroir_galerie: true,
tiroir_videos: true,
},
}
const { items: academie, pending, error } = useStrapi(
endpoint,
{
locale: "fr-FR",
populate,
limit: 1,
}
)
// ======================================
// PRÉPARATION DES DONNÉES POUR AFFICHAGE DANS LA PAGE
// ======================================
const academie_donnees_servies = computed(() => academie.value?.[0] || {})
const tiroirs = computed(() =>
(academie.value?.[0]?.tiroirs_academie || []).map((tiroir_item) => ({
decalage_titre: tiroir_item.decalage_parametres?.decalage_titre,
decalage_texte: tiroir_item.decalage_parametres?.decalage_texte,
tiroir_ouvert: Boolean(tiroir_item.decalage_parametres?.tiroir_ouvert),
decalage_sens:
tiroir_item.decalage_parametres?.decalage_sens === "droite" ? "right" : "left",
decalage_couleur:
tiroir_item.decalage_parametres?.decalage_couleur === "rouge"
? "brand"
: tiroir_item.decalage_parametres?.decalage_couleur === "vert clair"
? "brandreverse"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,
url: tiroir_item_img.url,
alt: tiroir_item_img.alternativeText,
})),
tiroir_videos: (tiroir_item.tiroir_videos || [])
.map((tiroir_item_video) => {
const id = getYoutubeId(tiroir_item_video?.lien_youtube)
if (!id) return null
return {
id: tiroir_item_video.id || id,
lien_youtube: `https://www.youtube-nocookie.com/embed/${id}?rel=0&modestbranding=1&iv_load_policy=3&playsinline=1`,
}
})
.filter(Boolean),
}))
)
function getYoutubeId(url = "") {
try {
const u = new URL(url)
if (u.hostname.includes("youtu.be")) return u.pathname.slice(1)
if (u.pathname.startsWith("/shorts/")) return u.pathname.split("/")[2]
if (u.pathname.startsWith("/embed/")) return u.pathname.split("/")[2]
return u.searchParams.get("v")
} catch {
return null
}
}
</script>
<style lang="scss">
// =======================
// SPÉCIFIQUE À CETTE PAGE
// =======================
.academie--page {
.chiffres_wp {
background-color: var(--c-background-vert);
margin-bottom: 50px;
}
.fiche_description {
display: flex;
justify-content: center;
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
> * {
max-width: 640px;
display: flex;
flex-direction: column;
}
}
.contact_spe_wp {
background-color: var(--c-background-jaune-clair);
margin-top: 50px;
margin-bottom: 40px;
}
}
/* ============================ */
/* GALERIES */
/* ============================ */
</style>

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'chantons-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -170,6 +172,8 @@ et de la programmation jeune public"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'insertion-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -170,6 +172,8 @@ et de la programmation jeune public"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'enfance-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -170,6 +172,8 @@ et de la programmation jeune public"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -65,6 +65,7 @@
:ensavoirplus-target="`texte_cache_${index + 3}`"
ensavoirplus-group="scolaires-details"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -189,6 +190,7 @@ et de la programmation jeune public"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -54,6 +54,8 @@
button-tone="invert"
:ensavoirplus-target="t.tiroir_ouvert ? undefined : `texte_cache_${index + 3}`"
:ensavoirplus-group="t.tiroir_ouvert ? undefined : 'hopital-details'"
:lien-cta="t.lien_cta"
:text-cta="t.text_cta"
>
<template #title>
{{ t.decalage_titre }}
@@ -170,6 +172,8 @@ et de la programmation jeune public"
: tiroir_item.decalage_parametres?.decalage_couleur === "jaune"
? "jaune"
: "dark",
lien_cta: tiroir_item.decalage_parametres?.lien_cta || "",
text_cta: tiroir_item.decalage_parametres?.text_cta || undefined,
tiroir_description: tiroir_item.tiroir_description,
tiroir_galerie: (tiroir_item.tiroir_galerie || []).map((tiroir_item_img) => ({
id: tiroir_item_img.id,

View File

@@ -435,7 +435,7 @@
rel="noopener noreferrer"
>
<div class="w-full aspect-[4/3] bg-surface-container-low rounded-lg flex items-center justify-center group-hover:bg-primary-container transition-colors">
<span class="material-symbols-outlined text-3xl text-outline group-hover:text-primary">insert_drive_file</span>
<span class="material-symbols-outlined text-3xl text-outline group-hover:text-primary">download</span>
</div>
<div class="text-center">
<p v-if="document.caption" class="text-xs font-bold uppercase tracking-tighter">{{ document.caption }}</p>
@@ -718,7 +718,6 @@
}
if (!programmerDebugData.value) return
console.log("pro-programmer Strapi content", programmerDebugData.value)
})