cinqcinqdev-seo 0.1.30 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/assets/admin-tw.css +1 -1
- package/dist/runtime/components/admin-cms/ContentCalendar.vue +7 -7
- package/dist/runtime/components/admin-cms/DynamicRenderer.vue +2 -2
- package/dist/runtime/components/admin-cms/Navbar.vue +5 -5
- package/dist/runtime/components/admin-cms/Sidebar.vue +8 -8
- package/dist/runtime/pages/admin/account.vue +31 -31
- package/dist/runtime/pages/admin/branding.vue +14 -14
- package/dist/runtime/pages/admin/calendar.vue +1 -1
- package/dist/runtime/pages/admin/content-library.vue +16 -16
- package/dist/runtime/pages/admin/editor/[id].vue +114 -114
- package/dist/runtime/pages/admin/index.vue +20 -20
- package/dist/runtime/pages/admin/pages/[type].vue +54 -54
- package/dist/runtime/pages/admin/personas/[id].vue +9 -9
- package/dist/runtime/pages/admin/personas/index.vue +6 -6
- package/dist/runtime/pages/admin/products.vue +7 -7
- package/dist/runtime/pages/admin/seo.vue +36 -36
- package/dist/runtime/pages/admin/setup.vue +6 -6
- package/dist/runtime/pages/admin/strategies/[id].vue +7 -7
- package/dist/runtime/pages/admin/strategies/index.vue +7 -7
- package/dist/runtime/pages/admin/todos.vue +8 -8
- package/package.json +1 -1
|
@@ -44,52 +44,52 @@ const { data: stats } = await useAsyncData("admin-dashboard-stats", async () =>
|
|
|
44
44
|
|
|
45
45
|
<!-- Header -->
|
|
46
46
|
<div class="mb-8">
|
|
47
|
-
<h1 class="text-2xl font-black text-
|
|
48
|
-
<p class="text-sm text-
|
|
47
|
+
<h1 class="text-2xl font-black text-mgray">Tableau de bord</h1>
|
|
48
|
+
<p class="text-sm text-mgray-400 mt-1">Gérez votre contenu et vos pages.</p>
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<!-- Stats row -->
|
|
52
52
|
<div class="grid grid-cols-4 gap-4 mb-10">
|
|
53
|
-
<div class="bg-
|
|
54
|
-
<p class="text-[10px] font-black uppercase tracking-widest text-
|
|
55
|
-
<p class="text-3xl font-black text-
|
|
53
|
+
<div class="bg-background rounded-2xl p-5 border border-mgray-100 shadow-sm">
|
|
54
|
+
<p class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2">Total pages</p>
|
|
55
|
+
<p class="text-3xl font-black text-mgray">{{ stats?.total ?? 0 }}</p>
|
|
56
56
|
</div>
|
|
57
|
-
<div class="bg-
|
|
58
|
-
<p class="text-[10px] font-black uppercase tracking-widest text-
|
|
57
|
+
<div class="bg-background rounded-2xl p-5 border border-mgray-100 shadow-sm">
|
|
58
|
+
<p class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2">Publiées</p>
|
|
59
59
|
<p class="text-3xl font-black text-green-600">{{ stats?.published ?? 0 }}</p>
|
|
60
60
|
</div>
|
|
61
|
-
<div class="bg-
|
|
62
|
-
<p class="text-[10px] font-black uppercase tracking-widest text-
|
|
61
|
+
<div class="bg-background rounded-2xl p-5 border border-mgray-100 shadow-sm">
|
|
62
|
+
<p class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2">Brouillons</p>
|
|
63
63
|
<p class="text-3xl font-black text-amber-500">{{ stats?.drafts ?? 0 }}</p>
|
|
64
64
|
</div>
|
|
65
|
-
<div class="bg-
|
|
66
|
-
<p class="text-[10px] font-black uppercase tracking-widest text-
|
|
67
|
-
<p class="text-3xl font-black text-
|
|
65
|
+
<div class="bg-background rounded-2xl p-5 border border-mgray-100 shadow-sm">
|
|
66
|
+
<p class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2">Templates</p>
|
|
67
|
+
<p class="text-3xl font-black text-primary">{{ stats?.templates ?? 0 }}</p>
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<!-- Page types grid -->
|
|
72
72
|
<div class="mb-3 flex items-center justify-between">
|
|
73
|
-
<h2 class="text-[11px] font-black uppercase tracking-widest text-
|
|
73
|
+
<h2 class="text-[11px] font-black uppercase tracking-widest text-mgray-400">Types de pages</h2>
|
|
74
74
|
</div>
|
|
75
75
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
76
76
|
<NuxtLink
|
|
77
77
|
v-for="type in pageTypes"
|
|
78
78
|
:key="type.id"
|
|
79
79
|
:to="`${basePath}/pages/${type.id}`"
|
|
80
|
-
class="group bg-
|
|
80
|
+
class="group bg-background rounded-2xl p-6 border border-mgray-100 shadow-sm hover:border-primary-300 hover:shadow-md transition-all"
|
|
81
81
|
>
|
|
82
82
|
<div class="flex items-start justify-between mb-4">
|
|
83
|
-
<div class="w-9 h-9 rounded-xl bg-
|
|
84
|
-
<svg class="text-
|
|
83
|
+
<div class="w-9 h-9 rounded-xl bg-mgray-50 group-hover:bg-primary-100 flex items-center justify-center transition-colors">
|
|
84
|
+
<svg class="text-mgray-400 group-hover:text-primary transition-colors" style="width:18px;height:18px" fill="none" stroke="currentColor" viewBox="0 0 24 24" v-html="type.svgPath || defaultIcons[type.id] || defaultIcons.blog_article"></svg>
|
|
85
85
|
</div>
|
|
86
|
-
<span class="text-[10px] font-black px-2 py-1 rounded-full bg-
|
|
86
|
+
<span class="text-[10px] font-black px-2 py-1 rounded-full bg-mgray-50 text-mgray-400">
|
|
87
87
|
{{ stats?.byType?.[type.id] ?? 0 }}
|
|
88
88
|
</span>
|
|
89
89
|
</div>
|
|
90
|
-
<h3 class="font-black text-
|
|
91
|
-
<p class="text-[11px] text-
|
|
92
|
-
<div class="mt-4 flex items-center gap-1 text-[10px] font-bold text-
|
|
90
|
+
<h3 class="font-black text-mgray text-sm group-hover:text-primary transition-colors">{{ type.label }}</h3>
|
|
91
|
+
<p class="text-[11px] text-mgray-400 mt-1">{{ type.desc }}</p>
|
|
92
|
+
<div class="mt-4 flex items-center gap-1 text-[10px] font-bold text-primary opacity-0 group-hover:opacity-100 transition-opacity">
|
|
93
93
|
Gérer <span>→</span>
|
|
94
94
|
</div>
|
|
95
95
|
</NuxtLink>
|
|
@@ -107,45 +107,45 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
107
107
|
<!-- Header -->
|
|
108
108
|
<div class="mb-8 flex items-center justify-between">
|
|
109
109
|
<div>
|
|
110
|
-
<div class="flex items-center gap-2 text-[11px] text-
|
|
111
|
-
<NuxtLink :to="basePath" class="hover:text-
|
|
110
|
+
<div class="flex items-center gap-2 text-[11px] text-mgray-400 mb-2">
|
|
111
|
+
<NuxtLink :to="basePath" class="hover:text-mgray-600 transition-colors">Tableau de bord</NuxtLink>
|
|
112
112
|
<span>/</span>
|
|
113
|
-
<span class="text-
|
|
113
|
+
<span class="text-mgray-600 font-medium">{{ typeLabel }}</span>
|
|
114
114
|
</div>
|
|
115
|
-
<h1 class="text-2xl font-black text-
|
|
116
|
-
<p class="text-sm text-
|
|
115
|
+
<h1 class="text-2xl font-black text-mgray">{{ typeLabel }}</h1>
|
|
116
|
+
<p class="text-sm text-mgray-400 mt-1">{{ pages?.length ?? 0 }} page{{ (pages?.length ?? 0) > 1 ? "s" : "" }}</p>
|
|
117
117
|
</div>
|
|
118
118
|
<button @click="isCreating = true"
|
|
119
|
-
class="bg-
|
|
119
|
+
class="bg-primary text-white px-5 py-2.5 rounded-xl text-sm font-bold hover:brightness-90 transition shadow-lg shadow-primary-200 active:scale-95">
|
|
120
120
|
+ Nouvelle page
|
|
121
121
|
</button>
|
|
122
122
|
</div>
|
|
123
123
|
|
|
124
124
|
<!-- Table -->
|
|
125
|
-
<div class="bg-
|
|
125
|
+
<div class="bg-background border border-mgray-100 rounded-2xl shadow-sm overflow-hidden">
|
|
126
126
|
<div v-if="!pages?.length" class="py-24 text-center">
|
|
127
|
-
<svg class="w-10 h-10 mx-auto text-
|
|
128
|
-
<p class="text-sm font-semibold text-
|
|
129
|
-
<p class="text-xs text-
|
|
130
|
-
<button @click="isCreating = true" class="mt-5 px-5 py-2 bg-
|
|
127
|
+
<svg class="w-10 h-10 mx-auto text-mgray-200 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
|
128
|
+
<p class="text-sm font-semibold text-mgray-500">Aucune page dans cette catégorie</p>
|
|
129
|
+
<p class="text-xs text-mgray-400 mt-1">Créez votre première page pour commencer</p>
|
|
130
|
+
<button @click="isCreating = true" class="mt-5 px-5 py-2 bg-primary text-white rounded-xl text-xs font-bold hover:brightness-90 transition">
|
|
131
131
|
+ Créer
|
|
132
132
|
</button>
|
|
133
133
|
</div>
|
|
134
134
|
|
|
135
135
|
<table v-else class="w-full text-left">
|
|
136
136
|
<thead>
|
|
137
|
-
<tr class="border-b border-
|
|
138
|
-
<th class="px-6 py-3.5 text-[10px] font-black text-
|
|
139
|
-
<th class="px-6 py-3.5 text-[10px] font-black text-
|
|
140
|
-
<th class="px-6 py-3.5 text-[10px] font-black text-
|
|
141
|
-
<th class="px-6 py-3.5 text-[10px] font-black text-
|
|
137
|
+
<tr class="border-b border-mgray-100">
|
|
138
|
+
<th class="px-6 py-3.5 text-[10px] font-black text-mgray-400 uppercase tracking-widest">Page</th>
|
|
139
|
+
<th class="px-6 py-3.5 text-[10px] font-black text-mgray-400 uppercase tracking-widest">SEO</th>
|
|
140
|
+
<th class="px-6 py-3.5 text-[10px] font-black text-mgray-400 uppercase tracking-widest">Statut</th>
|
|
141
|
+
<th class="px-6 py-3.5 text-[10px] font-black text-mgray-400 uppercase tracking-widest text-right">Actions</th>
|
|
142
142
|
</tr>
|
|
143
143
|
</thead>
|
|
144
144
|
<tbody class="divide-y divide-gray-50">
|
|
145
|
-
<tr v-for="page in pages" :key="page.id" class="group hover:bg-
|
|
145
|
+
<tr v-for="page in pages" :key="page.id" class="group hover:bg-mgray-50/60 transition-colors">
|
|
146
146
|
<td class="px-6 py-4">
|
|
147
|
-
<div class="font-bold text-
|
|
148
|
-
<div class="text-[11px] text-
|
|
147
|
+
<div class="font-bold text-mgray text-sm">{{ page.title }}</div>
|
|
148
|
+
<div class="text-[11px] text-primary font-mono mt-0.5 opacity-70">/{{ page.slug }}</div>
|
|
149
149
|
</td>
|
|
150
150
|
<td class="px-6 py-4">
|
|
151
151
|
<span v-if="page.seo_config?.meta_title" class="inline-flex items-center gap-1.5 text-[11px] font-semibold text-green-600">
|
|
@@ -158,24 +158,24 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
158
158
|
<td class="px-6 py-4">
|
|
159
159
|
<span :class="[
|
|
160
160
|
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-black uppercase tracking-wider',
|
|
161
|
-
page.status === 'published' ? 'bg-green-50 text-green-700' : 'bg-
|
|
161
|
+
page.status === 'published' ? 'bg-green-50 text-green-700' : 'bg-mgray-100 text-mgray-500'
|
|
162
162
|
]">
|
|
163
|
-
<span :class="['w-1.5 h-1.5 rounded-full', page.status === 'published' ? 'bg-green-500' : 'bg-
|
|
163
|
+
<span :class="['w-1.5 h-1.5 rounded-full', page.status === 'published' ? 'bg-green-500' : 'bg-mgray-400']"></span>
|
|
164
164
|
{{ page.status === "published" ? "Publi\xE9" : "Brouillon" }}
|
|
165
165
|
</span>
|
|
166
166
|
</td>
|
|
167
167
|
<td class="px-6 py-4">
|
|
168
168
|
<div class="flex items-center justify-end gap-1">
|
|
169
169
|
<NuxtLink :to="`${basePath}/editor/${page.id}`"
|
|
170
|
-
class="px-3 py-1.5 rounded-lg text-[11px] font-bold bg-
|
|
170
|
+
class="px-3 py-1.5 rounded-lg text-[11px] font-bold bg-mgray-100 text-mgray-600 hover:bg-primary hover:text-white transition-all">
|
|
171
171
|
Éditer
|
|
172
172
|
</NuxtLink>
|
|
173
173
|
<button @click="openTemplateModal(page)"
|
|
174
|
-
class="p-1.5 rounded-lg text-
|
|
174
|
+
class="p-1.5 rounded-lg text-mgray-300 hover:text-primary hover:bg-primary-50 transition-all" title="Sauvegarder comme template">
|
|
175
175
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
|
|
176
176
|
</button>
|
|
177
177
|
<button @click="confirmDelete(page.id, page.title)"
|
|
178
|
-
class="p-1.5 rounded-lg text-
|
|
178
|
+
class="p-1.5 rounded-lg text-mgray-300 hover:text-red-500 hover:bg-red-50 transition-all" title="Supprimer">
|
|
179
179
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
|
|
180
180
|
</button>
|
|
181
181
|
</div>
|
|
@@ -188,59 +188,59 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
188
188
|
<!-- ── Create modal ───────────────────────────────────── -->
|
|
189
189
|
<Teleport to="body">
|
|
190
190
|
<div v-if="isCreating" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
|
191
|
-
<div class="bg-
|
|
192
|
-
<div class="px-7 pt-7 pb-5 border-b border-
|
|
191
|
+
<div class="bg-background rounded-2xl max-w-md w-full shadow-2xl overflow-hidden">
|
|
192
|
+
<div class="px-7 pt-7 pb-5 border-b border-mgray-100 flex items-start justify-between">
|
|
193
193
|
<div>
|
|
194
|
-
<h3 class="text-lg font-black text-
|
|
195
|
-
<p class="text-xs text-
|
|
194
|
+
<h3 class="text-lg font-black text-mgray">Nouvelle page</h3>
|
|
195
|
+
<p class="text-xs text-mgray-400 mt-0.5">{{ typeLabel }}</p>
|
|
196
196
|
</div>
|
|
197
|
-
<button @click="isCreating = false" class="text-
|
|
197
|
+
<button @click="isCreating = false" class="text-mgray-300 hover:text-mgray-500 transition-colors mt-0.5">
|
|
198
198
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
199
199
|
</button>
|
|
200
200
|
</div>
|
|
201
201
|
|
|
202
202
|
<div class="px-7 py-6 space-y-5">
|
|
203
203
|
<div>
|
|
204
|
-
<label class="text-[10px] font-black uppercase tracking-widest text-
|
|
204
|
+
<label class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2 block">Titre</label>
|
|
205
205
|
<input v-model="newPageTitle" type="text" placeholder="Ex: Création site web"
|
|
206
|
-
class="w-full border border-
|
|
206
|
+
class="w-full border border-mgray-200 p-3 rounded-xl text-sm text-mgray focus:ring-2 focus:ring-primary outline-none transition" autofocus />
|
|
207
207
|
</div>
|
|
208
208
|
<div>
|
|
209
|
-
<label class="text-[10px] font-black uppercase tracking-widest text-
|
|
209
|
+
<label class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2 block">Départ</label>
|
|
210
210
|
<div class="grid grid-cols-2 gap-2">
|
|
211
211
|
<button @click="createMode = 'blank'"
|
|
212
212
|
:class="[
|
|
213
213
|
'py-3 rounded-xl border-2 text-xs font-black uppercase tracking-wide transition',
|
|
214
|
-
createMode === 'blank' ? 'border-
|
|
214
|
+
createMode === 'blank' ? 'border-primary bg-primary-50 text-primary' : 'border-mgray-100 text-mgray-400 hover:border-mgray-300'
|
|
215
215
|
]">
|
|
216
216
|
Page vierge
|
|
217
217
|
</button>
|
|
218
218
|
<button @click="createMode = 'template'"
|
|
219
219
|
:class="[
|
|
220
220
|
'py-3 rounded-xl border-2 text-xs font-black uppercase tracking-wide transition',
|
|
221
|
-
createMode === 'template' ? 'border-
|
|
221
|
+
createMode === 'template' ? 'border-primary bg-primary-50 text-primary' : 'border-mgray-100 text-mgray-400 hover:border-mgray-300'
|
|
222
222
|
]">
|
|
223
223
|
Template
|
|
224
224
|
</button>
|
|
225
225
|
</div>
|
|
226
226
|
</div>
|
|
227
227
|
<div v-if="createMode === 'template'">
|
|
228
|
-
<label class="text-[10px] font-black uppercase tracking-widest text-
|
|
229
|
-
<select v-model="selectedTemplateId" class="w-full border border-
|
|
228
|
+
<label class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2 block">Template</label>
|
|
229
|
+
<select v-model="selectedTemplateId" class="w-full border border-mgray-200 p-3 rounded-xl text-sm text-mgray outline-none focus:ring-2 focus:ring-primary transition bg-background">
|
|
230
230
|
<option value="">— Choisir —</option>
|
|
231
231
|
<option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">{{ tpl.name }}</option>
|
|
232
232
|
</select>
|
|
233
|
-
<p v-if="!templates.length" class="text-[11px] text-
|
|
233
|
+
<p v-if="!templates.length" class="text-[11px] text-mgray-400 mt-1.5">Aucun template disponible.</p>
|
|
234
234
|
</div>
|
|
235
235
|
</div>
|
|
236
236
|
|
|
237
237
|
<div class="px-7 pb-7 flex gap-3">
|
|
238
|
-
<button @click="isCreating = false" class="flex-1 px-4 py-2.5 border border-
|
|
238
|
+
<button @click="isCreating = false" class="flex-1 px-4 py-2.5 border border-mgray-200 rounded-xl text-sm font-semibold text-mgray hover:bg-mgray-50 transition">
|
|
239
239
|
Annuler
|
|
240
240
|
</button>
|
|
241
241
|
<button @click="handleCreate"
|
|
242
242
|
:disabled="!newPageTitle || createMode === 'template' && !selectedTemplateId"
|
|
243
|
-
class="flex-1 px-4 py-2.5 bg-
|
|
243
|
+
class="flex-1 px-4 py-2.5 bg-primary text-white rounded-xl text-sm font-bold hover:brightness-90 transition disabled:opacity-40 disabled:cursor-not-allowed shadow-lg shadow-primary-200">
|
|
244
244
|
Créer →
|
|
245
245
|
</button>
|
|
246
246
|
</div>
|
|
@@ -251,18 +251,18 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
251
251
|
<!-- ── Delete confirmation modal ─────────────────────── -->
|
|
252
252
|
<Teleport to="body">
|
|
253
253
|
<div v-if="deleteModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
|
254
|
-
<div class="bg-
|
|
254
|
+
<div class="bg-background rounded-2xl max-w-sm w-full shadow-2xl overflow-hidden">
|
|
255
255
|
<div class="p-7">
|
|
256
256
|
<div class="w-11 h-11 bg-red-50 rounded-xl flex items-center justify-center mb-4">
|
|
257
257
|
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>
|
|
258
258
|
</div>
|
|
259
|
-
<h3 class="text-base font-black text-
|
|
260
|
-
<p class="text-sm text-
|
|
261
|
-
<span class="font-semibold text-
|
|
259
|
+
<h3 class="text-base font-black text-mgray mb-1">Supprimer cette page ?</h3>
|
|
260
|
+
<p class="text-sm text-mgray-500">
|
|
261
|
+
<span class="font-semibold text-mgray-700">« {{ deleteModal.title }} »</span> sera définitivement supprimée. Cette action est irréversible.
|
|
262
262
|
</p>
|
|
263
263
|
</div>
|
|
264
264
|
<div class="px-7 pb-7 flex gap-3">
|
|
265
|
-
<button @click="deleteModal = null" class="flex-1 px-4 py-2.5 border border-
|
|
265
|
+
<button @click="deleteModal = null" class="flex-1 px-4 py-2.5 border border-mgray-200 rounded-xl text-sm font-semibold text-mgray hover:bg-mgray-50 transition">
|
|
266
266
|
Annuler
|
|
267
267
|
</button>
|
|
268
268
|
<button @click="handleDelete" :disabled="isDeleting"
|
|
@@ -277,27 +277,27 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
277
277
|
<!-- ── Save as template modal ─────────────────────────── -->
|
|
278
278
|
<Teleport to="body">
|
|
279
279
|
<div v-if="templateModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
|
280
|
-
<div class="bg-
|
|
281
|
-
<div class="px-7 pt-7 pb-5 border-b border-
|
|
280
|
+
<div class="bg-background rounded-2xl max-w-sm w-full shadow-2xl overflow-hidden">
|
|
281
|
+
<div class="px-7 pt-7 pb-5 border-b border-mgray-100 flex items-start justify-between">
|
|
282
282
|
<div>
|
|
283
|
-
<h3 class="text-base font-black text-
|
|
284
|
-
<p class="text-xs text-
|
|
283
|
+
<h3 class="text-base font-black text-mgray">Sauvegarder comme template</h3>
|
|
284
|
+
<p class="text-xs text-mgray-400 mt-0.5">Ce template sera disponible lors de la création de pages.</p>
|
|
285
285
|
</div>
|
|
286
|
-
<button @click="templateModal = null" class="text-
|
|
286
|
+
<button @click="templateModal = null" class="text-mgray-300 hover:text-mgray-500 transition-colors mt-0.5">
|
|
287
287
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
288
288
|
</button>
|
|
289
289
|
</div>
|
|
290
290
|
<div class="px-7 py-6">
|
|
291
|
-
<label class="text-[10px] font-black uppercase tracking-widest text-
|
|
291
|
+
<label class="text-[10px] font-black uppercase tracking-widest text-mgray-400 mb-2 block">Nom du template</label>
|
|
292
292
|
<input v-model="templateName" type="text" placeholder="Ex: Landing page service"
|
|
293
|
-
class="w-full border border-
|
|
293
|
+
class="w-full border border-mgray-200 p-3 rounded-xl text-sm text-mgray focus:ring-2 focus:ring-primary outline-none transition" autofocus />
|
|
294
294
|
</div>
|
|
295
295
|
<div class="px-7 pb-7 flex gap-3">
|
|
296
|
-
<button @click="templateModal = null" class="flex-1 px-4 py-2.5 border border-
|
|
296
|
+
<button @click="templateModal = null" class="flex-1 px-4 py-2.5 border border-mgray-200 rounded-xl text-sm font-semibold text-mgray hover:bg-mgray-50 transition">
|
|
297
297
|
Annuler
|
|
298
298
|
</button>
|
|
299
299
|
<button @click="handleTurnIntoTemplate" :disabled="!templateName.trim() || isSavingTemplate"
|
|
300
|
-
class="flex-1 px-4 py-2.5 bg-
|
|
300
|
+
class="flex-1 px-4 py-2.5 bg-primary text-white rounded-xl text-sm font-bold hover:brightness-90 transition disabled:opacity-40 shadow-lg shadow-primary-200">
|
|
301
301
|
{{ isSavingTemplate ? "Enregistrement..." : "Enregistrer" }}
|
|
302
302
|
</button>
|
|
303
303
|
</div>
|
|
@@ -310,7 +310,7 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
310
310
|
<Transition enter-from-class="opacity-0 translate-y-2" leave-to-class="opacity-0 translate-y-2" enter-active-class="transition duration-200" leave-active-class="transition duration-200">
|
|
311
311
|
<div v-if="toast" :class="[
|
|
312
312
|
'fixed bottom-6 right-6 z-[100] flex items-center gap-3 px-4 py-3 rounded-xl shadow-xl text-sm font-semibold',
|
|
313
|
-
toast.type === 'success' ? 'bg-
|
|
313
|
+
toast.type === 'success' ? 'bg-mgray-900 text-white' : 'bg-red-500 text-white'
|
|
314
314
|
]">
|
|
315
315
|
<svg v-if="toast.type === 'success'" class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
|
|
316
316
|
<svg v-else class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
@@ -58,9 +58,9 @@ const statusClass = {
|
|
|
58
58
|
<div v-else-if="persona">
|
|
59
59
|
<!-- Hero -->
|
|
60
60
|
<div class="bg-gradient-to-br from-blue-600 to-blue-800 rounded-3xl p-8 text-white mb-8 relative overflow-hidden">
|
|
61
|
-
<div class="absolute top-0 right-0 w-64 h-64 bg-
|
|
61
|
+
<div class="absolute top-0 right-0 w-64 h-64 bg-background/10 rounded-full blur-3xl -mr-16 -mt-16"></div>
|
|
62
62
|
<div class="relative z-10 flex flex-col md:flex-row items-start gap-8">
|
|
63
|
-
<div class="w-24 h-24 bg-
|
|
63
|
+
<div class="w-24 h-24 bg-background/20 backdrop-blur-xl rounded-2xl flex items-center justify-center border-2 border-white/40 overflow-hidden flex-shrink-0 text-2xl font-black">
|
|
64
64
|
<img v-if="persona.avatar_url" :src="persona.avatar_url" :alt="persona.name" class="w-full h-full object-cover" />
|
|
65
65
|
<span v-else>{{ getInitials(persona.name) }}</span>
|
|
66
66
|
</div>
|
|
@@ -73,7 +73,7 @@ const statusClass = {
|
|
|
73
73
|
</div>
|
|
74
74
|
</div>
|
|
75
75
|
<div class="flex gap-3">
|
|
76
|
-
<button @click="editMode = !editMode" class="px-5 py-2.5 bg-
|
|
76
|
+
<button @click="editMode = !editMode" class="px-5 py-2.5 bg-background/20 hover:bg-background/30 text-white rounded-xl font-bold text-sm transition-all flex items-center gap-2">
|
|
77
77
|
<Icon name="mdi:pencil" /> Modifier
|
|
78
78
|
</button>
|
|
79
79
|
</div>
|
|
@@ -81,7 +81,7 @@ const statusClass = {
|
|
|
81
81
|
</div>
|
|
82
82
|
|
|
83
83
|
<!-- Edit Form -->
|
|
84
|
-
<div v-if="editMode" class="bg-
|
|
84
|
+
<div v-if="editMode" class="bg-background rounded-3xl p-8 border border-slate-200 shadow-sm mb-8">
|
|
85
85
|
<h2 class="text-xl font-bold text-slate-900 mb-6">Modifier le Profil</h2>
|
|
86
86
|
<div class="grid grid-cols-2 gap-6 mb-6">
|
|
87
87
|
<div>
|
|
@@ -113,17 +113,17 @@ const statusClass = {
|
|
|
113
113
|
|
|
114
114
|
<!-- Info Cards -->
|
|
115
115
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
|
116
|
-
<div class="bg-
|
|
116
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200 shadow-sm">
|
|
117
117
|
<h3 class="font-black text-slate-900 mb-4 flex items-center gap-2"><Icon name="mdi:account-outline" class="text-blue-600" /> A propos</h3>
|
|
118
118
|
<p class="text-slate-600 leading-relaxed">{{ persona.about || "Aucune description." }}</p>
|
|
119
119
|
</div>
|
|
120
|
-
<div v-if="(persona.core_needs || []).length > 0" class="bg-
|
|
120
|
+
<div v-if="(persona.core_needs || []).length > 0" class="bg-background rounded-2xl p-6 border border-slate-200 shadow-sm">
|
|
121
121
|
<h3 class="font-black text-slate-900 mb-4 flex items-center gap-2"><Icon name="mdi:star-outline" class="text-amber-500" /> Besoins Principaux</h3>
|
|
122
122
|
<div class="flex flex-wrap gap-2">
|
|
123
123
|
<span v-for="need in persona.core_needs" :key="need" class="px-3 py-1.5 bg-blue-50 text-blue-700 rounded-xl text-sm font-semibold">{{ need }}</span>
|
|
124
124
|
</div>
|
|
125
125
|
</div>
|
|
126
|
-
<div v-if="(persona.goals || []).length > 0" class="bg-
|
|
126
|
+
<div v-if="(persona.goals || []).length > 0" class="bg-background rounded-2xl p-6 border border-slate-200 shadow-sm">
|
|
127
127
|
<h3 class="font-black text-slate-900 mb-4 flex items-center gap-2"><Icon name="mdi:target" class="text-emerald-500" /> Objectifs</h3>
|
|
128
128
|
<ul class="space-y-2">
|
|
129
129
|
<li v-for="goal in persona.goals" :key="goal" class="flex items-start gap-2 text-sm text-slate-600">
|
|
@@ -131,7 +131,7 @@ const statusClass = {
|
|
|
131
131
|
</li>
|
|
132
132
|
</ul>
|
|
133
133
|
</div>
|
|
134
|
-
<div v-if="(persona.pain_points || []).length > 0" class="bg-
|
|
134
|
+
<div v-if="(persona.pain_points || []).length > 0" class="bg-background rounded-2xl p-6 border border-slate-200 shadow-sm">
|
|
135
135
|
<h3 class="font-black text-slate-900 mb-4 flex items-center gap-2"><Icon name="mdi:lightning-bolt" class="text-red-500" /> Points de Douleur</h3>
|
|
136
136
|
<ul class="space-y-2">
|
|
137
137
|
<li v-for="pain in persona.pain_points" :key="pain" class="flex items-start gap-2 text-sm text-slate-600">
|
|
@@ -142,7 +142,7 @@ const statusClass = {
|
|
|
142
142
|
</div>
|
|
143
143
|
|
|
144
144
|
<!-- Recent Content -->
|
|
145
|
-
<div class="bg-
|
|
145
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200 shadow-sm">
|
|
146
146
|
<h3 class="font-black text-slate-900 mb-4 flex items-center gap-2">
|
|
147
147
|
<Icon name="mdi:file-document-multiple-outline" class="text-purple-500" /> Contenus associes ({{ contents.length }})
|
|
148
148
|
</h3>
|
|
@@ -102,12 +102,12 @@ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`
|
|
|
102
102
|
</div>
|
|
103
103
|
<div class="flex items-center gap-4">
|
|
104
104
|
<div class="relative group">
|
|
105
|
-
<button class="px-5 py-4 bg-
|
|
105
|
+
<button class="px-5 py-4 bg-background border border-slate-200 rounded-2xl font-bold text-slate-700 flex items-center gap-3 hover:border-blue-300 transition-all shadow-sm">
|
|
106
106
|
<Icon :name="layoutIcon" class="text-xl text-blue-600" />
|
|
107
107
|
<span class="text-sm">{{ layoutLabel }}</span>
|
|
108
108
|
<Icon name="mdi:chevron-down" class="text-slate-300" />
|
|
109
109
|
</button>
|
|
110
|
-
<div class="absolute right-0 mt-3 w-48 bg-
|
|
110
|
+
<div class="absolute right-0 mt-3 w-48 bg-background border border-slate-100 rounded-2xl shadow-2xl py-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-30">
|
|
111
111
|
<button @click="currentLayout = 'table'" class="w-full px-4 py-3 text-left hover:bg-blue-50 flex items-center gap-3">
|
|
112
112
|
<Icon name="mdi:table" class="text-slate-400" />
|
|
113
113
|
<span :class="currentLayout === 'table' ? 'text-blue-600 font-black' : 'text-slate-600 font-bold'">Tableau</span>
|
|
@@ -128,7 +128,7 @@ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`
|
|
|
128
128
|
<div v-for="i in 3" :key="i" class="h-40 bg-slate-100 animate-pulse rounded-3xl"></div>
|
|
129
129
|
</div>
|
|
130
130
|
|
|
131
|
-
<div v-else-if="personas.length === 0" class="bg-
|
|
131
|
+
<div v-else-if="personas.length === 0" class="bg-background rounded-[3rem] p-20 text-center border-2 border-dashed border-slate-200">
|
|
132
132
|
<Icon name="mdi:account-question-outline" class="text-6xl text-slate-200 mb-6" />
|
|
133
133
|
<h3 class="text-2xl font-black text-slate-800">Aucune persona</h3>
|
|
134
134
|
<p class="text-slate-400 mt-2 mb-8">Creez vos premiers profils comportementaux.</p>
|
|
@@ -137,7 +137,7 @@ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`
|
|
|
137
137
|
|
|
138
138
|
<div v-else>
|
|
139
139
|
<!-- Table Layout -->
|
|
140
|
-
<div v-if="currentLayout === 'table'" class="bg-
|
|
140
|
+
<div v-if="currentLayout === 'table'" class="bg-background rounded-2xl border border-slate-100 shadow-sm overflow-hidden">
|
|
141
141
|
<table class="w-full">
|
|
142
142
|
<thead>
|
|
143
143
|
<tr class="bg-slate-50 border-b border-slate-200">
|
|
@@ -182,7 +182,7 @@ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`
|
|
|
182
182
|
|
|
183
183
|
<!-- Bento Layout -->
|
|
184
184
|
<div v-else class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|
185
|
-
<div v-for="persona in personas" :key="persona.id" class="bg-
|
|
185
|
+
<div v-for="persona in personas" :key="persona.id" class="bg-background rounded-[2rem] border border-slate-100 p-8 shadow-sm hover:shadow-xl hover:scale-[1.02] transition-all cursor-pointer group" @click="viewPersona(persona)">
|
|
186
186
|
<div class="flex items-center gap-4 mb-6">
|
|
187
187
|
<div class="w-16 h-16 rounded-2xl shrink-0 overflow-hidden bg-gradient-to-br from-purple-400 to-pink-400 flex items-center justify-center text-white font-black text-xl">
|
|
188
188
|
<img v-if="persona.avatar_url" :src="persona.avatar_url" class="w-full h-full object-cover" />
|
|
@@ -209,7 +209,7 @@ const viewPersona = (persona) => navigateTo(`${basePath}/personas/${persona.id}`
|
|
|
209
209
|
<!-- Modal -->
|
|
210
210
|
<Teleport to="body">
|
|
211
211
|
<div v-if="showModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4" @click.self="closeModal">
|
|
212
|
-
<div class="bg-
|
|
212
|
+
<div class="bg-background rounded-3xl max-w-2xl w-full max-h-[90vh] overflow-y-auto shadow-2xl">
|
|
213
213
|
<div class="p-8">
|
|
214
214
|
<div class="flex items-center justify-between mb-6">
|
|
215
215
|
<h2 class="text-2xl font-black text-slate-900">{{ editingPersona ? "Modifier Persona" : "Nouvelle Persona" }}</h2>
|
|
@@ -86,7 +86,7 @@ const handleDelete = async (id) => {
|
|
|
86
86
|
const formatPrice = (n) => new Intl.NumberFormat("fr-FR").format(n);
|
|
87
87
|
const statusClass = (s) => ({
|
|
88
88
|
active: "px-3 py-1 bg-green-100 text-green-700 rounded-lg text-sm font-semibold",
|
|
89
|
-
inactive: "px-3 py-1 bg-
|
|
89
|
+
inactive: "px-3 py-1 bg-mgray-100 text-mgray-700 rounded-lg text-sm font-semibold",
|
|
90
90
|
draft: "px-3 py-1 bg-yellow-100 text-yellow-700 rounded-lg text-sm font-semibold",
|
|
91
91
|
archived: "px-3 py-1 bg-red-100 text-red-700 rounded-lg text-sm font-semibold"
|
|
92
92
|
})[s] || "px-3 py-1 bg-green-100 text-green-700 rounded-lg text-sm font-semibold";
|
|
@@ -108,7 +108,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
108
108
|
</div>
|
|
109
109
|
|
|
110
110
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-6 mb-8">
|
|
111
|
-
<div class="bg-
|
|
111
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200">
|
|
112
112
|
<div class="flex items-center justify-between mb-2">
|
|
113
113
|
<Icon name="mdi:package-variant" class="text-3xl text-blue-600" />
|
|
114
114
|
<span class="px-3 py-1 bg-blue-100 text-blue-700 text-xs font-bold rounded-full">Total</span>
|
|
@@ -116,7 +116,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
116
116
|
<p class="text-3xl font-black text-slate-900">{{ products.length }}</p>
|
|
117
117
|
<p class="text-sm text-slate-500">Produits</p>
|
|
118
118
|
</div>
|
|
119
|
-
<div class="bg-
|
|
119
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200">
|
|
120
120
|
<div class="flex items-center justify-between mb-2">
|
|
121
121
|
<Icon name="mdi:check-circle" class="text-3xl text-green-600" />
|
|
122
122
|
<span class="px-3 py-1 bg-green-100 text-green-700 text-xs font-bold rounded-full">Actifs</span>
|
|
@@ -124,7 +124,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
124
124
|
<p class="text-3xl font-black text-slate-900">{{ activeProducts }}</p>
|
|
125
125
|
<p class="text-sm text-slate-500">En vente</p>
|
|
126
126
|
</div>
|
|
127
|
-
<div class="bg-
|
|
127
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200">
|
|
128
128
|
<div class="flex items-center justify-between mb-2">
|
|
129
129
|
<Icon name="mdi:tag-multiple" class="text-3xl text-orange-600" />
|
|
130
130
|
<span class="px-3 py-1 bg-orange-100 text-orange-700 text-xs font-bold rounded-full">Promos</span>
|
|
@@ -132,7 +132,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
132
132
|
<p class="text-3xl font-black text-slate-900">{{ offerProducts }}</p>
|
|
133
133
|
<p class="text-sm text-slate-500">Offres actives</p>
|
|
134
134
|
</div>
|
|
135
|
-
<div class="bg-
|
|
135
|
+
<div class="bg-background rounded-2xl p-6 border border-slate-200">
|
|
136
136
|
<div class="flex items-center justify-between mb-2">
|
|
137
137
|
<Icon name="mdi:cash-multiple" class="text-3xl text-purple-600" />
|
|
138
138
|
<span class="px-3 py-1 bg-purple-100 text-purple-700 text-xs font-bold rounded-full">Revenue</span>
|
|
@@ -142,7 +142,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
142
142
|
</div>
|
|
143
143
|
</div>
|
|
144
144
|
|
|
145
|
-
<div class="bg-
|
|
145
|
+
<div class="bg-background rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
|
|
146
146
|
<div class="overflow-x-auto">
|
|
147
147
|
<table class="w-full">
|
|
148
148
|
<thead>
|
|
@@ -204,7 +204,7 @@ const statusLabel = (s) => ({ active: "Actif", inactive: "Inactif", draft: "Brou
|
|
|
204
204
|
|
|
205
205
|
<Teleport to="body">
|
|
206
206
|
<div v-if="showModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-6" @click.self="closeModal">
|
|
207
|
-
<div class="bg-
|
|
207
|
+
<div class="bg-background rounded-3xl p-8 max-w-4xl w-full max-h-[90vh] overflow-y-auto shadow-2xl">
|
|
208
208
|
<div class="flex items-center justify-between mb-6">
|
|
209
209
|
<h3 class="text-2xl font-bold text-slate-900">{{ editingProduct ? "Modifier le Produit" : "Nouveau Produit" }}</h3>
|
|
210
210
|
<button @click="closeModal" class="p-2 hover:bg-slate-100 rounded-xl transition-colors"><Icon name="mdi:close" class="text-2xl text-slate-400" /></button>
|