388 lines
9.4 KiB
Vue
388 lines
9.4 KiB
Vue
<template>
|
|
<div class="fiche-page">
|
|
<div class="fiche-inner">
|
|
|
|
<!-- En-tête -->
|
|
<div class="fiche-header">
|
|
<h1>{{ isEdit ? 'Modifier ma fiche' : 'Ma fiche' }}</h1>
|
|
<p class="fiche-lead">3 lignes pour te présenter. Le reste se passe entre nous.</p>
|
|
</div>
|
|
|
|
<!-- Formulaire -->
|
|
<form class="fiche-form" @submit.prevent="submit" novalidate>
|
|
|
|
<!-- Nom -->
|
|
<div class="field-group">
|
|
<label for="nom">
|
|
Prénom <span class="required">*</span>
|
|
</label>
|
|
<input
|
|
id="nom"
|
|
v-model="form.nom"
|
|
type="text"
|
|
placeholder="Ex : Camille"
|
|
required
|
|
minlength="2"
|
|
maxlength="50"
|
|
:disabled="loading"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Besoin -->
|
|
<div class="field-group">
|
|
<div class="label-row">
|
|
<label for="besoin">
|
|
Mon besoin actuel <span class="required">*</span>
|
|
</label>
|
|
<button type="button" class="tooltip-trigger" @click="toggleTip('besoin')" aria-label="C'est quoi un besoin ?">?</button>
|
|
</div>
|
|
<details v-if="activeTip === 'besoin'" class="tooltip-block" open>
|
|
<summary class="sr-only">Aide</summary>
|
|
<p>Un besoin, c'est ce qui te manque pour avancer. Ca peut etre concret (un coup de main sur un dossier) ou plus large (clarifier ou tu vas). Pas grave si c'est flou - la rencontre IRL aide a le preciser.</p>
|
|
</details>
|
|
<textarea
|
|
id="besoin"
|
|
v-model="form.besoin"
|
|
rows="3"
|
|
placeholder="Ex : J'ai besoin d'aide pour structurer mon offre de prestation"
|
|
required
|
|
minlength="5"
|
|
maxlength="300"
|
|
:disabled="loading"
|
|
/>
|
|
<span class="char-count" :class="{ 'char-warn': form.besoin.length > 260 }">
|
|
{{ form.besoin.length }}/300
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Offre -->
|
|
<div class="field-group">
|
|
<div class="label-row">
|
|
<label for="offre">
|
|
Ce que j'offre a la communaute <span class="required">*</span>
|
|
</label>
|
|
<button type="button" class="tooltip-trigger" @click="toggleTip('offre')" aria-label="C'est quoi une offre ?">?</button>
|
|
</div>
|
|
<details v-if="activeTip === 'offre'" class="tooltip-block" open>
|
|
<summary class="sr-only">Aide</summary>
|
|
<p>Une offre, c'est une competence, une experience ou une qualite que tu peux partager. Ce que les autres viennent chercher chez toi naturellement.</p>
|
|
</details>
|
|
<textarea
|
|
id="offre"
|
|
v-model="form.offre"
|
|
rows="3"
|
|
placeholder="Ex : Je peux partager mon expérience en facilitation de groupe"
|
|
required
|
|
minlength="5"
|
|
maxlength="300"
|
|
:disabled="loading"
|
|
/>
|
|
<span class="char-count" :class="{ 'char-warn': form.offre.length > 260 }">
|
|
{{ form.offre.length }}/300
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Hashtags -->
|
|
<div class="field-group">
|
|
<label for="hashtags">
|
|
Mots-clés
|
|
<span class="label-hint">(optionnel, 3 max, séparés par des virgules)</span>
|
|
</label>
|
|
<input
|
|
id="hashtags"
|
|
v-model="form.hashtagsRaw"
|
|
type="text"
|
|
placeholder="Ex : business, écriture, écologie"
|
|
maxlength="120"
|
|
:disabled="loading"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Erreur serveur -->
|
|
<div v-if="error" class="server-error" role="alert">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<!-- Bouton -->
|
|
<button type="submit" class="submit-btn" :disabled="loading">
|
|
{{ isEdit ? (loading ? 'Modification...' : 'Enregistrer les modifications') : (loading ? 'Envoi en cours...' : 'Ajouter ma fiche') }}
|
|
</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const route = useRoute()
|
|
const editId = computed(() => route.query.id ? Number(route.query.id) : null)
|
|
const isEdit = computed(() => editId.value !== null)
|
|
|
|
const form = ref({ nom: '', besoin: '', offre: '', hashtagsRaw: '' })
|
|
const error = ref('')
|
|
const loading = ref(false)
|
|
const activeTip = ref<'besoin' | 'offre' | null>(null)
|
|
|
|
useHead({ title: computed(() => isEdit.value ? 'Modifier ma fiche — Co-développement' : 'Ma fiche — Co-développement') })
|
|
|
|
onMounted(async () => {
|
|
if (!isEdit.value) return
|
|
try {
|
|
const fiche = await $fetch<any>(`/api/codev/fiches/${editId.value}`)
|
|
form.value.nom = fiche.nom
|
|
form.value.besoin = fiche.besoin
|
|
form.value.offre = fiche.offre
|
|
form.value.hashtagsRaw = fiche.hashtags.join(', ')
|
|
} catch {
|
|
error.value = 'Impossible de charger la fiche, elle a peut-etre ete supprimee.'
|
|
}
|
|
})
|
|
|
|
function toggleTip(field: 'besoin' | 'offre') {
|
|
activeTip.value = activeTip.value === field ? null : field
|
|
}
|
|
|
|
async function submit() {
|
|
error.value = ''
|
|
loading.value = true
|
|
try {
|
|
const hashtags = form.value.hashtagsRaw
|
|
.split(',')
|
|
.map((h) => h.trim().toLowerCase().replace(/^#/, ''))
|
|
.filter(Boolean)
|
|
.slice(0, 3)
|
|
|
|
const payload = {
|
|
nom: form.value.nom,
|
|
besoin: form.value.besoin,
|
|
offre: form.value.offre,
|
|
hashtags,
|
|
}
|
|
|
|
if (isEdit.value) {
|
|
await $fetch(`/api/codev/fiches/${editId.value}`, { method: 'PATCH', body: payload })
|
|
} else {
|
|
await $fetch('/api/codev/fiches', { method: 'POST', body: payload })
|
|
}
|
|
await navigateTo('/codev/carto')
|
|
} catch (e: any) {
|
|
error.value = e?.data?.message || e?.statusMessage || 'Erreur, reessaie'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* ── Layout ── */
|
|
|
|
.fiche-page {
|
|
min-height: 100vh;
|
|
background: var(--nav-bg, #fafafa);
|
|
padding: 1.5rem 1rem 4rem;
|
|
}
|
|
|
|
.fiche-inner {
|
|
max-width: 480px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.75rem;
|
|
}
|
|
|
|
/* ── En-tête ── */
|
|
|
|
.fiche-header h1 {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--nav-text, #1a1a2e);
|
|
margin: 0 0 0.375rem;
|
|
}
|
|
|
|
.fiche-lead {
|
|
font-size: 0.9rem;
|
|
color: var(--nav-text-muted, #6b7280);
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* ── Formulaire ── */
|
|
|
|
.fiche-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.25rem;
|
|
}
|
|
|
|
/* ── Champ ── */
|
|
|
|
.field-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.375rem;
|
|
}
|
|
|
|
.field-group label {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--nav-text, #1a1a2e);
|
|
}
|
|
|
|
.label-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.label-hint {
|
|
font-weight: 400;
|
|
font-size: 0.8rem;
|
|
color: var(--nav-text-muted, #6b7280);
|
|
margin-left: 0.25rem;
|
|
}
|
|
|
|
.required {
|
|
color: #c0392b;
|
|
}
|
|
|
|
.field-group input[type="text"],
|
|
.field-group input[type="password"],
|
|
.field-group textarea {
|
|
width: 100%;
|
|
padding: 0.75rem 0.875rem;
|
|
border: 1px solid var(--border-color, #d0d4dc);
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
color: var(--nav-text, #1a1a2e);
|
|
background: var(--nav-surface, #ffffff);
|
|
font-family: inherit;
|
|
transition: border-color 0.15s, box-shadow 0.15s;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.field-group input:focus,
|
|
.field-group textarea:focus {
|
|
outline: none;
|
|
border-color: var(--nav-primary-solid, #1B4436);
|
|
box-shadow: 0 0 0 2px rgba(27, 68, 54, 0.15);
|
|
}
|
|
|
|
.field-group input:disabled,
|
|
.field-group textarea:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.field-group textarea {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
.char-count {
|
|
font-size: 0.75rem;
|
|
color: var(--nav-text-muted, #6b7280);
|
|
text-align: right;
|
|
}
|
|
|
|
.char-warn {
|
|
color: #e67e22;
|
|
}
|
|
|
|
/* ── Tooltip ── */
|
|
|
|
.tooltip-trigger {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 20px;
|
|
height: 20px;
|
|
background: var(--nav-surface, #ffffff);
|
|
border: 1px solid var(--border-color, #d0d4dc);
|
|
border-radius: 50%;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
color: var(--nav-text-muted, #6b7280);
|
|
cursor: pointer;
|
|
padding: 0;
|
|
line-height: 1;
|
|
flex-shrink: 0;
|
|
transition: border-color 0.15s, color 0.15s;
|
|
}
|
|
|
|
.tooltip-trigger:hover {
|
|
border-color: var(--nav-primary-solid, #1B4436);
|
|
color: var(--nav-primary-solid, #1B4436);
|
|
}
|
|
|
|
.tooltip-block {
|
|
background: var(--nav-surface, #ffffff);
|
|
border: 1px solid var(--border-color, #d0d4dc);
|
|
border-radius: 8px;
|
|
padding: 0.75rem 0.875rem;
|
|
font-size: 0.85rem;
|
|
color: var(--nav-text-muted, #6b7280);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.tooltip-block p {
|
|
margin: 0;
|
|
}
|
|
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
border: 0;
|
|
}
|
|
|
|
/* ── Erreur serveur ── */
|
|
|
|
.server-error {
|
|
padding: 0.75rem 0.875rem;
|
|
background: #fdf0ee;
|
|
border: 1px solid #e74c3c;
|
|
border-radius: 8px;
|
|
font-size: 0.875rem;
|
|
color: #c0392b;
|
|
}
|
|
|
|
/* ── Bouton ── */
|
|
|
|
.submit-btn {
|
|
width: 100%;
|
|
padding: 0.875rem 1rem;
|
|
background: var(--nav-primary-solid, #1B4436);
|
|
color: #ffffff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: opacity 0.15s;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.submit-btn:hover:not(:disabled) {
|
|
opacity: 0.88;
|
|
}
|
|
|
|
.submit-btn:disabled {
|
|
opacity: 0.45;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ── Responsive ── */
|
|
|
|
@media (max-width: 480px) {
|
|
.fiche-page {
|
|
padding: 1.25rem 1rem 3rem;
|
|
}
|
|
}
|
|
</style>
|