diff --git a/assets/css/_bootstrap.scss b/assets/css/_bootstrap.scss
index 89d914e69bff99ce2e94aac8b7c37c95bd2bbfb2..814bf81b38172f5319e714fdab2e2902eae44561 100644
--- a/assets/css/_bootstrap.scss
+++ b/assets/css/_bootstrap.scss
@@ -65,10 +65,10 @@
 // @import "~bootstrap/scss/helpers/ratio";
 // @import "~bootstrap/scss/helpers/position";
 // @import "~bootstrap/scss/helpers/stacks";
-// @import "~bootstrap/scss/helpers/visually-hidden";
+@import "~bootstrap/scss/helpers/visually-hidden";
 // @import "~bootstrap/scss/helpers/stretched-link";
 @import "~bootstrap/scss/helpers/text-truncation";
-// @import "~bootstrap/scss/helpers/vr";
+@import "~bootstrap/scss/helpers/vr";
 
 // Utilities
 @import "~bootstrap/scss/utilities/api";
diff --git a/assets/css/style.scss b/assets/css/style.scss
index fd488ff536624064d4dd2d6736a8e4f78ffcb5a8..089d7bc5c746281f89f50265b75600ceed1c2065 100644
--- a/assets/css/style.scss
+++ b/assets/css/style.scss
@@ -1,14 +1,27 @@
 // Bootstrap variables customisation
+// Colors
+$border-color: var(--border-color);
+$secondary: #adb5bd;
+$success: #75b798;
+$info: #3d8bfd;
+$danger: #b02a37;
+$warning: #fd9843;
+
+// Mark
+$mark-padding: 0.2em 0;
+$mark-bg: yellow;
+
+// Forms
+$form-check-input-checked-bg-color: $info;
+
 // Fonts & Typo
 $link-decoration: none;
 $text-muted: var(--txt-muted-color);
 $font-family-base: Montserrat, Helvetica, Arial, serif;
 $small-font-size: 70%;
+$code-font-size: 85%;
 $link-hover-decoration: underline;
 
-// Forms
-$form-check-input-checked-bg-color: var(--txt-muted-color);
-
 // Badges
 $badge-font-size: 0.7em;
 $badge-font-weight: 500;
@@ -24,6 +37,7 @@ $table-striped-bg: var(--bg-menu-color);
 $breadcrumb-bg: var(--bg-menu-color);
 $breadcrumb-active-color: var(--txt-primary-color);
 $breadcrumb-divider-color: var(--txt-primary-color);
+$breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='#6c757d'/%3E%3C/svg%3E");
 
 // List-groups
 $list-group-bg: transparent;
@@ -58,13 +72,6 @@ $pagination-disabled-border-color: $pagination-border-color;
 $pagination-disabled-bg: var(--bg-menu-color);
 $pagination-disabled-color: var(--txt-secondary-color);
 
-// Mark
-$mark-padding: 0.2em 0;
-$mark-bg: yellow;
-
-// Colors
-$border-color: var(--border-color);
-
 @import "font";
 @import "bootstrap";
 
@@ -83,7 +90,9 @@ a:hover {
 }
 
 .bg-warning,
