From 41fa95d08c659b8d55239fb09dc0ca3c57baad6a Mon Sep 17 00:00:00 2001
From: Pierre-Jean CHANCELLIER <paidge_cs@hotmail.com>
Date: Mon, 7 Nov 2022 01:35:06 +0100
Subject: [PATCH] V2.5..0

---
 assets/css/_bootstrap.scss               |   6 +-
 assets/css/_custom_buttons.scss          | 122 +++++++++
 assets/css/_override-mixins.scss         |  43 +++
 assets/css/style.scss                    |  77 ++++--
 components/badge/Danger.vue              |   2 +-
 components/badge/Status.vue              |   9 +-
 components/btn/Favori.vue                |  21 +-
 components/btn/FavoriArray.vue           |   2 +-
 components/btn/Pagination.vue            | 112 --------
 components/btn/Search.vue                |   1 +
 components/certif/Group.vue              |   4 +-
 components/certif/List.vue               |  72 ++---
 components/member/Card.vue               |  42 ++-
 components/member/Filter.vue             | 118 +++++++--
 components/member/List.vue               | 152 ++++++-----
 components/navigation/menu/Sidebar.vue   |   1 +
 components/suivis/Tableau.vue            |  78 ++++++
 graphql/endpoints/coindufeu.js           |  26 --
 graphql/queries.js                       |   7 +-
 i18n/locales/de.json                     |  42 +--
 i18n/locales/en.json                     |  42 +--
 i18n/locales/es.json                     |  42 +--
 i18n/locales/fr.json                     |  42 +--
 i18n/locales/it.json                     |  42 +--
 layouts/default.vue                      |   2 +-
 nuxt.config.js                           |   1 -
 package.json                             |   2 +-
 pages/a-propos.vue                       |  36 ++-
 pages/favoris.vue                        | 107 --------
 pages/index.vue                          |   6 +
 pages/lexique.vue                        |   3 +-
 pages/membres/index.vue                  |   5 +-
 pages/{membre.vue => membres/profil.vue} |  70 ++---
 pages/mes-suivis.vue                     | 322 +++++++++++++++++++++++
 pages/parametres.vue                     |   3 +-
 pages/previsions/futures_entrees.vue     |  15 +-
 pages/previsions/futures_sorties.vue     |   7 +-
 plugins/bootstrap.js                     |  14 +-
 plugins/favourites.js                    |  58 +++-
 plugins/getApolloClient.js               |   5 +-
 web-ext/manifest.json                    |   2 +-
 41 files changed, 1165 insertions(+), 598 deletions(-)
 create mode 100644 assets/css/_custom_buttons.scss
 create mode 100644 assets/css/_override-mixins.scss
 delete mode 100644 components/btn/Pagination.vue
 create mode 100644 components/suivis/Tableau.vue
 delete mode 100644 graphql/endpoints/coindufeu.js
 delete mode 100644 pages/favoris.vue
 rename pages/{membre.vue => membres/profil.vue} (61%)
 create mode 100644 pages/mes-suivis.vue

diff --git a/assets/css/_bootstrap.scss b/assets/css/_bootstrap.scss
index 826ad60..9e8b2e0 100644
--- a/assets/css/_bootstrap.scss
+++ b/assets/css/_bootstrap.scss
@@ -12,6 +12,8 @@
 @import "~bootstrap/scss/mixins";
 @import "~bootstrap/scss/utilities";
 
+@import "./override-mixins";
+
 // Layout
 @import "~bootstrap/scss/root";
 @import "~bootstrap/scss/reboot";
@@ -33,7 +35,7 @@
 // @import "~bootstrap/scss/forms/validation";
 
 // Components
-@import "~bootstrap/scss/buttons";
+@import "./custom_buttons";
 @import "~bootstrap/scss/transitions";
 // @import "~bootstrap/scss/dropdown";
 @import "~bootstrap/scss/button-group";
@@ -42,7 +44,7 @@
 @import "~bootstrap/scss/card";
 // @import "~bootstrap/scss/accordion";
 @import "~bootstrap/scss/breadcrumb";
-@import "~bootstrap/scss/pagination";
+// @import "~bootstrap/scss/pagination";
 @import "~bootstrap/scss/badge";
 @import "~bootstrap/scss/alert";
 // @import "~bootstrap/scss/progress";
diff --git a/assets/css/_custom_buttons.scss b/assets/css/_custom_buttons.scss
new file mode 100644
index 0000000..6b14c93
--- /dev/null
+++ b/assets/css/_custom_buttons.scss
@@ -0,0 +1,122 @@
+//
+// Base styles
+//
+
+.btn {
+	display: inline-block;
+	font-family: $btn-font-family;
+	font-weight: $btn-font-weight;
+	line-height: $btn-line-height;
+	color: $body-color;
+	text-align: center;
+	text-decoration: if($link-decoration == none, null, none);
+	white-space: $btn-white-space;
+	vertical-align: middle;
+	cursor: if($enable-button-pointers, pointer, null);
+	user-select: none;
+	background-color: transparent;
+	border: $btn-border-width solid transparent;
+	@include button-size(
+		$btn-padding-y,
+		$btn-padding-x,
+		$btn-font-size,
+		$btn-border-radius
+	);
+	@include transition($btn-transition);
+
+	&:hover {
+		text-decoration: if($link-hover-decoration == underline, none, null);
+	}
+
+	.btn-check:focus + &,
+	&:focus {
+		outline: 0;
+		box-shadow: $btn-focus-box-shadow;
+	}
+
+	.btn-check:checked + &,
+	.btn-check:active + &,
+	&:active,
+	&.active {
+		@include box-shadow($btn-active-box-shadow);
+
+		&:focus {
+			@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
+		}
+	}
+
+	&:disabled,
+	&.disabled,
+	fieldset:disabled & {
+		pointer-events: none;
+		opacity: $btn-disabled-opacity;
+		@include box-shadow(none);
+	}
+}
+
+//
+// Alternate buttons
+//
+
+// scss-docs-start btn-variant-loops
+@each $color, $value in $theme-colors {
+	.btn-#{$color} {
+		@include button-variant($value, $value);
+	}
+}
+
+@each $color, $value in $theme-colors {
+	.btn-outline-#{$color} {
+		@include button-outline-variant($value);
+	}
+}
+// scss-docs-end btn-variant-loops
+
+//
+// Link buttons
+//
+
+// Make a button look and behave like a link
+.btn-link {
+	font-weight: $font-weight-normal;
+	color: $btn-link-color;
+	text-decoration: $link-decoration;
+
+	&:hover {
+		color: $btn-link-hover-color;
+		text-decoration: $link-hover-decoration;
+	}
+
+	&:focus {
+		text-decoration: $link-hover-decoration;
+	}
+
+	&:disabled,
+	&.disabled {
+		color: $btn-link-disabled-color;
+	}
+
+	// No need for an active state here
+}
+
+//
+// Button Sizes
+//
+
+.btn-lg {
+	@include button-size(
+		$btn-padding-y-lg,
+		$btn-padding-x-lg,
+		$btn-font-size-lg,
+		$btn-border-radius-lg
+	);
+}
+
+.btn-sm {
+	@include button-size(
+		$btn-padding-y-sm,
+		$btn-padding-x-sm,
+		$btn-font-size-sm,
+		$btn-border-radius-sm
+	);
+}
diff --git a/assets/css/_override-mixins.scss b/assets/css/_override-mixins.scss
new file mode 100644
index 0000000..5bbb36c
--- /dev/null
+++ b/assets/css/_override-mixins.scss
@@ -0,0 +1,43 @@
+@mixin button-outline-variant(
+	$color,
+	$color-hover: color-contrast($color),
+	$active-background: $color,
+	$active-border: $color,
+	$active-color: color-contrast($active-background)
+) {
+	color: $color;
+	border-color: $color;
+
+	.btn-check:focus + &,
+	&:focus {
+		box-shadow: 0 0 0 $btn-focus-width rgba($color, 0.5);
+	}
+
+	.btn-check:checked + &,
+	.btn-check:active + &,
+	&:active,
+	&.active,
+	&.dropdown-toggle.show {
+		color: $active-color;
+		background-color: $active-background;
+		border-color: $active-border;
+
+		&:focus {
+			@if $enable-shadows {
+				@include box-shadow(
+					$btn-active-box-shadow,
+					0 0 0 $btn-focus-width rgba($color, 0.5)
+				);
+			} @else {
+				// Avoid using mixin so we can pass custom focus shadow properly
+				box-shadow: 0 0 0 $btn-focus-width rgba($color, 0.5);
+			}
+		}
+	}
+
+	&:disabled,
+	&.disabled {
+		color: $color;
+		background-color: transparent;
+	}
+}
diff --git a/assets/css/style.scss b/assets/css/style.scss
index eb6eb00..2ba1538 100644
--- a/assets/css/style.scss
+++ b/assets/css/style.scss
@@ -51,27 +51,6 @@ $list-group-hover-bg: var(--bg-secondary-color);
 // Cards
 $card-bg: var(--bg-secondary-color);
 
-// Pagination
-$pagination-border-color: var(--border-color);
-$pagination-bg: var(--bg-menu-color);
-$pagination-color: var(--txt-secondary-color);
-
-$pagination-hover-border-color: $pagination-border-color;
-$pagination-hover-bg: var(--bg-secondary-color);
-$pagination-hover-color: var(--txt-secondary-color);
-
-$pagination-active-border-color: $pagination-border-color;
-$pagination-active-bg: var(--bg-secondary-color);
-$pagination-active-color: var(--txt-secondary-color);
-
-$pagination-focus-border-color: $pagination-hover-border-color;
-$pagination-focus-bg: $pagination-hover-bg;
-$pagination-focus-color: $pagination-hover-color;
-
-$pagination-disabled-border-color: $pagination-border-color;
-$pagination-disabled-bg: var(--bg-menu-color);
-$pagination-disabled-color: var(--txt-secondary-color);
-
 // Tooltips
 $tooltip-max-width: 300px;
 $tooltip-opacity: 1;
@@ -81,6 +60,18 @@ $tooltip-font-size: 1rem;
 $tooltip-arrow-height: 1rem;
 $tooltip-arrow-width: 1.5rem;
 
