Les pages de l'application sont organisées dans le dossier app/pages/ et suivent le système de routing file-based de Nuxt. Chaque fichier ou dossier représente une route accessible.
[reqID]/contract.vue)Page principale de gestion d'un contrat qui affiche le statut actuel et permet différentes actions selon l'état du contrat.
/{reqID}/contract
auth : Authentification requiserequest-id : Validation de l'ID de la requête| Statut | Description | Actions disponibles |
|---|---|---|
pending | Contrat en attente d'envoi | Renvoyer le contrat, Contacter le support |
waiting_signature | En attente de signature | Renvoyer le contrat, Contacter le support |
expired | Lien de signature expiré | Demander un nouveau contrat, Remplir le questionnaire de non-souscription |
waiting_payment | Signé, en attente de paiement | Procéder au paiement, Télécharger le contrat |
complete | Signé et payé | Télécharger le contrat, Contacter le support |
rejected | Contrat rejeté | Demander un nouveau contrat, Contacter le support |
ContractSummaryCard : Affichage du résumé du contratContactSupportModal : Modal de contact du supportUAlert : Alertes de statutUButton : Boutons d'actionAffiche une alerte contextuelle selon le statut du contrat avec les informations pertinentes (date d'expiration, actions possibles, etc.).
Affiche les détails du contrat et les informations du client via le composant ContractSummaryCard.
Pour les contrats expirés (waiting_signature + contractExpired), une section invite l'utilisateur à remplir un questionnaire pour comprendre les raisons de non-souscription.
<template>
<UCard class="max-w-6xl mx-auto lg:my-16 shadow-lg">
<!-- Alerte de statut -->
<LazyUAlert
v-if="signatureStatus === 'waiting_signature' && !contractExpired"
class="mb-8"
title="Signature en attente"
color="warning"
/>
<!-- Résumé du contrat -->
<ContractSummaryCard :request="request" class="mb-8" />
<!-- Actions -->
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<LazyUButton
v-if="contractExpired"
@click="requestNewContract"
label="Demander un nouveau contrat"
/>
</div>
</UCard>
</template>
[reqID]/refus-souscription.vue)Page de questionnaire permettant de recueillir les raisons pour lesquelles un client n'a pas souhaité souscrire au contrat.
/{reqID}/refus-souscription
auth : Authentification requiseContractSummaryCard : Affichage du résumé du contrat concernéUForm : Formulaire avec validation ZodUCheckboxGroup : Sélection multiple des raisonsUTextarea : Commentaires additionnelsUAlert : Message de confirmation d'envoiAffiche le résumé du contrat pour que l'utilisateur sache de quel contrat il s'agit.
Zone de texte optionnelle pour des détails supplémentaires.
/api/requests/{reqID}const refusalSchema = z.object({
reasons: z.array(z.string()).min(1, "Vous devez sélectionner au moins une raison"),
comment: z.string().optional(),
});
| Code | Description | Action |
|---|---|---|
| 403 | Questionnaire déjà rempli | Redirection vers la page de contrat |
| Autre | Erreur serveur | Affichage d'un toast d'erreur |
<template>
<UCard class="max-w-6xl mx-auto lg:my-16 shadow-lg">
<div class="text-center mb-6">
<UIcon name="i-heroicons-document-minus" class="w-12 h-12 text-orange-500 mx-auto mb-4"/>
<h3 class="text-xl font-semibold">Questionnaire de non-souscription</h3>
</div>
<!-- Résumé du contrat -->
<ContractSummaryCard v-if="request" :request="request" class="mb-8" />
<!-- Formulaire -->
<UForm :schema="refusalSchema" :state="form" @submit="handleSubmit">
<UFormField name="reasons" required label="Pourquoi ne souhaitez-vous pas souscrire ?">
<UCheckboxGroup v-model="form.reasons" :items="formatedReasons" />
</UFormField>
<UFormField label="Commentaires additionnels (optionnel)" name="comment">
<UTextarea v-model="form.comment" />
</UFormField>
<UButton type="submit" :loading="isSubmitting">
Envoyer le questionnaire
</UButton>
</UForm>
</UCard>
</template>
/[reqID]/refus-souscription/[reqID]/contractuseFetch pour les appels API avec clés de cacheinitializeFromCookies()isSubmitting, loadResend, etc.)const { getToken } = useAuth();
const token = getToken();
const route = useRoute();
const reqID = route.params.reqID as string;
const { data: request } = await useFetch(`/api/requests/${reqID}`, {
key: "currentRequest",
headers: {
Authorization: `Bearer ${token}`,
},
});
const loadResend = ref(false);
const toast = useToast();
const resendContract = async () => {
try {
loadResend.value = true;
const response = await useFetch(`/api/requests/${reqID}/send-contract`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.data.value) {
toast.add({
title: "Votre contrat a été renvoyé",
description: `Un email a été envoyé à l'adresse ${request.value?.contact.email}`,
color: "success",
});
}
} catch (error: any) {
console.error("Erreur lors de l'envoi:", error);
toast.add({
title: "Une erreur est survenue",
description: error.data?.detail,
color: "error",
});
} finally {
loadResend.value = false;
}
};
Vérifier que initializeFromCookies() est appelé dans onMounted().
Vérifier que le token est valide et que l'API est accessible.
Vérifier la validation Zod et les messages d'erreur dans la console.