-.bg-info {
+.bg-info,
+.bg-success,
+.bg-secondary {
 	@extend .text-dark;
 }
 
@@ -110,6 +119,10 @@ a:hover {
 
 	&-hover tbody tr {
 		cursor: pointer;
+
+		&:hover {
+			filter: brightness(90%);
+		}
 	}
 
 	.td-date {
@@ -122,23 +135,10 @@ a:hover {
 }
 
 .list-group-item {
-	&-action:not(.active) {
-		&:hover {
-			background: var(--bg-secondary-color);
-			color: var(--txt-secondary-color);
-		}
-	}
-
 	.menu-item {
 		transition: left 0.3s ease-in-out;
 		left: 0;
 
-		&::before {
-			content: "›";
-			position: relative;
-			left: -0.5em;
-		}
-
 		&:hover {
 			left: 0.5em;
 		}
@@ -146,5 +146,5 @@ a:hover {
 }
 
 .icon {
-	width: 1.15rem;
+	width: 1.3rem;
 }
diff --git a/components/alerte.vue b/components/alert/Default.vue
similarity index 54%
rename from components/alerte.vue
rename to components/alert/Default.vue
index 3110135754ec8859fe6864d23c2ceff2f9ef833c..4be74e73db595634c42ffb351550d239f72a79f1 100644
--- a/components/alerte.vue
+++ b/components/alert/Default.vue
@@ -1,16 +1,22 @@
 <template>
 	<div
 		class="alert alert-dismissible fade show"
-		:class="classType"
-		v-if="error">
-		{{ error }}
+		role="alert"
+		:class="classType">
+		<h3 class="alert-heading h5" v-if="title">
+			<span v-if="icon"
+				><component
+					aria-hidden="true"
+					:is="'solid-' + icon + '-icon'"
+					style="width: 1.5rem" />&nbsp;</span
+			>{{ title }}
+		</h3>
+		<slot></slot>
 		<button
 			type="button"
-			class="close"
-			data-dismiss="alert"
-			:aria-label="$t('aria.close')">
-			<span aria-hidden="true">&times;</span>
-		</button>
+			class="btn-close"
+			data-bs-dismiss="alert"
+			:aria-label="$t('aria.close')"></button>
 	</div>
 </template>
 
@@ -35,9 +41,13 @@ export default {
 				)
 			}
 		},
-		error: {
+		title: {
 			type: String,
-			default: null
+			default: ""
+		},
+		icon: {
+			type: String,
+			default: ""
 		}
 	},
 	computed: {
diff --git a/components/alert/Member.vue b/components/alert/Member.vue
new file mode 100644
index 0000000000000000000000000000000000000000..49d42dd84d965c72cde4d8af8e68787cbfbce99e
--- /dev/null
+++ b/components/alert/Member.vue
@@ -0,0 +1,179 @@
+<template>
+	<div v-if="displayAlert">
+		<AlertDefault
+			class="col-xxl-10 mx-auto mt-3"
+			:title="getTitle"
+			:icon="getIcon"
+			:type="typeAlerte">
+			<ul>
+				<li
+					v-if="hash.status == 'NEWCOMER' && !allCertifiersDispo"
+					v-html="$t('alert.notAllCertifiersDispo')"></li>
+				<li
+					v-if="this.hash.status == 'NEWCOMER' && this.isDossierOK"
+					v-html="$t('alert.dossierOK')"></li>
+				<li
+					v-if="hash.status == 'MISSING'"
+					v-html="
+						$tc(
+							'alert.missing',
+							Math.max(
+								1,
+								Math.round(
+									(hash.limitDate - Math.round(Date.now() / 1000)) / 3600 / 24
+								)
+							)
+						)
+					"></li>
+				<li
+					v-if="
+						hash.status == 'MEMBER' &&
+						['warning', 'danger'].includes(
+							$options.filters.dateStatus(hash.limitDate)
+						)
+					"
+					v-html="$t('alert.renew')"></li>
+				<li
+					v-if="hash.status != 'REVOKED' && !hash.distanceE.dist_ok"
+					v-html="$t('alert.distKO')"></li>
+				<li
+					v-if="
+						hash.status != 'REVOKED' && certifsNotExpired.received.length < 5
+					"
+					v-html="
+						$tc('alert.certifManquantes', 5 - certifsNotExpired.received.length)
+					"></li>
+				<li
+					v-if="
+						['MEMBER', 'MISSING'].includes(hash.status) &&
+						this.$options.filters.dateStatus(this.hash.certsLimit) == 'warning'
+					"
+					v-html="$t('alert.certifManquantesBientot')"></li>
+				<li
+					v-if="hash.status == 'MEMBER' && !hash.minDatePassed"
+					v-html="$t('alert.notAvailable')"></li>
+				<li
+					v-if="certifsNotExpired.sent.length == 100"
+					v-html="$t('alert.noMoreCert')"></li>
+				<li
+					v-else-if="certifsNotExpired.sent.length > 80"
+					v-html="
+						$tc('alert.certStockLim', 100 - certifsNotExpired.sent.length)
+					"></li>
+				<li v-if="hash.status == 'REVOKED'" v-html="$t('alert.revoked')"></li>
+			</ul>
+		</AlertDefault>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		hash: {
+			type: Object,
+			required: true
+		}
+	},
+	computed: {
+		displayAlert() {
+			return (
+				["MISSING", "REVOKED", "NEWCOMER"].includes(this.hash.status) ||
+				["warning", "danger"].includes(
+					this.$options.filters.dateStatus(this.hash.limitDate)
+				) ||
+				!this.hash.minDatePassed ||
+				this.certifsNotExpired.sent.length > 80 ||
+				this.certifsNotExpired.received.length < 5 ||
+				!this.hash.distanceE.dist_ok ||
+				this.$options.filters.dateStatus(this.hash.certsLimit) == "warning"
+			)
+		},
+		certifsNotExpired() {
+			return {
+				received: this.hash.received_certifications.filter((el) => !el.expired),
+				sent: this.hash.sent_certifications.filter((el) => !el.expired)
+			}
+		},
+		allCertifiersDispo() {
+			let nbCertifiersdispo = 0
+
+			for (const cert of this.certifsNotExpired.received) {
+				if (cert.from.minDatePassed) nbCertifiersdispo++
+			}
+
+			return nbCertifiersdispo > 4
+		},
+		isDossierOK() {
+			return (
+				this.hash.distanceE.dist_ok &&
+				this.hash.received_certifications.length > 4
+			)
+		},
+		typeAlerte() {
+			switch (this.hash.status) {
+				case "NEWCOMER":
+					return this.isDossierOK
+						? this.allCertifiersDispo
+							? "success"
+							: "info"
+						: "danger"
+				case "MISSING":
+					return "danger"
+				case "MEMBER":
+					if (
+						this.$options.filters.dateStatus(this.hash.certsLimit) ==
+							"danger" ||
+						this.$options.filters.dateStatus(this.hash.limitDate) == "danger"
+					)
+						return "danger"
+					if (!this.hash.distanceE.dist_ok) return "danger"
+					return "warning"
+				case "REVOKED":
+					return "secondary"
+			}
+		},
+		getTitle() {
+			let title = this.$t("alert.information")
+
+			switch (this.hash.status) {
+				case "MEMBER":
+					if (this.typeAlerte == "danger") title = this.$t("alert.attention")
+					break
+				case "NEWCOMER":
+					title = this.isDossierOK
+						? this.allCertifiersDispo
+							? this.$t("alert.dossierOKtitle")
+							: this.$t("alert.attenteCertifiers")
+						: this.$t("alert.dossierKOtitle")
+					break
+				case "MISSING":
+					title = this.$t("alert.attention")
+			}
+
+			return title
+		},
+		getIcon() {
+			let icon = "information-circle"
+
+			switch (this.hash.status) {
+				case "MEMBER":
+					if (this.typeAlerte == "danger") icon = "exclamation"
+					break
+				case "NEWCOMER":
+					if (!this.isDossierOK) {
+						icon = "exclamation"
+						break
+					}
+
+					if (this.allCertifiersDispo) icon = "check-circle"
+					break
+				case "MISSING":
+					icon = "exclamation"
+					break
+			}
+
+			return icon
+		}
+	}
+}
+</script>
diff --git a/components/badge/Danger.vue b/components/badge/Danger.vue
index d5a89ef3f15467629c1ff31433111ef16f588094..70154d9833651ded227db4c9f9909bded33857da 100644
--- a/components/badge/Danger.vue
+++ b/components/badge/Danger.vue
@@ -1,13 +1,14 @@
 <template>
 	<span
-		:class="classWarning"
+		class="d-inline-block"
+		:class="classBadge"
 		:title="title"
-		v-if="
-			$options.filters.dateStatus(limitDate) != 'success' &&
-			!['NEWCOMER', 'REVOKED'].includes(memberStatus)
-		"
-		><outline-exclamation-icon class="icon" />
-		<span class="sr-only" v-if="title">{{ title }}</span>
+		style="cursor: help"
+		v-if="!['NEWCOMER', 'REVOKED'].includes(memberStatus)"
+		><solid-shield-check-icon
+			v-if="$options.filters.dateStatus(limitDate) == 'success'" />
+		<solid-shield-exclamation-icon v-else />
+		<span class="visually-hidden">{{ title }}</span>
 	</span>
 </template>
 
@@ -18,19 +19,28 @@ export default {
 			type: Number,
 			default: 0
 		},
-		title: {
-			type: String
-		},
 		memberStatus: String
 	},
 	computed: {
-		classWarning() {
+		classBadge() {
 			return {
+				"text-success":
+					this.$options.filters.dateStatus(this.limitDate) == "success",
 				"text-danger":
 					this.$options.filters.dateStatus(this.limitDate) == "danger",
 				"text-warning":
 					this.$options.filters.dateStatus(this.limitDate) == "warning"
 			}
+		},
+		title() {
+			switch (this.$options.filters.dateStatus(this.limitDate)) {
+				case "success":
+					return this.$t("statut.allcertif")
+				case "warning":
+					return this.$t("statut.bientotmanquecertif")
+				case "danger":
+					return this.$t("statut.manquecertif")
+			}
 		}
 	}
 }
diff --git a/components/badge/Date.vue b/components/badge/Date.vue
index 51755b816d3a6f5c97123302a37f875b2130d26c..122b5c8eff5964b6076706da0984b783c94309d3 100644
--- a/components/badge/Date.vue
+++ b/components/badge/Date.vue
@@ -1,6 +1,12 @@
 <template>
 	<span class="badge" :class="'bg-' + $options.filters.dateStatus(date)">
-		{{ !date ? "N/A" : $d(new Date(date * 1000), styleDate) }}
+		{{
+			!date
+				? "N/A"
+				: 0 > deltaDays
+				? $tc("jours_avant", Math.abs(deltaDays))
+				: $tc("jours", Math.abs(deltaDays))
+		}}
 	</span>
 </template>
 
@@ -9,10 +15,11 @@ export default {
 	props: {
 		date: {
 			type: Number
-		},
-		styleDate: {
-			type: String,
-			required: true
+		}
+	},
+	computed: {
+		deltaDays() {
+			return Math.ceil((this.date - Math.round(Date.now() / 1000)) / 3600 / 24)
 		}
 	}
 }
diff --git a/components/badge/Status.vue b/components/badge/Status.vue
index b8b1d472af8cd49048adf0f7b304ebeb01f24bd7..fe79dfb570748bec26d1ea2bccfde345edd50b7b 100644
--- a/components/badge/Status.vue
+++ b/components/badge/Status.vue
@@ -1,5 +1,5 @@
 <template>
-	<span class="badge" :class="this.displayStatus(membre).class">
+	<span class="badge" :class="displayStatus(membre).class">
 		{{ this.displayStatus(membre).str }}
 	</span>
 </template>
diff --git a/components/btn/Clipboard.vue b/components/btn/Clipboard.vue
index 59f7e7e702fd0719af94dc5d54fec90cd6f55750..2663b3cea3935e6345ed6d956ab5b9b04c0f7541 100644
--- a/components/btn/Clipboard.vue
+++ b/components/btn/Clipboard.vue
@@ -6,7 +6,8 @@
 			type="button"
 			v-tooltip="$t('copie') + ' !'"
 			@click="copyText">
-			<outline-clipboard-copy-icon class="icon" />
+			<solid-share-icon class="icon" aria-hidden="true" />
+			<span class="visually-hidden">{{ $t("aria.clipboard") }}</span>
 		</button>
 		<input
 			type="text"
diff --git a/components/btn/Favori.vue b/components/btn/Favori.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e3c4f5d43c963315bf5af6cf755a34da377ca845
--- /dev/null
+++ b/components/btn/Favori.vue
@@ -0,0 +1,48 @@
+<template>
+	<button
+		id="favori"
+		class="btn btn-secondary"
+		v-tooltip="
+			$favourites.list.includes(uid)
+				? $t('favoris.enregistre')
+				: $t('favoris.supprime')
+		"
+		@click="$favourites.toggleFavourite(uid, $event)">
+		<span class="visually-hidden">{{
+			$favourites.list.includes(uid)
+				? $t("favoris.supprimer")
+				: $t("favoris.ajouter")
+		}}</span>
+		<solid-user-add-icon
+			aria-hidden="true"
+			class="icon"
+			v-if="!$favourites.list.includes(uid)" />
+		<solid-user-remove-icon
+			aria-hidden="true"
+			class="icon"
+			v-if="$favourites.list.includes(uid)" />
+	</button>
+</template>
+
+<script>
+export default {
+	props: {
+		uid: {
+			type: String,
+			required: true
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+#favori {
+	width: 50px;
+	height: 50px;
+	margin-bottom: 1rem;
+
+	@media (min-width: 576px) {
+		margin-bottom: 0;
+	}
+}
+</style>
diff --git a/components/btn/Pagination.vue b/components/btn/Pagination.vue
index e28f0bc4ce6d2d4d827a569c3985d2d5030d6bbe..d76bc198282b4f95c3943abd0e0d776f717c7599 100644
--- a/components/btn/Pagination.vue
+++ b/components/btn/Pagination.vue
@@ -1,8 +1,8 @@
 <template>
 	<nav :aria-label="$t('pagination.title')" v-if="nbPages > 1">
-		<ul class="pagination justify-content-center">
+		<ul class="pagination justify-content-center m-0">
 			<li class="page-item" v-if="currentPage > 2 && nbPages > 3">
-				<a class="page-link" href="#" @click="firstPage($event)">
+				<a class="page-link" href="#" @click="goto(1, $event)">
 					<span :aria-label="$t('pagination.page') + ' 1'">1</span>
 				</a>
 			</li>
@@ -31,7 +31,7 @@
 				<span class="page-link"><span aria-hidden="true">...</span></span>
 			</li>
 			<li class="page-item" v-if="currentPage < nbPages - 1 && nbPages > 3">
-				<a class="page-link" href="#" @click="lastPage($event)">
+				<a class="page-link" href="#" @click="goto(nbPages, $event)">
 					<span :aria-label="$t('pagination.page') + ' ' + nbPages">{{
 						nbPages
 					}}</span>
@@ -44,6 +44,7 @@
 <script>
 export default {
 	props: {
+		target: String,
 		currentPage: {
 			type: Number,
 			required: true
@@ -60,15 +61,13 @@ export default {
 	methods: {
 		goto(i, e) {
 			if (e !== undefined) e.preventDefault()
+			let query = { ...this.$route.query }
+			query["page-" + this.target] = i
+			this.$router.push({
+				path: this.$route.path,
+				query: query
+			})
 			this.$emit("update:currentPage", i)
-		},
-		firstPage: function (e) {
-			e.preventDefault()
-			this.$emit("update:currentPage", 1)
-		},
-		lastPage: function (e) {
-			e.preventDefault()
-			this.$emit("update:currentPage", this.nbPages)
 		}
 	},
 	computed: {
diff --git a/components/btn/Search.vue b/components/btn/Search.vue
index e43616bbf3efe219a5e52f20916478f4c12c8edf..756f701b716efbcf0ec9585ca38416952769a375 100644
--- a/components/btn/Search.vue
+++ b/components/btn/Search.vue
@@ -1,7 +1,9 @@
 <template>
 	<div>
 		<div class="input-group mb-3">
-			<span class="input-group-text"><outline-search-icon class="icon" /></span>
+			<span class="input-group-text"
+				><solid-search-icon class="icon" aria-hidden="true"
+			/></span>
 			<input
 				type="text"
 				class="form-control"
@@ -11,7 +13,14 @@
 				@keyup="$emit('keyup', $event.keyCode)"
 				:placeholder="$t('recherche.title')"
 				:aria-label="$t('recherche.title')"
-				:aria-describedby="help ? rechHelp : null" />
+				:aria-describedby="help ? 'rechHelp' : null" />
+			<button
+				:title="$t('recherche.effacer')"
+				class="btn"
+				type="button"
+				@click="$emit('erase')">
+				<solid-x-icon class="icon" aria-hidden="true" />
+			</button>
 		</div>
 		<div
 			v-if="help"
@@ -34,3 +43,18 @@ export default {
 	}
 }
 </script>
+
+<style lang="scss" scoped>
+.input-group > * {
+	border-color: var(--border-color);
+}
+
+.btn {
+	background: var(--bg-menu-color);
+	color: var(--txt-secondary-color);
+
+	&:hover {
+		background: var(--bg-secondary-color);
+	}
+}
+</style>
diff --git a/components/btn/Sort.vue b/components/btn/Sort.vue
index 71cedae7aa02622eae385e88cf5517b1307ee172..e9200fca5e1814c013089a0d69f66db79aaa7e02 100644
--- a/components/btn/Sort.vue
+++ b/components/btn/Sort.vue
@@ -1,10 +1,12 @@
 <template>
-	<div class="btn-sort px-2" tabindex="0">
-		<span>{{ title }}</span>
-		<outline-sort-ascending-icon
+	<div class="btn-sort px-2" tabindex="0" :title="$t('tri.action')">
+		<span class="text-truncate">{{ title }}</span>
+		<solid-sort-ascending-icon
+			aria-hidden="true"
 			class="ms-2 icon flex-shrink-0"
 			v-if="currentSortDir == 'desc' && currentSort == fieldName" />
-		<outline-sort-descending-icon
+		<solid-sort-descending-icon
+			aria-hidden="true"
 			class="ms-2 icon flex-shrink-0"
 			v-if="currentSortDir == 'asc' && currentSort == fieldName" />
 	</div>
diff --git a/components/btn/Theme.vue b/components/btn/Theme.vue
index 6638e0dcb6b8b65c947268158663499dc35e4284..4b39f5cd9778bd211f78589d962d7664e8c020dc 100644
--- a/components/btn/Theme.vue
+++ b/components/btn/Theme.vue
@@ -9,6 +9,13 @@
 			for="checkbox"
 			class="switch-label d-flex align-items-center justify-content-between position-relative mb-0 form-control"
 			tabindex="0">
+			<span class="visually-hidden">
+				{{
+					this.userTheme === "light-theme"
+						? $t("aria.themedark")
+						: $t("aria.themelight")
+				}}
+			</span>
 			<span>🌙</span>
 			<span>☀️</span>
 			<div
diff --git a/components/certif/All.vue b/components/certif/All.vue
new file mode 100644
index 0000000000000000000000000000000000000000..605be22ea3fbb32e61b47cdd124d3bb8f16f1c4b
--- /dev/null
+++ b/components/certif/All.vue
@@ -0,0 +1,52 @@
+<template>
+	<div class="container-lg" v-if="idFromHash.status != 'REVOKED'">
+		<div class="row mt-3">
+			<div class="col-sm-10 col-md-8 col-lg-5 mx-auto">
+				<h3
+					class="h4 text-center"
+					:class="{
+						'text-success':
+							$options.filters.dateStatus(idFromHash.certsLimit) == 'success',
+						'text-warning':
+							$options.filters.dateStatus(idFromHash.certsLimit) == 'warning',
+						'text-danger':
+							$options.filters.dateStatus(idFromHash.certsLimit) == 'danger'
+					}">
+					{{ $t("certification.recues") }}&nbsp;<BadgeDanger
+						style="width: 2rem"
+						:limitDate="idFromHash.certsLimit"
+						:memberStatus="idFromHash.status" />
+				</h3>
+				<CertifGroup
+					:certifs="idFromHash.received_certifications"
+					type="received"
+					:certifStatus="$options.filters.dateStatus(idFromHash.certsLimit)" />
+			</div>
+			<hr class="d-lg-none mt-4" style="height: 10px" />
+			<div
+				class="col-1 d-none d-lg-flex"
+				v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)">
+				<div class="vr mx-auto"></div>
+			</div>
+			<div
+				class="col-sm-10 col-md-8 col-lg-5 mx-auto"
+				v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)">
+				<h3 class="h4 text-center text-info">
+					{{ $t("certification.envoyees") }}
+				</h3>
+				<CertifGroup :certifs="idFromHash.sent_certifications" type="sent" />
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		idFromHash: {
+			type: Object,
+			required: true
+		}
+	}
+}
+</script>
diff --git a/components/certif/Group.vue b/components/certif/Group.vue
index 115deb6308ea78c1d93981c6aa3876cc3b14e6cc..90effc3d6b1c80cb3f0ab234158e78aef304f22c 100644
--- a/components/certif/Group.vue
+++ b/components/certif/Group.vue
@@ -1,12 +1,21 @@
 <template>
 	<div>
-		<CertifList :certifs="certifsPending" :type="type" />
+		<CertifList
+			:title="$t('certification.enattente')"
+			:certifs="certifsPending"
+			:collapseId="type + '-entraitement'" />
 		<hr v-if="certifsPending.length > 0 && certifsNotPending.length > 0" />
 		<CertifList
+			:certifStatus="certifStatus"
+			:openDefault="true"
+			:title="$t('certification.encours')"
 			:certifs="certifsNotPending"
-			:type="type"
-			class="pb-3"
-			:class="{ 'pt-3': certifsPending.length > 0 }" />
+			:collapseId="type + '-encours'" />
+		<hr v-if="certifsExpired.length > 0" />
+		<CertifList
+			:title="$t('certification.perimees')"
+			:certifs="certifsExpired"
+			:collapseId="type + '-perimees'" />
 	</div>
 </template>
 
@@ -14,6 +23,10 @@
 export default {
 	props: {
 		certifs: Array,
+		certifStatus: {
+			type: String,
+			default: ""
+		},
 		type: {
 			type: String,
 			required: true,
@@ -24,17 +37,14 @@ export default {
 		}
 	},
 	computed: {
-		certifsMapped() {
-			return this.certifs.map((certif) => ({
-				...certif,
-				...(this.type === "received" ? certif.from : certif.to)
-			}))
-		},
 		certifsNotPending() {
-			return this.certifsMapped.filter((el) => el.pending == false)
+			return this.certifs.filter((el) => el.pending == false)
 		},
 		certifsPending() {
-			return this.certifsMapped.filter((el) => el.pending == true)
+			return this.certifs.filter((el) => el.pending == true)
+		},
+		certifsExpired() {
+			return this.certifs.filter((el) => el.expired == true)
 		}
 	}
 }
diff --git a/components/certif/List.vue b/components/certif/List.vue
index 982ea728ebfac262c0cdc196d5b11175f6d5b594..43b8611bf129c770bc5a822d6c79f09c96d92b77 100644
--- a/components/certif/List.vue
+++ b/components/certif/List.vue
@@ -1,82 +1,127 @@
 <template>
-	<div class="table-responsive" v-if="certifs.length > 0">
-		<BtnSearch v-model="search" class="px-2 pt-2" v-if="certifs.length > 5" />
-		<table class="table table-striped table-hover table-fixed sortable border">
-			<thead class="thead-light">
-				<tr>
-					<th class="p-0" @click="sort('uid')" @keyup.enter="sort('uid')">
-						<BtnSort
-							:title="$t('membre.title')"
-							fieldName="uid"
-							:currentSort="currentSort"
-							:currentSortDir="currentSortDir" />
-					</th>
-					<th
-						class="p-0 td-date"
-						@click="sort('expires_on')"
-						@keyup.enter="sort('expires_on')">
-						<BtnSort
-							:title="$t('expire')"
-							fieldName="expires_on"
-							:currentSort="currentSort"
-							:currentSortDir="currentSortDir" />
-					</th>
-				</tr>
-			</thead>
-			<tbody>
-				<tr
-					v-for="certif in certifsTriees"
-					:key="certif.uid"
-					tabindex="0"
-					@click="
-						$router.push(
-							localePath({
-								name: 'membre',
-								query: { hash: certif.hash }
-							})
-						)
-					"
-					@keyup.enter="
-						$router.push(
-							localePath({
-								name: 'membre',
-								query: { hash: certif.hash }
-							})
-						)
-					">
-					<td class="py-1">
-						<div class="d-flex">
-							<span v-if="$favourites.list.includes(certif.uid)">★&nbsp;</span>
-							<div class="text-truncate">{{ certif.uid }}</div>
-							&nbsp;
-							<BadgeDanger
-								:limitDate="certif.certsLimit"
-								:memberStatus="certif.status" />
-						</div>
-						<BadgeStatus :membre="certif" />
-						<BadgeQuality
-							:quality="certif.quality.ratio"
-							v-if="!['REVOKED', 'NEWCOMER'].includes(certif.status)" />
-						<BadgeDispo
-							:isDispo="certif.minDatePassed"
-							:dateDispo="certif.minDate"
-							:certifs="certif.sent_certifications"
-							v-if="certif.status == 'MEMBER'" />
-					</td>
-					<td class="py-1 text-center">
-						<BadgeDate :date="certif.expires_on" styleDate="short" />
-						<span class="badge bg-secondary" v-if="certif.pending">{{
-							$t("traitement")
-						}}</span>
-					</td>
-				</tr>
-			</tbody>
-		</table>
-		<BtnPagination
-			:currentPage.sync="currentPage"
-			:pageSize="pageSize"
-			:arrayLength="certifsFiltrees.length"
-			v-if="certifsFiltrees.length > pageSize" />
+	<div v-if="certifs.length > 0">
+		<button
+			:title="
+				isOpen ? $t('certification.masquer') : $t('certification.afficher')
+			"
+			@click="isOpen = !isOpen"
+			class="btn w-100 m-auto d-block rounded-0"
+			:class="btnClass"
+			type="button"
+			data-bs-toggle="collapse"
+			:data-bs-target="'#' + collapseId"
+			aria-expanded="false"
+			:aria-controls="collapseId">
+			<span v-if="!isOpen">
+				<solid-eye-icon class="icon" aria-hidden="true" />
+			</span>
+			<span v-else>
+				<solid-eye-off-icon class="icon" aria-hidden="true" />
+			</span>
+			<h4 class="d-inline align-middle">
+				{{ title }}&nbsp;&nbsp;
+				<span class="badge rounded-pill bg-white opacity-75 text-dark">{{
+					certifs.length
+				}}</span>
+			</h4>
+		</button>
+		<div
+			:id="collapseId"
+			class="table-responsive collapse p-3"
+			:class="collapseClass">
+			<BtnSearch
+				@erase="search = ''"
+				v-model="search"
+				class="px-2"
+				v-if="certifs.length > 5" />
+			<table
+				class="table table-striped table-hover table-fixed sortable border m-0">
+				<thead class="thead-light">
+					<tr>
+						<th class="p-0" @click="sort('uid')" @keyup.enter="sort('uid')">
+							<BtnSort
+								:title="$t('membre.title')"
+								fieldName="uid"
+								:currentSort="currentSort"
+								:currentSortDir="currentSortDir" />
+						</th>
+						<th
+							class="p-0 col-4"
+							@click="sort('expires_on')"
+							@keyup.enter="sort('expires_on')">
+							<BtnSort
+								:title="$t('expire')"
+								fieldName="expires_on"
+								:currentSort="currentSort"
+								:currentSortDir="currentSortDir" />
+						</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr
+						v-for="certif in certifsTriees"
+						:key="certif.uid"
+						tabindex="0"
+						:title="$t('membre.voirinfos')"
+						@click="
+							$router.push(
+								localePath({
+									name: 'membre',
+									query: { hash: certif.hash }
+								})
+							)
+						"
+						@keyup.enter="
+							$router.push(
+								localePath({
+									name: 'membre',
+									query: { hash: certif.hash }
+								})
+							)
+						">
+						<td class="py-1">
+							<div class="d-flex">
+								<span v-if="$favourites.list.includes(certif.uid)"
+									>★&nbsp;</span
+								>
+								<div class="text-truncate">{{ certif.uid }}</div>
+								&nbsp;
+								<BadgeDanger
+									style="width: 1.2rem"
+									:limitDate="certif.certsLimit"
+									:memberStatus="certif.status" />
+							</div>
+							<BadgeStatus :membre="certif" />
+							<BadgeQuality
+								:quality="certif.quality.ratio"
+								v-if="!['REVOKED', 'NEWCOMER'].includes(certif.status)" />
+							<BadgeDispo
+								:isDispo="certif.minDatePassed"
+								:dateDispo="certif.minDate"
+								:certifs="certif.sent_certifications"
+								v-if="certif.status == 'MEMBER'" />
+						</td>
+						<td class="p-0 text-center col-4">
+							<div class="d-flex flex-column gap-1">
+								<BadgeDate :date="certif.expires_on" />
+								<span
+									class="badge bg-secondary text-truncate d-block"
+									v-if="certif.pending"
+									>{{ $t("traitement") }}</span
+								>
+							</div>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+			<BtnPagination
+				class="mt-3"
+				:target="collapseId"
+				:currentPage.sync="currentPage"
+				:pageSize="pageSize"
+				:arrayLength="certifsFiltrees.length"
+				v-if="certifsFiltrees.length > pageSize" />
+		</div>
 	</div>
 </template>
 
@@ -88,11 +133,19 @@ export default {
 			currentSort: "expires_on",
 			currentSortDir: "asc",
 			currentPage: 1,
-			pageSize: 5
+			pageSize: 5,
+			isOpen: this.openDefault
 		}
 	},
 	props: {
-		certifs: Array
+		certifs: Array,
+		certifStatus: String,
+		collapseId: String,
+		title: String,
+		openDefault: {
+			type: Boolean,
+			default: false
+		}
 	},
 	methods: {
 		sort(s) {
@@ -100,6 +153,20 @@ export default {
 				this.currentSortDir = this.currentSortDir === "asc" ? "desc" : "asc"
 			}
 			this.currentSort = s
+		},
+		setPage() {
+			let page = this.$route.query["page-" + this.collapseId]
+				? parseInt(this.$route.query["page-" + this.collapseId])
+				: 1
+
+			if (
+				isNaN(page) ||
+				page > Math.ceil(this.certifsFiltrees.length / this.pageSize) ||
+				page < 1
+			)
+				page = 1
+
+			this.currentPage = page
 		}
 	},
 	computed: {
@@ -133,6 +200,33 @@ export default {
 					let end = this.currentPage * this.pageSize
 					if (index >= start && index < end) return true
 				})
+		},
+		collapseClass() {
+			if (this.collapseId.includes("entraitement")) return "bg-secondary"
+			if (this.collapseId.includes("perimees")) return "bg-warning"
+			if (this.collapseId == "sent-encours") return "bg-info"
+			return {
+				"bg-success": this.certifStatus == "success",
+				"bg-warning": this.certifStatus == "warning",
+				"bg-danger": this.certifStatus == "danger"
+			}
+		},
+		btnClass() {
+			if (this.collapseId.includes("entraitement")) return "btn-secondary"
+			if (this.collapseId.includes("perimees")) return "btn-warning"
+			if (this.collapseId == "sent-encours") return "btn-info"
+			return {
+				"btn-success": this.certifStatus == "success",
+				"btn-warning": this.certifStatus == "warning",
+				"btn-danger": this.certifStatus == "danger"
+			}
+		}
+	},
+	mounted() {
+		this.setPage()
+
+		if (this.openDefault && this.certifs.length > 0) {
+			document.querySelector("#" + this.collapseId).classList.add("show")
 		}
 	},
 	watch: {
@@ -140,14 +234,26 @@ export default {
 			handler(n, o) {
 				this.search = ""
 			}
+		},
+		$route(to, from) {
+			this.setPage()
 		}
 	}
 }
 </script>
 
 <style lang="scss" scoped>