+// Tabs
+$nav-tabs-link-active-bg: var(--txt-secondary-color);
+$nav-tabs-link-active-color: var(--bg-primary-color);
+$nav-tabs-link-hover-bg: $nav-tabs-link-active-bg;
+$nav-tabs-border-color: var(--txt-primary-color);
+$nav-tabs-link-active-border-color: $nav-tabs-border-color
+	$nav-tabs-border-color $nav-tabs-link-active-bg;
+$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color
+	$nav-tabs-link-hover-bg;
+$nav-link-color: var(--txt-link);
+$nav-link-hover-color: $nav-tabs-link-active-color;
+
 @import "font";
 @import "bootstrap";
 
@@ -110,6 +101,21 @@ a:hover {
 		table-layout: fixed;
 	}
 
+	&-responsive {
+		tbody {
+			display: block;
+			overflow: auto;
+			scrollbar-width: thin;
+		}
+
+		thead,
+		tbody tr {
+			display: table;
+			width: 100%;
+			table-layout: fixed;
+		}
+	}
+
 	&.sortable {
 		thead {
 			th:not(:last-child) {
@@ -122,6 +128,7 @@ a:hover {
 			border-top: none;
 		}
 	}
+
 	tbody tr > * {
 		vertical-align: middle;
 	}
@@ -165,3 +172,31 @@ a:hover {
 .pointer {
 	cursor: pointer;
 }
+
+.tab-content {
+	border-width: 0 1px 1px;
+	border-color: var(--txt-primary-color);
+	border-style: solid;
+	min-height: 420px;
+}
+
+.nav-tabs {
+	.nav-link {
+		border-color: var(--txt-primary-color);
+
+		&:focus {
+			background: var(--txt-secondary-color);
+			box-shadow: 0 0 0 0.25rem rgba(173, 181, 189, 0.5);
+		}
+
+		&:not(.active):hover {
+			background: $nav-tabs-link-hover-bg;
+		}
+	}
+
+	@media (min-width: 768px) {
+		.nav-item + .nav-item {
+			margin-left: 0.5rem;
+		}
+	}
+}
diff --git a/components/badge/Danger.vue b/components/badge/Danger.vue
index 080111e..acbfc02 100644
--- a/components/badge/Danger.vue
+++ b/components/badge/Danger.vue
@@ -1,6 +1,6 @@
 <template>
 	<span
-		class="d-inline-block help"
+		class="d-inline-block help flex-shrink-0"
 		:class="classBadge"
 		:title="title"
 		v-if="!['NEWCOMER', 'REVOKED'].includes(memberStatus)"
diff --git a/components/badge/Status.vue b/components/badge/Status.vue
index fe79dfb..38951de 100644
--- a/components/badge/Status.vue
+++ b/components/badge/Status.vue
@@ -20,13 +20,14 @@ export default {
 				case "MISSING":
 					return { str: this.$i18n.t("statut.missing"), class: "bg-danger" }
 				case "MEMBER":
+					return {
+						str: this.$i18n.t("statut.member"),
+						class: "bg-success"
+					}
+				case "RENEW":
 					if (this.$options.filters.dateStatus(member.limitDate) == "warning") {
 						return { str: this.$i18n.t("statut.renew"), class: "bg-warning" }
 					} else {
-						return {
-							str: this.$i18n.t("statut.member"),
-							class: "bg-success"
-						}
 					}
 				case "REVOKED":
 					return {
diff --git a/components/btn/Favori.vue b/components/btn/Favori.vue
index 604408d..ee4dffc 100644
--- a/components/btn/Favori.vue
+++ b/components/btn/Favori.vue
@@ -1,17 +1,16 @@
 <template>
 	<button
-		id="favori"
 		class="btn btn-secondary"
 		v-tooltip-click="
 			$favourites.list.includes(uid)
-				? $t('favoris.enregistre')
-				: $t('favoris.supprime')
+				? $t('suivis.enregistre')
+				: $t('suivis.supprime')
 		"
 		@click="$favourites.toggleFavourite(uid, $event)">
 		<span class="visually-hidden">{{
 			$favourites.list.includes(uid)
-				? $t("favoris.supprimer")
-				: $t("favoris.ajouter")
+				? $t("suivis.supprimer")
+				: $t("suivis.ajouter")
 		}}</span>
 		<solid-user-add-icon
 			aria-hidden="true"
@@ -34,15 +33,3 @@ export default {
 	}
 }
 </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/FavoriArray.vue b/components/btn/FavoriArray.vue
index 5846f41..3e92728 100644
--- a/components/btn/FavoriArray.vue
+++ b/components/btn/FavoriArray.vue
@@ -2,7 +2,7 @@
 	<button
 		v-if="membersToAdd.length != 0"
 		class="btn btn-secondary position-relative"
-		v-tooltip="{ title: $t('favoris.ajouter'), placement: 'left' }"
+		v-tooltip="{ title: $t('suivis.ajouter'), placement: 'left' }"
 		@click="$favourites.addFavorisArray(membersToAdd, $event)">
 		<solid-user-group-icon aria-hidden="true" class="icon" />
 		<span
diff --git a/components/btn/Pagination.vue b/components/btn/Pagination.vue
deleted file mode 100644
index d76bc19..0000000
--- a/components/btn/Pagination.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<template>
-	<nav :aria-label="$t('pagination.title')" v-if="nbPages > 1">
-		<ul class="pagination justify-content-center m-0">
-			<li class="page-item" v-if="currentPage > 2 && nbPages > 3">
-				<a class="page-link" href="#" @click="goto(1, $event)">
-					<span :aria-label="$t('pagination.page') + ' 1'">1</span>
-				</a>
-			</li>
-			<li class="page-item disabled" v-if="currentPage > 3 && nbPages > 4">
-				<span class="page-link"><span aria-hidden="true">...</span></span>
-			</li>
-			<li
-				class="page-item"
-				v-for="index in listPagesUtiles"
-				:key="index"
-				:class="{ active: index == currentPage }">
-				<a
-					v-if="index != currentPage"
-					class="page-link"
-					href="#"
-					@click="goto(index, $event)">
-					<span :aria-label="$t('pagination.page') + ' ' + index">{{
-						index
-					}}</span></a
-				>
-				<span v-else class="page-link">{{ index }}</span>
-			</li>
-			<li
-				class="page-item disabled"
-				v-if="currentPage < nbPages - 2 && nbPages > 4">
-				<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="goto(nbPages, $event)">
-					<span :aria-label="$t('pagination.page') + ' ' + nbPages">{{
-						nbPages
-					}}</span>
-				</a>
-			</li>
-		</ul>
-	</nav>
-</template>
-
-<script>
-export default {
-	props: {
-		target: String,
-		currentPage: {
-			type: Number,
-			required: true
-		},
-		pageSize: {
-			type: Number,
-			required: true
-		},
-		arrayLength: {
-			type: Number,
-			required: true
-		}
-	},
-	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)
-		}
-	},
-	computed: {
-		nbPages() {
-			return Math.ceil(this.arrayLength / this.pageSize)
-		},
-		listPagesUtiles() {
-			if (this.nbPages < 3) {
-				return [1, 2]
-			}
-
-			if (this.currentPage == 1) {
-				return [1, 2, 3]
-			}
-
-			if (this.currentPage == this.nbPages) {
-				return [this.nbPages - 2, this.nbPages - 1, this.nbPages]
-			}
-
-			return [this.currentPage - 1, this.currentPage, this.currentPage + 1]
-		}
-	},
-	watch: {
-		arrayLength: {
-			handler(n, o) {
-				this.goto(1)
-			}
-		}
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-.pagination {
-	user-select: none;
-}
-
-.disabled,
-.active {
-	cursor: not-allowed;
-}
-</style>
diff --git a/components/btn/Search.vue b/components/btn/Search.vue
index 756f701..7017275 100644
--- a/components/btn/Search.vue
+++ b/components/btn/Search.vue
@@ -15,6 +15,7 @@
 				:aria-label="$t('recherche.title')"
 				:aria-describedby="help ? 'rechHelp' : null" />
 			<button
+				v-if="value != ''"
 				:title="$t('recherche.effacer')"
 				class="btn"
 				type="button"
diff --git a/components/certif/Group.vue b/components/certif/Group.vue
index 83df611..39627e6 100644
--- a/components/certif/Group.vue
+++ b/components/certif/Group.vue
@@ -25,7 +25,9 @@
 					:limitDate="limitDate"
 					:memberStatus="memberStatus" />
 			</h3>
-			<BtnFavoriArray :listUID="certifsNotPending" />
+			<BtnFavoriArray
+				:listUID="certifsNotPending"
+				v-if="$parent.$parent.registeredAccount.hash != $route.query.hash" />
 		</div>
 		<CertifList
 			:title="$t('certification.enattente')"
diff --git a/components/certif/List.vue b/components/certif/List.vue
index 43b8611..3369d44 100644
--- a/components/certif/List.vue
+++ b/components/certif/List.vue
@@ -1,5 +1,5 @@
 <template>
-	<div v-if="certifs.length > 0">
+	<div class="certifList" v-if="certifs.length > 0">
 		<button
 			:title="
 				isOpen ? $t('certification.masquer') : $t('certification.afficher')
@@ -66,7 +66,7 @@
 						@click="
 							$router.push(
 								localePath({
-									name: 'membre',
+									name: 'membres-profil',
 									query: { hash: certif.hash }
 								})
 							)
@@ -74,7 +74,7 @@
 						@keyup.enter="
 							$router.push(
 								localePath({
-									name: 'membre',
+									name: 'membres-profil',
 									query: { hash: certif.hash }
 								})
 							)
@@ -102,7 +102,7 @@
 								v-if="certif.status == 'MEMBER'" />
 						</td>
 						<td class="p-0 text-center col-4">
-							<div class="d-flex flex-column gap-1">
+							<div class="d-inline-flex flex-column gap-1">
 								<BadgeDate :date="certif.expires_on" />
 								<span
 									class="badge bg-secondary text-truncate d-block"
@@ -114,13 +114,6 @@
 					</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>
@@ -132,8 +125,6 @@ export default {
 			search: "",
 			currentSort: "expires_on",
 			currentSortDir: "asc",
-			currentPage: 1,
-			pageSize: 5,
 			isOpen: this.openDefault
 		}
 	},
@@ -153,20 +144,6 @@ 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: {
@@ -180,6 +157,13 @@ export default {
 		},
 		certifsTriees() {
 			return this.certifsFiltrees
+				.map((el) => {
+					el.status =
+						this.$options.filters.dateStatus(el.limitDate) == "warning"
+							? "RENEW"
+							: el.status
+					return el
+				})
 				.sort((a, b) => {
 					let modifier = this.currentSortDir === "desc" ? -1 : 1
 
@@ -195,11 +179,6 @@ export default {
 
 					return 0
 				})
-				.filter((row, index) => {
-					let start = (this.currentPage - 1) * this.pageSize
-					let end = this.currentPage * this.pageSize
-					if (index >= start && index < end) return true
-				})
 		},
 		collapseClass() {
 			if (this.collapseId.includes("entraitement")) return "bg-secondary"
@@ -223,8 +202,6 @@ export default {
 		}
 	},
 	mounted() {
-		this.setPage()
-
 		if (this.openDefault && this.certifs.length > 0) {
 			document.querySelector("#" + this.collapseId).classList.add("show")
 		}
@@ -234,26 +211,29 @@ export default {
 			handler(n, o) {
 				this.search = ""
 			}
-		},
-		$route(to, from) {
-			this.setPage()
 		}
 	}
 }
 </script>
 
-<style lang="scss" scoped>
-tbody tr {
-	height: 80px;
-}
-
-@media (min-width: 576px) {
-	button {
-		font-size: 1.3rem;
+<style lang="scss">
+.certifList {
+	.table-responsive tbody {
+		max-height: 456px;
 	}
 
 	tbody tr {
-		height: initial;
+		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 2cb2646..1f0d4f5 100644
--- a/components/member/Card.vue
+++ b/components/member/Card.vue
@@ -9,7 +9,27 @@
 					</span>
 					<small><BadgeStatus class="ms-2" :membre="hash" /></small>
 				</h2>
-				<BtnFavori :uid="hash.uid" />
+				<div class="btn-group" role="group">
+					<button
+						v-if="
+							$parent.registeredAccount.uid == '' ||
+							$parent.registeredAccount.uid == hash.uid
+						"
+						class="btn"
+						:class="{
+							'btn-success': $parent.registeredAccount.uid != hash.uid,
+							'btn-warning': $parent.registeredAccount.uid == hash.uid
+						}"
+						@click="updateCurrentHash($event)">
+						<span v-if="$parent.registeredAccount.uid != hash.uid">{{
+							$t("suivis.iam")
+						}}</span>
+						<span v-else>{{ $t("suivis.iamnot") }}</span>
+					</button>
+					<BtnFavori
+						:uid="hash.uid"
+						v-if="$parent.registeredAccount.uid != hash.uid" />
+				</div>
 			</div>
 			<BtnClipboard :textContent="hash.pubkey" />
 			<AlertMember :hash="hash" />
@@ -113,11 +133,6 @@
 
 <script>
 export default {
-	data() {
-		return {
-			favourites: []
-		}
-	},
 	props: {
 		hash: Object
 	},
@@ -125,6 +140,21 @@ export default {
 		sentCertNotExpired() {
 			return this.hash.sent_certifications.filter((el) => !el.expired)
 		}
+	},
+	methods: {
+		updateCurrentHash(e) {
+			if (this.$parent.registeredAccount.uid == this.hash.uid) {
+				this.$parent.registeredAccount = { hash: "", uid: "" }
+			} else {
+				this.$parent.registeredAccount = {
+					hash: this.hash.hash,
+					uid: this.hash.uid
+				}
+				if (this.$favourites.list.includes(this.hash.uid)) {
+					this.$favourites.toggleFavourite(this.hash.uid, e)
+				}
+			}
+		}
 	}
 }
 </script>
diff --git a/components/member/Filter.vue b/components/member/Filter.vue
index 94e5f40..95420b9 100644
--- a/components/member/Filter.vue
+++ b/components/member/Filter.vue
@@ -1,52 +1,101 @@
 <template>
 	<div class="text-muted">
 		<p id="filterStatutTitle">{{ $t("filter.statut") }}&nbsp;:</p>
-		<ul class="p-0" aria-labelledby="filterStatutTitle" role="group">
-			<li class="form-check form-check-inline">
+		<ul class="p-0 m-0" aria-labelledby="filterStatutTitle" role="group">
+			<li
+				class="form-check form-check-inline mb-3"
+				v-if="type != 'certificateurs'">
 				<input
-					class="form-check-input"
+					class="btn-check"
 					v-model="checkedStatus"
 					type="checkbox"
-					id="check-newcomer"
-					value="NEWCOMER" />
-				<label class="form-check-label" for="check-newcomer">{{
+					:id="'check-newcomer-' + _uid"
+					value="NEWCOMER"
+					@change="fixColumns" />
+				<label class="btn btn-outline-info" :for="'check-newcomer-' + _uid">{{
 					$t("statut.newcomer")
 				}}</label>
 			</li>
-			<li class="form-check form-check-inline">
+			<li class="form-check form-check-inline mb-3">
 				<input
-					class="form-check-input"
+					class="btn-check"
 					v-model="checkedStatus"
 					type="checkbox"
-					id="check-member"
-					value="MEMBER" />
-				<label class="form-check-label" for="check-member">{{
+					:id="'check-member-' + _uid"
+					value="MEMBER"
+					@change="fixColumns" />
+				<label class="btn btn-outline-success" :for="'check-member-' + _uid">{{
 					$t("statut.member")
 				}}</label>
 			</li>
-			<li class="form-check form-check-inline">
+			<li class="form-check form-check-inline mb-3">
 				<input
-					class="form-check-input"
+					class="btn-check"
 					v-model="checkedStatus"
 					type="checkbox"
-					id="check-missing"
-					value="MISSING" />
-				<label class="form-check-label" for="check-missing">{{
-					$t("statut.missing")
+					:id="'check-renew-' + _uid"
+					value="RENEW"
+					@change="fixColumns" />
+				<label class="btn btn-outline-warning" :for="'check-renew-' + _uid">{{
+					$t("statut.renew")
 				}}</label>
 			</li>
-			<li class="form-check form-check-inline">
+			<li class="form-check form-check-inline mb-3">
 				<input
-					class="form-check-input"
+					class="btn-check"
 					v-model="checkedStatus"
 					type="checkbox"
-					id="check-revoked"
-					value="REVOKED" />
-				<label class="form-check-label" for="check-revoked">{{
-					$t("statut.revoked")
+					:id="'check-missing-' + _uid"
+					value="MISSING"
+					@change="fixColumns" />
+				<label class="btn btn-outline-danger" :for="'check-missing-' + _uid">{{
+					$t("statut.missing")
 				}}</label>
 			</li>
+			<li class="form-check form-check-inline mb-3" v-if="type == 'favoris'">
+				<input
+					class="btn-check"
+					v-model="checkedStatus"
+					type="checkbox"
+					:id="'check-revoked-' + _uid"
+					value="REVOKED"
+					@change="fixColumns" />
+				<label
+					class="btn btn-outline-secondary"
+					:for="'check-revoked-' + _uid"
+					>{{ $t("statut.revoked") }}</label
+				>
+			</li>
 		</ul>
+		<div v-if="type != 'favoris'">
+			<p>{{ $t("filter.certif") }}&nbsp;:</p>
+			<input
+				class="btn-check"
+				v-model="certifStatus"
+				type="radio"
+				:name="'certifStatus-' + _uid"
+				:id="'radio-current-' + _uid"
+				value="current"
+				autocomplete="off" />
+			<label
+				class="btn btn-outline-success me-3 mb-3"
+				:for="'radio-current-' + _uid">
+				{{ $t("certification.title") + " " + $t("certification.encours") }}
+			</label>
+			<input
+				class="btn-check"
+				v-model="certifStatus"
+				type="radio"
+				:name="'certifStatus-' + _uid"
+				:id="'radio-outdated-' + _uid"
+				value="outdated"
+				autocomplete="off" />
+			<label
+				class="btn btn-outline-danger mb-3"
+				:for="'radio-outdated-' + _uid">
+				{{ $t("certification.title") + " " + $t("certification.perimees") }}
+			</label>
+		</div>
 	</div>
 </template>
 
@@ -54,13 +103,29 @@
 export default {
 	data() {
 		return {
-			checkedStatus: this.selectedStatus
+			checkedStatus: this.selectedStatus,
+			certifStatus: this.selectedCertifStatus
 		}
 	},
 	props: {
 		selectedStatus: {
 			type: Array,
 			required: true
+		},
+		selectedCertifStatus: {
+			type: String,
+			required: true
+		},
+		type: {
+			type: String,
+			default: ""
+		}
+	},
+	methods: {
+		fixColumns(e) {
+			setTimeout(() => {
+				this.$favourites.fixColumns()
+			}, 5)
 		}
 	},
 	watch: {
@@ -68,6 +133,11 @@ export default {
 			handler(n, o) {
 				this.$emit("update:selectedStatus", n)
 			}
+		},
+		certifStatus: {
+			handler(n, o) {
+				this.$emit("update:selectedCertifStatus", n)
+			}
 		}
 	}
 }
diff --git a/components/member/List.vue b/components/member/List.vue
index 1fe5e2a..5f348ce 100644
--- a/components/member/List.vue
+++ b/components/member/List.vue
@@ -30,7 +30,9 @@
 					<th
 						scope="col"
 						class="td-quality d-none d-lg-table-cell p-0"
-						v-if="['favoris', 'search'].includes(id)"
+						v-if="
+							['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
+						"
 						@click="sort('quality')"
 						@keyup.enter="sort('quality')">
 						<BtnSort
@@ -42,7 +44,9 @@
 					<th
 						scope="col"
 						class="d-none d-xl-table-cell p-0"
-						v-if="['favoris', 'search'].includes(id)"
+						v-if="
+							['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
+						"
 						@click="sort('dispo')"
 						@keyup.enter="sort('dispo')">
 						<BtnSort
@@ -56,7 +60,15 @@
 						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(id)">
+						v-if="
+							[
+								'adhesion',
+								'favoris',
+								'search',
+								'certificateurs',
+								'certifies'
+							].includes(id)
+						">
 						<BtnSort
 							fieldName="date_membership"
 							:title="
@@ -76,7 +88,15 @@
 						}"
 						@click="sort('date_certs')"
 						@keyup.enter="sort('date_certs')"
-						v-if="['certif', 'favoris', 'search'].includes(id)">
+						v-if="
+							[
+								'certif',
+								'favoris',
+								'search',
+								'certificateurs',
+								'certifies'
+							].includes(id)
+						">
 						<BtnSort
 							fieldName="date_certs"
 							:title="
@@ -101,7 +121,7 @@
 					<td>
 						<div class="d-flex">
 							<div
-								class="d-flex flex-column align-items-center justify-content-evenly flex-grow-1">
+								class="d-flex flex-column align-items-center justify-content-evenly flex-grow-1 mw-100">
 								<div class="d-flex justify-content-center mw-100">
 									<span v-if="$favourites.list.includes(member.uid)"
 										>★&nbsp;</span
@@ -124,7 +144,11 @@
 							</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(id)">
+								v-if="
+									['favoris', 'search', 'certificateurs', 'certifies'].includes(
+										id
+									)
+								">
 								<BadgeStatus :membre="member" class="mw-100 text-truncate" />
 								<BadgeQuality :quality="member.quality.ratio" />
 								<BadgeDispo
@@ -140,12 +164,16 @@
 					</td>
 					<td
 						class="d-none d-lg-table-cell"
-						v-if="['favoris', 'search'].includes(id)">
+						v-if="
+							['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
+						">
 						<BadgeQuality :quality="member.quality.ratio" />
 					</td>
 					<td
 						class="d-none d-xl-table-cell"
-						v-if="['favoris', 'search'].includes(id)">
+						v-if="
+							['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
+						">
 						<BadgeDispo
 							:isDispo="member.minDatePassed"
 							:dateDispo="member.minDate"
@@ -153,7 +181,15 @@
 					</td>
 					<td
 						class="d-none d-sm-table-cell"
-						v-if="['adhesion', 'favoris', 'search'].includes(id)">
+						v-if="
+							[
+								'adhesion',
+								'favoris',
+								'search',
+								'certificateurs',
+								'certifies'
+							].includes(id)
+						">
 						<BadgeDate :date="member.limitDate" />
 					</td>
 					<td
@@ -162,7 +198,15 @@
 							'd-sm-table-cell': id == 'certif',
 							'd-md-table-cell': id != 'certif'
 						}"
-						v-if="['certif', 'favoris', 'search'].includes(id)">
+						v-if="
+							[
+								'certif',
+								'favoris',
+								'search',
+								'certificateurs',
+								'certifies'
+							].includes(id)
+						">
 						<BadgeDate :date="member.certsLimit" />
 					</td>
 					<td class="py-1" v-if="id == 'favoris'" style="width: 60px">
@@ -170,18 +214,13 @@
 							class="btn btn-danger"
 							v-if="$favourites.list.includes(member.uid)"
 							@click="$favourites.toggleFavourite(member.uid, $event)"
-							:title="$t('favoris.supprimer')">
+							:title="$t('suivis.supprimer')">
 							<solid-trash-icon class="icon" aria-hidden="true" />
 						</button>
 					</td>
 				</tr>
 			</tbody>
 		</table>
-		<BtnPagination
-			:target="id"
-			:currentPage.sync="currentPage"
-			:pageSize="pageSize"
-			:arrayLength="members.length" />
 	</div>
 </template>
 
@@ -190,9 +229,7 @@ export default {
 	data() {
 		return {
 			currentSort: this.defaultSort,
-			currentSortDir: this.defaultSortDir,
-			pageSize: 5,
-			currentPage: 1
+			currentSortDir: this.defaultSortDir
 		}
 	},
 	props: {
@@ -215,7 +252,9 @@ export default {
 	},
 	methods: {
 		redirect(hash) {
-			this.$router.push(this.localePath({ name: "membre", query: { hash } }))
+			this.$router.push(
+				this.localePath({ name: "membres-profil", query: { hash } })
+			)
 		},
 		sort(s) {
 			if (s === this.currentSort) {
@@ -232,63 +271,38 @@ export default {
 			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: {
 		membersSorted() {
-			return this.members
-				.sort((a, b) => {
-					let modifier = this.currentSortDir === "desc" ? -1 : 1
+			return this.members.sort((a, b) => {
+				let modifier = this.currentSortDir === "desc" ? -1 : 1
 
-					if (this.currentSort == "uid") {
-						return this.getOrder(
-							a["uid"].toLowerCase(),
-							b["uid"].toLowerCase(),
-							modifier
-						)
-					} else if (this.currentSort == "dispo") {
-						if (a.minDate == null) return 1 * modifier
-						if (b.minDate == null) return -1 * modifier
-						return this.getOrder(a.minDate, b.minDate, modifier)
-					} else if (this.currentSort == "quality") {
-						return this.getOrder(a.quality.ratio, b.quality.ratio, modifier)
-					} else if (this.currentSort == "statut") {
-						return this.getOrder(a["status"], b["status"], modifier)
-					} else if (this.currentSort == "date_membership") {
-						return this.getOrder(a["limitDate"], b["limitDate"], modifier)
-					} else if (this.currentSort == "date_certs") {
-						return this.getOrder(a["certsLimit"], b["certsLimit"], modifier)
-					}
-					return 0
-				})
-				.filter((row, index) => {
-					let start = (this.currentPage - 1) * this.pageSize
-					let end = this.currentPage * this.pageSize
-					if (index >= start && index < end) return true
-				})
+				if (this.currentSort == "uid") {
+					return this.getOrder(
+						a["uid"].toLowerCase(),
+						b["uid"].toLowerCase(),
+						modifier
+					)
+				} else if (this.currentSort == "dispo") {
+					if (a.minDate == null) return 1 * modifier
+					if (b.minDate == null) return -1 * modifier
+					return this.getOrder(a.minDate, b.minDate, modifier)
+				} else if (this.currentSort == "quality") {
+					return this.getOrder(a.quality.ratio, b.quality.ratio, modifier)
+				} else if (this.currentSort == "statut") {
+					return this.getOrder(a["status"], b["status"], modifier)
+				} else if (this.currentSort == "date_membership") {
+					return this.getOrder(a["limitDate"], b["limitDate"], modifier)
+				} else if (this.currentSort == "date_certs") {
+					return this.getOrder(a["certsLimit"], b["certsLimit"], modifier)
+				}
+				return 0
+			})
 		}
 	},
 	mounted() {
-		this.setPage()
-	},
-	watch: {
-		$route(to, from) {
-			this.setPage()
-		}
+		this.$favourites.fixColumns()
 	}
 }
 </script>
diff --git a/components/navigation/menu/Sidebar.vue b/components/navigation/menu/Sidebar.vue
index d9a79ee..671bee7 100644
--- a/components/navigation/menu/Sidebar.vue
+++ b/components/navigation/menu/Sidebar.vue
@@ -77,6 +77,7 @@ export default {
 			this.$emit("toggleMenu")
 		},
 		onResize() {
+			this.$favourites.fixColumns()
 			this.screenwidth = window.innerWidth
 		}
 	},
diff --git a/components/suivis/Tableau.vue b/components/suivis/Tableau.vue
new file mode 100644
index 0000000..8d63f64
--- /dev/null
+++ b/components/suivis/Tableau.vue
@@ -0,0 +1,78 @@
+<template>
+	<div class="container pt-5">
+		<div class="row suivis">
+			<div class="col">
+				<div v-if="members && members.length != 0">
+					<BtnSearch
+						@erase="search = ''"
+						v-model="search"
+						class="col-sm-7 col-md-6 col-lg-5 col-xl-4 mx-auto mb-4" />
+					<MemberFilter
+						:selectedStatus.sync="filterStatus"
+						:selectedCertifStatus.sync="filterCerts"
+						:type="type" />
+					<MemberList
+						defaultSort="date_membership"
+						:members="filteredMembers"
+						:id="type" />
+					<button
+						class="btn btn-danger d-block m-auto mb-3"
+						v-if="type == 'favoris'"
+						@click="deleteAllFavorites">
+						<solid-trash-icon class="icon" aria-hidden="true" />
+						{{ $t("suivis.supprimer_tous") }}
+					</button>
+				</div>
+				<div class="alert alert-info" v-if="members && members.length == 0">
+					{{ $t("suivis.none") }}
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			search: "",
+			filterStatus: ["NEWCOMER", "", "RENEW", "MISSING", ""],
+			filterCerts: "current"
+		}
+	},
+	props: {
+		type: {
+			type: String,
+			default: ""
+		},
+		members: {
+			type: Array,
+			required: true
+		}
+	},
+	computed: {
+		filteredMembers() {
+			return this.members.filter(
+				(el) =>
+					el.uid.toLowerCase().includes(this.search.toLowerCase()) &&
+					this.filterStatus.includes(el.status) &&
+					el.expired == (this.filterCerts == "outdated")
+			)
+		}
+	},
+	methods: {
+		deleteAllFavorites() {
+			if (window.confirm(this.$t("suivis.confirm"))) {
+				this.$favourites.list = []
+				localStorage.removeItem("favourites")
+			}
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+.suivis .table-responsive tbody {
+	max-height: 43.5vh;
+}
+</style>
diff --git a/graphql/endpoints/coindufeu.js b/graphql/endpoints/coindufeu.js
deleted file mode 100644
index a7d1801..0000000
--- a/graphql/endpoints/coindufeu.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { HttpLink } from "apollo-link-http"
-import { setContext } from "apollo-link-context"
-import { from } from "apollo-link"
-import { cache } from "../cache"
-
-export default (ctx) => {
-	const ssrMiddleware = setContext((_, { headers }) => {
-		if (process.client) return headers
-		return {
-			headers
-		}
-	})
-
-	const httpLink = new HttpLink({
-		uri: "https://wwgql.coinduf.eu"
-	})
-
-	const link = from([ssrMiddleware, httpLink])
-
-	return {
-		link,
-		cache,
-		// https://github.com/nuxt-community/apollo-module/issues/306#issuecomment-607225431
-		defaultHttpLink: false
-	}
-}
diff --git a/graphql/queries.js b/graphql/queries.js
index 0fe8e80..a96726d 100644
--- a/graphql/queries.js
+++ b/graphql/queries.js
@@ -169,6 +169,7 @@ export const SEARCH_MEMBER = gql`
 		__typename
 		uid
 		hash
+		pubkey
 		status
 		certsLimit
 		limitDate
@@ -211,9 +212,9 @@ export const PARAMS = gql`
 		}
 	}
 `
-// Pour la page favoris
-export const FAVORIS = gql`
-	query getFavoris($group: [String!]!) {
+// Pour la page des suivis
+export const MEMBERS = gql`
+	query getMembers($group: [String!]!) {
 		filterGroup(group: $group) {
 			__typename
 			selected {
diff --git a/i18n/locales/de.json b/i18n/locales/de.json
index 33bfa6d..e7a1986 100644
--- a/i18n/locales/de.json
+++ b/i18n/locales/de.json
@@ -108,17 +108,9 @@
 		"title": "Duniter"
 	},
 	"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!",
-		"supprimer": "Aus den Favoriten entfernen",
-		"title": "Meine Favoriten",
-		"use": "Nur meine Favoriten"
-	},
 	"filter": {
-		"statut": "Nach Status filtern"
+		"certif": "Nach Zertifizierungsstatus filtern",
+		"statut": "Nach Mitgliedsstatus filtern"
 	},
 	"infos": "Informationen",
 	"inout": "Bei- und Austritte des Vertrauensnetz in den 2 letzten Tagen",
@@ -181,10 +173,6 @@
 	"nom": "Name",
 	"non": "Nein",
 	"oui": "Ja",
-	"pagination": {
-		"page": "Buchseite",
-		"title": "Paginierungsschaltflächen"
-	},
 	"params": {
 		"breadcrumb": "Parameter",
 		"name": {
@@ -236,7 +224,6 @@
 	"pubkey": "Öffentlicher Schlüssel",
 	"recherche": {
 		"desc": {
-			"favourites": "Suchen Sie unter Ihren Favoriten",
 			"lexicon": "Suche im Lexikon",
 			"member": "Geben Sie den Anfang eines Pseudonyms oder eines öffentlichen Schlüssels ein"
 		},
@@ -260,6 +247,31 @@
 		"revoked": "Widerrufenes Mitglied",
 		"title": "Status"
 	},
+	"suivis": {
+		"ajouter": "Zu den Favoriten hinzufügen",
+		"alert": {
+			"btn": "Finden Sie Ihre Identität",
+			"desc": "Bitte geben Sie ein Mitglied als Ihre Identität an, indem Sie auf die Schaltfläche « Ich bin dieses Mitglied » klicken.",
+			"title": "Sie sind noch nicht registriert"
+		},
+		"confirm": "Sind Sie sicher ?",
+		"enregistre": "In den Favoriten gespeichert!",
+		"enregistre_uid": "Schützen",
+		"iam": "Ich bin dieses Mitglied",
+		"iamnot": "Ich bin nicht dieses Mitglied",
+		"none": "Die Liste ist leer",
+		"supprime": "Aus den Favoriten entfernt!",
+		"supprimer": "Aus den Favoriten entfernen",
+		"supprimer_tous": "Alle Favoriten löschen",
+		"tabs": {
+			"certificateurs": "Meine Zertifizierer",
+			"certifies": "Meine Zertifizierten",
+			"favoris": "Meine Favoriten"
+		},
+		"title": "Meine Nachfolge",
+		"use": "Nur meine Favoriten",
+		"voir_profil": "Siehe mein Profil"
+	},
 	"time": {
 		"a": "um"
 	},
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 73cccc6..a0e34b6 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -109,17 +109,9 @@
 		"title": "Duniter"
 	},
 	"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;!",
-		"supprimer": "Delete from favourites",
-		"title": "My favourites",
-		"use": "My favorites only"
-	},
 	"filter": {
-		"statut": "Filter by status"
+		"certif": "Filter by certification status",
+		"statut": "Filter by member status"
 	},
 	"infos": "Informations",
 	"inout": "Entries and exits of the web of trust for the last 2 days",
@@ -182,10 +174,6 @@
 	"nom": "Name",
 	"non": "No",
 	"oui": "Yes",
-	"pagination": {
-		"page": "Page",
-		"title": "Pagination buttons"
-	},
 	"params": {
 		"breadcrumb": "Parameters",
 		"name": {
@@ -237,7 +225,6 @@
 	"pubkey": "Public key",
 	"recherche": {
 		"desc": {
-			"favourites": "Search among your favorites",
 			"lexicon": "Search in the lexicon",
 			"member": "Enter the start of a nickname or public key"
 		},
@@ -261,6 +248,31 @@
 		"revoked": "Revoked member",
 		"title": "Status"
 	},
+	"suivis": {
+		"ajouter": "Add to favorites",
+		"alert": {
+			"btn": "Find your identity",
+			"desc": "Please indicate a member as your identity by clicking on the button « I am this member »",
+			"title": "You're not registered yet"
+		},
+		"confirm": "Are you sure ?",
+		"enregistre": "Saved to favorites&nbsp;!",
+		"enregistre_uid": "Save",
+		"iam": "I am that member",
+		"iamnot": "I am not that member",
+		"none": "The list is empty",
+		"supprime": "Deleted from favourites&nbsp;!",
+		"supprimer": "Delete from favourites",
+		"supprimer_tous": "Delete all favourites",
+		"tabs": {
+			"certificateurs": " My certifiers",
+			"certifies": "My certified members",
+			"favoris": "My favorites"
+		},
+		"title": "My follow-ups",
+		"use": "My favorites only",
+		"voir_profil": "See my profile"
+	},
 	"time": {
 		"a": "at"
 	},
diff --git a/i18n/locales/es.json b/i18n/locales/es.json
index 490dfc5..b41228f 100644
--- a/i18n/locales/es.json
+++ b/i18n/locales/es.json
@@ -109,17 +109,9 @@
 		"title": "Duniter"
 	},
 	"expire": "Expiración",
-	"favoris": {
-		"ajouter": "Agregar a los favoritos",
-		"enregistre": "¡Guardado en favoritos!",
-		"none": "Aún no tienes favoritos",
-		"supprime": "¡Eliminado de favoritos!",
-		"supprimer": "Eliminar de favoritos",
-		"title": "Mis favoritos",
-		"use": "Solo mis favoritos"
-	},
 	"filter": {
-		"statut": "Filtrar por estado"
+		"certif": "Filtrar por estado de certificación",
+		"statut": "Filtrar por estado de miembro"
 	},
 	"infos": "Informaciones",
 	"inout": "Entradas y salidas de la red de confianza en los últimos 2 días",
@@ -182,10 +174,6 @@
 	"nom": "Nombre",
 	"non": "No",
 	"oui": "Sí",
-	"pagination": {
-		"page": "Página",
-		"title": "Botones de paginación"
-	},
 	"params": {
 		"breadcrumb": "Parámetros",
 		"name": {
@@ -237,7 +225,6 @@
 	"pubkey": "Llave pública",
 	"recherche": {
 		"desc": {
-			"favourites": "Busca entre tus favoritos",
 			"lexicon": "Buscar en el léxico",
 			"member": "Introduce el comienzo de un pseudónimo o llave pública"
 		},
@@ -261,6 +248,31 @@
 		"revoked": "Membresía revocada",
 		"title": "Estado"
 	},
+	"suivis": {
+		"ajouter": "Agregar a los favoritos",
+		"alert": {
+			"btn": "Encuentra tu identidad",
+			"desc": "Indique un miembro como su identidad haciendo clic en el botón « Soy este miembro »",
+			"title": "Aún no estás registrado"
+		},
+		"confirm": "Está usted seguro ?",
+		"enregistre": "¡Guardado en favoritos!",
+		"enregistre_uid": "Salvaguardar",
+		"iam": "Soy este miembro",
+		"iamnot": "No soy este miembro",
+		"none": "La lista está vacía",
+		"supprime": "¡Eliminado de favoritos!",
+		"supprimer": "Eliminar de favoritos",
+		"supprimer_tous": "Eliminar todos los favoritos",
+		"tabs": {
+			"certificateurs": "Mis certificadores",
+			"certifies": "Mis miembros certificados",
+			"favoris": "Mis favoritos"
+		},
+		"title": "Mis seguimientos",
+		"use": "Solo mis favoritos",
+		"voir_profil": "Mira mi perfil"
+	},
 	"time": {
 		"a": "a"
 	},
diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json
index d20ce91..93ce146 100644
--- a/i18n/locales/fr.json
+++ b/i18n/locales/fr.json
@@ -109,17 +109,9 @@
 		"title": "Duniter"
 	},
 	"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;!",
-		"supprimer": "Supprimer des favoris",
-		"title": "Mes favoris",
-		"use": "Mes favoris uniquement"
-	},
 	"filter": {
-		"statut": "Filtrer par statut"
+		"certif": "Filtrer en fonction du statut de la certification",
+		"statut": "Filtrer par statut du membre"
 	},
 	"infos": "Informations",
 	"inout": "Entrées et sorties de la toile de confiance des 2 derniers jours",
@@ -182,10 +174,6 @@
 	"nom": "Nom",
 	"non": "Non",
 	"oui": "Oui",
-	"pagination": {
-		"page": "Page",
-		"title": "Boutons de pagination"
-	},
 	"params": {
 		"breadcrumb": "Paramètres",
 		"name": {
@@ -237,7 +225,6 @@
 	"pubkey": "Clef publique",
 	"recherche": {
 		"desc": {
-			"favourites": "Recherchez dans vos favoris",
 			"lexicon": "Recherchez dans le lexique",
 			"member": "Saisissez le début d'un pseudo ou d'une clé publique"
 		},
@@ -261,6 +248,31 @@
 		"revoked": "Membre révoqué",
 		"title": "Statut"
 	},
+	"suivis": {
+		"ajouter": "Ajouter aux favoris",
+		"alert": {
+			"btn": "Rechercher votre identité",
+			"desc": "Veuillez indiquer un membre comme étant votre identité en cliquant sur le bouton « Je suis ce membre »",
+			"title": "Vous n'êtes pas encore enregistré"
+		},
+		"confirm": "Êtes-vous sûr ?",
+		"enregistre": "Enregistré dans les favoris&nbsp;!",
+		"enregistre_uid": "Sauvegarder",
+		"iam": "Je suis ce membre",
+		"iamnot": "Je ne suis pas ce membre",
+		"none": "La liste est vide",
+		"supprime": "Supprimé des favoris&nbsp;!",
+		"supprimer": "Supprimer des favoris",
+		"supprimer_tous": "Supprimer tous les favoris",
+		"tabs": {
+			"certificateurs": "Mes certificateurs",
+			"certifies": "Mes certifiés",
+			"favoris": "Mes favoris"
+		},
+		"title": "Mes suivis",
+		"use": "Mes favoris uniquement",
+		"voir_profil": "Voir mon profil"
+	},
 	"time": {
 		"a": "à"
 	},
diff --git a/i18n/locales/it.json b/i18n/locales/it.json
index 1edfa4b..d95bea3 100644
--- a/i18n/locales/it.json
+++ b/i18n/locales/it.json
@@ -109,17 +109,9 @@
 		"title": "Duniter"
 	},
 	"expire": "Scadenza",
-	"favoris": {
-		"ajouter": "Aggiungere ai favoriti",
-		"enregistre": "Salvato nei favoriti!",
-		"none": "Non hay ancora favoriti",
-		"supprime": "¡Eliminato dai favoriti!",
-		"supprimer": "Eliminare dai favoriti",
-		"title": "I miei favoriti",
-		"use": "Solo i miei favoriti"
-	},
 	"filter": {
-		"statut": "Filtrare per lo stato"
+		"certif": "Filtra per stato di certificazione",
+		"statut": "Filtra per stato membro"
 	},
 	"infos": "Informacioni",
 	"inout": "Entrate e uscite dalla Rete di Fiducia negli ultimi 2 giorni",
@@ -182,10 +174,6 @@
 	"nom": "Nome",
 	"non": "No",
 	"oui": "Sí",
-	"pagination": {
-		"page": "Pagina",
-		"title": "Bottoni di impaginazione"
-	},
 	"params": {
 		"breadcrumb": "Parametri",
 		"name": {
@@ -237,7 +225,6 @@
 	"pubkey": "Chiave pubblica",
 	"recherche": {
 		"desc": {
-			"favourites": "Cerca tra i tuoi favoriti",
 			"lexicon": "Cerca nel lessico",
 			"member": "Introduci l'inizio di uno pseudonimo o chiave pubblica"
 		},
@@ -261,6 +248,31 @@
 		"revoked": "Adesione alla RdF revocata",
 		"title": "Stado"
 	},
+	"suivis": {
+		"ajouter": "Aggiungere ai favoriti",
+		"alert": {
+			"btn": "Trova la tua identità",
+			"desc": "Indica un membro come tua identità facendo clic sul pulsante « Sono questo membro »",
+			"title": "Non sei ancora registrato"
+		},
+		"confirm": "Sei sicuro ?",
+		"enregistre": "Salvato nei favoriti!",
+		"enregistre_uid": "Salvaguardare",
+		"iam": "Sono questo membro",
+		"iamnot": "Non sono questo membro",
+		"none": "L'elenco è vuoto",
+		"supprime": "¡Eliminato dai favoriti!",
+		"supprimer": "Eliminare dai favoriti",
+		"supprimer_tous": "Elimina tutti i favoriti",
+		"tabs": {
+			"certificateurs": "I mei certificatori",
+			"certifies": "I miei membri certificati",
+			"favoris": "I mei favoriti"
+		},
+		"title": "I miei follow-up",
+		"use": "Solo i miei favoriti",
+		"voir_profil": "Guarda il mio profilo"
+	},
 	"time": {
 		"a": "a"
 	},
diff --git a/layouts/default.vue b/layouts/default.vue
index 8ad8450..75687f5 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -24,7 +24,7 @@ export default {
 					title: "wot.title",
 					items: [
 						{ path: "/membres", title: "membres", icon: "search" },
-						{ path: "/favoris", title: "favoris.title", icon: "user-group" }
+						{ path: "/mes-suivis", title: "suivis.title", icon: "user-group" }
 					]
 				},
 				{
diff --git a/nuxt.config.js b/nuxt.config.js
index 316cd0a..3031e2f 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -127,7 +127,6 @@ export default {
 
 	apollo: {
 		clientConfigs: {
-			coindufeu: "~/graphql/endpoints/coindufeu",
 			trentesaux: "~/graphql/endpoints/trentesaux"
 		}
 	},
diff --git a/package.json b/package.json
index 56449c5..aca3ec6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "wotwizard-ui",
-	"version": "2.4.2",
+	"version": "2.5.0",
 	"private": true,
 	"scripts": {
 		"dev": "nuxt",
diff --git a/pages/a-propos.vue b/pages/a-propos.vue
index 930f3ba..b4403f3 100644
--- a/pages/a-propos.vue
+++ b/pages/a-propos.vue
@@ -7,6 +7,31 @@
 			:title="$t('alert.information')">
 			<span v-html="$t('apropos.alert')"></span>
 		</AlertDefault>
+		<div class="text-center my-4">
+			<div class="btn-group" role="group">
+				<a
+					href="https://wotwizard.axiom-team.fr"
+					target="_blank"
+					title="web version"
+					class="btn btn-secondary">
+					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+						<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
+						<path
+							d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 21 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z" />
+					</svg>
+				</a>
+				<a
+					href="https://addons.mozilla.org/fr/firefox/addon/wotwizard-ui"
+					target="_blank"
+					title="Firefox extension"
+					class="btn btn-secondary">
+					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+						<path
+							d="M130.22 127.548C130.38 127.558 130.3 127.558 130.22 127.548V127.548ZM481.64 172.898C471.03 147.398 449.56 119.898 432.7 111.168C446.42 138.058 454.37 165.048 457.4 185.168C457.405 185.306 457.422 185.443 457.45 185.578C429.87 116.828 383.098 89.1089 344.9 28.7479C329.908 5.05792 333.976 3.51792 331.82 4.08792L331.7 4.15792C284.99 30.1109 256.365 82.5289 249.12 126.898C232.503 127.771 216.219 131.895 201.19 139.035C199.838 139.649 198.736 140.706 198.066 142.031C197.396 143.356 197.199 144.87 197.506 146.323C197.7 147.162 198.068 147.951 198.586 148.639C199.103 149.327 199.76 149.899 200.512 150.318C201.264 150.737 202.096 150.993 202.954 151.071C203.811 151.148 204.676 151.045 205.491 150.768L206.011 150.558C221.511 143.255 238.408 139.393 255.541 139.238C318.369 138.669 352.698 183.262 363.161 201.528C350.161 192.378 326.811 183.338 304.341 187.248C392.081 231.108 368.541 381.784 246.951 376.448C187.487 373.838 149.881 325.467 146.421 285.648C146.421 285.648 157.671 243.698 227.041 243.698C234.541 243.698 255.971 222.778 256.371 216.698C256.281 214.698 213.836 197.822 197.281 181.518C188.434 172.805 184.229 168.611 180.511 165.458C178.499 163.75 176.392 162.158 174.201 160.688C168.638 141.231 168.399 120.638 173.51 101.058C148.45 112.468 128.96 130.508 114.8 146.428H114.68C105.01 134.178 105.68 93.7779 106.25 85.3479C106.13 84.8179 99.022 89.0159 98.1 89.6579C89.5342 95.7103 81.5528 102.55 74.26 110.088C57.969 126.688 30.128 160.242 18.76 211.318C14.224 231.701 12 255.739 12 263.618C12 398.318 121.21 507.508 255.92 507.508C376.56 507.508 478.939 420.281 496.35 304.888C507.922 228.192 481.64 173.82 481.64 172.898Z" />
+					</svg>
+				</a>
+			</div>
+		</div>
 		<p class="lead">
 			{{ $t("apropos.desc") }}
 		</p>
@@ -130,7 +155,9 @@ export default {
 	methods: {
 		chemin(contrib) {
 			if (this.hash_contrib[contrib] != "") {
-				return this.localePath("/membre?hash=" + this.hash_contrib[contrib])
+				return this.localePath(
+					"/membres/profil?hash=" + this.hash_contrib[contrib]
+				)
 			} else {
 				return this.localePath("/")
 			}
@@ -141,7 +168,8 @@ export default {
 			fr: "/a-propos",
 			en: "/about",
 			es: "/a-proposito",
-			de: "/uber"
+			de: "/uber",
+			it: "/di"
 		}
 	},
 	mounted() {
@@ -156,4 +184,8 @@ export default {
 		border-top-width: thin;
 	}
 }
+
+svg {
+	width: 50px;
+}
 </style>
diff --git a/pages/favoris.vue b/pages/favoris.vue
deleted file mode 100644
index 34af878..0000000
--- a/pages/favoris.vue
+++ /dev/null
@@ -1,107 +0,0 @@
-<template>
-	<main class="container">
-		<h2 class="text-center my-5 font-weight-light">
-			<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">
-					<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
-				</transition>
-				<transition name="fade">
-					<div
-						v-if="
-							!$apollo.queries.favoris.loading && favoris && favoris.length != 0
-						">
-						<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" />
-						<MemberFilter :selectedStatus.sync="filterStatus" />
-						<MemberList
-							defaultSort="date_membership"
-							:members="filteredFavoris"
-							id="favoris" />
-					</div>
-				</transition>
-				<transition name="fade">
-					<div
-						class="alert alert-info"
-						v-if="
-							!$apollo.queries.favoris.loading && favoris && favoris.length == 0
-						">
-						{{ $t("favoris.none") }}
-					</div>
-				</transition>
-			</div>
-		</div>
-	</main>
-</template>
-
-<script>
-import { FAVORIS } from "@/graphql/queries.js"
-
-export default {
-	data() {
-		return {
-			breadcrumb: [
-				{
-					text: this.$t("accueil"),
-					to: "/"
-				},
-				{
-					text: this.$t("favoris.title"),
-					active: true
-				}
-			],
-			search: "",
-			error: null,
-			filterStatus: ["NEWCOMER", "MEMBER", "MISSING", "REVOKED"]
-		}
-	},
-	computed: {
-		filteredFavoris() {
-			return this.favoris.filter(
-				(el) =>
-					el.uid.toLowerCase().includes(this.search.toLowerCase()) &&
-					this.filterStatus.includes(el.status)
-			)
-		}
-	},
-	apollo: {
-		$client() {
-			return this.getApolloClient
-		},
-		favoris: {
-			query: FAVORIS,
-			variables() {
-				return {
-					group: this.$favourites.list
-				}
-			},
-			update(data) {
-				return data.filterGroup.selected.map((el) => el.id)
-			},
-			error(err) {
-				this.error = err.message
-			}
-		}
-	},
-	nuxtI18n: {
-		paths: {
-			fr: "/favoris",
-			en: "/favourites",
-			es: "/favoritos",
-			de: "/favoriten"
-		}
-	},
-	mounted() {
-		$nuxt.$emit("changeRoute", this.breadcrumb)
-	}
-}
-</script>
diff --git a/pages/index.vue b/pages/index.vue
index 40c2126..0b8f092 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -133,3 +133,9 @@ export default {
 	}
 }
 </script>
+
+<style lang="scss">
+.result .table-responsive tbody {
+	max-height: 56vh;
+}
+</style>
diff --git a/pages/lexique.vue b/pages/lexique.vue
index e32ae48..e0dd7c3 100644
--- a/pages/lexique.vue
+++ b/pages/lexique.vue
@@ -79,7 +79,8 @@ export default {
 			fr: "/lexique",
 			en: "/lexicon",
 			es: "/lexico",
-			de: "/lexikon"
+			de: "/lexikon",
+			it: "/lessico"
 		}
 	},
 	methods: {
diff --git a/pages/membres/index.vue b/pages/membres/index.vue
index 18e0157..f4bc6d9 100644
--- a/pages/membres/index.vue
+++ b/pages/membres/index.vue
@@ -20,7 +20,7 @@
 		</transition>
 		<transition name="fade">
 			<div
-				class="row"
+				class="row result"
 				v-if="
 					idSearch && param.length > 2 && !$apollo.queries.idSearch.loading
 				">
@@ -79,7 +79,8 @@ export default {
 			fr: "/membres",
 			en: "/members",
 			es: "/miembros",
-			de: "/mitglieder"
+			de: "/mitglieder",
+			it: "membri"
 		}
 	},
 	mounted() {
diff --git a/pages/membre.vue b/pages/membres/profil.vue
similarity index 61%
rename from pages/membre.vue
rename to pages/membres/profil.vue
index fd0c819..2d899f8 100644
--- a/pages/membre.vue
+++ b/pages/membres/profil.vue
@@ -40,7 +40,8 @@ export default {
 					active: true
 				}
 			],
-			error: null
+			error: null,
+			registeredAccount: {}
 		}
 	},
 	apollo: {
@@ -69,11 +70,11 @@ export default {
 
 				return {
 					...idFromHash,
-					sent_certifications: this.compileArrays(
+					sent_certifications: this.$favourites.compileArrays(
 						idFromHash.all_certifiedIO,
 						sent_certifications
 					),
-					received_certifications: this.compileArrays(
+					received_certifications: this.$favourites.compileArrays(
 						idFromHash.all_certifiersIO,
 						received_certifications
 					)
@@ -84,62 +85,29 @@ export default {
 			}
 		}
 	},
-	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",
-			en: "/member",
-			es: "/miembro",
-			de: "/mitglied"
+			fr: "/membres/profil",
+			en: "/members/profile",
+			es: "/miembros/perfil",
+			de: "/mitglieder/profil",
+			it: "/membri/profilo"
 		}
 	},
 	mounted() {
 		$nuxt.$emit("changeRoute", this.breadcrumb)
+		this.registeredAccount = JSON.parse(localStorage.getItem("my_account")) || {
+			hash: "",
+			uid: ""
+		}
 	},
 	watch: {
-		idFromHash: {
-			handler(n, o) {
-				this.breadcrumb[2].text = this.idFromHash.uid
-				$nuxt.$emit("changeRoute", this.breadcrumb)
-			}
+		idFromHash(n, o) {
+			this.breadcrumb[2].text = this.idFromHash.uid
+			$nuxt.$emit("changeRoute", this.breadcrumb)
+		},
+		registeredAccount(n, o) {
+			localStorage.setItem("my_account", JSON.stringify(n))
 		}
 	}
 }
diff --git a/pages/mes-suivis.vue b/pages/mes-suivis.vue
new file mode 100644
index 0000000..57e4934
--- /dev/null
+++ b/pages/mes-suivis.vue
@@ -0,0 +1,322 @@
+<template>
+	<main class="container">
+		<h2 class="text-center my-5 font-weight-light">
+			<solid-user-group-icon style="width: 2rem" aria-hidden="true" />&nbsp;{{
+				$t("suivis.title")
+			}}
+		</h2>
+		<div v-if="currentAccount.hash == ''">
+			<AlertDefault
+				type="info"
+				:title="$t('suivis.alert.title')"
+				icon="exclamation">
+				{{ $t("suivis.alert.desc") }}
+				<br />
+				<NuxtLink :to="localePath('membres')" class="btn btn-info mt-3">
+					<solid-search-icon width="1.5rem" /> {{ $t("suivis.alert.btn") }}
+				</NuxtLink>
+			</AlertDefault>
+		</div>
+		<div v-else>
+			<div class="row">
+				<div class="col text-center">
+					<NuxtLink
+						class="btn btn-secondary mb-4"
+						:to="localePath('/membres/profil?hash=' + currentAccount.hash)">
+						<solid-user-circle-icon class="icon" aria-hidden="true" />
+						{{ $t("suivis.voir_profil") }} ({{ currentAccount.uid }})
+					</NuxtLink>
+					<ul class="nav nav-tabs nav-justified" id="suivis" role="tablist">
+						<li class="nav-item" role="presentation">
+							<button
+								class="nav-link active"
+								id="favoris"
+								@click="pushRoute($event)"
+								data-bs-toggle="tab"
+								data-bs-target="#favoris-tab-pane"
+								type="button"
+								role="tab"
+								aria-controls="favoris-tab-pane"
+								aria-selected="true">
+								{{ $t("suivis.tabs.favoris") }} ({{ $favourites.list.length }})
+							</button>
+						</li>
+						<li class="nav-item" role="presentation">
+							<button
+								class="nav-link"
+								id="certificateurs"
+								@click="pushRoute($event)"
+								data-bs-toggle="tab"
+								data-bs-target="#certificateurs-tab-pane"
+								type="button"
+								role="tab"
+								aria-controls="certificateurs-tab-pane"
+								aria-selected="false">
+								{{ $t("suivis.tabs.certificateurs") }}
+								<span v-if="!$apollo.queries.contacts.loading">
+									({{ contacts.received_certifications.length }})
+								</span>
+							</button>
+						</li>
+						<li class="nav-item" role="presentation">
+							<button
+								class="nav-link"
+								id="certifies"
+								@click="pushRoute($event)"
+								data-bs-toggle="tab"
+								data-bs-target="#certifies-tab-pane"
+								type="button"
+								role="tab"
+								aria-controls="certifies-tab-pane"
+								aria-selected="false">
+								{{ $t("suivis.tabs.certifies") }}
+								<span v-if="!$apollo.queries.contacts.loading">
+									({{ contacts.sent_certifications.length }})
+								</span>
+							</button>
+						</li>
+					</ul>
+					<div class="tab-content">
+						<div
+							class="tab-pane fade show active"
+							id="favoris-tab-pane"
+							role="tabpanel"
+							aria-labelledby="favoris"
+							tabindex="0">
+							<NavigationLoader :isLoading="$apollo.queries.favoris.loading" />
+							<transition name="fade">
+								<AlertDefault type="danger" v-if="error">{{
+									error
+								}}</AlertDefault>
+							</transition>
+							<SuivisTableau :members="favoris" type="favoris" />
+						</div>
+						<div
+							class="tab-pane fade"
+							id="certificateurs-tab-pane"
+							role="tabpanel"
+							aria-labelledby="certificateurs"
+							tabindex="0">
+							<NavigationLoader :isLoading="$apollo.queries.contacts.loading" />
+							<transition name="fade">
+								<AlertDefault type="danger" v-if="error">{{
+									error
+								}}</AlertDefault>
+							</transition>
+							<transition name="fade">
+								<div v-if="!$apollo.queries.contacts.loading">
+									<SuivisTableau
+										:members="contacts.received_certifications"
+										type="certificateurs" />
+								</div>
+							</transition>
+						</div>
+						<div
+							class="tab-pane fade"
+							id="certifies-tab-pane"
+							role="tabpanel"
+							aria-labelledby="certifies"
+							tabindex="0">
+							<NavigationLoader :isLoading="$apollo.queries.contacts.loading" />
+							<transition name="fade">
+								<AlertDefault type="danger" v-if="error">{{
+									error
+								}}</AlertDefault>
+							</transition>
+							<transition name="fade">
+								<div v-if="!$apollo.queries.contacts.loading">
+									<SuivisTableau
+										:members="contacts.sent_certifications"
+										type="certifies" />
+								</div>
+							</transition>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</main>
+</template>
+
+<script>
+import Tab from "~/node_modules/bootstrap/js/dist/tab"
+import { SEARCH_MEMBER, MEMBERS } from "@/graphql/queries"
+
+export default {
+	data() {
+		return {
+			breadcrumb: [
+				{
+					text: this.$t("accueil"),
+					to: "/"
+				},
+				{
+					text: this.$t("suivis.title"),
+					active: true
+				}
+			],
+			error: null
+		}
+	},
+	computed: {
+		currentAccount() {
+			const account = JSON.parse(localStorage.getItem("my_account")) || {
+				hash: "",
+				uid: ""
+			}
+			return account
+		}
+	},
+	apollo: {
+		$client() {
+			return this.getApolloClient
+		},
+		favoris: {
+			query: MEMBERS,
+			variables() {
+				return {
+					group: this.$favourites.list
+				}
+			},
+			update(data) {
+				return data.filterGroup.selected.map((el) => {
+					return {
+						...el.id,
+						status:
+							this.$options.filters.dateStatus(el.id.limitDate) == "warning" &&
+							el.id.status != "NEWCOMER"
+								? "RENEW"
+								: el.id.status,
+						expired: false
+					}
+				})
+			},
+			error(err) {
+				this.error = err.message
+			}
+		},
+		contacts: {
+			query: SEARCH_MEMBER,
+			variables() {
+				return {
+					hash: this.currentAccount.hash
+				}
+			},
+			update(data) {
+				if (!data.idFromHash) return {}
+				let idFromHash = data.idFromHash
+
+				let received_certifications = this.$favourites
+					.compileArrays(
+						idFromHash.all_certifiersIO,
+						idFromHash.received_certifications.map((certif) => ({
+							...certif,
+							...certif.from
+						}))
+					)
+					.map((el) => {
+						const ret = el.from ? el.from : el
+						const member = el.id ? el.id : el
+
+						return {
+							...ret,
+							status:
+								this.$options.filters.dateStatus(member.limitDate) ==
+									"warning" && member.status != "NEWCOMER"
+									? "RENEW"
+									: member.status,
+							expired: member.expired || false
+						}
+					})
+
+				let sent_certifications = this.$favourites
+					.compileArrays(
+						idFromHash.all_certifiedIO,
+						idFromHash.sent_certifications.map((certif) => ({
+							...certif,
+							...certif.to
+						}))
+					)
+					.map((el) => {
+						const ret = el.to ? el.to : el
+						const member = el.id ? el.id : el
+
+						return {
+							...ret,
+							status:
+								this.$options.filters.dateStatus(member.limitDate) ==
+									"warning" && member.status != "NEWCOMER"
+									? "RENEW"
+									: member.status,
+							expired: member.expired || false
+						}
+					})
+
+				return {
+					sent_certifications,
+					received_certifications
+				}
+			},
+			error(err) {
+				this.error = err.message
+			}
+		}
+	},
+	methods: {
+		saveUID() {
+			localStorage.setItem("UID", document.getElementById("UID").value)
+		},
+		showPane(pane) {
+			const panel = new Tab(pane)
+
+			if (panel) panel.show()
+		},
+		pushRoute(e) {
+			this.$router.push(
+				this.localePath({
+					name: "mes-suivis",
+					hash: "#" + e.target.id
+				})
+			)
+		}
+	},
+	nuxtI18n: {
+		paths: {
+			fr: "/mes-suivis",
+			en: "/my-follow-ups",
+			es: "/mis-seguimientos",
+			de: "/meine-nachfolge",
+			it: "/i-miei-follow-up"
+		}
+	},
+	mounted() {
+		$nuxt.$emit("changeRoute", this.breadcrumb)
+
+		const $this = this
+		document.querySelectorAll('button[data-bs-toggle="tab"]').forEach((el) => {
+			el.addEventListener("shown.bs.tab", function (event) {
+				$this.$favourites.fixColumns()
+			})
+		})
+
+		if (this.$route.hash && this.currentAccount.hash != "") {
+			this.showPane(this.$route.hash)
+		}
+	},
+	watch: {
+		$route(n, o) {
+			this.showPane(n.hash ? n.hash : "#favoris")
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+#suivis {
+	flex-direction: column;
+
+	@media (min-width: 768px) {
+		flex-direction: row;
+	}
+}
+</style>
diff --git a/pages/parametres.vue b/pages/parametres.vue
index 62ebdac..652db79 100644
--- a/pages/parametres.vue
+++ b/pages/parametres.vue
@@ -113,7 +113,8 @@ export default {
 			fr: "/parametres",
 			en: "/parameters",
 			es: "/parametros",
-			de: "/einstellungen"
+			de: "/einstellungen",
+			it: "/impostazioni"
 		}
 	},
 	mounted() {
diff --git a/pages/previsions/futures_entrees.vue b/pages/previsions/futures_entrees.vue
index a6d1155..302da1c 100644
--- a/pages/previsions/futures_entrees.vue
+++ b/pages/previsions/futures_entrees.vue
@@ -58,7 +58,7 @@
 											@click="
 												$router.push(
 													localePath({
-														name: 'membre',
+														name: 'membres-profil',
 														query: { hash: forecast.member.hash }
 													})
 												)
@@ -66,7 +66,7 @@
 											@keyup.enter="
 												$router.push(
 													localePath({
-														name: 'membre',
+														name: 'membres-profil',
 														query: { hash: forecast.member.hash }
 													})
 												)
@@ -135,7 +135,9 @@
 														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)
+															localePath(
+																'/membres/profil/?hash=' + member.member.hash
+															)
 														"
 														v-for="member in forecast.forecasts"
 														:key="member.member.uid">
@@ -285,9 +287,10 @@ export default {
 	nuxtI18n: {
 		paths: {
 			fr: "/previsions/futures_entrees",
-			en: "/forecasts/future_exits2",
-			es: "/pronosticos/futuras_salidas2",
-			de: "/prognosen/zukunftige_veroffentlichungen2"
+			en: "/forecasts/future_exits",
+			es: "/pronosticos/futuras_salidas",
+			de: "/prognosen/zukunftige_veroffentlichungen",
+			it: "/previsioni/ingressi-futuri"
 		}
 	},
 	mounted() {
diff --git a/pages/previsions/futures_sorties.vue b/pages/previsions/futures_sorties.vue
index c5d5218..1123ab6 100644
--- a/pages/previsions/futures_sorties.vue
+++ b/pages/previsions/futures_sorties.vue
@@ -14,7 +14,7 @@
 				v-model="favoris"
 				@change="save()" />
 			<label for="favoris" class="form-check-label">{{
-				$t("favoris.use")
+				$t("suivis.use")
 			}}</label>
 		</div>
 		<NavigationLoader :isLoading="$apollo.loading" />
@@ -22,7 +22,7 @@
 			<AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault>
 		</transition>
 		<transition name="fade">
-			<div class="row" v-if="!$apollo.loading">
+			<div class="row result" v-if="!$apollo.loading">
 				<div class="col-md-6 mb-5" v-if="memEnds">
 					<h2 class="h4 text-danger text-center text-truncate">
 						{{ $t("statut.renew") }}
@@ -115,7 +115,8 @@ export default {
 			fr: "/previsions/futures_sorties",
 			en: "/forecasts/future_exits",
 			es: "/pronosticos/futuras_salidas",
-			de: "/prognosen/zukunftige_veroffentlichungen"
+			de: "/prognosen/zukunftige_veroffentlichungen",
+			it: "/previsioni/future-uscite"
 		}
 	},
 	mounted() {
diff --git a/plugins/bootstrap.js b/plugins/bootstrap.js
index eb028a5..5c8a2ad 100644
--- a/plugins/bootstrap.js
+++ b/plugins/bootstrap.js
@@ -27,13 +27,11 @@ Vue.directive("tooltip", function (el, binding, vnode) {
 			trigger: "hover"
 		})
 
-		vnode.context.$nextTick(() => {
-			var x = new MutationObserver(function (e) {
-				if (e[0].removedNodes) tooltip.hide()
-			});
-
-			x.observe(el.parentNode, { childList: true })
-
-		});
+		// vnode.context.$nextTick(() => {
+		// 	var x = new MutationObserver(function (e) {
+		// 		if (e[0].removedNodes) tooltip.hide()
+		// 	})
+		// 	x.observe(el.parentNode, { childList: true })
+		// })
 	}
 })
diff --git a/plugins/favourites.js b/plugins/favourites.js
index 23f56b5..219cf51 100644
--- a/plugins/favourites.js
+++ b/plugins/favourites.js
@@ -24,12 +24,68 @@ export default (context, inject) => {
 		}
 	}
 
+	let 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
+	}
+
+	let fixColumns = () => {
+		let tables = document.querySelectorAll(".table-responsive table")
+
+		tables.forEach((table) => {
+			let colWidth = [...table.querySelectorAll("tbody tr:first-child td")].map(
+				function (el) {
+					return el.offsetWidth
+				}
+			)
+
+			table.querySelectorAll("thead tr th").forEach((el, i) => {
+				el.style.width = colWidth[i] - 0.5 + "px"
+			})
+		})
+	}
+
 	inject(
 		"favourites",
 		Vue.observable({
 			list: liste_favoris,
 			toggleFavourite: toggleFavourite,
-			addFavorisArray: addFavorisArray
+			addFavorisArray: addFavorisArray,
+			compileArrays: compileArrays,
+			fixColumns: fixColumns
 		})
 	)
 }
diff --git a/plugins/getApolloClient.js b/plugins/getApolloClient.js
index 0ce8066..0e1981f 100644
--- a/plugins/getApolloClient.js
+++ b/plugins/getApolloClient.js
@@ -3,10 +3,7 @@ import Vue from "vue"
 let mixin = {
 	computed: {
 		getApolloClient() {
-			return (
-				localStorage.getItem("apollo-client") ||
-				Object.keys($nuxt.$apolloProvider.clients)[0]
-			)
+			return localStorage.getItem("apollo-client") || "trentesaux"
 		}
 	}
 }
diff --git a/web-ext/manifest.json b/web-ext/manifest.json
index 68b4a39..8c4605a 100644
--- a/web-ext/manifest.json
+++ b/web-ext/manifest.json
@@ -2,7 +2,7 @@
 	"manifest_version": 2,
 	"name": "Wotwizard UI",
 	"description": "Le magicien de la toile de confiance",
-	"version": "2.4.2",
+	"version": "2.4.3",
 	"homepage_url": "https://wotwizard.axiom-team.fr",
 	"browser_action": {
 		"browser_style": true,
-- 
GitLab