-thead th:last-child {
-	padding-right: 1.5rem;
-	text-align: right;
+tbody tr {
+	height: 80px;
+}
+
+@media (min-width: 576px) {
+	button {
+		font-size: 1.3rem;
+	}
+
+	tbody tr {
+		height: initial;
+	}
 }
 </style>
diff --git a/components/member/Card.vue b/components/member/Card.vue
index e4a6995df660b07ee143994997a9349c0722560c..b0594f3f66eb9a93d923a7e2dc45eaff43d92893 100644
--- a/components/member/Card.vue
+++ b/components/member/Card.vue
@@ -2,38 +2,27 @@
 	<div class="card member">
 		<div class="card-body">
 			<div
-				class="uid-wrapper d-flex align-items-center justify-content-between mb-4">
-				<h2 class="h3 card-title text-center d-flex align-items-center m-0">
-					<span class="text-truncate d-inline-block">
+				class="d-flex align-items-center justify-content-between flex-column-reverse flex-sm-row mb-4">
+				<h2
+					class="h1 card-title text-center d-flex align-items-center flex-column m-0 w-100">
+					<span class="text-truncate d-inline-block mw-100">
 						{{ hash.uid }}
 					</span>
 					<small><BadgeStatus class="ms-2" :membre="hash" /></small>
 				</h2>
-				<button
-					id="favori"
-					class="btn btn-secondary"
-					v-tooltip="
-						$favourites.list.includes(hash.uid)
-							? $t('favoris.enregistre')
-							: $t('favoris.supprime')
-					"
-					@click="$favourites.toggleFavourite(hash.uid, $event)">
-					<outline-user-add-icon
-						class="icon"
-						v-if="!$favourites.list.includes(hash.uid)" />
-					<outline-user-remove-icon
-						class="icon"
-						v-if="$favourites.list.includes(hash.uid)" />
-				</button>
+				<BtnFavori :uid="hash.uid" />
 			</div>
 			<BtnClipboard :textContent="hash.pubkey" />
+			<AlertMember :hash="hash" />
 			<div class="table-responsive">
 				<table
 					class="table table-sm table-borderless"
 					v-if="hash.status != 'REVOKED'">
 					<tbody>
 						<tr v-if="hash.status == 'MEMBER'">
-							<th scope="row">{{ $t("membre.referent.title") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.referent.title") }}&nbsp;:
+							</th>
 							<td
 								:class="{
 									'table-success': hash.sentry,
@@ -43,7 +32,9 @@
 							</td>
 						</tr>
 						<tr v-if="hash.status != 'NEWCOMER'">
-							<th scope="row">{{ $t("membre.qualite.title") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.qualite.title") }}&nbsp;:
+							</th>
 							<td
 								:class="{
 									'table-success': hash.quality.ratio >= 80,
@@ -53,7 +44,9 @@
 							</td>
 						</tr>
 						<tr>
-							<th scope="row">{{ $t("membre.distance.title") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.distance.title") }}&nbsp;:
+							</th>
 							<td
 								:class="{
 									'table-success':
@@ -73,7 +66,7 @@
 							</td>
 						</tr>
 						<tr>
-							<th scope="row">
+							<th scope="row" class="fw-normal">
 								{{
 									hash.status != "MISSING"
 										? $t("membre.datelimadhesion")
@@ -90,7 +83,9 @@
 							</td>
 						</tr>
 						<tr v-if="hash.status == 'MEMBER'">
-							<th scope="row">{{ $t("membre.datemanquecertifs") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.datemanquecertifs") }}&nbsp;:
+							</th>
 							<td
 								:class="
 									'table-' + $options.filters.dateStatus(hash.certsLimit)
@@ -99,7 +94,9 @@
 							</td>
 						</tr>
 						<tr v-if="hash.status == 'MEMBER'">
-							<th scope="row">{{ $t("membre.dispocertif") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.dispocertif") }}&nbsp;:
+							</th>
 							<td
 								:class="{
 									'table-success': hash.minDatePassed,
@@ -112,14 +109,16 @@
 							</td>
 						</tr>
 						<tr v-if="hash.status == 'MEMBER'">
-							<th scope="row">{{ $t("membre.nb_certifs") }}&nbsp;:</th>
+							<th scope="row" class="fw-normal">
+								{{ $t("membre.nb_certifs") }}&nbsp;:
+							</th>
 							<td
 								:class="{
-									'table-success': hash.sent_certifications.length <= 80,
-									'table-warning': hash.sent_certifications.length > 80,
-									'table-danger': hash.sent_certifications.length > 90
+									'table-success': sentCertNotExpired.length <= 80,
+									'table-warning': sentCertNotExpired.length > 80,
+									'table-danger': sentCertNotExpired.length > 90
 								}">
-								{{ 100 - hash.sent_certifications.length }}
+								{{ 100 - sentCertNotExpired.length }}
 							</td>
 						</tr>
 					</tbody>
@@ -138,36 +137,16 @@ export default {
 	},
 	props: {
 		hash: Object
+	},
+	computed: {
+		sentCertNotExpired() {
+			return this.hash.sent_certifications.filter((el) => !el.expired)
+		}
 	}
 }
 </script>
 
 <style lang="scss">
-.uid-wrapper {
-	flex-direction: column-reverse;
-
-	button {
-		margin-bottom: 1rem;
-	}
-
-	.text-truncate {
-		max-width: 300px;
-	}
-
-	@media (min-width: 576px) {
-		flex-direction: row;
-
-		button {
-			margin-bottom: 0;
-		}
-	}
-}
-
-#favori {
-	width: 50px;
-	height: 50px;
-}
-
 .member {
 	.table {
 		text-align: center;
diff --git a/components/member/List.vue b/components/member/List.vue
index ab1bec3952e45fa853e220a45d874573dcaa64c6..0e9538c7be443048e0da11dbf941985df7d14499 100644
--- a/components/member/List.vue
+++ b/components/member/List.vue
@@ -18,7 +18,7 @@
 					<th
 						scope="col"
 						class="d-none d-lg-table-cell p-0"
-						v-if="['favoris', 'search'].includes(type)"
+						v-if="id != 'default'"
 						@click="sort('statut')"
 						@keyup.enter="sort('statut')">
 						<BtnSort
@@ -30,7 +30,7 @@
 					<th
 						scope="col"
 						class="td-quality d-none d-lg-table-cell p-0"
-						v-if="['favoris', 'search'].includes(type)"
+						v-if="['favoris', 'search'].includes(id)"
 						@click="sort('quality')"
 						@keyup.enter="sort('quality')">
 						<BtnSort
@@ -42,12 +42,12 @@
 					<th
 						scope="col"
 						class="d-none d-xl-table-cell p-0"
-						v-if="['favoris', 'search'].includes(type)"
+						v-if="['favoris', 'search'].includes(id)"
 						@click="sort('dispo')"
 						@keyup.enter="sort('dispo')">
 						<BtnSort
 							fieldName="dispo"
-							:title="$t('membre.dispo')"
+							:title="$t('membre.disponibilite')"
 							:currentSort="currentSort"
 							:currentSortDir="currentSortDir" />
 					</th>
@@ -56,11 +56,11 @@
 						class="td-date d-none d-sm-table-cell p-0"
 						@click="sort('date_membership')"
 						@keyup.enter="sort('date_membership')"
-						v-if="['adhesion', 'favoris', 'search'].includes(type)">
+						v-if="['adhesion', 'favoris', 'search'].includes(id)">
 						<BtnSort
 							fieldName="date_membership"
 							:title="
-								['certif', 'adhesion'].includes(this.type)
+								['certif', 'adhesion'].includes(id)
 									? $t('date')
 									: $t('membre.datelimpertestatut')
 							"
@@ -71,23 +71,23 @@
 						scope="col"
 						class="td-date d-none p-0"
 						:class="{
-							'd-sm-table-cell': type == 'certif',
-							'd-md-table-cell': type != 'certif'
+							'd-sm-table-cell': id == 'certif',
+							'd-md-table-cell': id != 'certif'
 						}"
 						@click="sort('date_certs')"
 						@keyup.enter="sort('date_certs')"
-						v-if="['certif', 'favoris', 'search'].includes(type)">
+						v-if="['certif', 'favoris', 'search'].includes(id)">
 						<BtnSort
 							fieldName="date_certs"
 							:title="
-								['certif', 'adhesion'].includes(this.type)
+								['certif', 'adhesion'].includes(id)
 									? $t('date')
 									: $t('membre.datemanquecertifs')
 							"
 							:currentSort="currentSort"
 							:currentSortDir="currentSortDir" />
 					</th>
-					<th v-if="type == 'favoris'" style="width: 60px"></th>
+					<th v-if="id == 'favoris'" style="width: 60px"></th>
 				</tr>
 			</thead>
 			<tbody>
@@ -95,6 +95,7 @@
 					v-for="member in membersSorted"
 					:key="member.uid"
 					tabindex="0"
+					:title="$t('membre.voirinfos')"
 					@click="redirect(member.hash)"
 					@keyup.enter="redirect(member.hash)">
 					<td>
@@ -108,6 +109,7 @@
 									<div class="text-truncate">{{ member.uid }}</div>
 									&nbsp;
 									<BadgeDanger
+										style="width: 1.2rem"
 										:limitDate="member.certsLimit"
 										:memberStatus="member.status" />
 								</div>
@@ -115,14 +117,14 @@
 									{{ member.pubkey.substring(0, 10) }}
 								</div>
 								<div
-									v-if="['adhesion', 'certif'].includes(type)"
+									v-if="['adhesion', 'certif'].includes(id)"
 									class="d-sm-none">
-									<BadgeDate :date="getDate(member)" styleDate="short" />
+									<BadgeDate :date="getDate(member)" />
 								</div>
 							</div>
 							<div
 								class="w-50 d-flex flex-column align-items-center justify-content-evenly gap-1 d-lg-none"
-								v-if="['favoris', 'search'].includes(type)">
+								v-if="['favoris', 'search'].includes(id)">
 								<BadgeStatus :membre="member" class="mw-100 text-truncate" />
 								<BadgeQuality :quality="member.quality.ratio" />
 								<BadgeDispo
@@ -133,19 +135,17 @@
 							</div>
 						</div>
 					</td>
-					<td
-						class="d-none d-lg-table-cell"
-						v-if="['favoris', 'search'].includes(type)">
+					<td class="d-none d-lg-table-cell" v-if="id != 'default'">
 						<BadgeStatus :membre="member" />
 					</td>
 					<td
 						class="d-none d-lg-table-cell"
-						v-if="['favoris', 'search'].includes(type)">
+						v-if="['favoris', 'search'].includes(id)">
 						<BadgeQuality :quality="member.quality.ratio" />
 					</td>
 					<td
 						class="d-none d-xl-table-cell"
-						v-if="['favoris', 'search'].includes(type)">
+						v-if="['favoris', 'search'].includes(id)">
 						<BadgeDispo
 							:isDispo="member.minDatePassed"
 							:dateDispo="member.minDate"
@@ -153,31 +153,32 @@
 					</td>
 					<td
 						class="d-none d-sm-table-cell"
-						v-if="['adhesion', 'favoris', 'search'].includes(type)">
-						<BadgeDate :date="member.limitDate" styleDate="short" />
+						v-if="['adhesion', 'favoris', 'search'].includes(id)">
+						<BadgeDate :date="member.limitDate" />
 					</td>
 					<td
 						class="d-none"
 						:class="{
-							'd-sm-table-cell': type == 'certif',
-							'd-md-table-cell': type != 'certif'
+							'd-sm-table-cell': id == 'certif',
+							'd-md-table-cell': id != 'certif'
 						}"
-						v-if="['certif', 'favoris', 'search'].includes(type)">
-						<BadgeDate :date="member.certsLimit" styleDate="short" />
+						v-if="['certif', 'favoris', 'search'].includes(id)">
+						<BadgeDate :date="member.certsLimit" />
 					</td>
-					<td class="py-1" v-if="type == 'favoris'" style="width: 60px">
+					<td class="py-1" v-if="id == 'favoris'" style="width: 60px">
 						<button
 							class="btn btn-danger"
 							v-if="$favourites.list.includes(member.uid)"
 							@click="$favourites.toggleFavourite(member.uid, $event)"
 							:title="$t('favoris.supprimer')">
-							<outline-trash-icon class="icon" />
+							<solid-trash-icon class="icon" aria-hidden="true" />
 						</button>
 					</td>
 				</tr>
 			</tbody>
 		</table>
 		<BtnPagination
+			:target="id"
 			:currentPage.sync="currentPage"
 			:pageSize="pageSize"
 			:arrayLength="members.length" />
@@ -199,16 +200,9 @@ export default {
 			type: Array,
 			required: true
 		},
-		type: {
+		id: {
 			type: String,
-			default: "default",
-			validator: function (value) {
-				return (
-					["adhesion", "certif", "favoris", "search", "default"].indexOf(
-						value
-					) !== -1
-				)
-			}
+			required: true
 		},
 		defaultSortDir: {
 			type: String,
@@ -230,14 +224,28 @@ export default {
 			this.currentSort = s
 		},
 		getDate(member) {
-			if (this.type == "adhesion") return member.limitDate
-			if (this.type == "certif") return member.certsLimit
+			if (this.id == "adhesion") return member.limitDate
+			if (this.id == "certif") return member.certsLimit
 			return Math.min(member.limitDate, member.certsLimit)
 		},
 		getOrder(a, b, order) {
 			if (a < b) return -1 * order
 			if (a > b) return 1 * order
 			return 0
+		},
+		setPage() {
+			let page = this.$route.query["page-" + this.id]
+				? parseInt(this.$route.query["page-" + this.id])
+				: 1
+
+			if (
+				isNaN(page) ||
+				page > Math.ceil(this.members.length / this.pageSize) ||
+				page < 1
+			)
+				page = 1
+
+			this.currentPage = page
 		}
 	},
 	computed: {
@@ -273,6 +281,14 @@ export default {
 					if (index >= start && index < end) return true
 				})
 		}
+	},
+	mounted() {
+		this.setPage()
+	},
+	watch: {
+		$route(to, from) {
+			this.setPage()
+		}
 	}
 }
 </script>
diff --git a/components/navigation/Bar.vue b/components/navigation/Bar.vue
index 3e861f0483970bf4632aad0e638600d2b31f9ea9..585e677333a038b9ad829a95a83988216d03086a 100644
--- a/components/navigation/Bar.vue
+++ b/components/navigation/Bar.vue
@@ -2,6 +2,7 @@
 	<header class="header position-fixed">
 		<div class="position-relative">
 			<button
+				:title="isOpen ? $t('aria.closemenu') : $t('aria.openmenu')"
 				class="toggle btn border-secondary position-absolute p-1 ms-4"
 				@click="toggleMenu">
 				<span></span>
diff --git a/components/navigation/menu/Group.vue b/components/navigation/menu/Group.vue
index 9d6056a2b2ef822c247dfec9944f23acda428d38..c90d22ab0db9ebfa21454e26d1657a9f693cf2d4 100644
--- a/components/navigation/menu/Group.vue
+++ b/components/navigation/menu/Group.vue
@@ -1,6 +1,6 @@
 <template>
 	<div class="mb-4">
-		<h2 class="small text-muted text-uppercase ms-4 mb-0 pb-2">
+		<h2 class="small text-muted text-uppercase ms-5 mb-0 pb-2">
 			{{ $t(menu.title) }}
 		</h2>
 		<div class="nav navbar-nav list-group list-group-flush">
@@ -10,7 +10,12 @@
 				v-for="item in menu.items"
 				:key="item.path"
 				@click.native="toggleMenu()">
-				<div class="menu-item position-relative py-2">{{ $t(item.title) }}</div>
+				<div class="menu-item position-relative py-2">
+					<component
+						aria-hidden="true"
+						:is="'solid-' + item.icon + '-icon'"
+						class="icon" />&nbsp;{{ $t(item.title) }}
+				</div>
 			</NuxtLink>
 		</div>
 	</div>
diff --git a/components/navigation/menu/Sidebar.vue b/components/navigation/menu/Sidebar.vue
index c75ebd0e247d70e345084c08f248836476d7274e..90b3117fe7d65099dff962cd4f62718f540c71dd 100644
--- a/components/navigation/menu/Sidebar.vue
+++ b/components/navigation/menu/Sidebar.vue
@@ -47,7 +47,10 @@
 					}
 					$router.push(localePath('a-propos'))
 				">
-				v{{ $config.clientVersion }} | {{ $t("apropos.title") }}
+				<solid-terminal-icon
+					style="width: 0.9rem"
+					aria-hidden="true" />&nbsp;v{{ $config.clientVersion }} |
+				{{ $t("apropos.title") }}
 			</button>
 		</div>
 	</aside>
diff --git a/graphql/cache.js b/graphql/cache.js
index b8dff3ab0aa40ff1915392b50022bfd76b8cfe1a..435b8921d707b8e77b356b2582f815f8ab8b3c95 100644
--- a/graphql/cache.js
+++ b/graphql/cache.js
@@ -25,6 +25,12 @@ export const cache = new InMemoryCache({
 				return `${object.member.hash}:${object.date}:${object.after}:${object.proba}`
 			case "GroupId":
 				return `${object.id.hash}`
+			case "CertHist":
+				return `${object.id.uid}:${object.hist[0].block.number}`
+			case "CertEvent":
+				return `${object.in}:${object.block.number}`
+			case "Block":
+				return `${object.number}`
 			default:
 				return defaultDataIdFromObject(object) // fall back to default handling
 		}
diff --git a/graphql/queries.js b/graphql/queries.js
index fe68c143272898e499db251629ec4afba14e3f6d..671ff50feb6fc811a1e6b9ec1614eae75a122b18 100644
--- a/graphql/queries.js
+++ b/graphql/queries.js
@@ -124,6 +124,12 @@ export const SEARCH_MEMBER = gql`
 			isLeaving
 			sentry
 			membership_pending
+			all_certifiersIO {
+				...IO
+			}
+			all_certifiedIO {
+				...IO
+			}
 			distanceE {
 				__typename
 				value {
@@ -176,6 +182,21 @@ export const SEARCH_MEMBER = gql`
 			pending
 		}
 	}
+	fragment IO on CertHist {
+		__typename
+		id {
+			...attr
+		}
+		hist {
+			__typename
+			in
+			block {
+				__typename
+				number
+				utc0
+			}
+		}
+	}
 `
 
 // Pour la page parametres
diff --git a/i18n/locales/de.json b/i18n/locales/de.json
index 6d3c809d3583a1dda71c498aec08bd8be9249d29..e3ab65f08479075f42e4e337428d12640fc8b083 100644
--- a/i18n/locales/de.json
+++ b/i18n/locales/de.json
@@ -4,9 +4,28 @@
 		"desc": "Aufnahme im Vertrauensnetz. Die Mitgliedschaft ist ein Jahr lang gültig und muss erneuert werden, wenn man weiterhin seine Universaldividende mitproduzieren möchte.",
 		"title": "Mitgliedschaft"
 	},
+	"alert": {
+		"attenteCertifiers": "Warten auf Zertifizierer",
+		"attention": "Warnung",
+		"certStockLim": "Dieses Konto hat nur <b>{n} Zertifizierung</b> auf Lager. | Dieses Konto hat nur <b>{n} Zertifizierungen</b> auf Lager.",
+		"certifManquantes": "Diesem Konto fehlen <b>{n} Zertifizierung</b>, um Mitglied zu werden. | Diesem Konto fehlen <b>{n} Zertifizierungen</b>, um Mitglied zu werden.",
+		"certifManquantesBientot": "Dieses Konto hat bald <b>keine Zertifizierungen mehr</b>.",
+		"distKO": "Dieses Konto verstößt gegen die <b>Abstandsregel</b>.",
+		"dossierKOtitle": "Unvollständige Datei",
+		"dossierOK": "Die Datei für dieses Konto ist <b>vollständig</b> und wird <b>bald Mitglied</b>.",
+		"dossierOKtitle": "Komplette Datei",
+		"information": "Information",
+		"missing": "Dieses Konto hat seine Mitgliedschaft verloren. Er hat <b>{n} Tag</b> Zeit, um die Mitgliedschaft erneut zu beantragen, bevor er automatisch widerrufen wird. | Dieses Konto hat seine Mitgliedschaft verloren. Er hat <b>{n} Tage</b> Zeit, um die Mitgliedschaft erneut zu beantragen, bevor er automatisch widerrufen wird.",
+		"noMoreCert": "Dieses Konto hat <b>keine weiteren Zertifizierungen</b> auf Lager.",
+		"notAllCertifiersDispo": "Mindestens ein Zertifizierer ist <b>nicht verfügbar</b>.",
+		"notAvailable": "Dieses Mitglied ist zur Zertifizierung <b>nicht verfügbar</b>.",
+		"renew": "Dieses Konto muss seine Mitgliedschaft <b>erneuern</b>.",
+		"revoked": "Dieses Konto wurde <b>aufgehoben</b> und kann nicht länger Mitglied werden."
+	},
 	"apropos": {
+		"alert": "Die von der Anwendung angezeigten Daten stammen von einem <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>Wotwizard-Server mit graphQL-Technologie</a> und hängen vom abgefragten Duniter-Knoten ab. Aus diesem Grund können Daten, die noch nicht in die Blockchain geschrieben wurden, von Anwendung zu Anwendung unterschiedlich sein.",
 		"bienvenue": "Willkommen im Wotwizard!",
-		"contribuer": "Der Quellcode von Wotwizard befindet sich auf <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">unserem Gitlab-Server</a>. Zögern Sie nicht, dort Fehler oder Verbesserungsvorschläge auf der <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\"><b>Issues</b>-Seite</a> zu melden. The API graphql source code can be found at <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">this URL</a>.",
+		"contribuer": "Der Quellcode von Wotwizard befindet sich auf <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">unserem Gitlab-Server</a>. Zögern Sie nicht, dort Fehler oder Verbesserungsvorschläge auf der <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\"><b>Issues</b>-Seite</a> zu melden.",
 		"deploiement": "Deployment:",
 		"desc": "Dieses Tool ermöglicht es Ihnen, die Bei- und Austritte des Vertrauensnetzes der freien Währung Ğ1 zu verfolgen und ist das Ergebnis einer Zusammenarbeit zwischen verschiedenen Entwicklende und Nutzende der Währung. Zögern Sie nicht, sich bei ihnen mit einer Spende auf den folgenden öffentlichen Schlüssel zu bedanken:",
 		"developpeurs": "Entwicklung:",
@@ -17,7 +36,13 @@
 	},
 	"aria": {
 		"ariane": "Brotkrümel",
-		"close": "Nah dran"
+		"clipboard": "In die Zwischenablage kopieren",
+		"close": "Nah dran",
+		"closemenu": "Menü schließen",
+		"openmenu": "Menü öffnen",
+		"scrolltotop": "Zurück nach oben",
+		"themedark": "Wechseln Sie zum dunklen Thema",
+		"themelight": "Wechseln Sie zum Lichtdesign"
 	},
 	"aurevoir": "Auf Wiedersehen",
 	"bienvenue": "Willkommen",
@@ -34,8 +59,13 @@
 		"title": "Zentralität"
 	},
 	"certification": {
+		"afficher": "Zertifizierungen Anzeigen",
 		"desc": "In der Blockchain gespeicherte Bestätigung einer Person, dass sie einer anderen vertraut. Eine Zertifizierung ist zwei Jahre lang gültig.",
+		"enattente": "Warten auf Behandlung",
+		"encours": "Aktuell gültig",
 		"envoyees": "Ausgestellte Zertifizierungen",
+		"masquer": "Zertifizierungen Verstecken",
+		"perimees": "Erneuern",
 		"recues": "Erhaltene Zertifizierungen",
 		"title": "Zertifizierung"
 	},
@@ -79,6 +109,7 @@
 	},
 	"expire": "Ablaufdatum",
 	"favoris": {
+		"ajouter": "Zu den Favoriten hinzufügen",
 		"enregistre": "In den Favoriten gespeichert!",
 		"none": "Sie haben noch keine Favoriten",
 		"supprime": "Aus den Favoriten entfernt!",
@@ -91,7 +122,8 @@
 	"infos": "Informationen",
 	"inout": "Bei- und Austritte des Vertrauensnetz in den 2 letzten Tagen",
 	"inpreparation": "In Vorbereitung",
-	"jours": "24 Stunden | {n} Tage",
+	"jours": "< 24 Stunden | {n} Tagen",
+	"jours_avant": "< 24h | vor {n} Tagen",
 	"lang": "Wähle deine Sprache",
 	"lexique": "Lexikon",
 	"membre": {
@@ -105,7 +137,8 @@
 		"datemanquecertifs": "Fehlende Zertifizierungen",
 		"desc": "Einzelperson, die in das Vertrauensnetz beigetreten ist, die die fünf erforderlichen Zertifizierungen erhalten hat und die Distanzregel einhält. Ein Mitglied schöpft die Währung über seine Universaldividende mit.",
 		"dispo": "Kann zertifizieren",
-		"dispocertif": "Kann zertifizieren",
+		"dispocertif": "Verfügbarkeit",
+		"disponibilite": "Disponibilidad",
 		"distance": {
 			"desc": "Anteil der referierenden Mitglieder, die in höchstens 5 Sprüngen im Vertrauensnetz erreichbar sind. Um Mitglied zu werden, muss man mindestens 5 Zertifizierungen sammeln, mit denen man 80% der referierenden Mitglieder in max. 5 Sprüngen erreichen kann.",
 			"title": "Distanz"
@@ -120,7 +153,8 @@
 			"desc": "Mitglied, das eine bestimmte Anzahl von Zertifizierungen ausgestellt UND erhalten hat, die von der Gesamtzahl der Mitglieder abhängt. Dieser Status bietet keine Vorteile, ermöglicht aber die Sicherung des Vertrauensnetzes, insbesondere durch die Distanzregel.",
 			"title": "Referierendes Mitglied"
 		},
-		"title": "Mitglied"
+		"title": "Mitglied",
+		"voirinfos": "Mitgliedsinformationen anzeigen"
 	},
 	"membres": "Mitglieder",
 	"mot": "Wort oder Ausdruck",
@@ -188,6 +222,7 @@
 			"lexicon": "Suche im Lexikon",
 			"member": "Geben Sie den Anfang eines Pseudonyms oder eines öffentlichen Schlüssels ein"
 		},
+		"effacer": "Löschen Sie Ihre Suche",
 		"title": "Ihre Suche"
 	},
 	"revocation": {
@@ -197,7 +232,8 @@
 	"revoila": "Hier sind sie wieder",
 	"slogan": "Der Zauberer des Vertrauensnetzes",
 	"statut": {
-		"bientotmanquecertif": "Wenig Zertifizierungen",
+		"allcertif": "5 Zertifizierungen erhalten",
+		"bientotmanquecertif": "Bald wenig Zertifizierungen",
 		"manquecertif": "Zu wenig Zertifizierungen",
 		"member": "Mitglied",
 		"missing": "Verlorene Mitgliedschaft",
@@ -211,6 +247,7 @@
 	},
 	"traitement": "In Bearbeitung",
 	"tri": {
+		"action": "Sortieren",
 		"pardate": "Nach Datum sortieren",
 		"parmembres": "Alphabetisch sortieren"
 	},
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index e6eed8c16d84cb111c3514fb33eae7c8ca46fd80..ad91e7a60d098b22a4e57bd5a2d59816a9cfef41 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -4,9 +4,28 @@
 		"desc": "Registration in the web of trust. Membership is valid for one year and must be renewed if you wish to continue to co-produce your Universal Dividend",
 		"title": "Membership"
 	},
+	"alert": {
+		"attenteCertifiers": "Waiting for certifiers",
+		"attention": "Warning",
+		"certStockLim": "This account has only <b>{n} certification</b> in stock. | This account has only <b>{n} certifications</b> in stock.",
+		"certifManquantes": "This account lacks <b>{n} certification</b> to become a member. | This account lacks <b>{n} certifications</b> to become a member.",
+		"certifManquantesBientot": "This account will soon <b>run out of certifications</b>.",
+		"distKO": "This account violates <b>the distance rule</b>.",
+		"dossierKOtitle": "Incomplete file",
+		"dossierOK": "The file for this account is <b>complete</b> and will <b>soon be a member</b>.",
+		"dossierOKtitle": "Complete file",
+		"information": "Information",
+		"missing": "This account has lost its membership. He has <b>{n} day</b> to reapply for membership before automatic revocation. | This account has lost its membership. He has <b>{n} days</b> to reapply for membership before automatic revocation.",
+		"noMoreCert": "This account has <b>no more certifications</b> in stock.",
+		"notAllCertifiersDispo": "One or more certifiers are <b>not available</b>.",
+		"notAvailable": "This member is <b>unavailable</b> to certify.",
+		"renew": "This account needs to <b>renew its membership</b>.",
+		"revoked": "This account is <b>revoked</b> and can no longer become a member."
+	},
 	"apropos": {
+		"alert": "The data displayed by the application comes from a <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>Wotwizard server using graphQL technology</a> and depends on the Duniter node queried. This is why data that is not yet written to the blockchain may differ from one application to another.",
 		"bienvenue": "Welcome to Wotwizard !",
-		"contribuer": "The source code of the application is on <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">our Gitlab server</a>. Don't hesitate to report bugs or suggestions for improvement in <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">the <b>Tickets</b> section</a>. The API graphql source code can be found at <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">this URL</a>.",
+		"contribuer": "The source code of the application is on <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">our Gitlab server</a>. Don't hesitate to report bugs or suggestions for improvement in <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">the <b>Tickets</b> section</a>.",
 		"deploiement": "Deployment :",
 		"desc": "This tool allows you to track entries and exits from the Äž1 free currency web of trust and is the result of a collaboration between various developers and users of the currency. Do not hesitate to thank them by making a donation on the following public key:",
 		"developpeurs": "Developers :",
@@ -18,7 +37,13 @@
 	},
 	"aria": {
 		"ariane": "Breadcrumb",
-		"close": "Close"
+		"clipboard": "Copy to clipboard",
+		"close": "Close",
+		"closemenu": "Close menu",
+		"openmenu": "Open menu",
+		"scrolltotop": "Back to top",
+		"themedark": "Toggle dark theme",
+		"themelight": "Toggle light theme"
 	},
 	"aurevoir": "Goodbye to",
 	"bienvenue": "Welcome to",
@@ -35,8 +60,13 @@
 		"title": "Centrality"
 	},
 	"certification": {
+		"afficher": "Display certificates",
 		"desc": "Recognition link between an individual and another recorded in the blockchain. A certification is valid for 2 years",
+		"enattente": "Waiting for treatment",
+		"encours": "Currently valid",
 		"envoyees": "Certificates sent",
+		"masquer": "Hide certificates",
+		"perimees": "To renew",
 		"recues": "Certificates received ",
 		"title": "Certification"
 	},
@@ -80,6 +110,7 @@
 	},
 	"expire": "Expiry",
 	"favoris": {
+		"ajouter": "Add to favorites",
 		"enregistre": "Saved to favorites&nbsp;!",
 		"none": "You don't have any favorites yet",
 		"supprime": "Deleted from favourites&nbsp;!",
@@ -92,7 +123,8 @@
 	"infos": "Informations",
 	"inout": "Entries and exits of the web of trust for the last 2 days",
 	"inpreparation": "In preparation",
-	"jours": "24h | {n} days",
+	"jours": "< 24h | {n} days",
+	"jours_avant": "< 24h | {n} days ago",
 	"lang": "Choose your language",
 	"lexique": "Lexicon",
 	"membre": {
@@ -107,6 +139,7 @@
 		"desc": "Individual whose membership is in progress and who has received the 5 necessary certifications allowing him to respect the distance rule. Which allows him to be co-creator of the currency via his Universal Dividend",
 		"dispo": "Available",
 		"dispocertif": "Available to certify",
+		"disponibilite": "Availablity",
 		"distance": {
 			"desc": "% of referring members reachable in 5 hops or less in the web of trust. To become a member, you must collect at least 5 certifications allowing you to reach 80% of the referring members in 5 jumps max.",
 			"title": "Distance"
@@ -121,7 +154,8 @@
 			"desc": "Member having issued AND received a certain number of certifications, variable according to the number of total members. This status does not offer any advantages but allows the security of the web of trust in particular thanks to the distance rule",
 			"title": "Referring member"
 		},
-		"title": "Member"
+		"title": "Member",
+		"voirinfos": "See member informations"
 	},
 	"membres": "Members",
 	"mot": "Word or expression",
@@ -189,6 +223,7 @@
 			"lexicon": "Search in the lexicon",
 			"member": "Enter the start of a nickname or public key"
 		},
+		"effacer": "Erase your search",
 		"title": "Your search"
 	},
 	"revocation": {
@@ -198,8 +233,9 @@
 	"revoila": "Here they are again",
 	"slogan": "The Web of Trust’s wizard",
 	"statut": {
-		"bientotmanquecertif": "Needs certifications",
-		"manquecertif": "Needs certifications",
+		"allcertif": "5 recieved certificates",
+		"bientotmanquecertif": "Needs certificates soon",
+		"manquecertif": "Needs certificates",
 		"member": "Member",
 		"missing": "Membership lost",
 		"newcomer": "Future member",
@@ -212,6 +248,7 @@
 	},
 	"traitement": "Ongoing treatment",
 	"tri": {
+		"action": "Sort by",
 		"pardate": "Sort by date",
 		"parmembres": "Sort by members"
 	},
diff --git a/i18n/locales/es.json b/i18n/locales/es.json
index 527aba0a999cd365416a6e32443ac6b5b8c68e7e..2146232a4037f42c44b7ae113d706cf897d1f83f 100644
--- a/i18n/locales/es.json
+++ b/i18n/locales/es.json
@@ -4,9 +4,28 @@
 		"desc": "Registro en la red de confianza. La membresía es válida por un año y debe renovarse si desea continuar cocreando su Dividendo Universal",
 		"title": "Membresía"
 	},
+	"alert": {
+		"attenteCertifiers": "Esperando certificadores",
+		"attention": "Atención",
+		"certStockLim": "Esta cuenta solo tiene <b>{n} certificación</b> en stock. | Esta cuenta solo tiene <b>{n} certificaciones</b> en stock.",
+		"certifManquantes": "Esta cuenta carece de <b>{n} certificación</b> para convertirse en miembro. | Esta cuenta carece de <b>{n} certificaciones</b> para convertirse en miembro.",
+		"certifManquantesBientot": "Esta cuenta pronto <b>se quedará sin certificaciones</b>.",
+		"distKO": "Esta cuenta infringe la <b>regla de distancia</b>.",
+		"dossierKOtitle": "Archivo incompleto",
+		"dossierOK": "El archivo de esta cuenta está <b>completo</b> y <b>pronto será miembro</b>.",
+		"dossierOKtitle": "Archivo completo",
+		"information": "Información",
+		"missing": "Esta cuenta ha perdido su membresía. Tiene <b>{n} día</b> para volver a solicitar la membresía antes de la revocación automática. | Esta cuenta ha perdido su membresía. Tiene <b>{n} días</b> para volver a solicitar la membresía antes de la revocación automática.",
+		"noMoreCert": "Esta cuenta <b>no tiene más certificaciones</b> en stock.",
+		"notAllCertifiersDispo": "Uno o más certificadores <b>no están disponibles</b>.",
+		"notAvailable": "Este miembro <b>no está disponible</b> para certificar.",
+		"renew": "Esta cuenta necesita <b>renovar su membresía</b>.",
+		"revoked": "Esta cuenta está <b>revocada</b> y ya no puede convertirse en miembro."
+	},
 	"apropos": {
+		"alert": "Los datos que muestra la aplicación provienen de <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>un servidor Wotwizard con tecnología graphQL</a> y dependen del nodo Duniter consultado. Esta es la razón por la que los datos que aún no se escriben en la cadena de bloques pueden diferir de una aplicación a otra.",
 		"bienvenue": "¡Bienvenido a Wotwizard!",
-		"contribuer": "El código fuente de la aplicación está en <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">nuestro servidor Gitlab</a>. No dudes en reportar errores o sugerencias de mejora en <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la sección de <b>Tickets</b></a>. El código de la API Graphqlse encuentra en <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">esta URL</a>.",
+		"contribuer": "El código fuente de la aplicación está en <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">nuestro servidor Gitlab</a>. No dudes en reportar errores o sugerencias de mejora en <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la sección de <b>Tickets</b></a>.",
 		"deploiement": "Despliegue :",
 		"desc": "Esta herramienta le permite realizar un seguimiento de las entradas y salidas de la red de confianza de la moneda libre Ğ1 y es el resultado de una colaboración entre varios desarrolladores y usuarios de la moneda. No dude en agradecerles haciendo una donación a la siguiente llave pública:",
 		"developpeurs": "Desarrolladores :",
@@ -18,7 +37,13 @@
 	},
 	"aria": {
 		"ariane": "Hilo de Ariadna",
-		"close": "Cerrar"
+		"clipboard": "Copiar al portapapeles",
+		"close": "Cerrar",
+		"closemenu": "Cerrar menú",
+		"openmenu": "Menú abierto",
+		"scrolltotop": "Volver arriba",
+		"themedark": "Cambiar a tema oscuro",
+		"themelight": "Cambiar a tema claro"
 	},
 	"aurevoir": "Salen",
 	"bienvenue": "Entran",
@@ -35,8 +60,13 @@
 		"title": "Centralidad"
 	},
 	"certification": {
+		"afficher": "Para mostrar certificaciones",
 		"desc": "Vínculo de reconocimiento entre un individuo y otro registrado en la cadena de bloques. Una certificación es válida por 2 años.",
+		"enattente": "Esperando tratamiento",
+		"encours": "Actualmente válido",
 		"envoyees": "Certificaciones enviadas",
+		"masquer": "Esconder certificaciones",
+		"perimees": "Renovar",
 		"recues": "Certificaciones recibidas ",
 		"title": "Certificacione"
 	},
@@ -80,6 +110,7 @@
 	},
 	"expire": "Expiración",
 	"favoris": {
+		"ajouter": "Agregar a los favoritos",
 		"enregistre": "¡Guardado en favoritos!",
 		"none": "Aún no tienes favoritos",
 		"supprime": "¡Eliminado de favoritos!",
@@ -92,7 +123,8 @@
 	"infos": "Informaciones",
 	"inout": "Entradas y salidas de la red de confianza en los últimos 2 días",
 	"inpreparation": "En preparación",
-	"jours": "24 horas | {n} días",
+	"jours": "< 24 horas | {n} días",
+	"jours_avant": "< 24h | hay {n} días",
 	"lang": "Elige tu idioma",
 	"lexique": "Léxico",
 	"membre": {
@@ -107,6 +139,7 @@
 		"desc": "Individuo cuya candidatura está en curso y que ha recibido las 5 certificaciones necesarias y que le permitan respetar la regla de la distancia. Lo que le permite ser co-creador de la moneda a través de su Dividendo Universal",
 		"dispo": "Disponible",
 		"dispocertif": "Disponible para certificar",
+		"disponibilite": "Disponibilidad",
 		"distance": {
 			"desc": "% de miembros de referencia o control accesibles en 5 pasos o menos en la Red de Confianza. Para convertirse en miembro, debe obtener al menos 5 certificaciones que le permitan llegar al 80% de los miembros de referencia o control en 5 pasos como máximo.",
 			"title": "Distancia"
@@ -121,7 +154,8 @@
 			"desc": "Miembro que haya emitido Y recibido un número determinado de certificaciones, variable según el número total de miembros. Este estatus no ofrece ninguna ventaja o privilegio personal pero permite la seguridad de la red de confianza en particular gracias a la regla de distancia",
 			"title": "Miembro de referencia o control"
 		},
-		"title": "Miembro"
+		"title": "Miembro",
+		"voirinfos": "Ver información de miembros"
 	},
 	"membres": "Miembros",
 	"mot": "Palabra o expresión",
@@ -189,6 +223,7 @@
 			"lexicon": "Buscar en el léxico",
 			"member": "Introduce el comienzo de un pseudónimo o llave pública"
 		},
+		"effacer": "Limpia tu búsqueda",
 		"title": "Buscar"
 	},
 	"revocation": {
@@ -198,6 +233,7 @@
 	"revoila": "Regresan",
 	"slogan": "El mago de la Red de Confianza",
 	"statut": {
+		"allcertif": "5 certificaciones recibidas",
 		"bientotmanquecertif": "Necesitará certificaciones",
 		"manquecertif": "Faltan certificaciones",
 		"member": "Miembro",
@@ -212,8 +248,9 @@
 	},
 	"traitement": "Tratamiento en curso",
 	"tri": {
+		"action": "Ordenar por",
 		"pardate": "Ordenar por fecha",
-		"parmembres": "Clasificar por miembros"
+		"parmembres": "Ordenar por miembros"
 	},
 	"trm": {
 		"desc": "Teoría que demuestra que es posible una moneda libre con una propiedad de invariancia y que la solución a este problema es única: Una moneda es libre si y solo si es coproducida por igual en el espacio y en el tiempo por todos sus miembros",
diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json
index 6bf6c4d35b5c6aa517c3b305f223f2427a5e36f4..6d9e057c35a965603317640a67f1d34979b1b02a 100644
--- a/i18n/locales/fr.json
+++ b/i18n/locales/fr.json
@@ -4,9 +4,28 @@
 		"desc": "Inscription dans la toile de confiance. L'adhésion est valable un an et doit être renouvelée si on souhaite continuer à co-produire son Dividende Universel",
 		"title": "Adhésion"
 	},
+	"alert": {
+		"attenteCertifiers": "En attente des certificateurs",
+		"attention": "Attention",
+		"certStockLim": "Ce compte n'a plus que <b>{n} certification</b> en stock. | Ce compte n'a plus que <b>{n} certifications</b> en stock.",
+		"certifManquantes": "Il manque <b>{n} certification</b> à ce compte pour devenir membre. | Il manque <b>{n} certifications</b> à ce compte pour devenir membre.",
+		"certifManquantesBientot": "Ce compte va bientôt <b>manquer de certifications</b>.",
+		"distKO": "Ce compte ne respecte pas <b>la règle de distance</b>.",
+		"dossierKOtitle": "Dossier incomplet",
+		"dossierOK": "Le dossier de ce compte est <b>complet</b> et sera <b>bientôt membre</b>.",
+		"dossierOKtitle": "Dossier complet",
+		"information": "Information",
+		"missing": "Ce compte a perdu son adhésion. Il lui reste <b>{n} jour</b> pour refaire une demande d'adhésion avant la révocation automatique. | Ce compte a perdu son adhésion. Il lui reste <b>{n} jours</b> pour refaire une demande d'adhésion avant la révocation automatique.",
+		"noMoreCert": "Ce compte n'a <b>plus de certifications</b> en stock.",
+		"notAllCertifiersDispo": "Un ou plusieurs certificateurs ne sont <b>pas disponibles</b>.",
+		"notAvailable": "Ce membre n'est <b>pas disponible</b> pour certifier.",
+		"renew": "Ce compte doit <b>renouveler son adhésion</b>.",
+		"revoked": "Ce compte est <b>révoqué</b> et ne peut plus devenir membre."
+	},
 	"apropos": {
+		"alert": "Les données affichées par l'application sont issues d'un <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>serveur Wotwizard utilisant la technologie graphQL</a> et dépendent du noeud Duniter interrogé. C'est pourquoi les données qui ne sont pas encore inscrites en blockchain peuvent différer d'une application à une autre.",
 		"bienvenue": "Bienvenue dans Wotwizard !",
-		"contribuer": "Le code source de l'application se trouve sur <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">notre serveur Gitlab</a>. N'hésitez pas à y remonter les bugs ou les propositions d'amélioration dans <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la section <b>Tickets</b></a>. Le code source de l'API graphQL se trouve à <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">cette adresse</a>.",
+		"contribuer": "Le code source de l'application se trouve sur <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">notre serveur Gitlab</a>. N'hésitez pas à y remonter les bugs ou les propositions d'amélioration dans <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la section <b>Tickets</b></a>.",
 		"deploiement": "Déploiement :",
 		"desc": "Cet outil vous permet de suivre les entrées et les sorties de la toile de confiance de la monnaie libre Ğ1 et est le fruit d'une collaboration entre différents développeurs et utilisateurs de la monnaie. N'hésitez pas à les remercier en faisant un don sur la clé publique suivante:",
 		"developpeurs": "Développeurs :",
@@ -18,7 +37,13 @@
 	},
 	"aria": {
 		"ariane": "Fil d'ariane",
-		"close": "Fermer"
+		"clipboard": "Copier dans le presse-papier",
+		"close": "Fermer",
+		"closemenu": "Fermer le menu",
+		"openmenu": "Ouvrir le menu",
+		"scrolltotop": "Retour en haut",
+		"themedark": "Basculer sur le thème foncé",
+		"themelight": "Basculer sur le thème clair"
 	},
 	"aurevoir": "Au revoir à",
 	"bienvenue": "Bienvenue à",
@@ -35,8 +60,13 @@
 		"title": "Centralité"
 	},
 	"certification": {
+		"afficher": "Afficher les certifications",
 		"desc": "Lien de reconnaissance entre un individu et un autre enregistré dans la blockchain. Une certification est valable 2 ans",
+		"enattente": "En attente de traitement",
+		"encours": "En cours de validité",
 		"envoyees": "Certifications envoyées",
+		"masquer": "Masquer les certifications",
+		"perimees": "A renouveler",
 		"recues": "Certifications reçues ",
 		"title": "Certification"
 	},
@@ -80,6 +110,7 @@
 	},
 	"expire": "Expiration",
 	"favoris": {
+		"ajouter": "Ajouter aux favoris",
 		"enregistre": "Enregistré dans les favoris&nbsp;!",
 		"none": "Vous n'avez pas encore de favoris",
 		"supprime": "Supprimé des favoris&nbsp;!",
@@ -92,7 +123,8 @@
 	"infos": "Informations",
 	"inout": "Entrées et sorties de la toile de confiance des 2 derniers jours",
 	"inpreparation": "En préparation",
-	"jours": "24h | {n} jours",
+	"jours": "< 24h | {n} jours",
+	"jours_avant": "< 24h | il y a {n} jours",
 	"lang": "Choisissez votre langue",
 	"lexique": "Lexique",
 	"membre": {
@@ -107,6 +139,7 @@
 		"desc": "Individu dont l'adhésion est en cours et ayant reçu les 5 certifications nécessaires lui permettant de respecter la règle de distance. Ce qui lui permet d'être co-créateur de la monnaie via son Dividende Universel",
 		"dispo": "Disponible",
 		"dispocertif": "Disponible pour certifier",
+		"disponibilite": "Disponibilité",
 		"distance": {
 			"desc": "% des membres référents atteignables en 5 sauts ou moins dans la toile de confiance. Pour devenir membre, il faut récolter au minimum 5 certifications permettant de rejoindre 80% des membres référents en 5 sauts max",
 			"title": "Distance"
@@ -121,7 +154,8 @@
 			"desc": "Membre ayant émis ET reçu un certain nombre de certifications, variable en fonction du nombre de membres total. Ce statut n'offre pas d'avantages mais permet la sécurisation de la toile de confiance notammen grâce à la règle de distance",
 			"title": "Membre référent"
 		},
-		"title": "Membre"
+		"title": "Membre",
+		"voirinfos": "Voir les informations du membre"
 	},
 	"membres": "Membres",
 	"mot": "Mot ou expression",
@@ -189,6 +223,7 @@
 			"lexicon": "Recherchez dans le lexique",
 			"member": "Saisissez le début d'un pseudo ou d'une clé publique"
 		},
+		"effacer": "Effacer la recherche",
 		"title": "Votre recherche"
 	},
 	"revocation": {
@@ -198,7 +233,8 @@
 	"revoila": "Les revoilà",
 	"slogan": "Le magicien de la Toile de Confiance",
 	"statut": {
-		"bientotmanquecertif": "En manque de certifications",
+		"allcertif": "5 certifications reçues",
+		"bientotmanquecertif": "Bientôt en manque de certifications",
 		"manquecertif": "En manque de certifications",
 		"member": "Membre",
 		"missing": "Adhésion perdue",
@@ -212,6 +248,7 @@
 	},
 	"traitement": "En cours de traitement",
 	"tri": {
+		"action": "Trier par",
 		"pardate": "Tri par date",
 		"parmembres": "Tri par membres"
 	},
diff --git a/layouts/default.vue b/layouts/default.vue
index 1eaca3d2202c85f671fb7f7ce78fa129e71b4bd7..7c8e85b2cc1dee8e76edb82196039956789d4a23 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -1,12 +1,13 @@
 <template>
-	<div class="app">
+	<div class="app pb-3">
 		<NavigationBar :breadcrumb="breadcrumb" :menus="menus" />
 		<Nuxt />
 		<a
 			class="scrollToTop fade bg-secondary position-fixed text-white d-flex align-items-center justify-content-center rounded"
+			:title="$t('aria.scrolltotop')"
 			href="#"
 			@click="scrollToTop($event)">
-			<outline-chevron-double-up-icon class="icon" />
+			<solid-chevron-double-up-icon class="icon" aria-hidden="true" />
 		</a>
 	</div>
 </template>
@@ -17,26 +18,39 @@ export default {
 		return {
 			breadcrumb: [],
 			// Les title correspondent aux chaînes de traduction dans /i18n/locales
+			// Les icônes disponibles sont ici : https://vue-hero-icons.netlify.app/
 			menus: [
 				{
 					title: "wot.title",
 					items: [
-						{ path: "/membres", title: "membres" },
-						{ path: "/favoris", title: "favoris.title" }
+						{ path: "/membres", title: "membres", icon: "search" },
+						{ path: "/favoris", title: "favoris.title", icon: "user-group" }
 					]
 				},
 				{
 					title: "previsions.title",
 					items: [
-						{ path: "/previsions", title: "futuremembers" },
-						{ path: "/previsions/futures_sorties", title: "futureexits" }
+						{
+							path: "/previsions",
+							title: "futuremembers",
+							icon: "login"
+						},
+						{
+							path: "/previsions/futures_sorties",
+							title: "futureexits",
+							icon: "logout"
+						}
 					]
 				},
 				{
 					title: "infos",
 					items: [
-						{ path: "/lexique", title: "lexique" },
-						{ path: "/parametres", title: "params.title" }
+						{
+							path: "/lexique",
+							title: "lexique",
+							icon: "question-mark-circle"
+						},
+						{ path: "/parametres", title: "params.title", icon: "adjustments" }
 					]
 				}
 			]
@@ -72,11 +86,11 @@ export default {
 :root {
 	--txt-primary-color: #000;
 	--txt-secondary-color: #435770;
-	--txt-muted-color: #5e7690;
+	--txt-muted-color: #5b728e;
 	--txt-link: #6e7881;
-	--bg-primary-color: #fff;
-	--bg-secondary-color: #e9ecef;
-	--bg-menu-color: #fafafa;
+	--bg-primary-color: #e9ecef;
+	--bg-secondary-color: #dee2e6;
+	--bg-menu-color: #ced4da;
 	--border-color: #d8d8d8;
 }
 
@@ -129,7 +143,6 @@ main {
 	width: 50px;
 	height: 50px;
 	opacity: 0.7;
-	transition: all 0.3s ease-in;
 
 	&.show {
 		right: 1rem;
diff --git a/nuxt.config.js b/nuxt.config.js
index d7b5715fee897a37480b964e6dba396201612601..c683b76b52ff99697a393fad94751a110f361f74 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -44,7 +44,7 @@ export default {
 		// https://go.nuxtjs.dev/pwa
 		"@nuxtjs/pwa",
 		// https://github.com/whardier/nuxt-hero-icons
-		"@nuxt-hero-icons/outline/nuxt"
+		"@nuxt-hero-icons/solid/nuxt"
 	],
 
 	// Modules: https://go.nuxtjs.dev/config-modules
diff --git a/package.json b/package.json
index 070623ad392528a16bbcbb4bf9bc4451d29f2f18..ba506dcc9b5a3d531eb854b4e9294cede9c232d3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "wotwizard-ui",
-	"version": "1.0.1",
+	"version": "2.0.0",
 	"private": true,
 	"scripts": {
 		"dev": "nuxt",
@@ -15,7 +15,7 @@
 		"nuxt-generate": "nuxt generate"
 	},
 	"dependencies": {
-		"@nuxt-hero-icons/outline": "^1.0.1",
+		"@nuxt-hero-icons/solid": "^1.0.1",
 		"@nuxtjs/apollo": "^4.0.1-rc.5",
 		"@nuxtjs/i18n": "^7.2.0",
 		"@nuxtjs/pwa": "^3.3.5",
diff --git a/pages/a-propos.vue b/pages/a-propos.vue
index 83231214cf8a44362b7dfbedcf057c341f598c35..d34d076ecf797f3e361e9a1b465757a12f361d38 100644
--- a/pages/a-propos.vue
+++ b/pages/a-propos.vue
@@ -1,10 +1,17 @@
 <template>
 	<main class="container">
 		<h2 class="display-5 my-5">{{ $t("apropos.bienvenue") }}</h2>
+		<AlertDefault
+			icon="information-circle"
+			type="info"
+			:title="$t('alert.information')">
+			<span v-html="$t('apropos.alert')"></span>
+		</AlertDefault>
 		<p class="lead">
 			{{ $t("apropos.desc") }}
 		</p>
 		<BtnClipboard textContent="78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8" />
+		<p v-html="$t('apropos.contribuer')"></p>
 		<hr class="my-4" />
 		<h3 class="h4 mb-4">{{ $t("apropos.participants") }}</h3>
 		<div class="row">
@@ -19,7 +26,7 @@
 					<NuxtLink
 						v-for="contrib in cat.contrib"
 						:key="contrib"
-						class="list-group-item list-group-item-action"
+						class="list-group-item list-group-item-action list-group-item-dark"
 						:to="chemin(contrib)">
 						{{ contrib }}
 					</NuxtLink>
@@ -41,7 +48,6 @@
 				</div>
 			</div>
 		</div>
-		<p v-html="$t('apropos.contribuer')"></p>
 	</main>
 </template>
 
diff --git a/pages/favoris.vue b/pages/favoris.vue
index b1d8f6be3fc4e59bb93e9825551033049dc4cce8..fd2387dd876000ca62c3afd118532894e790a1f3 100644
--- a/pages/favoris.vue
+++ b/pages/favoris.vue
@@ -1,13 +1,15 @@
 <template>
 	<main class="container">
 		<h2 class="text-center my-5 font-weight-light">
-			{{ $t("favoris.title") }}
+			<solid-user-group-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+				$t("favoris.title")
+			}}
 		</h2>
 		<NavigationLoader :isLoading="$apollo.queries.favoris.loading" />
 		<div class="row">
 			<div class="col">
 				<transition name="fade">
-					<Alerte type="danger" :error="error" />
+					<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 				</transition>
 				<transition name="fade">
 					<div
@@ -16,13 +18,14 @@
 						">
 						<BtnSearch
 							v-if="favoris.length > 5"
+							@erase="search = ''"
 							v-model="search"
 							:help="$t('recherche.desc.favourites')"
 							class="col-sm-7 col-md-6 col-lg-5 col-xl-4 mx-auto mb-4" />
 						<MemberList
 							defaultSort="date_membership"
 							:members="filteredFavoris"
-							type="favoris" />
+							id="favoris" />
 					</div>
 				</transition>
 				<transition name="fade">
diff --git a/pages/index.vue b/pages/index.vue
index e57d6417e96193701385f71cb0ea27aef9e66605..dd7acdb686a85638ac48e66bfc2311fd2a6b6f58 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1,24 +1,40 @@
 <template>
 	<main class="container">
-		<h2 class="text-center my-5 font-weight-light">{{ $t("inout") }}</h2>
+		<h2 class="text-center my-5 font-weight-light">
+			<solid-home-icon style="width: 1.5rem" aria-hidden="true" />&nbsp;{{
+				$t("inout")
+			}}
+		</h2>
 		<NavigationLoader :isLoading="$apollo.queries.newMembers.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div class="result" v-if="newMembers">
 				<div class="row text-center">
 					<div class="col-md-6 col-lg-4 mb-5">
-						<h2 class="h4 text-success">{{ $t("bienvenue") }}</h2>
-						<MemberList :members="newMembers['entrees']" />
+						<h2 class="h4 text-success">
+							<solid-login-icon class="icon" aria-hidden="true" />&nbsp;{{
+								$t("bienvenue")
+							}}
+						</h2>
+						<MemberList id="entrees" :members="newMembers['entrees']" />
 					</div>
 					<div class="col-md-6 col-lg-4 mb-5">
-						<h2 class="h4 text-danger">{{ $t("aurevoir") }}</h2>
-						<MemberList :members="newMembers['sorties']" />
+						<h2 class="h4 text-danger">
+							<solid-logout-icon class="icon" aria-hidden="true" />&nbsp;{{
+								$t("aurevoir")
+							}}
+						</h2>
+						<MemberList id="sorties" :members="newMembers['sorties']" />
 					</div>
 					<div class="col-lg-4 mb-5">
-						<h2 class="h4 text-info">{{ $t("revoila") }}</h2>
-						<MemberList :members="newMembers['renew']" />
+						<h2 class="h4 text-info">
+							<solid-reply-icon class="icon" aria-hidden="true" />&nbsp;{{
+								$t("revoila")
+							}}
+						</h2>
+						<MemberList id="renew" :members="newMembers['renew']" />
 					</div>
 				</div>
 			</div>
diff --git a/pages/lexique.vue b/pages/lexique.vue
index 9061987a2a8e957282b6d76afc9b5344df46b205..9072a446bb763c16b808bb328d5388caf47f1479 100644
--- a/pages/lexique.vue
+++ b/pages/lexique.vue
@@ -2,8 +2,15 @@
 	<main class="container">
 		<div class="rech-lexique row pb-3">
 			<div class="col-md-6 mx-auto">
-				<h2 class="text-center font-weight-light">{{ $t("lexique") }}</h2>
-				<BtnSearch v-model="search" :help="$t('recherche.desc.lexicon')" />
+				<h2 class="text-center font-weight-light">
+					<solid-question-mark-circle-icon
+						style="width: 2rem"
+						aria-hidden="true" />&nbsp;{{ $t("lexique") }}
+				</h2>
+				<BtnSearch
+					@erase="search = ''"
+					v-model="search"
+					:help="$t('recherche.desc.lexicon')" />
 			</div>
 		</div>
 		<div class="table-responsive mt-1">
diff --git a/pages/membre.vue b/pages/membre.vue
index 809a0e3307908de7577d03f981d4a69715382170..864b3869a31cbdfd9ac7f310e44880da54b765d1 100644
--- a/pages/membre.vue
+++ b/pages/membre.vue
@@ -2,7 +2,7 @@
 	<main>
 		<NavigationLoader :isLoading="$apollo.queries.idFromHash.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div v-if="idFromHash && !$apollo.queries.idFromHash.loading">
@@ -13,48 +13,7 @@
 						</div>
 					</div>
 				</div>
-				<div class="container">
-					<div class="row mt-3" v-if="idFromHash.status != 'REVOKED'">
-						<div class="col-sm-10 col-md-8 col-lg-5 mb-3 mx-auto">
-							<h3
-								class="h5 text-center"
-								:class="{
-									'text-success':
-										['NEWCOMER', 'MISSING'].includes(idFromHash.status) &&
-										idFromHash.received_certifications.length >= 5,
-									'text-danger':
-										['NEWCOMER', 'MISSING'].includes(idFromHash.status) &&
-										idFromHash.received_certifications.length < 5
-								}">
-								{{ $t("certification.recues") }} ({{ nbCertifsReceived
-								}}<span v-if="nbCertifsPendingReceived != 0">{{
-									"&nbsp;+&nbsp;" + nbCertifsPendingReceived
-								}}</span
-								>)
-								<BadgeDanger
-									:limitDate="idFromHash.certsLimit"
-									:memberStatus="idFromHash.status" />
-							</h3>
-							<CertifGroup
-								:certifs="idFromHash.received_certifications"
-								type="received" />
-						</div>
-						<div
-							class="col-sm-10 col-md-8 col-lg-5 mx-auto"
-							v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)">
-							<h3 class="h5 text-center">
-								{{ $t("certification.envoyees") }} ({{ nbCertifsSent
-								}}<span v-if="nbCertifsPendingSent != 0">{{
-									"&nbsp;+&nbsp;" + nbCertifsPendingSent
-								}}</span
-								>)
-							</h3>
-							<CertifGroup
-								:certifs="idFromHash.sent_certifications"
-								type="sent" />
-						</div>
-					</div>
-				</div>
+				<CertifAll :idFromHash="idFromHash" />
 			</div>
 		</transition>
 	</main>
@@ -90,11 +49,77 @@ export default {
 			variables() {
 				return { hash: this.$route.query.hash }
 			},
+			update(data) {
+				let idFromHash = data.idFromHash
+				let received_certifications = idFromHash.received_certifications.map(
+					(certif) => ({
+						...certif,
+						...certif.from
+					})
+				)
+				let sent_certifications = idFromHash.sent_certifications.map(
+					(certif) => ({
+						...certif,
+						...certif.to
+					})
+				)
+
+				return {
+					...idFromHash,
+					sent_certifications: this.compileArrays(
+						idFromHash.all_certifiedIO,
+						sent_certifications
+					),
+					received_certifications: this.compileArrays(
+						idFromHash.all_certifiersIO,
+						received_certifications
+					)
+				}
+			},
 			error(err) {
 				this.error = err.message
 			}
 		}
 	},
+	methods: {
+		compileArrays(allIO, actualIO) {
+			let all = allIO.map(function (CertHist) {
+				return {
+					...CertHist.id,
+					dateout: CertHist.hist
+						.filter((CertEvent) => !CertEvent.in)
+						.reduce(
+							function (prev, current) {
+								return prev.block.utc0 > current.block.utc0 ? prev : current
+							},
+							{ block: { utc0: 0 } }
+						).block.utc0
+				}
+			})
+
+			all.forEach((certifier) => {
+				let isFound = false
+
+				for (const certif of actualIO) {
+					if (certifier.uid == certif.uid) {
+						isFound = true
+						break
+					}
+				}
+
+				if (!isFound && certifier.status != "REVOKED") {
+					actualIO.push({
+						__typename: "Certification",
+						expires_on: certifier.dateout,
+						...certifier,
+						expired: true
+					})
+				}
+			})
+
+			return actualIO
+		}
+	},
 	nuxtI18n: {
 		paths: {
 			fr: "/membre",
@@ -102,36 +127,6 @@ export default {
 			es: "/miembro"
 		}
 	},
-	computed: {
-		classWarning() {
-			return {
-				"text-danger": !this.idFromHash.certsLimit,
-				"text-warning":
-					this.$options.filters.dateStatus(this.idFromHash.certsLimit) ==
-					"warning"
-			}
-		},
-		nbCertifsSent() {
-			return this.idFromHash.sent_certifications.filter((el) => {
-				return el.pending == false
-			}).length
-		},
-		nbCertifsReceived() {
-			return this.idFromHash.received_certifications.filter((el) => {
-				return el.pending == false
-			}).length
-		},
-		nbCertifsPendingSent() {
-			return this.idFromHash.sent_certifications.filter((el) => {
-				return el.pending == true
-			}).length
-		},
-		nbCertifsPendingReceived() {
-			return this.idFromHash.received_certifications.filter((el) => {
-				return el.pending == true
-			}).length
-		}
-	},
 	mounted() {
 		$nuxt.$emit("changeRoute", this.breadcrumb)
 	},
diff --git a/pages/membres/index.vue b/pages/membres/index.vue
index 6fbe0b0c4ab3f8256f837c0aa60a4a668e30cbb1..7d7d864bcef34b682e1c9661545f67b5d770a724 100644
--- a/pages/membres/index.vue
+++ b/pages/membres/index.vue
@@ -1,9 +1,14 @@
 <template>
 	<main class="container">
-		<h2 class="text-center my-5 font-weight-light">{{ $t("membres") }}</h2>
+		<h2 class="text-center my-5 font-weight-light">
+			<solid-search-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+				$t("membres")
+			}}
+		</h2>
 		<div class="row mb-4">
 			<div class="col-sm-8 col-md-6 col-lg-5 m-auto">
 				<BtnSearch
+					@erase="param = ''"
 					v-model="param"
 					:help="$t('recherche.desc.member')"
 					@keyup="save" />
@@ -11,7 +16,7 @@
 		</div>
 		<NavigationLoader :isLoading="$apollo.queries.idSearch.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div
@@ -20,7 +25,7 @@
 					idSearch && param.length > 2 && !$apollo.queries.idSearch.loading
 				">
 				<div class="col m-auto">
-					<MemberList :members="idSearch.ids" type="search" />
+					<MemberList :members="idSearch.ids" id="search" />
 				</div>
 			</div>
 		</transition>
diff --git a/pages/parametres.vue b/pages/parametres.vue
index ed8afe4ab3817d788144b46919f86ba48399fa03..9d36ef3447b982d9acc41adf1565fb36efa621a2 100644
--- a/pages/parametres.vue
+++ b/pages/parametres.vue
@@ -1,9 +1,13 @@
 <template>
 	<main class="container">
-		<h2 class="text-center my-5 font-weight-light">{{ $t("params.title") }}</h2>
+		<h2 class="text-center my-5 font-weight-light">
+			<solid-adjustments-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+				$t("params.title")
+			}}
+		</h2>
 		<NavigationLoader :isLoading="$apollo.queries.allParameters.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div class="table-responsive" v-if="allParameters">
diff --git a/pages/previsions/futures_sorties.vue b/pages/previsions/futures_sorties.vue
index eb0e97a3ba92a6d1e4dd3d950be95cce0b0bb324..88cd90857d4630eeea688000f65a1df1e1600424 100644
--- a/pages/previsions/futures_sorties.vue
+++ b/pages/previsions/futures_sorties.vue
@@ -1,6 +1,10 @@
 <template>
 	<main class="container">
-		<h2 class="text-center my-5 font-weight-light">{{ $t("futureexits") }}</h2>
+		<h2 class="text-center my-5 font-weight-light">
+			<solid-logout-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+				$t("futureexits")
+			}}
+		</h2>
 		<div class="form-check form-switch mb-4 d-flex justify-content-center">
 			<input
 				type="checkbox"
@@ -15,7 +19,7 @@
 		</div>
 		<NavigationLoader :isLoading="$apollo.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div class="row" v-if="!$apollo.loading">
@@ -26,7 +30,7 @@
 					<MemberList
 						defaultSort="date_membership"
 						:members="memEnds"
-						type="adhesion" />
+						id="adhesion" />
 				</div>
 				<div class="col-md-6 mb-5" v-if="certEnds">
 					<h2 class="h4 text-danger text-center text-truncate">
@@ -35,7 +39,7 @@
 					<MemberList
 						defaultSort="date_certs"
 						:members="certEnds"
-						type="certif" />
+						id="certif" />
 				</div>
 			</div>
 		</transition>
diff --git a/pages/previsions/index.vue b/pages/previsions/index.vue
index 1f3810d195c6eae6534f0dd44e96d599e0aaad9c..ec0d9e518670e14d1c717000a155c1d66e3b5218 100644
--- a/pages/previsions/index.vue
+++ b/pages/previsions/index.vue
@@ -2,12 +2,14 @@
 	<main class="container">
 		<NavigationLoader :isLoading="$apollo.queries.wwResult.loading" />
 		<transition name="fade">
-			<Alerte type="danger" :error="error" />
+			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
 			<div v-if="wwResult">
 				<h2 class="text-center my-5 font-weight-light">
-					{{ $t("previsions.title") }}
+					<solid-login-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+						$t("previsions.title")
+					}}
 					<small
 						><span class="badge bg-secondary">{{
 							$tc("dossier.attente", wwResult.dossiers_nb)
@@ -52,6 +54,7 @@
 											v-for="forecast in wwResult.forecastsByNames"
 											:key="forecast.member.uid"
 											tabindex="0"
+											:title="$t('membre.voirinfos')"
 											@click="
 												$router.push(
 													localePath({
@@ -130,6 +133,7 @@
 												<div class="list-group rounded-0">
 													<nuxt-link
 														class="list-group-item list-group-item-action border-0 d-flex justify-content-between align-items-center"
+														:title="$t('membre.voirinfos')"
 														:to="
 															localePath('/membre/?hash=' + member.member.hash)
 														"
diff --git a/plugins/bootstrap.js b/plugins/bootstrap.js
index 2e44162926f52db1bbd36fb3b3a47962d4092fa4..86f2fc29efe912ecae110d52f0bd9b3a7524e01d 100644
--- a/plugins/bootstrap.js
+++ b/plugins/bootstrap.js
@@ -1,5 +1,7 @@
 import Vue from "vue"
 import Tooltip from "~/node_modules/bootstrap/js/dist/tooltip"
+import Collapse from "~/node_modules/bootstrap/js/dist/collapse"
+import Alert from "~/node_modules/bootstrap/js/dist/alert"
 
 Vue.directive("tooltip", function (el, binding) {
 	let tooltip = new Tooltip(el, {