Repository: datagouv/udata-front (language: python)

Issue:
Add data.gouv.fr components
Files and components in datagouv-components are converted to Typescript to improve IDE support and allow types to be exported.

Existing files and components in udata-front are converted on a case-by-case basis to improve readability and use complex types not available with the current JsDoc-like syntax.

Most of the time there is no difference between JS/TS files except the move from the old JsDoc syntax to the TS one.

Due to dependency and build updates, `entryFiles` are versioned with the package version too. This cause issues with the `burst` strategy in python using `time` in debug mode. I'm adding a new parameter `force_version`, as a workaround for now. ~~Another solution without version number is to use a manifest again. This was removed because Parcel didn't support it but vite does.~~ (Manifest were removed due to other issues so we'll stay with the version number in file name).

`PKG-INFO` is used to find the current udata-front version in both local and CI. The CI build number is used for each CI steps. In some cases, develop requirements were not already installed so `setup.py` is used for now instead of `invoke`. I think using `invoke` everywhere is better but I don't know if develop requirement were not installed for a particular reason or only because they were not needed yet. 

Closes https://github.com/etalab/data.gouv.fr/issues/1179
Closes https://github.com/etalab/data.gouv.fr/issues/1101


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5e43363c..9188a5941 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
 > The update to the next version is mandatory to have a valid js build.
 
 - Fix dependencies according to udata's Flask-Babel migration [#300](https://github.com/etalab/udata-front/pull/300)
+- User pages are back [#338](https://github.com/etalab/udata-front/pull/338)
 
 ## 3.2.12 (2023-12-15)
 
diff --git a/package-lock.json b/package-lock.json
index ee435a2f8..3a61eff1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,9 @@
       "version": "3.5.1.dev",
       "license": "LGPL-2.1-only",
       "devDependencies": {
-        "@conciergerie.dev/select-a11y": "^3.4.0",
+        "@conciergerie.dev/select-a11y": "^3.5.0",
         "@conciergerie.dev/vue-toaster": "^2.4.0",
-        "@etalab/data.gouv.fr-components": "^1.13.4",
+        "@etalab/data.gouv.fr-components": "^1.13.6",
         "@etalab/udata-front-plugins-helper": "1.1.0",
         "@gouvfr/dsfr": "~1.9.2",
         "@intlify/unplugin-vue-i18n": "^1.5.0",
@@ -2044,9 +2044,9 @@
       }
     },
     "node_modules/@conciergerie.dev/select-a11y": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/@conciergerie.dev/select-a11y/-/select-a11y-3.4.0.tgz",
-      "integrity": "sha512-UWhn5l75WPEPWyRFishry3C9vh0jl8A0KmHZ9Olyb+/fy3kRmPniWasSx19frHEVX7EnBE+rrNueFvg6mzfo+g==",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/@conciergerie.dev/select-a11y/-/select-a11y-3.5.0.tgz",
+      "integrity": "sha512-l4TI2y+lR9gAZwTD51YJhGOEg9DmyuMTP7xoFy6pMNYatD1j+lukE4GjsBGMZVw6q9UYvVIxz9zmjbNcioUeTg==",
       "dev": true
     },
     "node_modules/@conciergerie.dev/vue-toaster": {
@@ -2165,9 +2165,9 @@
       }
     },
     "node_modules/@etalab/data.gouv.fr-components": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmjs.org/@etalab/data.gouv.fr-components/-/data.gouv.fr-components-1.13.4.tgz",
-      "integrity": "sha512-kHSO0YHuNtZRDhVgvnbb2VOrTPWbszZveBtr+Ljm5PDK9QK3JKs+3SaQ4xGFkzHqoBiWJhlwQosJsy0/KqXM6Q==",
+      "version": "1.13.6",
+      "resolved": "https://registry.npmjs.org/@etalab/data.gouv.fr-components/-/data.gouv.fr-components-1.13.6.tgz",
+      "integrity": "sha512-/5Fy3XY5pC9cSHn9MvM/OkPjah4CE6UFprsjUHmI9tKPeICNkztfoWvu35FHmGkV18kZ8jNzLYZWObH7Yu7DBQ==",
       "dev": true,
       "dependencies": {
         "vue": "^3.3.8"
@@ -17491,9 +17491,9 @@
       }
     },
     "@conciergerie.dev/select-a11y": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/@conciergerie.dev/select-a11y/-/select-a11y-3.4.0.tgz",
-      "integrity": "sha512-UWhn5l75WPEPWyRFishry3C9vh0jl8A0KmHZ9Olyb+/fy3kRmPniWasSx19frHEVX7EnBE+rrNueFvg6mzfo+g==",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/@conciergerie.dev/select-a11y/-/select-a11y-3.5.0.tgz",
+      "integrity": "sha512-l4TI2y+lR9gAZwTD51YJhGOEg9DmyuMTP7xoFy6pMNYatD1j+lukE4GjsBGMZVw6q9UYvVIxz9zmjbNcioUeTg==",
       "dev": true
     },
     "@conciergerie.dev/vue-toaster": {
@@ -17580,9 +17580,9 @@
       "optional": true
     },
     "@etalab/data.gouv.fr-components": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmjs.org/@etalab/data.gouv.fr-components/-/data.gouv.fr-components-1.13.4.tgz",
-      "integrity": "sha512-kHSO0YHuNtZRDhVgvnbb2VOrTPWbszZveBtr+Ljm5PDK9QK3JKs+3SaQ4xGFkzHqoBiWJhlwQosJsy0/KqXM6Q==",
+      "version": "1.13.6",
+      "resolved": "https://registry.npmjs.org/@etalab/data.gouv.fr-components/-/data.gouv.fr-components-1.13.6.tgz",
+      "integrity": "sha512-/5Fy3XY5pC9cSHn9MvM/OkPjah4CE6UFprsjUHmI9tKPeICNkztfoWvu35FHmGkV18kZ8jNzLYZWObH7Yu7DBQ==",
       "dev": true,
       "requires": {
         "vue": "^3.3.8"
diff --git a/package.json b/package.json
index 6a69fa6d1..1d34f8f86 100644
--- a/package.json
+++ b/package.json
@@ -28,9 +28,9 @@
     "fsevents": "^2.3.3"
   },
   "devDependencies": {
-    "@conciergerie.dev/select-a11y": "^3.4.0",
+    "@conciergerie.dev/select-a11y": "^3.5.0",
     "@conciergerie.dev/vue-toaster": "^2.4.0",
-    "@etalab/data.gouv.fr-components": "^1.13.4",
+    "@etalab/data.gouv.fr-components": "^1.13.6",
     "@etalab/udata-front-plugins-helper": "1.1.0",
     "@gouvfr/dsfr": "~1.9.2",
     "@intlify/unplugin-vue-i18n": "^1.5.0",
diff --git a/udata_front/theme/gouvfr/assets/js/api/licenses.js b/udata_front/theme/gouvfr/assets/js/api/licenses.js
index 9ae0f56aa..3d0a8803e 100644
--- a/udata_front/theme/gouvfr/assets/js/api/licenses.js
+++ b/udata_front/theme/gouvfr/assets/js/api/licenses.js
@@ -1,8 +1,6 @@
 import { getLocalizedUrl } from "../i18n";
-import {api} from "../plugins/api";
+import { api } from "../plugins/api";
 
-
-let licenses = null;
 let licensesRequest = null;
 
 export function getLicensesUrl() {
@@ -18,6 +16,5 @@ export default function fetchLicenses() {
     return licensesRequest;
   }
   return licensesRequest = api.get(getLicensesUrl())
-  .then((resp) => resp.data)
-  .then((data) => licenses = data);
+  .then((resp) => resp.data);
 }
diff --git a/udata_front/theme/gouvfr/assets/js/api/reuses.ts b/udata_front/theme/gouvfr/assets/js/api/reuses.ts
new file mode 100644
index 000000000..6d4759fc7
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/api/reuses.ts
@@ -0,0 +1,27 @@
+import { getLocalizedUrl } from "../i18n";
+import { api } from "../plugins/api";
+import { Reuse } from "../types";
+
+let reuseTypesRequest: Promise<Array<ReuseType>> | null = null;
+
+export type ReuseType = {
+  id: string,
+  label: string,
+};
+
+export function getReuseTypesUrl() {
+  return getLocalizedUrl("reuses/types/");
+}
+
+export function fetchReuseTypes() {
+  if (reuseTypesRequest) {
+    return reuseTypesRequest;
+  }
+  return reuseTypesRequest = api.get<Promise<Array<ReuseType>>>(getReuseTypesUrl())
+  .then((resp) => resp.data);
+}
+
+export function getType(types: Array<ReuseType>, id: string): string {
+  const type = types.find(t => t.id === id);
+  return type ? type.label : "";
+}
diff --git a/udata_front/theme/gouvfr/assets/js/components/Form/ActionCard/ActionCard.vue b/udata_front/theme/gouvfr/assets/js/components/Form/ActionCard/ActionCard.vue
index 862974989..3ea144c2b 100644
--- a/udata_front/theme/gouvfr/assets/js/components/Form/ActionCard/ActionCard.vue
+++ b/udata_front/theme/gouvfr/assets/js/components/Form/ActionCard/ActionCard.vue
@@ -53,6 +53,10 @@ export default defineComponent({
       type: String,
       required: true,
     },
+    content: {
+      type: String,
+      required: true,
+    },
     type: {
       type: String,
       default: "default"
diff --git a/udata_front/theme/gouvfr/assets/js/components/Form/FileCard/FileCard.vue b/udata_front/theme/gouvfr/assets/js/components/Form/FileCard/FileCard.vue
index 3e8f7b935..86902c134 100644
--- a/udata_front/theme/gouvfr/assets/js/components/Form/FileCard/FileCard.vue
+++ b/udata_front/theme/gouvfr/assets/js/components/Form/FileCard/FileCard.vue
@@ -82,7 +82,7 @@ export default defineComponent({
     const file = ref({...props.file});
     const loading = computed(() => isLoading(file.value));
     const loaded = computed(() => isLoaded(file.value));
-    const resourceImage = useResourceImage(props.file.format || "");
+    const {url: resourceImage} = useResourceImage(file.value);
     const { stateErrors, stateWarnings, validateRequiredRules } = useFileValidation(file);
 
     onMounted(() => validateRequiredRules());
diff --git a/udata_front/theme/gouvfr/assets/js/components/Form/UploadGroup/UploadGroup.vue b/udata_front/theme/gouvfr/assets/js/components/Form/UploadGroup/UploadGroup.vue
index d2342ce47..4db1b022f 100644
--- a/udata_front/theme/gouvfr/assets/js/components/Form/UploadGroup/UploadGroup.vue
+++ b/udata_front/theme/gouvfr/assets/js/components/Form/UploadGroup/UploadGroup.vue
@@ -28,10 +28,12 @@
           :accept="accept"
           :multiple="multiple"
           :required="required"
+          :disabled="disabled"
         />
       </div>
       <button
         @click="open"
+        :disabled="disabled"
         class="fr-btn fr-btn--secondary fr-btn--secondary-grey-500"
         :title="$t('Browse')"
         :aria-controls="id"
@@ -129,13 +131,13 @@ export default defineComponent({
     const ariaDescribedBy = computed(() => {
       let describedBy = "";
       if(props.hintText) {
-        describedBy += hintTextId;
+        describedBy += hintTextId.value;
       }
       if (props.isValid) {
-        describedBy += " " + validTextId;
+        describedBy += " " + validTextId.value;
       }
       else if (props.hasError) {
-        describedBy += " " + errorTextId;
+        describedBy += " " + errorTextId.value;
       }
       return describedBy;
     });
@@ -168,7 +170,9 @@ export default defineComponent({
     const change = (event) => {
       const target = /** @type {HTMLInputElement | null} */ (event.target);
       const files = Array.from(target?.files ?? []);
-      onChange(files);
+      if(!props.disabled) {
+        onChange(files);
+      }
     }
 
     useDropZone(dropFilesHereRef, onChange);
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.mdx b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.mdx
new file mode 100644
index 000000000..50c58754f
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.mdx
@@ -0,0 +1,15 @@
+import { Canvas, Controls, Meta } from '@storybook/blocks';
+
+import * as ReuseStories from './Reuse.stories';
+
+<Meta of={ReuseStories} />
+
+# Reuse Card
+
+A reuse card.
+
+<Canvas of={ReuseStories.ReuseWithCertifiedOrganization} />
+
+## Props
+
+<Controls />
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.stories.ts b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.stories.ts
new file mode 100644
index 000000000..21690ca0c
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.stories.ts
@@ -0,0 +1,171 @@
+import { CERTIFIED, PUBLIC_SERVICE, type Organization } from '@etalab/data.gouv.fr-components';
+import Reuse, { type ReuseProps } from './Reuse.vue';
+import { withActions } from '@storybook/addon-actions/decorator';
+import type { Meta, StoryObj } from "@storybook/vue3";
+
+const meta = {
+  title: 'Components/Reuse Card',
+  component: Reuse,
+  parameters: {
+    actions: {
+      handles: ['click a'],
+    },
+  },
+  decorators: [withActions],
+} satisfies Meta<typeof Reuse>;
+
+export default meta;
+
+const args: ReuseProps = {
+  reuse: {
+    id: "someId",
+    description: "Some description",
+    last_update: (new Date()).toDateString(),
+    page: "https://www.data.gouv.fr",
+    deleted: false,
+    private: false,
+    datasets: [],
+    slug: "some-slug",
+    topic: "housing_and_development",
+    type: "application",
+    tags: [],
+    owner: {
+      id: "someUserId",
+      first_name: "First",
+      last_name: "Last",
+      page: "https://demo.data.gouv.fr/en/users/antonin-garrone/",
+    },
+    organization: null,
+    title: "My new dataset",
+    image: "https://static.data.gouv.fr/images/aa/c1f583251a4697850bd01e2cc95877.png",
+    image_thumbnail: "https://static.data.gouv.fr/images/aa/c1f583251a4697850bd01e2cc95877.png",
+  }
+};
+
+const updateLastMonth = new Date();
+updateLastMonth.setMonth(updateLastMonth.getMonth() - 1, 20);
+
+const updateLastYear = new Date();
+updateLastYear.setFullYear(updateLastYear.getFullYear() - 1);
+
+const organization: Organization = {
+  id: "someId",
+  acronym: null,
+  name: "My Organization",
+  badges: [],
+  page: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  uri: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  slug: "data-gouv-fr",
+  logo: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a.jpeg",
+  logo_thumbnail: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a-100.jpeg",
+};
+
+const organizationWithLogo: Organization = {
+  ...organization,
+  logo_thumbnail: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a-100.jpeg"
+};
+
+const argsWithOrganizationWithLogo: ReuseProps = {
+  reuse: {
+    ...args.reuse,
+    organization: organizationWithLogo,
+    owner: null,
+  }
+};
+
+const argsWithCertifiedOrganization: ReuseProps = {
+  reuse: {
+    ...argsWithOrganizationWithLogo.reuse,
+    organization: {
+      ...organizationWithLogo,
+      badges: [{kind: PUBLIC_SERVICE}, {kind: CERTIFIED}]
+    },
+    owner: null
+  }
+};
+
+/**
+ * #### Reuse with a owner
+ * This is how a reuse owned by a owner is shown.
+ */
+export const ReuseWithOwner: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { Reuse },
+    setup() {
+      return { args };
+    },
+    template: '<Reuse v-bind="args"/>',
+  }),
+  args,
+};
+
+/**
+ * #### Reuse with an organization
+ * This is how a reuse owned by an organization is shown.
+ */
+export const ReuseWithOrganization: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { Reuse },
+    setup() {
+      return { args };
+    },
+    template: '<Reuse v-bind="args"/>',
+  }),
+  args: argsWithOrganizationWithLogo,
+};
+
+/**
+ * #### Reuse with a certified organization
+ * This is how a reuse owned by a certified organization is shown.
+ */
+export const ReuseWithCertifiedOrganization: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { Reuse },
+    setup() {
+      return { args };
+    },
+    template: '<Reuse v-bind="args"/>',
+  }),
+  args: argsWithCertifiedOrganization,
+};
+
+
+/**
+ * #### Reuse updated last month
+ * This is how a reuse owned by an organization is shown.
+ */
+export const ReuseUpdatedLastMonth: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { Reuse },
+    setup() {
+      return { args };
+    },
+    template: '<Reuse v-bind="args"/>',
+  }),
+  args: {
+    reuse: {
+      ...argsWithOrganizationWithLogo.reuse,
+      last_update: updateLastMonth.toDateString(),
+    }
+  },
+};
+
+/**
+ * #### Reuse updated last year
+ * This is how a reuse owned by an organization is shown.
+ */
+export const ReuseUpdatedLastYear: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { Reuse },
+    setup() {
+      return { args };
+    },
+    template: '<Reuse v-bind="args"/>',
+  }),
+  args: {
+    reuse: {
+      ...argsWithOrganizationWithLogo.reuse,
+      last_update: updateLastYear.toDateString(),
+    }
+  },
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.vue b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.vue
new file mode 100644
index 000000000..1d0548e88
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/Reuse.vue
@@ -0,0 +1,67 @@
+<template>
+  <article class="fr-card fr-card--no-icon fr-enlarge-link">
+    <div class="fr-card__body">
+      <div class="fr-card__content fr-p-2w">
+        <h3 class="fr-card__title fr-text--md fr-mt-1w">
+          <a :href="reuse.page">
+            {{ truncate(reuse.title, 55) }}
+            <span
+              v-if="reuse.private"
+              class="fr-badge fr-ml-1w"
+            >
+              {{ t('Private') }}
+            </span>
+          </a>
+        </h3>
+        <p class="fr-card__detail fr-m-0">
+          {{ reuseType }}
+        </p>
+        <p class="fr-card__desc fr-mt-1w text-mention-grey not-enlarged">
+          {{ t('Updated {date}', {date: formatRelativeIfRecentDate(reuse.last_update)}) }}
+          {{ t(' by ') }}
+          <span class="not-enlarged" v-if="reuse.organization">
+            <a class="fr-link" :href="reuse.organization.page">
+              <OrganizationNameWithCertificate :organization="reuse.organization" />
+            </a>
+          </span>
+          <span class="not-enlarged" v-else>
+            <a class="fr-link" :href="reuse.owner.page">
+              {{ ownerName }}
+            </a>
+          </span>
+        </p>
+      </div>
+    </div>
+    <div class="fr-card__header">
+      <div class="fr-card__img">
+        <Placeholder
+          class="fr-responsive-img"
+          type="reuse"
+          :src="reuse.image"
+          />
+      </div>
+    </div>
+  </article>
+</template>
+
+<script lang="ts">
+export type ReuseProps = {
+  reuse: Reuse,
+};
+</script>
+
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n';
+import Placeholder from '../utils/placeholder.vue';
+import { truncate } from "../../helpers";
+import type { Reuse } from '../../types';
+import { formatRelativeIfRecentDate, useOwnerName, OrganizationNameWithCertificate } from "@etalab/data.gouv.fr-components";
+import useReuseType from '../../composables/useReuseType';
+
+const props = defineProps<ReuseProps>();
+
+const { t } = useI18n();
+
+const ownerName = useOwnerName(props.reuse);
+const { label: reuseType } = useReuseType(() => props.reuse.type);
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.mdx b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.mdx
new file mode 100644
index 000000000..fe019c480
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.mdx
@@ -0,0 +1,15 @@
+import { Canvas, Controls, Meta } from '@storybook/blocks';
+
+import * as ReuseListStories from './ReuseList.stories';
+
+<Meta of={ReuseListStories} />
+
+# Reuse List
+
+A list of reuses.
+
+<Canvas of={ReuseListStories.DefaultReuseList} />
+
+## Props
+
+<Controls />
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.stories.ts b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.stories.ts
new file mode 100644
index 000000000..debab737e
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.stories.ts
@@ -0,0 +1,103 @@
+import ReuseList, { type ReuseListProps } from './ReuseList.vue';
+import { withActions } from '@storybook/addon-actions/decorator';
+import type { Meta, StoryObj } from "@storybook/vue3";
+import { ReuseWithOwner } from './Reuse.stories';
+
+const meta = {
+  title: 'Lists/Reuse Card List',
+  component: ReuseList,
+  parameters: {
+    actions: {
+      handles: ['click a'],
+    },
+  },
+  decorators: [withActions],
+} satisfies Meta<typeof ReuseList>;
+
+export default meta;
+
+const reuse = {
+  ...ReuseWithOwner.args.reuse,
+}
+
+const args: ReuseListProps = {
+  reuses: [reuse, reuse, reuse],
+  totalResults: 3,
+};
+
+const twoReusesArgs: ReuseListProps = {
+  reuses: [reuse, reuse],
+  totalResults: 2,
+};
+
+const paginatedArgs: ReuseListProps = {
+  reuses: [reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse,
+    reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse, reuse],
+  totalResults: 24,
+};
+
+const customPageSizeArgs: ReuseListProps = {
+  reuses: [reuse, reuse],
+  pageSize: 2,
+  totalResults: 3,
+};
+
+/**
+ * #### Reuse List
+ * A list of reuse cards.
+ */
+export const DefaultReuseList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { ReuseList },
+    setup() {
+      return { args };
+    },
+    template: '<ReuseList v-bind="args"/>',
+  }),
+  args,
+};
+
+/**
+ * #### 2-reuses List
+ * A list of large reuse cards.
+ */
+export const TwoReusesList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { ReuseList },
+    setup() {
+      return { args };
+    },
+    template: '<ReuseList v-bind="args"/>',
+  }),
+  args: twoReusesArgs,
+};
+
+/**
+ * #### Reuse List With Pagination
+ * A list of reuse cards with pagination.
+ */
+export const PaginatedReuseList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { ReuseList },
+    setup() {
+      return { args };
+    },
+    template: '<ReuseList v-bind="args"/>',
+  }),
+  args: paginatedArgs,
+};
+
+/**
+ * #### Reuse List With Custom Page Size
+ * A list of reuse cards with custom page size.
+ */
+export const CustomPageSizeReuseList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { ReuseList },
+    setup() {
+      return { args };
+    },
+    template: '<ReuseList v-bind="args"/>',
+  }),
+  args: customPageSizeArgs,
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.vue b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.vue
new file mode 100644
index 000000000..4741c3a7b
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/Reuse/ReuseList.vue
@@ -0,0 +1,71 @@
+<template>
+  <div v-if="loading">
+    <Loader />
+  </div>
+  <template v-else-if="totalResults">
+    <ul ref="listRef" class="fr-grid-row fr-grid-row--gutters">
+      <li
+        class="fr-col-12"
+        :class="{'fr-col-md-6': reuses.length <= 2, 'fr-col-md-4': reuses.length >= 3}"
+        v-for="reuse in reuses"
+        :key="reuse.id"
+      >
+        <ReuseCard :reuse="reuse" />
+      </li>
+    </ul>
+    <Pagination
+      v-if="totalResults > pageSize"
+      :page="currentPage"
+      :pageSize="pageSize"
+      :totalResults="totalResults"
+      :changePage="changePage"
+      class="fr-mt-2w"
+    />
+  </template>
+</template>
+
+<script lang="ts">
+import type { Reuse } from '../../types';
+export type ReuseListProps = {
+  reuses: Array<Reuse>,
+  loading?: boolean,
+  totalResults: number,
+  pageSize?: number,
+};
+
+/** TODO : use a config when the component moved to data.gouv.fr-components */
+const defaultPageSize = 20;
+</script>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { Pagination } from '@etalab/udata-front-plugins-helper';
+import Loader from "./loader.vue";
+import ReuseCard from './Reuse.vue';
+
+withDefaults(defineProps<ReuseListProps>(), {
+  loading: false,
+  pageSize: defaultPageSize,
+});
+
+const emit = defineEmits<{
+  (event: 'changePage', page: number): void,
+}>();
+
+const currentPage = ref(1);
+
+const listRef = ref<HTMLElement | undefined>();
+
+function scrollToTop () {
+  if (listRef.value) {
+    listRef.value.scrollIntoView({ behavior: "smooth" });
+  }
+};
+
+function changePage (page: number) {
+  currentPage.value = page;
+  emit("changePage", page);
+  scrollToTop();
+}
+
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/reuse/loader.vue b/udata_front/theme/gouvfr/assets/js/components/Reuse/loader.vue
similarity index 100%
rename from udata_front/theme/gouvfr/assets/js/components/reuse/loader.vue
rename to udata_front/theme/gouvfr/assets/js/components/Reuse/loader.vue
diff --git a/udata_front/theme/gouvfr/assets/js/components/UserDatasetList/UserDatasetList.vue b/udata_front/theme/gouvfr/assets/js/components/UserDatasetList/UserDatasetList.vue
new file mode 100644
index 000000000..b04051505
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/UserDatasetList/UserDatasetList.vue
@@ -0,0 +1,79 @@
+<template>
+  <h2 class="subtitle subtitle--uppercase fr-mb-5v" v-if="totalResults">
+    {{ t('{n} datasets', totalResults) }}
+  </h2>
+  <CardMDList
+    :page-size="pageSize"
+    :datasets="datasets"
+    :loading="loading"
+    :total-results="totalResults"
+    @change-page="loadPage"
+  />
+</template>
+
+<script lang="ts">
+import type { Dataset } from '../../types';
+
+export type UserDatasetListProps = {
+  owner: string,
+};
+</script>
+
+<script setup lang="ts">
+import axios, { type CancelTokenSource } from 'axios';
+import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useToast } from '../../composables/useToast';
+import { apiv2, generateCancelToken } from '../../plugins/api';
+import CardMDList from '../dataset/CardMDList.vue';
+
+const { t } = useI18n();
+const toast = useToast();
+
+const props = defineProps<UserDatasetListProps>();
+
+const datasets = ref<Array<Dataset>>([]);
+const totalResults = ref(0);
+const loading = ref(false);
+
+/**
+ * Current request to cancel before a new one comes
+ */
+const currentRequest = ref<CancelTokenSource | null>(null);
+
+/** TODO : use a config when the component moved to data.gouv.fr-components */
+const pageSize = 3;
+
+/**
+ * Load new datasets
+ */
+function loadPage (page: number) {
+  loading.value = true;
+  if (currentRequest.value) currentRequest.value.cancel();
+  currentRequest.value = generateCancelToken();
+  apiv2
+    .get("/datasets/search/", {
+      cancelToken: currentRequest.value.token,
+      params: {
+        page,
+        owner: props.owner,
+        page_size: pageSize,
+      },
+    })
+    .then((res) => res.data)
+    .then((result) => {
+      datasets.value = result.data;
+      totalResults.value = result.total;
+      loading.value = false;
+    })
+    .catch((error) => {
+      if (!axios.isCancel(error)) {
+        toast.error(t("Error getting search results."));
+        loading.value = false;
+      }
+    });
+};
+onMounted(() => {
+  loadPage(1);
+});
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/UserReuseList/UserReuseList.vue b/udata_front/theme/gouvfr/assets/js/components/UserReuseList/UserReuseList.vue
new file mode 100644
index 000000000..8d6a4ea70
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/UserReuseList/UserReuseList.vue
@@ -0,0 +1,80 @@
+<template>
+  <h2 class="subtitle subtitle--uppercase fr-mb-5v" v-if="totalResults">
+    {{ t('{n} reuses', totalResults) }}
+  </h2>
+  <ReuseList
+    :page-size="pageSize"
+    :reuses="reuses"
+    :loading="loading"
+    :total-results="totalResults"
+    @change-page="loadPage"
+  />
+</template>
+
+<script lang="ts">
+import type { Reuse } from '../../types';
+
+export type UserReuseListProps = {
+  owner: string,
+};
+</script>
+
+<script setup lang="ts">
+import axios, { type CancelTokenSource } from 'axios';
+import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useToast } from '../../composables/useToast';
+import { apiv2, generateCancelToken } from '../../plugins/api';
+import ReuseList from '../Reuse/ReuseList.vue';
+
+const { t } = useI18n();
+const toast = useToast();
+
+const props = defineProps<UserReuseListProps>();
+
+const reuses = ref<Array<Reuse>>([]);
+const totalResults = ref(0);
+const loading = ref(false);
+
+/**
+ * Current request to cancel before a new one comes
+ */
+const currentRequest = ref<CancelTokenSource | null>(null);
+
+/** TODO : use a config when the component moved to data.gouv.fr-components */
+const pageSize = 6;
+
+/**
+ * Load new reuses
+ */
+function loadPage (page: number) {
+  loading.value = true;
+  if (currentRequest.value) currentRequest.value.cancel();
+  currentRequest.value = generateCancelToken();
+  apiv2
+    .get("/reuses/search/", {
+      cancelToken: currentRequest.value.token,
+      params: {
+        page,
+        owner: props.owner,
+        page_size: pageSize,
+      },
+    })
+    .then((res) => res.data)
+    .then((result) => {
+      reuses.value = result.data;
+      totalResults.value = result.total;
+      loading.value = false;
+    })
+    .catch((error) => {
+      if (!axios.isCancel(error)) {
+        toast.error(t("Error getting search results."));
+        loading.value = false;
+      }
+    });
+};
+
+onMounted(() => {
+  loadPage(1);
+});
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.js b/udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.ts
similarity index 52%
rename from udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.js
rename to udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.ts
index ad690c28a..d11979f29 100644
--- a/udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.js
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardLG.stories.ts
@@ -1,8 +1,10 @@
-import { CERTIFIED, PUBLIC_SERVICE } from '../../composables/useOrganizationCertified';
+import { CERTIFIED, PUBLIC_SERVICE, type Organization, type User } from '@etalab/data.gouv.fr-components';
 import CardLG from './card-lg.vue';
 import { withActions } from '@storybook/addon-actions/decorator';
+import { Meta, StoryObj } from '@storybook/vue3';
+import { Dataset } from '../../types';
 
-export default {
+const meta = {
   title: 'Components/Dataset Card/Card Large',
   component: CardLG,
   parameters: {
@@ -11,44 +13,68 @@ export default {
     },
   },
   decorators: [withActions],
+} satisfies Meta<typeof CardLG>;
+
+export default meta;
+
+type CardLGProps = {
+  readonly dataset: Dataset,
+  showMetrics?: boolean | undefined,
+  style?: Record<string, any> | undefined,
 };
 
-/**
- * @typedef {{
- *  [Property in keyof import("../../types").NewDataset
- *    as Extract<Property, "acronym" | "archived" | "description" | "last_update" | "license" | "page" | "private" | "quality" | "title" | "owner" | "organization">
- *  ]: import("../../types").NewDataset[Property]
- * } & {metrics?: Object, showMetrics: Boolean, style?: Object}} CardLGProps
- */
+const owner: User = {
+  id: "someId",
+  first_name: "Jean",
+  last_name: "Dupond",
+  page: "http://www.data.gouv.fr",
+};
 
-/** @type {CardLGProps} */
-const args = {
-  acronym: "",
-  archived: false,
-  description: "Some description",
-  last_update: new Date(),
-  license: "lov2",
-  page: "https://www.data.gouv.fr",
-  private: false,
-  quality: {
-    all_resources_available: true,
-    dataset_description_quality: true,
-    has_open_format: true,
-    has_resources: true,
-    license: true,
-    resources_documentation: true,
-    score: 1,
-    spatial: true,
-    temporal_coverage: true,
-    update_frequency: true,
-    update_fulfilled_in_time: true,
+const args: CardLGProps = {
+  dataset: {
+    id: "someId",
+    acronym: "",
+    archived: false,
+    description: "Some description",
+    last_update: (new Date()).toDateString(),
+    license: "lov2",
+    page: "https://www.data.gouv.fr",
+    private: false,
+    quality: {
+      all_resources_available: true,
+      dataset_description_quality: true,
+      has_open_format: true,
+      has_resources: true,
+      license: true,
+      resources_documentation: true,
+      score: 1,
+      spatial: true,
+      temporal_coverage: true,
+      update_frequency: true,
+      update_fulfilled_in_time: true,
+    },
+    title: "My new dataset",
+    resources: [],
+    community_resources: [],
+    created_at: "2014-12-12T12:01:06.889000+00:0",
+    last_modified: "2020-11-03T09:16:55.837000+00:00",
+    uri: "https://www.data.gouv.fr",
+    slug: "data-gouv-fr",
+    tags: [],
+    frequency: "Unknown",
+    temporal_coverage: "Test",
+    frequency_date: null,
+    spatial: null,
+    organization: null,
+    owner,
+    metrics: {
+      discussions: 20,
+      reuses: 100,
+      followers: 500,
+      views: 50000
+    },
   },
-  title: "My new dataset",
   showMetrics: true,
-  metrics: {
-    reuses: 100,
-    favorites: 500
-  }
 };
 
 const updateLastMonth = new Date();
@@ -57,81 +83,88 @@ updateLastMonth.setMonth(updateLastMonth.getMonth() - 1, 20);
 const updateLastYear = new Date();
 updateLastYear.setFullYear(updateLastYear.getFullYear() - 1);
 
-/** @type {import('../../types').User} */
-const owner = {
-  first_name: "Jean",
-  last_name: "Dupond",
-};
-
-/** @type {import('../../types').User} */
-const ownerWithAvatar = {
+const ownerWithAvatar: User = {
   ...owner,
   avatar_thumbnail: "https://static.data.gouv.fr/avatars/0d/aebc1b126b410aa73701fde285dd76-100.jpg"
-}
+};
 
-/** @type {import('../../types').Organization} */
-const organization = {
+const organization: Organization = {
+  id: "someId",
+  acronym: null,
   name: "My Organization",
   badges: [],
-  page: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/"
+  page: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  uri: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  slug: "data-gouv-fr",
+  logo: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a.jpeg",
+  logo_thumbnail: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a-100.jpeg",
 };
 
-/** @type {import('../../types').Organization} */
-const organizationWithLogo = {
+const organizationWithLogo: Organization = {
   ...organization,
   logo_thumbnail: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a-100.jpeg"
 };
 
-/** @type {CardLGProps} */
-const argsWithOwner = {
+const argsWithOwnerWithAvatar: CardLGProps = {
   ...args,
-  owner,
+  dataset: {
+    ...args.dataset,
+    owner: ownerWithAvatar,
+    organization: null,
+  },
 };
 
-/** @type {CardLGProps} */
-const argsWithOwnerWithAvatar = {
+const argsWithOrganization: CardLGProps = {
   ...args,
-  owner: ownerWithAvatar,
+  dataset: {
+    ...args.dataset,
+    organization: organization,
+    owner: null,
+  },
 };
 
-/** @type {CardLGProps} */
-const argsWithOrganization = {
+const argsWithOrganizationWithLogo: CardLGProps = {
   ...args,
-  organization: organization,
+  dataset: {
+    ...args.dataset,
+    organization: organizationWithLogo,
+    owner: null,
+  },
 };
 
-/** @type {CardLGProps} */
-const argsWithOrganizationWithLogo = {
+const argsWithAcronymAndOrganizationWithLogo: CardLGProps = {
   ...args,
-  organization: organizationWithLogo,
-};
-
-/** @type {CardLGProps} */
-const argsWithAcronymAndOrganizationWithLogo = {
-  ...argsWithOrganizationWithLogo,
-  acronym: "MND"
+  dataset: {
+  ...argsWithOrganizationWithLogo.dataset,
+  acronym: "MND",
+  },
 };
 
-/** @type {CardLGProps} */
-const argsWithAcronymAndOwnerWithAvatar = {
-  ...argsWithOwnerWithAvatar,
-  acronym: "MND"
+const argsWithAcronymAndOwnerWithAvatar: CardLGProps = {
+  ...args,
+  dataset: {
+    ...argsWithOwnerWithAvatar.dataset,
+    acronym: "MND",
+  },
 };
 
-/** @type {CardLGProps} */
-const argsWithCertifiedOrganization = {
-  ...argsWithAcronymAndOrganizationWithLogo,
-  organization: {
-    ...organizationWithLogo,
-    badges: [{kind: PUBLIC_SERVICE}, {kind: CERTIFIED}]
-  },
+const argsWithCertifiedOrganization: CardLGProps = {
+  ...args,
+  dataset: {
+    ...argsWithAcronymAndOrganizationWithLogo.dataset,
+    organization: {
+      ...organizationWithLogo,
+      badges: [{kind: PUBLIC_SERVICE}, {kind: CERTIFIED}]
+    },
+    owner: null,
+  }
 };
 
 /**
  * #### Card Large with an organization logo
  * This is how a dataset owned by an organization is shown.
  */
-export const CardLGWithOrganizationLogo = {
+export const CardLGWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -146,7 +179,7 @@ export const CardLGWithOrganizationLogo = {
  * #### Card Large with a certified organization
  * This is how a dataset owned by a certified organization is shown.
  */
-export const CardLGWithCertifiedOrganization = {
+export const CardLGWithCertifiedOrganization: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -161,7 +194,7 @@ export const CardLGWithCertifiedOrganization = {
  * #### Card Large with an acronym and an organization logo
  * This is how a dataset owned by an organization is shown.
  */
-export const CardLGWithAcronymAndOrganizationLogo = {
+export const CardLGWithAcronymAndOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -176,7 +209,7 @@ export const CardLGWithAcronymAndOrganizationLogo = {
  * #### Card Large with a description and a certified organization
  * This is how a dataset with a really long description is shown.
  */
-export const CardLGWithDescriptionAndCertifiedOrganization = {
+export const CardLGWithDescriptionAndCertifiedOrganization: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -186,23 +219,26 @@ export const CardLGWithDescriptionAndCertifiedOrganization = {
   }),
   args: {
     ...argsWithCertifiedOrganization,
-    description: `Le plan cadastral est le découpage du territoire français en unités de surfaces permettant le calcul de certains impôts.
+    dataset: {
+      ...argsWithCertifiedOrganization.dataset,
+      description: `Le plan cadastral est le découpage du territoire français en unités de surfaces permettant le calcul de certains impôts.
 
-    Chaque commune est subdivisée en sections, elles-mêmes subdivisées en feuilles (ou planches).
-    Une feuille cadastrale comporte des parcelles, qui peuvent supporter des bâtiments.
+        Chaque commune est subdivisée en sections, elles-mêmes subdivisées en feuilles (ou planches).
+        Une feuille cadastrale comporte des parcelles, qui peuvent supporter des bâtiments.
 
-    Différence par rapport au PCI Vecteur
-    Contrairement au Plan Cadastral Informatisé au format EDIGÉO qui est un ensemble de 600 000 feuilles cadastrales avec de nombreux éléments liés à la fiscalité ou à l'habillage du plan, cette version retravaillée par Etalab se concentre sur le découpage parcellaire et sur les bâtiments.
+        Différence par rapport au PCI Vecteur
+        Contrairement au Plan Cadastral Informatisé au format EDIGÉO qui est un ensemble de 600 000 feuilles cadastrales avec de nombreux éléments liés à la fiscalité ou à l'habillage du plan, cette version retravaillée par Etalab se concentre sur le découpage parcellaire et sur les bâtiments.
 
-    Les différentes couches d'information géographique sont constituées par juxtaposition des feuilles, sans correction.`
-  },
+        Les différentes couches d'information géographique sont constituées par juxtaposition des feuilles, sans correction.`
+      },
+    }
 };
 
 /**
  * #### Card Large updated last month with an organization logo
  * This is how a dataset owned by an organization is shown.
  */
-export const CardLGUpdatedLastMonthWithOrganizationLogo = {
+export const CardLGUpdatedLastMonthWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -212,7 +248,10 @@ export const CardLGUpdatedLastMonthWithOrganizationLogo = {
   }),
   args: {
     ...argsWithOrganizationWithLogo,
-    last_update: updateLastMonth
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      last_update: updateLastMonth.toISOString()
+    }
   },
 };
 
@@ -220,7 +259,7 @@ export const CardLGUpdatedLastMonthWithOrganizationLogo = {
  * #### Card Large updated last year with an organization logo
  * This is how a dataset owned by an organization is shown.
  */
-export const CardLGUpdatedLastYearWithOrganizationLogo = {
+export const CardLGUpdatedLastYearWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -230,7 +269,10 @@ export const CardLGUpdatedLastYearWithOrganizationLogo = {
   }),
   args: {
     ...argsWithOrganizationWithLogo,
-    last_update: updateLastYear
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      last_update: updateLastYear.toISOString()
+    }
   },
 };
 
@@ -238,7 +280,7 @@ export const CardLGUpdatedLastYearWithOrganizationLogo = {
  * #### Card Large without metrics with an organization logo
  * This is how a dataset without metrics is shown.
  */
-export const CardLGWithoutMetricsWithOrganizationLogo = {
+export const CardLGWithoutMetricsWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -256,7 +298,7 @@ export const CardLGWithoutMetricsWithOrganizationLogo = {
  * #### Card Large for a private dataset with an organization logo
  * This is how a private dataset owned by an organization is shown.
  */
-export const CardLGPrivateWithOrganizationLogo = {
+export const CardLGPrivateWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -266,7 +308,10 @@ export const CardLGPrivateWithOrganizationLogo = {
   }),
   args: {
     ...argsWithOrganizationWithLogo,
-    private: true
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      private: true,
+    },
   },
 };
 
@@ -274,7 +319,7 @@ export const CardLGPrivateWithOrganizationLogo = {
  * #### Card Large for an archived dataset with organization logo
  * This is how an archived dataset owned by an organization is shown.
  */
-export const CardLGArchivedWithOrganizationLogo = {
+export const CardLGArchivedWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -284,7 +329,10 @@ export const CardLGArchivedWithOrganizationLogo = {
   }),
   args: {
     ...argsWithOrganizationWithLogo,
-    archived: true
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      archived: true,
+    },
   },
 };
 
@@ -292,7 +340,7 @@ export const CardLGArchivedWithOrganizationLogo = {
  * #### Card Large for a private archived dataset with organization logo
  * This is how a private archived dataset owned by an organization is shown.
  */
-export const CardLGPrivateArchivedWithOrganizationLogo = {
+export const CardLGPrivateArchivedWithOrganizationLogo: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -302,8 +350,11 @@ export const CardLGPrivateArchivedWithOrganizationLogo = {
   }),
   args: {
     ...argsWithOrganizationWithLogo,
-    archived: true,
-    private: true,
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      archived: true,
+      private: true,
+    },
   },
 };
 
@@ -311,7 +362,7 @@ export const CardLGPrivateArchivedWithOrganizationLogo = {
  * #### Card Large with owner avatar
  * This is how a dataset with an owner is shown.
  */
-export const CardLGWithOwnerAvatar = {
+export const CardLGWithOwnerAvatar: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -326,7 +377,7 @@ export const CardLGWithOwnerAvatar = {
  * #### Card Large with acronym and owner avatar
  * This is how a dataset with an owner is shown.
  */
-export const CardLGWithAcronymAndOwnerWithAvatar = {
+export const CardLGWithAcronymAndOwnerWithAvatar: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -341,7 +392,7 @@ export const CardLGWithAcronymAndOwnerWithAvatar = {
  * #### Card Large for a private dataset with owner avatar
  * This is how a private dataset with an owner is shown.
  */
-export const CardLGPrivateWithOwnerAvatar = {
+export const CardLGPrivateWithOwnerAvatar: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -351,7 +402,10 @@ export const CardLGPrivateWithOwnerAvatar = {
   }),
   args: {
     ...argsWithOwnerWithAvatar,
-    private: true
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      private: true,
+    }
   },
 };
 
@@ -359,7 +413,7 @@ export const CardLGPrivateWithOwnerAvatar = {
  * #### Card Large for an archived dataset with owner avatar
  * This is how an archived dataset with an owner is shown.
  */
-export const CardLGArchivedWithOwnerAvatar = {
+export const CardLGArchivedWithOwnerAvatar: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -369,7 +423,10 @@ export const CardLGArchivedWithOwnerAvatar = {
   }),
   args: {
     ...argsWithOwnerWithAvatar,
-    archived: true
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      archived: true,
+    }
   },
 };
 
@@ -377,7 +434,7 @@ export const CardLGArchivedWithOwnerAvatar = {
  * #### Card Large for a private archived dataset with owner avatar
  * This is how a private archived dataset with an owner is shown.
  */
-export const CardLGPrivateArchivedWithOwnerAvatar = {
+export const CardLGPrivateArchivedWithOwnerAvatar: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -387,8 +444,11 @@ export const CardLGPrivateArchivedWithOwnerAvatar = {
   }),
   args: {
     ...argsWithOwnerWithAvatar,
-    archived: true,
-    private: true,
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      archived: true,
+      private: true,
+    }
   },
 };
 
@@ -396,7 +456,7 @@ export const CardLGPrivateArchivedWithOwnerAvatar = {
  * #### Card Large with organization placeholder
  * This is how a dataset owned by an organization is shown.
  */
-export const CardLGWithOrganizationPlaceholder = {
+export const CardLGWithOrganizationPlaceholder: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -411,7 +471,7 @@ export const CardLGWithOrganizationPlaceholder = {
  * #### Card Large with owner placeholder
  * This is how a dataset with an owner is shown. When the user doesn't have an avatar, udata computes an identicon.
  */
-export const CardLGWithOwnerPlaceholder = {
+export const CardLGWithOwnerPlaceholder: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -419,7 +479,7 @@ export const CardLGWithOwnerPlaceholder = {
     },
     template: '<CardLG v-bind="args"/>',
   }),
-  args: argsWithOwner,
+  args,
 };
 
 /**
@@ -427,7 +487,7 @@ export const CardLGWithOwnerPlaceholder = {
  * This is how a dataset without owner is shown.
  * It shouldn't exist based on API requirement but it's a `else` path for the card.
  */
-export const CardLGWithDatasetPlaceholder = {
+export const CardLGWithDatasetPlaceholder: StoryObj<typeof meta> = {
   render: (args) => ({
     components: { CardLG },
     setup() {
@@ -435,5 +495,13 @@ export const CardLGWithDatasetPlaceholder = {
     },
     template: '<CardLG v-bind="args"/>',
   }),
-  args,
+  args: {
+    ...args,
+    // @ts-ignore
+    dataset: {
+      ...args.dataset,
+      owner: null,
+      organization: null,
+    }
+  },
 };
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.mdx b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.mdx
new file mode 100644
index 000000000..f35dc9b8f
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.mdx
@@ -0,0 +1,108 @@
+import { Canvas, Controls, Description, Meta } from '@storybook/blocks';
+
+import * as CardMDStories from './CardMD.stories';
+
+<Meta of={CardMDStories} />
+
+# Card Medium
+
+A medium card to show a dataset. It's usually used in a list but it also work alone.
+It contains all informations except the quality score and metrics.
+
+<Canvas of={CardMDStories.CardMDWithOrganizationLogo} />
+
+## Props
+
+<Controls />
+
+## Stories
+
+There are lots of differents cases here based on the dataset owner and all the optional fields.
+
+### Cards with logo
+
+The card logo is the organization logo or the owner avatar.
+
+<Description of={CardMDStories.CardMDWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDWithDescriptionAndCertifiedOrganization} />
+<Canvas of={CardMDStories.CardMDWithDescriptionAndCertifiedOrganization} />
+
+<Description of={CardMDStories.CardMDWithOwnerAvatar} />
+<Canvas of={CardMDStories.CardMDWithOwnerAvatar} />
+
+### Cards of dataset belonging to an organization
+
+Public organizations should be certified while non-public organizations can't be.
+
+<Description of={CardMDStories.CardMDWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDWithCertifiedOrganization} />
+<Canvas of={CardMDStories.CardMDWithCertifiedOrganization} />
+
+### Cards with different update dates
+
+<Description of={CardMDStories.CardMDWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDUpdatedLastMonthWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDUpdatedLastMonthWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDUpdatedLastYearWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDUpdatedLastYearWithOrganizationLogo} />
+
+### Cards with Acronym
+
+The card logo is the organization logo or the owner avatar.
+
+<Description of={CardMDStories.CardMDWithAcronymAndOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDWithAcronymAndOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDWithAcronymAndOwnerWithAvatar} />
+<Canvas of={CardMDStories.CardMDWithAcronymAndOwnerWithAvatar} />
+
+### Archived dataset
+
+An archived dataset has a badge on it.
+
+<Description of={CardMDStories.CardMDArchivedWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDArchivedWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDArchivedWithOwnerAvatar} />
+<Canvas of={CardMDStories.CardMDArchivedWithOwnerAvatar} />
+
+### Private dataset
+
+A private dataset has another badge on it.
+
+<Description of={CardMDStories.CardMDPrivateWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDPrivateWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDPrivateWithOwnerAvatar} />
+<Canvas of={CardMDStories.CardMDPrivateWithOwnerAvatar} />
+
+### Archived private dataset
+
+An archived private dataset has 2 badges on it.
+
+<Description of={CardMDStories.CardMDPrivateArchivedWithOrganizationLogo} />
+<Canvas of={CardMDStories.CardMDPrivateArchivedWithOrganizationLogo} />
+
+<Description of={CardMDStories.CardMDPrivateArchivedWithOwnerAvatar} />
+<Canvas of={CardMDStories.CardMDPrivateArchivedWithOwnerAvatar} />
+
+### Cards with placeholder
+
+When the organization doesn't have a logo, the dataset placeholder is shown.
+For an owner, the avatar is used or an identicon is computed.
+
+<Description of={CardMDStories.CardMDWithOrganizationPlaceholder} />
+<Canvas of={CardMDStories.CardMDWithOrganizationPlaceholder} />
+
+<Description of={CardMDStories.CardMDWithOwnerPlaceholder} />
+<Canvas of={CardMDStories.CardMDWithOwnerPlaceholder} />
+
+<Description of={CardMDStories.CardMDWithDatasetPlaceholder} />
+<Canvas of={CardMDStories.CardMDWithDatasetPlaceholder} />
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.stories.ts b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.stories.ts
new file mode 100644
index 000000000..36cbadf60
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.stories.ts
@@ -0,0 +1,469 @@
+import { CERTIFIED, PUBLIC_SERVICE, type Organization, type User } from '@etalab/data.gouv.fr-components';
+import CardMD, { type CardMDProps } from './CardMD.vue';
+import { withActions } from '@storybook/addon-actions/decorator';
+import type { Meta, StoryObj } from "@storybook/vue3";
+
+const meta = {
+  title: 'Components/Dataset Card/Card Medium',
+  component: CardMD,
+  parameters: {
+    actions: {
+      handles: ['click a'],
+    },
+  },
+  decorators: [withActions],
+} satisfies Meta<typeof CardMD>;
+
+export default meta;
+
+const owner: User = {
+  id: "someId",
+  first_name: "Jean",
+  last_name: "Dupond",
+  page: "http://www.data.gouv.fr",
+};
+
+const args: CardMDProps = {
+  dataset: {
+    id: "someId",
+    acronym: "",
+    archived: false,
+    description: "Some description",
+    last_update: (new Date()).toDateString(),
+    license: "lov2",
+    page: "https://www.data.gouv.fr",
+    private: false,
+    quality: {
+      all_resources_available: true,
+      dataset_description_quality: true,
+      has_open_format: true,
+      has_resources: true,
+      license: true,
+      resources_documentation: true,
+      score: 1,
+      spatial: true,
+      temporal_coverage: true,
+      update_frequency: true,
+      update_fulfilled_in_time: true,
+    },
+    resources: [],
+    community_resources: [],
+    created_at: "2014-12-12T12:01:06.889000+00:0",
+    last_modified: "2020-11-03T09:16:55.837000+00:00",
+    uri: "https://www.data.gouv.fr",
+    slug: "data-gouv-fr",
+    tags: [],
+    frequency: "Unknown",
+    metrics: {discussions: 3, followers: 5, reuses: 9, views: 115},
+    temporal_coverage: "Test",
+    frequency_date: null,
+    spatial: null,
+    organization: null,
+    owner,
+    title: "My new dataset",
+  }
+};
+
+const updateLastMonth = new Date();
+updateLastMonth.setMonth(updateLastMonth.getMonth() - 1, 20);
+
+const updateLastYear = new Date();
+updateLastYear.setFullYear(updateLastYear.getFullYear() - 1);
+
+const ownerWithAvatar: User = {
+  ...owner,
+  avatar_thumbnail: "https://static.data.gouv.fr/avatars/0d/aebc1b126b410aa73701fde285dd76-100.jpg",
+}
+
+const organization: Organization = {
+  id: "someId",
+  acronym: null,
+  name: "My Organization",
+  badges: [],
+  page: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  uri: "https://www.data.gouv.fr/fr/organizations/data-gouv-fr/",
+  slug: "data-gouv-fr",
+  logo: "",
+  logo_thumbnail: "",
+};
+
+const organizationWithLogo: Organization = {
+  ...organization,
+  logo: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a.jpeg",
+  logo_thumbnail: "https://static.data.gouv.fr/avatars/09/1ba932cbfa48dc8c158981de6c700a-100.jpeg"
+};
+
+const argsWithOwner: CardMDProps = {
+  dataset: {
+    ...args.dataset,
+    owner,
+    organization: null
+  },
+};
+
+const argsWithOwnerWithAvatar: CardMDProps = {
+  dataset: {
+    ...args.dataset,
+    owner: ownerWithAvatar,
+    organization: null,
+  }
+};
+
+const argsWithOrganization: CardMDProps = {
+  dataset: {
+    ...args.dataset,
+    organization: organization,
+    owner: null,
+  }
+};
+
+const argsWithOrganizationWithLogo: CardMDProps = {
+  dataset: {
+    ...args.dataset,
+    organization: organizationWithLogo,
+    owner: null,
+  }
+};
+
+const argsWithAcronymAndOrganizationWithLogo: CardMDProps = {
+  dataset: {
+    ...argsWithOrganizationWithLogo.dataset,
+    acronym: "MND"
+  }
+};
+
+const argsWithAcronymAndOwnerWithAvatar: CardMDProps = {
+  dataset: {
+    ...argsWithOwnerWithAvatar.dataset,
+    acronym: "MND"
+  }
+};
+
+const argsWithCertifiedOrganization: CardMDProps = {
+  dataset: {
+    ...argsWithAcronymAndOrganizationWithLogo.dataset,
+    organization: {
+      ...organizationWithLogo,
+      badges: [{kind: PUBLIC_SERVICE}, {kind: CERTIFIED}]
+    },
+    owner: null
+  }
+};
+
+/**
+ * #### Card Medium with an organization logo
+ * This is how a dataset owned by an organization is shown.
+ */
+export const CardMDWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithOrganizationWithLogo,
+};
+
+/**
+ * #### Card Medium with a certified organization
+ * This is how a dataset owned by a certified organization is shown.
+ */
+export const CardMDWithCertifiedOrganization: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithCertifiedOrganization,
+};
+
+/**
+ * #### Card Medium with an acronym and an organization logo
+ * This is how a dataset owned by an organization is shown.
+ */
+export const CardMDWithAcronymAndOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithAcronymAndOrganizationWithLogo,
+};
+
+/**
+ * #### Card Medium with a description and a certified organization
+ * This is how a dataset with a really long description is shown.
+ */
+export const CardMDWithDescriptionAndCertifiedOrganization: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithCertifiedOrganization.dataset,
+    description: `Le plan cadastral est le découpage du territoire français en unités de surfaces permettant le calcul de certains impôts.
+
+    Chaque commune est subdivisée en sections, elles-mêmes subdivisées en feuilles (ou planches).
+    Une feuille cadastrale comporte des parcelles, qui peuvent supporter des bâtiments.
+
+    Différence par rapport au PCI Vecteur
+    Contrairement au Plan Cadastral Informatisé au format EDIGÉO qui est un ensemble de 600 000 feuilles cadastrales avec de nombreux éléments liés à la fiscalité ou à l'habillage du plan, cette version retravaillée par Etalab se concentre sur le découpage parcellaire et sur les bâtiments.
+
+    Les différentes couches d'information géographique sont constituées par juxtaposition des feuilles, sans correction.`
+    }
+  },
+};
+
+/**
+ * #### Card Medium updated last month with an organization logo
+ * This is how a dataset owned by an organization is shown.
+ */
+export const CardMDUpdatedLastMonthWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      last_update: updateLastMonth.toDateString(),
+    }
+  },
+};
+
+/**
+ * #### Card Medium updated last year with an organization logo
+ * This is how a dataset owned by an organization is shown.
+ */
+export const CardMDUpdatedLastYearWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      last_update: updateLastYear.toDateString(),
+    }
+  },
+};
+
+/**
+ * #### Card Medium for a private dataset with an organization logo
+ * This is how a private dataset owned by an organization is shown.
+ */
+export const CardMDPrivateWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      private: true,
+    },
+  },
+};
+
+/**
+ * #### Card Medium for an archived dataset with organization logo
+ * This is how an archived dataset owned by an organization is shown.
+ */
+export const CardMDArchivedWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      archived: true,
+    },
+  },
+};
+
+/**
+ * #### Card Medium for a private archived dataset with organization logo
+ * This is how a private archived dataset owned by an organization is shown.
+ */
+export const CardMDPrivateArchivedWithOrganizationLogo: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOrganizationWithLogo.dataset,
+      archived: true,
+      private: true,
+    },
+  },
+};
+
+/**
+ * #### Card Medium with owner avatar
+ * This is how a dataset with an owner is shown.
+ */
+export const CardMDWithOwnerAvatar: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithOwnerWithAvatar,
+};
+
+/**
+ * #### Card Medium with acronym and owner avatar
+ * This is how a dataset with an owner is shown.
+ */
+export const CardMDWithAcronymAndOwnerWithAvatar: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithAcronymAndOwnerWithAvatar,
+};
+
+/**
+ * #### Card Medium for a private dataset with owner avatar
+ * This is how a private dataset with an owner is shown.
+ */
+export const CardMDPrivateWithOwnerAvatar: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      private: true,
+    },
+  },
+};
+
+/**
+ * #### Card Medium for an archived dataset with owner avatar
+ * This is how an archived dataset with an owner is shown.
+ */
+export const CardMDArchivedWithOwnerAvatar: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      archived: true,
+    }
+  },
+};
+
+/**
+ * #### Card Medium for a private archived dataset with owner avatar
+ * This is how a private archived dataset with an owner is shown.
+ */
+export const CardMDPrivateArchivedWithOwnerAvatar: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    dataset: {
+      ...argsWithOwnerWithAvatar.dataset,
+      archived: true,
+      private: true,
+    },
+  },
+};
+
+/**
+ * #### Card Medium with organization placeholder
+ * This is how a dataset owned by an organization is shown.
+ */
+export const CardMDWithOrganizationPlaceholder: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithOrganization,
+};
+
+/**
+ * #### Card Medium with owner placeholder
+ * This is how a dataset with an owner is shown. When the user doesn't have an avatar, udata computes an identicon.
+ */
+export const CardMDWithOwnerPlaceholder: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: argsWithOwner,
+};
+
+/**
+ * #### Card Medium with dataset placeholder
+ * This is how a dataset without owner is shown.
+ * It shouldn't exist based on API requirement but it's a `else` path for the card.
+ */
+export const CardMDWithDatasetPlaceholder: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMD },
+    setup() {
+      return { args };
+    },
+    template: '<CardMD v-bind="args"/>',
+  }),
+  args: {
+    // @ts-ignore
+    dataset: {
+      ...args.dataset,
+      owner: null,
+      organization: null,
+    }
+  },
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.vue b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.vue
new file mode 100644
index 000000000..388316c8e
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMD.vue
@@ -0,0 +1,91 @@
+<template>
+  <article class="fr-my-3w fr-p-3w border border-default-grey fr-enlarge-link" :style="style">
+    <div class="absolute top-0 fr-grid-row fr-grid-row--middle fr-mt-n3v" v-if="dataset.private || dataset.archived">
+      <p class="fr-badge fr-badge--mention-grey fr-mr-1w" v-if="dataset.private">
+        <span class="fr-icon-lock-line" aria-hidden="true"></span>
+        {{ t('Private') }}
+      </p>
+      <p class="fr-badge fr-badge--mention-grey" v-if="dataset.archived">
+        <span class="fr-icon-archive-line" aria-hidden="true"></span>
+        {{ t('Archived') }}
+      </p>
+    </div>
+    <div class="fr-grid-row fr-grid-row--gutters fr-grid-row--top">
+      <div class="fr-col-auto">
+        <div class="logo">
+          <Placeholder
+            v-if="dataset.organization"
+            type="dataset"
+            :src="dataset.organization.logo_thumbnail"
+            :alt="dataset.organization.name"
+            :size="60"
+          />
+          <Avatar
+            v-else-if="dataset.owner"
+            :user="dataset.owner"
+            :size="60"
+          />
+          <Placeholder
+            v-else
+            type="dataset"
+            :size="60"
+          />
+        </div>
+      </div>
+      <div class="fr-col">
+        <h4 class="fr-mb-1v fr-grid-row">
+          <a :href="dataset.page" class="text-grey-500">
+            {{ dataset.title }}
+            <small v-if="dataset.acronym">{{ dataset.acronym }}</small>
+          </a>
+        </h4>
+        <p class="fr-m-0 fr-text--sm" v-if="dataset.organization || dataset.owner">
+          {{ t('From') }}
+          <span class="not-enlarged" v-if="dataset.organization">
+            <a class="fr-link" :href="dataset.organization.page">
+              <OrganizationNameWithCertificate :organization="dataset.organization" />
+            </a>
+          </span>
+          <span class="not-enlarged" v-else>
+            <a class="fr-link" :href="dataset.owner.page">
+              {{ ownerName }}
+            </a>
+          </span>
+        </p>
+        <p class="fr-my-3v fr-hidden fr-unhidden-sm overflow-wrap-anywhere">
+          {{ excerpt(dataset.description, 500) }}
+        </p>
+        <div class="fr-m-0 fr-grid-row fr-grid-row--middle fr-text--sm text-mention-grey">
+          <p class="fr-m-0">{{ t('Updated {date}', {date: formatRelativeIfRecentDate(dataset.last_update)}) }}</p>
+        </div>
+      </div>
+    </div>
+  </article>
+</template>
+
+<script lang="ts">
+import { Dataset } from "../../types";
+export type CardMDProps = {
+  dataset: Dataset,
+  style?: CSSProperties,
+};
+</script>
+
+<script setup lang="ts">
+import Avatar from "../discussions/Avatar/Avatar.vue";
+import Placeholder from "../utils/placeholder.vue";
+import { excerpt } from "../../helpers";
+import { useI18n } from "vue-i18n";
+import { type CSSProperties } from "vue";
+import { formatRelativeIfRecentDate, useOwnerName, OrganizationNameWithCertificate } from "@etalab/data.gouv.fr-components";
+
+defineOptions({inheritAttrs: false});
+
+const props = withDefaults(defineProps<CardMDProps>(), {
+  style: () => ({})
+});
+
+const { t } = useI18n();
+
+const ownerName = useOwnerName(props.dataset);
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.mdx b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.mdx
new file mode 100644
index 000000000..7dcba2ef8
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.mdx
@@ -0,0 +1,15 @@
+import { Canvas, Controls, Meta } from '@storybook/blocks';
+
+import * as CardMDListStories from './CardMDList.stories';
+
+<Meta of={CardMDListStories} />
+
+# Dataset Card Medium List
+
+A paginated list of dataset cards.
+
+<Canvas of={CardMDListStories.DefaultCardMDList} />
+
+## Props
+
+<Controls />
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.stories.ts b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.stories.ts
new file mode 100644
index 000000000..a8d322c12
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.stories.ts
@@ -0,0 +1,83 @@
+import CardMDList, { type CardMDListProps } from './CardMDList.vue';
+import { withActions } from '@storybook/addon-actions/decorator';
+import type { Meta, StoryObj } from "@storybook/vue3";
+import { CardMDWithCertifiedOrganization } from './CardMD.stories';
+
+const meta = {
+  title: 'Lists/Dataset Card List',
+  component: CardMDList,
+  parameters: {
+    actions: {
+      handles: ['click a'],
+    },
+  },
+  decorators: [withActions],
+} satisfies Meta<typeof CardMDList>;
+
+export default meta;
+
+const dataset = {
+  ...CardMDWithCertifiedOrganization.args.dataset,
+};
+
+const args: CardMDListProps = {
+  datasets: [dataset, dataset, dataset],
+  totalResults: 3,
+};
+
+const paginatedArgs: CardMDListProps = {
+  datasets: [dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset,
+    dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset, dataset],
+  totalResults: 25,
+};
+
+const customPaginationArgs: CardMDListProps = {
+  datasets: [dataset, dataset],
+  pageSize: 2,
+  totalResults: 3,
+};
+
+/**
+ * #### Card Medium List
+ * A list of dataset cards.
+ */
+export const DefaultCardMDList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMDList },
+    setup() {
+      return { args };
+    },
+    template: '<CardMDList v-bind="args"/>',
+  }),
+  args,
+};
+
+/**
+ * #### Card Medium List With Pagination
+ * A list of dataset cards.
+ */
+export const PaginatedCardMDList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMDList },
+    setup() {
+      return { args };
+    },
+    template: '<CardMDList v-bind="args"/>',
+  }),
+  args: paginatedArgs,
+};
+
+/**
+ * #### Card Medium List With Custom Page Size
+ * A list of dataset cards with custom page size.
+ */
+export const CustomPageSizeCardMDList: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { CardMDList },
+    setup() {
+      return { args };
+    },
+    template: '<CardMDList v-bind="args"/>',
+  }),
+  args: customPaginationArgs,
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.vue b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.vue
new file mode 100644
index 000000000..8527cb936
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/CardMDList.vue
@@ -0,0 +1,66 @@
+<template>
+  <div v-if="loading">
+    <Loader />
+  </div>
+  <template v-else-if="totalResults">
+    <ul ref="listRef">
+      <li v-for="dataset in datasets" :key="dataset.id">
+        <CardMD :dataset="dataset" />
+      </li>
+    </ul>
+    <Pagination
+      v-if="totalResults > pageSize"
+      :page="currentPage"
+      :pageSize="pageSize"
+      :totalResults="totalResults"
+      :changePage="changePage"
+      class="fr-mt-2w"
+    />
+  </template>
+</template>
+
+<script lang="ts">
+import type { Dataset } from '../../types';
+export type CardMDListProps = {
+  datasets: Array<Dataset>,
+  loading?: boolean,
+  totalResults: number,
+  pageSize?: number,
+};
+
+/** TODO : use a config when the component moved to data.gouv.fr-components */
+const defaultPageSize = 20;
+</script>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { Pagination } from '@etalab/udata-front-plugins-helper';
+import Loader from "./loader.vue";
+import CardMD from './CardMD.vue';
+
+withDefaults(defineProps<CardMDListProps>(), {
+  loading: false,
+  pageSize: defaultPageSize,
+});
+
+const emit = defineEmits<{
+  (event: 'changePage', page: number): void,
+}>();
+
+const currentPage = ref(1);
+
+const listRef = ref<HTMLElement | undefined>();
+
+function scrollToTop () {
+  if (listRef.value) {
+    listRef.value.scrollIntoView({ behavior: "smooth" });
+  }
+};
+
+function changePage (page: number) {
+  currentPage.value = page;
+  emit("changePage", page);
+  scrollToTop();
+}
+
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/components/dataset/card-lg.vue b/udata_front/theme/gouvfr/assets/js/components/dataset/card-lg.vue
index 2cc58938f..bf1a403ed 100644
--- a/udata_front/theme/gouvfr/assets/js/components/dataset/card-lg.vue
+++ b/udata_front/theme/gouvfr/assets/js/components/dataset/card-lg.vue
@@ -46,7 +46,11 @@
               <OrganizationNameWithCertificate :organization="dataset.organization" />
             </a>
           </span>
-          <template v-if="dataset.owner">{{ownerName}}</template>
+          <span class="not-enlarged" v-else-if="dataset.owner">
+            <a class="fr-link" :href="dataset.owner.page">
+              {{ ownerName }}
+            </a>
+          </span>
         </p>
         <p class="fr-mt-1w fr-mb-2w fr-hidden fr-unhidden-sm overflow-wrap-anywhere">
           {{ excerpt(dataset.description, 160) }}
diff --git a/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.js b/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.js
deleted file mode 100644
index 8db1f489a..000000000
--- a/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import MenuSearch from "./MenuSearch.vue";
-
-export default {
-  title: 'Components/MenuSearch',
-  component: MenuSearch,
-};
-
-export const DefaultMenuSearch = {
-  render: (args) => ({
-    components: { MenuSearch },
-    setup() {
-      return { args };
-    },
-    template: ` <MenuSearch v-bind="args" />`,
-  }),
-  args: {},
-};
diff --git a/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.ts b/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.ts
new file mode 100644
index 000000000..fafc7e7e8
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.stories.ts
@@ -0,0 +1,22 @@
+import { Meta, StoryObj } from "@storybook/vue3";
+import MenuSearch from "./MenuSearch.vue";
+
+const meta = {
+  title: 'Components/MenuSearch',
+  component: MenuSearch,
+} satisfies Meta<typeof MenuSearch>;
+
+export default meta;
+
+export const DefaultMenuSearch: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { MenuSearch },
+    setup() {
+      return { args };
+    },
+    template: ` <div class="fr-grid-row relative">
+                  <MenuSearch v-bind="args" />
+                </div>`,
+  }),
+  args: {},
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.vue b/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.vue
index 46454994c..095ac83f5 100644
--- a/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.vue
+++ b/udata_front/theme/gouvfr/assets/js/components/search/MenuSearch/MenuSearch.vue
@@ -1,7 +1,7 @@
 <template>
   <div
     :id="id"
-    class="fr-col fr-search-bar"
+    class="fr-col fr-search-bar wrap"
     role="search"
   >
     <label class="fr-label" :for="inputId" :id="labelId">
diff --git a/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.mdx b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.mdx
new file mode 100644
index 000000000..02d725060
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.mdx
@@ -0,0 +1,16 @@
+import { Canvas, Controls, Meta } from '@storybook/blocks';
+
+import * as DeleteButtonStories from './DeleteButton.stories';
+
+<Meta of={DeleteButtonStories} />
+
+# Delete button
+
+A simple button to delete various entities on the website.
+
+
+<Canvas of={DeleteButtonStories.DefaultDeleteButton} />
+
+## Props
+
+<Controls />
diff --git a/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.stories.ts b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.stories.ts
new file mode 100644
index 000000000..8fa19e481
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.stories.ts
@@ -0,0 +1,33 @@
+import DeleteButton from "./DeleteButton.vue";
+import { withActions } from '@storybook/addon-actions/decorator';
+import type { Meta, StoryObj } from "@storybook/vue3";
+
+const meta = {
+  title: 'Buttons/Delete Button',
+  component: DeleteButton,
+  parameters: {
+    actions: {
+      handles: ['click a'],
+    },
+  },
+  decorators: [withActions],
+} satisfies Meta<typeof DeleteButton>;
+
+export default meta;
+
+/**
+ * #### Delete Button
+ * A button to delete something
+ */
+export const DefaultDeleteButton: StoryObj<typeof meta> = {
+  render: (args) => ({
+    components: { DeleteButton },
+    setup() {
+      return { args };
+    },
+    template: '<DeleteButton v-bind="args"/>',
+  }),
+  args: {
+    url: "http://someUrl"
+  },
+};
diff --git a/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.vue b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.vue
new file mode 100644
index 000000000..f08d4dcfc
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/components/utils/DeleteButton/DeleteButton.vue
@@ -0,0 +1,72 @@
+<template>
+  <button
+    type="button"
+    class="fr-btn fr-btn--sm fr-btn--secondary fr-btn--secondary-warning-425 fr-btn--icon-left fr-icon-delete-line"
+    :disabled="readOnlyEnabled"
+    data-fr-opened="false"
+    :aria-controls="modalId"
+  >
+    {{ t("Delete") }}
+  </button>
+  <Teleport to="body">
+    <dialog :aria-labelledby="modalTitleId" :id="modalId" class="fr-modal" role="dialog">
+      <div class="fr-container fr-container--fluid fr-container-md">
+          <div class="fr-grid-row fr-grid-row--center">
+              <div class="fr-col-12 fr-col-md-8 fr-col-lg-6">
+                  <div class="fr-modal__body">
+                      <div class="fr-modal__header">
+                          <button class="fr-btn--close fr-btn" :aria-controls="modalId">{{ t("Close") }}</button>
+                      </div>
+                      <div class="fr-modal__content">
+                          <h1 :id="modalTitleId" class="fr-modal__title">
+                              <span class="fr-icon-delete-line fr-icon--lg"></span>
+                              {{ t('Are you sure ?') }}
+                          </h1>
+                          <p>{{ t('The content will be permanently deleted.') }}</p>
+                      </div>
+                      <div class="fr-modal__footer">
+                          <div class="fr-btns-group fr-btns-group--right fr-btns-group--inline-reverse fr-btns-group--inline-lg fr-btns-group--icon-left">
+                              <button
+                                class="fr-btn fr-btn--secondary fr-btn--secondary-warning-425 fr-btn--icon-left fr-icon-delete-line"
+                                role="button"
+                                @click="deleteObject"
+                              >
+                                  {{ t("Delete") }}
+                              </button>
+                          </div>
+                      </div>
+                  </div>
+              </div>
+          </div>
+      </div>
+    </dialog>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import { read_only_enabled } from "../../../config";
+import { api } from "../../../plugins/api";
+import { auth } from "../../../plugins/auth";
+import { useI18n } from "vue-i18n";
+import { getRandomId } from "@etalab/data.gouv.fr-components";
+import { useToast } from "../../../composables/useToast";
+
+const props = defineProps<{
+  url: string,
+}>();
+
+const { t } = useI18n();
+const { toast } = useToast();
+
+const readOnlyEnabled = read_only_enabled;
+
+const modalId = getRandomId("delete-modal");
+const modalTitleId = modalId + "-title";
+
+function deleteObject() {
+  auth();
+  return api.delete(props.url)
+  .then(() => window.location.reload())
+  .catch(() => toast.error(t("An error occured during the deletion.")));
+};
+</script>
diff --git a/udata_front/theme/gouvfr/assets/js/composables/useReuseType.ts b/udata_front/theme/gouvfr/assets/js/composables/useReuseType.ts
new file mode 100644
index 000000000..5ecd16668
--- /dev/null
+++ b/udata_front/theme/gouvfr/assets/js/composables/useReuseType.ts
@@ -0,0 +1,14 @@
+import { ref, type MaybeRefOrGetter, toRef, watchEffect, toValue } from "vue";
+import { fetchReuseTypes, getType } from "../api/reuses";
+
+export default function useReuseType(id: MaybeRefOrGetter<string>) {
+  const label = ref("");
+  watchEffect(async () => {
+    const idValue = toValue(id);
+    const types = await fetchReuseTypes();
+    label.value = getType(types, idValue);
+  });
+  return {
+    label,
+  };
+}
diff --git a/udata_front/theme/gouvfr/assets/js/config.ts b/udata_front/theme/gouvfr/assets/js/config.ts
index 9eab9b527..55b1f6a72 100644
--- a/udata_front/theme/gouvfr/assets/js/config.ts
+++ b/udata_front/theme/gouvfr/assets/js/config.ts
@@ -4,7 +4,7 @@
  * Lots of variables here are probably useless for the front-end
  */
 
-import { User } from "@etalab/data.gouv.fr-components";
+import type { User } from "@etalab/data.gouv.fr-components";
 import type { Primitive } from "@sentry/types";
 
 /**
@@ -40,6 +40,7 @@ const userEl = document.querySelector<HTMLElement>("meta[name=current-user]");
 export const user: User = {
   id: userEl?.getAttribute("content") || "",
   slug: userEl?.dataset.slug,
+  page: "/users/" + userEl?.dataset.slug,
   first_name: userEl?.dataset.first_name || "",
   last_name: userEl?.dataset.last_name || "",
   avatar: userEl?.dataset.avatar,
diff --git a/udata_front/theme/gouvfr/assets/js/index.ts b/udata_front/theme/gouvfr/assets/js/index.ts
index 49165ba58..8396abfa6 100644
--- a/udata_front/theme/gouvfr/assets/js/index.ts
+++ b/udata_front/theme/gouvfr/assets/js/index.ts
@@ -10,6 +10,7 @@ import Threads from "./components/discussions/Threads.vue";
 import ThreadCreate from "./components/discussions/ThreadCreate/ThreadCreate.vue";
 import MenuSearch from "./components/search/MenuSearch/MenuSearch.vue";
 import Search from "./components/search/Search/Search.vue";
+import DeleteButton from "./components/utils/DeleteButton/DeleteButton.vue";
 import FeaturedButton from './components/utils/featured.vue';
 import FollowButton from "./components/utils/follow-button.vue";
 import ReadMore from "./components/ReadMore/ReadMore.vue";
@@ -18,6 +19,8 @@ import ResourceFromHash from "./components/dataset/resource/resourceFromHash.vue
 import Resources from "./components/dataset/resource/resources.vue";
 import Captcha from "./components/utils/captcha.vue";
 import Toggletip from "./components/utils/Toggletip/Toggletip.vue";
+import UserDatasetList from "./components/UserDatasetList/UserDatasetList.vue";
+import UserReuseList from "./components/UserReuseList/UserReuseList.vue";
 import PublishingForm from "./pages/PublishingForm/PublishingForm.vue";
 
 import "./components/vanilla/tabs.js";
@@ -61,8 +64,9 @@ const configAndMountApp = (el: HTMLElement) => {
   app.component("discussion-create", ThreadCreate);
   app.component("menu-search", MenuSearch);
   app.component("search", Search);
-  app.component("follow-button", FollowButton);
+  app.component("delete-button", DeleteButton);
   app.component("featured-button", FeaturedButton);
+  app.component("follow-button", FollowButton);
   app.component("read-more", ReadMore);
   app.component("request-membership", RequestMembership);
   app.component("dataset-resources", Resources);
@@ -71,6 +75,8 @@ const configAndMountApp = (el: HTMLElement) => {
   app.component("chart", Chart);
   app.component("toggletip", Toggletip);
   app.component("publishing-form", PublishingForm);
+  app.component("user-dataset-list", UserDatasetList);
+  app.component("user-reuse-list", UserReuseList);
 
   // @ts-ignore unset delimiters used in html templates to prevent injections using {{ }}
   app.config.compilerOptions.delimiters = [];
diff --git a/udata_front/theme/gouvfr/assets/js/locales/de.json b/udata_front/theme/gouvfr/assets/js/locales/de.json
index 6101a7c96..771bc3fe8 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/de.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/de.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/en.json b/udata_front/theme/gouvfr/assets/js/locales/en.json
index c64691fc4..66bc20a24 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/en.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/en.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "{n} datasets | {n} dataset | {n} datasets",
+  " by ": " by ",
   "Search for data": "Search for data",
-  "{n} downloads": "{n} downloads"
-}
+  "{n} downloads": "{n} downloads",
+  "Are you sure ?": "Are you sure ?",
+  "The content will be permanently deleted.": "The content will be permanently deleted.",
+  "An error occured during the deletion.": ""
+}
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/es.json b/udata_front/theme/gouvfr/assets/js/locales/es.json
index 739d31e5f..aefa228d3 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/es.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/es.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/fr.json b/udata_front/theme/gouvfr/assets/js/locales/fr.json
index 9a2bd308f..0b09f50dc 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/fr.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/fr.json
@@ -316,6 +316,11 @@
   "The formats must be:": "Les formats doivent être :",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "Il est possible d'identifier un schéma de données déjà existant en consultant le site {schema}, qui référence une liste de schémas de données existants.",
   "Edit file": "Éditer le fichier",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/it.json b/udata_front/theme/gouvfr/assets/js/locales/it.json
index 6101a7c96..771bc3fe8 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/it.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/it.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/pt.json b/udata_front/theme/gouvfr/assets/js/locales/pt.json
index ad4db1d53..b9a13bde9 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/pt.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/pt.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/locales/sr.json b/udata_front/theme/gouvfr/assets/js/locales/sr.json
index a2aa8c983..ee4402190 100644
--- a/udata_front/theme/gouvfr/assets/js/locales/sr.json
+++ b/udata_front/theme/gouvfr/assets/js/locales/sr.json
@@ -316,6 +316,11 @@
   "Publish the correct file types": "Publish the correct file types",
   "The formats must be:": "The formats must be:",
   "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.": "It is possible to identify an existing data schema by visiting the {schema} website, that references a list of existing data schema.",
+  "{n} datasets": "",
+  " by ": "",
   "Search for data": "",
-  "{n} downloads": ""
+  "{n} downloads": "",
+  "Are you sure ?": "",
+  "The content will be permanently deleted.": "",
+  "An error occured during the deletion.": ""
 }
\ No newline at end of file
diff --git a/udata_front/theme/gouvfr/assets/js/pages/PublishingForm/Step4CompleteThePublication.vue b/udata_front/theme/gouvfr/assets/js/pages/PublishingForm/Step4CompleteThePublication.vue
index b4e34abe1..af4354e1f 100644
--- a/udata_front/theme/gouvfr/assets/js/pages/PublishingForm/Step4CompleteThePublication.vue
+++ b/udata_front/theme/gouvfr/assets/js/pages/PublishingForm/Step4CompleteThePublication.vue
@@ -17,7 +17,7 @@
           </div>
         </div>
       </Well>
-      <CardLg v-bind="dataset" :showMetrics="false" />
+      <CardLg :dataset="dataset" :showMetrics="false" />
       <div class="fr-grid-row justify-between">
         <a
             :href="feedbackUrl"
diff --git a/udata_front/theme/gouvfr/assets/js/types.ts b/udata_front/theme/gouvfr/assets/js/types.ts
index 91f7990f7..a45a30a8a 100644
--- a/udata_front/theme/gouvfr/assets/js/types.ts
+++ b/udata_front/theme/gouvfr/assets/js/types.ts
@@ -126,6 +126,23 @@ quality: Quality;
 metrics: { discussions: number; followers: number; reuses: number; views: number; };
 };
 
+export type Reuse = Owned & {
+  id: string;
+  title: string;
+  description: string;
+  tags: Array<string> | null;
+  page: string;
+  private: boolean;
+  deleted: boolean;
+  datasets: Array<Dataset>;
+  image: string;
+  image_thumbnail: string;
+  slug: string;
+  topic: string;
+  type: string;
+  last_update: string;
+};
+
 export type AxisAlignment = "start" | "center" | "end";
 
 export default {};
diff --git a/udata_front/theme/gouvfr/assets/less/components/breadcrumbs.less b/udata_front/theme/gouvfr/assets/less/components/breadcrumbs.less
index 0aa01520a..9e1cf517d 100644
--- a/udata_front/theme/gouvfr/assets/less/components/breadcrumbs.less
+++ b/udata_front/theme/gouvfr/assets/less/components/breadcrumbs.less
@@ -40,3 +40,7 @@ Breadcrumbs allow a user to see where it is in a website.
 
 // tabs from dsfr
 @import (less) "@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css";
+
+.fr-breadcrumb__link:not(button):not([href]) {
+    &:extend(.fr-breadcrumb__link[aria-current]:not(button):not([href]));
+}
diff --git a/udata_front/theme/gouvfr/assets/less/content/typography.less b/udata_front/theme/gouvfr/assets/less/content/typography.less
index 30b41f9e9..7ba79168e 100644
--- a/udata_front/theme/gouvfr/assets/less/content/typography.less
+++ b/udata_front/theme/gouvfr/assets/less/content/typography.less
@@ -223,17 +223,13 @@ each(@font-sizes, {
 }
 
 .text-decoration-underline--dsfr {
-    text-underline-offset: 0.125em;
+    text-underline-position: under;
     text-decoration-thickness: 0.0625em;
     &:hover {
         text-decoration-thickness: 0.125em;
     }
 }
 
-.text-underline-position-under {
-    text-underline-position: under;
-}
-
 .text-decoration-none {
     text-decoration: none !important;
 }
diff --git a/udata_front/theme/gouvfr/assets/less/layout/grid.less b/udata_front/theme/gouvfr/assets/less/layout/grid.less
index a969d4267..023d667c6 100644
--- a/udata_front/theme/gouvfr/assets/less/layout/grid.less
+++ b/udata_front/theme/gouvfr/assets/less/layout/grid.less
@@ -6,6 +6,10 @@
     display: inline !important;
 }
 
+.inline-block {
+    display: inline-block;
+}
+
 .relative {
     position: relative;
 }
diff --git a/udata_front/theme/gouvfr/datagouv-components/src/composables/index.ts b/udata_front/theme/gouvfr/datagouv-components/src/composables/index.ts
index e6daee2fa..72bb3e237 100644
--- a/udata_front/theme/gouvfr/datagouv-components/src/composables/index.ts
+++ b/udata_front/theme/gouvfr/datagouv-components/src/composables/index.ts
@@ -1,6 +1,6 @@
-import useOrganizationCertified from "./organizations/useOrganizationCertified";
+import useOrganizationCertified, { CERTIFIED, PUBLIC_SERVICE } from "./organizations/useOrganizationCertified";
 import useOwnerName from "./organizations/useOwnerName";
 import useResourceImage from "./resources/useResourceImage";
 import useSchema from "./resources/useSchema";
 
-export { useOrganizationCertified, useOwnerName, useResourceImage, useSchema };
+export { useOrganizationCertified, useOwnerName, useResourceImage, useSchema, CERTIFIED, PUBLIC_SERVICE };
diff --git a/udata_front/theme/gouvfr/datagouv-components/src/types/users.ts b/udata_front/theme/gouvfr/datagouv-components/src/types/users.ts
index 3f362e70e..f584f2a0e 100644
--- a/udata_front/theme/gouvfr/datagouv-components/src/types/users.ts
+++ b/udata_front/theme/gouvfr/datagouv-components/src/types/users.ts
@@ -4,6 +4,7 @@ export type User = {
   first_name: string;
   last_name: string;
   avatar?: string;
+  page: string;
   avatar_thumbnail?: string;
   roles?: Array<string>;
 }
diff --git a/udata_front/theme/gouvfr/templates/dataset/card-lg.html b/udata_front/theme/gouvfr/templates/dataset/card-lg.html
index 6795bd5db..38d300dfb 100644
--- a/udata_front/theme/gouvfr/templates/dataset/card-lg.html
+++ b/udata_front/theme/gouvfr/templates/dataset/card-lg.html
@@ -65,7 +65,11 @@ <h4 class="fr-mb-1v">
                         </a>
                     </span>
                 {% elif dataset.owner %}
-                    {{owner_name(dataset)}}
+                <span class="not-enlarged">
+                    <a class="fr-link" href="{{ url_for('users.show', user=dataset.owner) }}">
+                        {{owner_name(dataset)}}
+                    </a>
+                </span>
                 {% endif %}
             </p>
             {% endif %}
diff --git a/udata_front/theme/gouvfr/templates/dataset/card-sm.html b/udata_front/theme/gouvfr/templates/dataset/card-sm.html
index 11776de60..23d73b8cf 100644
--- a/udata_front/theme/gouvfr/templates/dataset/card-sm.html
+++ b/udata_front/theme/gouvfr/templates/dataset/card-sm.html
@@ -64,7 +64,11 @@ <h4 class="fr-mb-1v">
                     </a>
                 </span>
                 {% elif dataset.owner %}
-                {{owner_name(dataset)}}
+                <span class="not-enlarged">
+                    <a class="fr-link" href="{{ url_for('users.show', user=dataset.owner) }}">
+                        {{owner_name(dataset)}}
+                    </a>
+                    </span>
                 {% endif %}
             </p>
             {% endif %}
diff --git a/udata_front/theme/gouvfr/templates/dataset/card-xs.html b/udata_front/theme/gouvfr/templates/dataset/card-xs.html
index b31d96244..16bf870ca 100644
--- a/udata_front/theme/gouvfr/templates/dataset/card-xs.html
+++ b/udata_front/theme/gouvfr/templates/dataset/card-xs.html
@@ -63,10 +63,19 @@ <h4 class="fr-mb-1v">
                     <a
                         class="fr-link"
                         href="{{ url_for('organizations.show', org=dataset.organization, _external=True) }}"
-                    >{{organization_name_with_certificate(dataset.organization)}}</a>
+                    >
+                        {{organization_name_with_certificate(dataset.organization)}}
+                    </a>
                 </span>
                 {% elif dataset.owner %}
-                {{owner_name(dataset)}}
+                <span class="not-enlarged">
+                    <a
+                        class="fr-link"
+                        href="{{ url_for('users.show', user=dataset.owner, _external=True) }}"
+                    >
+                        {{owner_name(dataset)}}
+                    </a>
+                </span>
                 {% endif %}
             </p>
             {% endif %}
diff --git a/udata_front/theme/gouvfr/templates/header.html b/udata_front/theme/gouvfr/templates/header.html
index d5f3a1392..155771f04 100644
--- a/udata_front/theme/gouvfr/templates/header.html
+++ b/udata_front/theme/gouvfr/templates/header.html
@@ -65,7 +65,10 @@
                         <ul class="fr-btns-group">
                             {% if current_user.is_authenticated %}
                                 <li>
-                                    <div class="fr-btn fr-icon-svg fr-icon--sm fr-grid-row text-grey-500">
+                                    <a
+                                        href="{{ url_for('users.show', user=current_user) }}"
+                                        class="fr-btn fr-icon-svg fr-icon--sm fr-grid-row"
+                                    >
                                         <img
                                             src="{{ current_user|avatar_url(24) }}"
                                             width="24"
@@ -75,7 +78,7 @@
                                             class="fr-mr-1w rounded-circle"
                                         />
                                         {{ current_user.fullname }}
-                                    </div>
+                                    </a>
                                 </li>
                                 <li>
                                     <a
diff --git a/udata_front/theme/gouvfr/templates/macros/delete.html b/udata_front/theme/gouvfr/templates/macros/delete.html
new file mode 100644
index 000000000..9442d3390
--- /dev/null
+++ b/udata_front/theme/gouvfr/templates/macros/delete.html
@@ -0,0 +1,9 @@
+{% macro delete_btn(obj, class=None) %}
+{% set basename = obj.__class__.__name__|lower %}
+<div class="fr-col-auto vuejs">
+    <delete-button
+        url="{{ url_for('api.{0}'.format(basename), user=obj, _external=True) }}"
+    >
+    </delete-button>
+</div>
+{% endmacro %}
diff --git a/udata_front/theme/gouvfr/templates/organization/producer-name-with-logo.html b/udata_front/theme/gouvfr/templates/organization/producer-name-with-logo.html
index 9a5e16d30..30b4f25fb 100644
--- a/udata_front/theme/gouvfr/templates/organization/producer-name-with-logo.html
+++ b/udata_front/theme/gouvfr/templates/organization/producer-name-with-logo.html
@@ -17,7 +17,9 @@
                 {{organization_name_with_certificate(organization)}}
             </a>
         {% else %}
+        <a class="fr-link" href="{{ url_for('users.show', user=organization) }}">
             {{ organization.fullname }}
+        </a>
         {% endif %}
         </p>
 </div>
diff --git a/udata_front/theme/gouvfr/templates/reuse/card.html b/udata_front/theme/gouvfr/templates/reuse/card.html
index 998165a28..2964b15b8 100644
--- a/udata_front/theme/gouvfr/templates/reuse/card.html
+++ b/udata_front/theme/gouvfr/templates/reuse/card.html
@@ -26,7 +26,12 @@ <h3 class="fr-card__title fr-text--md fr-mt-1w">
                         {{organization_name_with_certificate(reuse.organization)}}
                     </a>
                     {% elif reuse.owner %}
-                    {{owner_name(reuse)}}
+                     <a
+                        class="fr-link"
+                        href="{{ url_for('users.show', user=reuse.owner) }}"
+                    >
+                        {{owner_name(reuse)}}
+                    </a>
                     {% endif %}
                 {% endif %}
             </p>
diff --git a/udata_front/theme/gouvfr/templates/user/base.html b/udata_front/theme/gouvfr/templates/user/base.html
index d61c0d584..396a36e31 100644
--- a/udata_front/theme/gouvfr/templates/user/base.html
+++ b/udata_front/theme/gouvfr/templates/user/base.html
@@ -1,5 +1,7 @@
 {% extends theme('layouts/1-column.html') %}
+{% from theme('macros/delete.html') import delete_btn with context %}
 {% from theme('macros/follow.html') import follow_btn with context %}
+{% from theme('macros/organization_name_with_certificate.html') import organization_name_with_certificate %}
 
 {% set bundle = 'user' %}
 
@@ -24,102 +26,127 @@
 {% endblock %}
 
 {% block toolbar %}
+{% if current_user != user %}
+    {{ follow_btn(user) }}
+{% endif %}
 {% if can_edit %}
-<div class="btn-group btn-group-xs">
-    <a href="{{ url_for('admin.index', path='users/{id}/'.format(id=user.id)) }}" class="btn btn-success">
-        <span class="fa fa-pencil"></span>
+    {% if current_user != user %}
+    <a
+        href="{{ url_for('admin.index', path='user/{id}/'.format(id=user.id)) }}"
+        class="fr-mx-2w fr-btn fr-btn--sm fr-btn--secondary fr-icon-pencil-line fr-btn--icon-left"
+    >
+        {{ _("User's admin profile") }}
+    </a>
+    {{ delete_btn(user) }}
+    {% else %}
+    <a
+        class="fr-ml-2w fr-btn fr-btn--sm fr-btn--secondary fr-btn--secondary-warning-425 fr-icon-pencil-line fr-btn--icon-left"
+        href="{{ url_for('admin.index', path='me') }}"
+    >
         {{ _('Edit') }}
     </a>
-</div>
-{% endif %}
-{# Social actions #}
-{% if current_user != user %}
-<div class="btn-group btn-group-xs">
-    {{ follow_btn(user) }}
-</div>
+    {% endif %}
 {% endif %}
 {% endblock %}
 
 {% block main_content %}
-<div class="fr-container">
-    <div class="fr-alert fr-alert--info">
-        <p class="fr-alert__title">{{ _('Work in progress') }}</p>
-        <p>{{ _('The user page is currently being renovated to improve user experience.') }}</p>
+<div class="fr-container fr-pb-5w">
+    <img
+        src="{{ user|avatar_url(80) }}"
+        alt=""
+        class="rounded-circle"
+    />
+    <h1 class="fr-h3 fr-my-1v">{{ user.fullname }}</h1>
+    <div class="markdown fr-text--sm fr-m-0">
+        {{ user.about|markdown(wrap=False) }}
     </div>
-    <div class="fr-my-2w fr-grid-row fr-grid-row--gutters">
-        <div class="fr-col-md-3">
-            <div class="panel panel-default">
-                <div class="panel-body">
-                    <div class="text-center">
-                        <img src="{{ user|avatar_url(150) }}"
-                            alt="{{ user.fullname }}" class="scalable"/>
-                    </div>
-                    <h2>{{ user.fullname }}</h2>
-                    {{ user.about|markdown }}
-                    {# <hr/> #}
-                    <div>
-                        <ul class="list-unstyled">
-                            {% if user.website %}
-                            <li>
-                                <span class="fa fa-link"></span>
-                                <a href="{{user.website}}" rel="nofollow">{{user.website}}</a>
-                            </li>
-                            {% endif %}
-                        </ul>
-                    </div>
-                </div>
+    {% if user.website %}
+        <p class="fr-m-0">
+            <a
+                href="{{user.website}}"
+                rel="ugc nofollow"
+                class="fr-link fr-text--sm"
+                target="_blank"
+            >
+                {% set truncate_length = 18 %}
+                    {% set website = user.website.lstrip("https://").lstrip("http://").lstrip("www.") %}
+                <span class="fr-icon-global-line" aria-hidden="true"></span>
+                {{ website|truncate(truncate_length, True) }}
+            </a>
+        </p>
+    {% endif %}
 
-                <div class="list-group text-center">
-                    <a href="{{url_for('users.show', user=user)}}"
-                        class="list-group-item {% if user_tab == 'activity' %}active{% endif %}">
-                        <strong>{{ _('Activity') }}</strong>
-                    </a>
-                    <a href="{{url_for('users.followers', user=user)}}"
-                        class="list-group-item {% if user_tab == 'followers' %}active{% endif %}">
-                        <strong>
-                            {{ ngettext('%(num)d follower', '%(num)d followers', user.metrics.followers or 0) }}
-                        </strong>
-                    </a>
-                    <a href="{{url_for('users.following', user=user)}}"
-                        class="list-group-item {% if user_tab == 'following' %}active{% endif %}">
-                        <strong>
-                            {{ ngettext('%(num)d followed', '%(num)d followed', user.metrics.following or 0) }}
-                        </strong>
-                    </a>
-                    <a href="{{url_for('users.datasets', user=user)}}"
-                        class="list-group-item {% if user_tab == 'datasets' %}active{% endif %}">
-                        <strong>
-                            {{ ngettext('%(num)d dataset', '%(num)d datasets', user.metrics.datasets or 0) }}
-                        </strong>
-                    </a>
-                    <a href="{{url_for('users.reuses', user=user)}}"
-                        class="list-group-item {% if user_tab == 'reuses' %}active{% endif %}">
-                        <strong>
-                        {{ ngettext('%(num)d reuse', '%(num)d reuses', user.metrics.reuses or 0) }}
-                        </strong>
-                    </a>
-                </div>
+    {% if can_edit %}
+        <ul class="fr-mt-1w">
+            <li>
+                <a
+                    href="{{url_for('users.followers', user=user)}}"
+                    class="fr-link fr-text--sm"
+                >
+                    {{ ngettext('%(num)d follower', '%(num)d followers', user.metrics.followers or 0) }}
+                </a>
+            </li>
 
-                {% if organizations %}
-                <div class="panel-body">
-                    <h4>{{ _('Organizations') }}</h4>
-                    {% for org in organizations %}
-                    <a href="{{ url_for('organizations.show', org=org) }}">
-                        <img src="{{ org.logo(48)|placeholder('organization') }}"
-                            class="avatar" width="48" height="48" alt="{{ org.name }}" />
-                    </a>
-                    {% endfor %}
-                </div>
-                {% endif %}
-                {% if sysadmin %}
-                <a href="{{ url_for('admin.index', path='user/{id}/'.format(id=user.id)) }}" class="fr-btn fr-mt-1w">
-                    {{ _("User's admin profile") }}
+            <li>
+                <a
+                    href="{{url_for('users.following', user=user)}}"
+                    class="fr-link fr-text--sm"
+                >
+                    {{ ngettext('%(num)d followed', '%(num)d followed', user.metrics.following or 0) }}
                 </a>
-                {% endif %}
-            </div>
+            </li>
+        </ul>
+    {% endif %}
 
+    {% if organizations %}
+        <div class="fr-mt-4w">
+            <h2 class="subtitle subtitle--uppercase fr-mt-0 fr-mb-3v">{{ _('Organizations') }}</h2>
+            {% for org in organizations %}
+                <ul>
+                    <li class="fr-mb-1w">
+                        <a
+                            href="{{ url_for('organizations.show', org=org) }}"
+                            class="fr-raw-link text-blue-400 fr-text--bold fr-text--sm"
+                        >
+                            <div class="inline-block border border-default-grey rounded-xxs fr-p-1v bg-white">
+                                <img
+                                    src="{{ org.logo(24)|placeholder('organization') }}"
+                                    width="24"
+                                    height="24"
+                                    alt=""
+                                />
+                            </div>
+                            <span class="text-decoration-underline text-underline-position-under text-decoration-underline--dsfr">{{ organization_name_with_certificate(org) }}</span>
+                        </a>
+                    </li>
+                </ul>
+            {% endfor %}
         </div>
-        <div class="fr-col-md-9">{% block user_content %}{% endblock %}</div>
-    </div>
+    {% endif %}
+</div>
+<hr class="fr-py-0-5v" />
+<div class="fr-container fr-mb-3w fr-mt-9v vuejs">
+    {% block user_content %}
+        <user-dataset-list
+            owner="{{user.id}}"
+        ></user-dataset-list>
+        <user-reuse-list
+            owner="{{user.id}}"
+        ></user-reuse-list>
+        <div data-show-no-js>
+            <ul>
+                <li>
+                    <a href="{{ url_for('datasets.list', owner=user.id) }}" class="fr-link">
+                        {{ _('Browse the datasets of this user.') }}
+                    </a>
+                </li>
+                <li class="fr-mt-1w">
+                    <a href="{{ url_for('reuses.list', owner=user.id) }}" class="fr-link">
+                        {{ _('Browse the reuses of this user.') }}
+                    </a>
+                </li>
+            </ul>
+        </div>
+    {% endblock %}
 </div>
 {% endblock %}
diff --git a/udata_front/theme/gouvfr/templates/user/followers.html b/udata_front/theme/gouvfr/templates/user/followers.html
index 9c6af88bd..d07cb13c9 100644
--- a/udata_front/theme/gouvfr/templates/user/followers.html
+++ b/udata_front/theme/gouvfr/templates/user/followers.html
@@ -9,7 +9,10 @@
     </a>
 </li>
 <li>
-    <a class="fr-breadcrumb__link">
+    <a
+        class="fr-breadcrumb__link"
+        href="{{url_for('users.show', user=user)}}"
+    >
         {{ user.fullname }}
     </a>
 </li>
@@ -23,26 +26,16 @@
 {% block user_content %}
 {% if followers|length > 0 %}
     <h3>{{ ngettext('%(num)d follower', '%(num)d followers', followers|length) }}</h3>
-    <div class="fr-grid-row">
-        <ul class="card-list card-list--columned">
+    <div class="fr-grid-row fr-grid-row--gutters">
             {% for follow in followers %}
-            <li class="fr-col-md-4">
+            <div class="fr-col-md-4">
                 {% include theme('follow/follower-card.html') %}
-            </li>
+            </div>
             {% endfor %}
-        </ul>
     </div>
-
 {% else %}
-
-<div class="fr-grid-row">
-    <div class="fr-col-12">
-
-    </div>
-</div>
-<p class="text-center lead">
-{{ _('%(user)s has no follower', user=user.fullname) }}
-</p>
-
+    <p class="text-center lead">
+    {{ _('%(user)s has no follower', user=user.fullname) }}
+    </p>
 {% endif %}
 {% endblock %}
diff --git a/udata_front/theme/gouvfr/templates/user/following.html b/udata_front/theme/gouvfr/templates/user/following.html
index 0dff7e5a3..dfb433030 100644
--- a/udata_front/theme/gouvfr/templates/user/following.html
+++ b/udata_front/theme/gouvfr/templates/user/following.html
@@ -9,7 +9,10 @@
     </a>
 </li>
 <li>
-    <a class="fr-breadcrumb__link">
+    <a
+        class="fr-breadcrumb__link"
+        href="{{url_for('users.show', user=user)}}"
+    >
         {{ user.fullname }}
     </a>
 </li>
@@ -23,122 +26,89 @@
 {% block user_content %}
 {% if followed_users or followed_datasets or followed_organizations or followed_reuses %}
 
-{% set activated = False %}
-{% set tabs = (
-    ('datasets', _('Datasets'), followed_datasets),
-    ('reuses', _('Reuses'), followed_reuses),
-    ('organizations', _('Organizations'), followed_organizations),
-    ('users', _('Users'), followed_users),
-) %}
-
-<div class="fr-grid-row">
-    <ul class="nav nav-pills" data-tabs="tabs">
-        {% for tab_id, label, content in tabs %}
-        {% if content %}
-        <li {% if loop.first %}class="active"{% endif %}>
-            <a href="#{{tab_id}}" data-toggle="tab">{{ label }}</a>
-        </li>
-        {% endif %}
-        {% endfor %}
-    </ul>
-</div>
+    {% set activated = False %}
+    {% set tabs = (
+        ('datasets', _('Datasets'), followed_datasets),
+        ('reuses', _('Reuses'), followed_reuses),
+        ('organizations', _('Organizations'), followed_organizations),
+        ('users', _('Users'), followed_users),
+    ) %}
 
-{% set activated = False %}
-<div class="fr-grid-row">
-    <div class="tab-content">
+    {% set activated = False %}
 
-        {% if followed_datasets %}
-        <div id="datasets"
-            class="tab-pane {% if not actived %}active{% set actived = True %}{% endif %}">
-            <h3>
-            {{ ngettext(
-                'Follow %(num)d dataset',
-                'Follow %(num)d datasets',
-                followed_datasets|length
-            ) }}
-            </h3>
-            <ul class="card-list card-list--columned">
-                {% for dataset in followed_datasets %}
-                <li class="fr-col-sm-6">
-                    {% include theme('dataset/card-xs.html') %}
-                </li>
-                {% endfor %}
-            </ul>
+    {% if followed_datasets %}
+        <h3>
+        {{ ngettext(
+            'Follow %(num)d dataset',
+            'Follow %(num)d datasets',
+            followed_datasets|length
+        ) }}
+        </h3>
+        <div class="fr-grid-row fr-grid-row--gutters fr-mb-3w">
+            {% for dataset in followed_datasets %}
+            <div class="fr-col-sm-6">
+                {% include theme('dataset/card-xs.html') %}
+            </div>
+            {% endfor %}
         </div>
-        {% endif %}
+    {% endif %}
 
-        {% if followed_reuses %}
-        <div id="reuses"
-            class="tab-pane {% if not actived %}active{% set actived = True %}{% endif %}">
-            <h3>
-            {{ ngettext(
-                'Follow %(num)d reuse',
-                'Follow %(num)d reuses',
-                followed_reuses|length
-            ) }}
-            </h3>
-            <ul class="card-list card-list--columned">
-                {% for reuse in followed_reuses %}
-                <li class="fr-col-sm-6">
-                    {% include theme('reuse/card.html') %}
-                </li>
-                {% endfor %}
-            </ul>
+    {% if followed_reuses %}
+        <h3>
+        {{ ngettext(
+            'Follow %(num)d reuse',
+            'Follow %(num)d reuses',
+            followed_reuses|length
+        ) }}
+        </h3>
+        <div class="fr-grid-row fr-grid-row--gutters fr-mb-3w">
+            {% for reuse in followed_reuses %}
+            <div class="fr-col-sm-6">
+                {% include theme('reuse/card.html') %}
+            </div>
+            {% endfor %}
         </div>
-        {% endif %}
+    {% endif %}
 
-        {% if followed_organizations %}
-        <div id="organizations"
-            class="tab-pane {% if not actived %}active{% set actived = True %}{% endif %}">
-            <h3>
-            {{ ngettext(
-                'Follow %(num)d organization',
-                'Follow %(num)d organizations',
-                followed_organizations|length
-            ) }}
-            </h3>
-            <ul class="card-list card-list--columned">
-                {% for organization in followed_organizations %}
-                <li class="fr-col-sm-6">
-                    {% include theme('organization/card.html') %}
-                </li>
-                {% endfor %}
-            </ul>
+    {% if followed_organizations %}
+        <h3>
+        {{ ngettext(
+            'Follow %(num)d organization',
+            'Follow %(num)d organizations',
+            followed_organizations|length
+        ) }}
+        </h3>
+        <div class="fr-grid-row fr-grid-row--gutters fr-mb-3w">
+            {% for organization in followed_organizations %}
+            <div class="fr-col-sm-6">
+                {% include theme('organization/card.html') %}
+            </div>
+            {% endfor %}
         </div>
-        {% endif %}
+    {% endif %}
 
-        {% if followed_users %}
-        <div id="users"
-            class="tab-pane {% if not actived %}active{% set actived = True %}{% endif %}">
-            <h3>
-            {{ ngettext(
-                'Follow %(num)d user',
-                'Follow %(num)d users',
-                followed_users|length
-            ) }}
-            </h3>
-            <ul class="card-list card-list--columned">
-            {% for user in followed_users %}
-                <li class="fr-col-sm-6">
-                    {% include theme('user/card.html') %}
-                </li>
-            {% endfor %}
-            </ul>
+    {% if followed_users %}
+        <h3>
+        {{ ngettext(
+            'Follow %(num)d user',
+            'Follow %(num)d users',
+            followed_users|length
+        ) }}
+        </h3>
+        <div class="fr-grid-row fr-grid-row--gutters fr-mb-3w">
+        {% for user in followed_users %}
+            <div class="fr-col-sm-6">
+                {% include theme('user/card.html') %}
+            </div>
+        {% endfor %}
         </div>
-        {% endif %}
-    </div>
-</div>
+    {% endif %}
 
 {% else %}
 
-<div class="fr-grid-row">
-    <div class="fr-col-12">
-
-    </div>
-</div>
-<p class="text-center lead">
-{{ _('%(user)s is not following anyone', user=user.fullname) }}
-</p>
+    <p class="text-center lead">
+        {{ _('%(user)s is not following anyone', user=user.fullname) }}
+    </p>
 
 {% endif %}
 {% endblock %}
diff --git a/udata_front/theme/gouvfr/translations/gouvfr.pot b/udata_front/theme/gouvfr/translations/gouvfr.pot
index 7d9a39ed7..8fa4aa3d8 100644
--- a/udata_front/theme/gouvfr/translations/gouvfr.pot
+++ b/udata_front/theme/gouvfr/translations/gouvfr.pot
@@ -6,10 +6,10 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: udata-front 3.2.13.dev0\n"
+"Project-Id-Version: udata-front 3.5.1a5\n"
 "Report-Msgid-Bugs-To: data.gouv@data.gouv.fr\n"
-"POT-Creation-Date: 2024-01-24 15:27+0100\n"
-"PO-Revision-Date: 2024-01-24 15:27+0100\n"
+"POT-Creation-Date: 2024-02-02 16:57+0100\n"
+"PO-Revision-Date: 2024-02-02 16:57+0100\n"
 "Last-Translator: Data.gouv.fr Team <data.gouv@data.gouv.fr>\n"
 "Language: en\n"
 "Language-Team: Data.gouv.fr Team <data.gouv@data.gouv.fr>\n"
@@ -98,7 +98,7 @@ msgstr ""
 #: udata_front/theme/gouvfr/__init__.py:47
 #: udata_front/theme/gouvfr/templates/dataset/display.html:215
 #: udata_front/theme/gouvfr/templates/dataset/display.html:280
-#: udata_front/theme/gouvfr/templates/home.html:85
+#: udata_front/theme/gouvfr/templates/home.html:84
 #: udata_front/theme/gouvfr/templates/organization/display.html:26
 #: udata_front/theme/gouvfr/templates/organization/display.html:128
 #: udata_front/theme/gouvfr/templates/organization/display.html:172
@@ -111,19 +111,19 @@ msgstr ""
 #: udata_front/theme/gouvfr/templates/reuse/list.html:49
 #: udata_front/theme/gouvfr/templates/topic/browse.html:15
 #: udata_front/theme/gouvfr/templates/topic/reuses.html:25
-#: udata_front/theme/gouvfr/templates/user/following.html:29
+#: udata_front/theme/gouvfr/templates/user/following.html:32
 #: udata_front/theme/gouvfr/templates/user/reuses.html:17
 msgid "Reuses"
 msgstr ""
 
 #: udata_front/theme/gouvfr/__init__.py:48
-#: udata_front/theme/gouvfr/templates/home.html:87
+#: udata_front/theme/gouvfr/templates/home.html:86
 #: udata_front/theme/gouvfr/templates/organization/display.html:35
 #: udata_front/theme/gouvfr/templates/organization/list.html:6
 #: udata_front/theme/gouvfr/templates/organization/list.html:17
 #: udata_front/theme/gouvfr/templates/organization/list.html:31
-#: udata_front/theme/gouvfr/templates/user/base.html:105
-#: udata_front/theme/gouvfr/templates/user/following.html:30
+#: udata_front/theme/gouvfr/templates/user/base.html:103
+#: udata_front/theme/gouvfr/templates/user/following.html:33
 msgid "Organizations"
 msgstr ""
 
@@ -148,7 +148,7 @@ msgid "data.gouv.fr guides"
 msgstr ""
 
 #: udata_front/theme/gouvfr/__init__.py:71
-#: udata_front/theme/gouvfr/templates/home.html:62
+#: udata_front/theme/gouvfr/templates/home.html:61
 #: udata_front/theme/gouvfr/templates/post/display.html:19
 msgid "News"
 msgstr ""
@@ -248,8 +248,8 @@ msgid "Add to your own website"
 msgstr ""
 
 #: udata_front/theme/gouvfr/templates/embed-dataset.html:206
-#: udata_front/theme/gouvfr/templates/header.html:123
-#: udata_front/theme/gouvfr/templates/header.html:161
+#: udata_front/theme/gouvfr/templates/header.html:126
+#: udata_front/theme/gouvfr/templates/header.html:164
 msgid "Close"
 msgstr ""
 
@@ -344,10 +344,10 @@ msgstr ""
 
 #: udata_front/theme/gouvfr/templates/header.html:41
 #: udata_front/theme/gouvfr/templates/header.html:43
-#: udata_front/theme/gouvfr/templates/header.html:135
-#: udata_front/theme/gouvfr/templates/header.html:139
-#: udata_front/theme/gouvfr/templates/header.html:147
-#: udata_front/theme/gouvfr/templates/header.html:149
+#: udata_front/theme/gouvfr/templates/header.html:138
+#: udata_front/theme/gouvfr/templates/header.html:142
+#: udata_front/theme/gouvfr/templates/header.html:150
+#: udata_front/theme/gouvfr/templates/header.html:152
 msgid "Search for data"
 msgstr ""
 
@@ -360,32 +360,32 @@ msgstr ""
 msgid "Go back to home page"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:88
+#: udata_front/theme/gouvfr/templates/header.html:91
 msgid "Administration"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:96
+#: udata_front/theme/gouvfr/templates/header.html:99
 msgid "Logout"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:105
+#: udata_front/theme/gouvfr/templates/header.html:108
 #: udata_front/theme/gouvfr/templates/security/login_user.html:12
 #: udata_front/theme/gouvfr/templates/security/login_user.html:27
 #: udata_front/theme/gouvfr/templates/territories/territory.html:28
 msgid "Log in"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:114
+#: udata_front/theme/gouvfr/templates/header.html:117
 #: udata_front/theme/gouvfr/templates/security/register_user.html:11
 #: udata_front/theme/gouvfr/templates/security/register_user.html:62
 msgid "Sign up"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:163
+#: udata_front/theme/gouvfr/templates/header.html:166
 msgid "Main Menu"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/header.html:213
+#: udata_front/theme/gouvfr/templates/header.html:216
 msgid ""
 "This is a degraded experience of {site}. Please enable JavaScript and use an "
 "up to date browser."
@@ -412,11 +412,11 @@ msgstr ""
 msgid "Open platform for French public data"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:56
+#: udata_front/theme/gouvfr/templates/home.html:55
 msgid "All featured topics"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:73
+#: udata_front/theme/gouvfr/templates/home.html:72
 msgid "All news"
 msgstr ""
 
@@ -426,8 +426,8 @@ msgstr ""
 #: udata_front/theme/gouvfr/templates/dataset/list.html:14
 #: udata_front/theme/gouvfr/templates/dataset/list.html:33
 #: udata_front/theme/gouvfr/templates/dataset/publishing-form.html:16
-#: udata_front/theme/gouvfr/templates/home.html:83
-#: udata_front/theme/gouvfr/templates/home.html:126
+#: udata_front/theme/gouvfr/templates/home.html:82
+#: udata_front/theme/gouvfr/templates/home.html:125
 #: udata_front/theme/gouvfr/templates/organization/display.html:110
 #: udata_front/theme/gouvfr/templates/organization/display.html:162
 #: udata_front/theme/gouvfr/templates/page.html:32
@@ -435,23 +435,23 @@ msgstr ""
 #: udata_front/theme/gouvfr/templates/topic/browse.html:9
 #: udata_front/theme/gouvfr/templates/topic/datasets.html:25
 #: udata_front/theme/gouvfr/templates/user/datasets.html:16
-#: udata_front/theme/gouvfr/templates/user/following.html:28
+#: udata_front/theme/gouvfr/templates/user/following.html:31
 msgid "Datasets"
 msgstr ""
 
 #: udata_front/theme/gouvfr/templates/dataset/display.html:197
 #: udata_front/theme/gouvfr/templates/dataset/display.html:270
-#: udata_front/theme/gouvfr/templates/home.html:84
+#: udata_front/theme/gouvfr/templates/home.html:83
 msgid "Files"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:86
+#: udata_front/theme/gouvfr/templates/home.html:85
 #: udata_front/theme/gouvfr/templates/user/activity.html:6
-#: udata_front/theme/gouvfr/templates/user/base.html:16
+#: udata_front/theme/gouvfr/templates/user/base.html:18
 #: udata_front/theme/gouvfr/templates/user/datasets.html:6
 #: udata_front/theme/gouvfr/templates/user/followers.html:8
 #: udata_front/theme/gouvfr/templates/user/following.html:8
-#: udata_front/theme/gouvfr/templates/user/following.html:31
+#: udata_front/theme/gouvfr/templates/user/following.html:34
 #: udata_front/theme/gouvfr/templates/user/list.html:5
 #: udata_front/theme/gouvfr/templates/user/list.html:16
 #: udata_front/theme/gouvfr/templates/user/reuses.html:7
@@ -460,49 +460,49 @@ msgstr ""
 
 #: udata_front/theme/gouvfr/templates/dataset/display.html:230
 #: udata_front/theme/gouvfr/templates/dataset/display.html:290
-#: udata_front/theme/gouvfr/templates/home.html:88
+#: udata_front/theme/gouvfr/templates/home.html:87
 #: udata_front/theme/gouvfr/templates/reuse/display.html:184
 msgid "Discussions"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:96
+#: udata_front/theme/gouvfr/templates/home.html:95
 msgid "is"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:116
+#: udata_front/theme/gouvfr/templates/home.html:115
 #: udata_front/theme/gouvfr/templates/topic/display.html:51
 msgid "Featured datasets"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:117
+#: udata_front/theme/gouvfr/templates/home.html:116
 #: udata_front/theme/gouvfr/templates/topic/display.html:52
 msgid "Latest datasets"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:118
+#: udata_front/theme/gouvfr/templates/home.html:117
 msgid "Reference datasets"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:150
+#: udata_front/theme/gouvfr/templates/home.html:149
 #: udata_front/theme/gouvfr/templates/topic/display.html:70
 msgid "See more datasets"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:175
+#: udata_front/theme/gouvfr/templates/home.html:174
 msgid "Featured reuses"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:176
-#: udata_front/theme/gouvfr/templates/home.html:207
+#: udata_front/theme/gouvfr/templates/home.html:175
+#: udata_front/theme/gouvfr/templates/home.html:206
 #: udata_front/theme/gouvfr/templates/topic/display.html:35
 msgid "Latest reuses"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:184
+#: udata_front/theme/gouvfr/templates/home.html:183
 msgid "Data reuses"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/home.html:210
+#: udata_front/theme/gouvfr/templates/home.html:209
 msgid "See more reuses"
 msgstr ""
 
@@ -599,7 +599,7 @@ msgstr ""
 #: udata_front/theme/gouvfr/templates/dataset/display.html:58
 #: udata_front/theme/gouvfr/templates/post/display.html:102
 #: udata_front/theme/gouvfr/templates/reuse/display.html:62
-#: udata_front/theme/gouvfr/templates/user/base.html:31
+#: udata_front/theme/gouvfr/templates/user/base.html:46
 msgid "Edit"
 msgstr ""
 
@@ -702,13 +702,13 @@ msgstr ""
 msgid "From"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:78
-#: udata_front/theme/gouvfr/templates/dataset/card-sm.html:73
+#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:82
+#: udata_front/theme/gouvfr/templates/dataset/card-sm.html:77
 #, python-format
 msgid "Updated %(date)s"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:94
+#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:98
 #: udata_front/theme/gouvfr/templates/organization/card.html:30
 #, python-format
 msgid "<strong class=\"fr-mr-1v\">%(num)d</strong> reuse"
@@ -716,7 +716,7 @@ msgid_plural "<strong class=\"fr-mr-1v\">%(num)d</strong> reuses"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:99
+#: udata_front/theme/gouvfr/templates/dataset/card-lg.html:103
 #, python-format
 msgid "<strong class=\"fr-mr-1v\">%(num)d</strong> favorite"
 msgid_plural "<strong class=\"fr-mr-1v\">%(num)d</strong> favorites"
@@ -964,13 +964,13 @@ msgstr ""
 #: udata_front/theme/gouvfr/templates/organization/display.html:25
 #: udata_front/theme/gouvfr/templates/organization/list.html:25
 #: udata_front/theme/gouvfr/templates/reuse/list.html:44
-#: udata_front/theme/gouvfr/templates/user/followers.html:18
+#: udata_front/theme/gouvfr/templates/user/followers.html:21
 msgid "Followers"
 msgstr ""
 
 #: udata_front/theme/gouvfr/templates/dataset/followers.html:27
-#: udata_front/theme/gouvfr/templates/user/base.html:80
-#: udata_front/theme/gouvfr/templates/user/followers.html:25
+#: udata_front/theme/gouvfr/templates/user/base.html:86
+#: udata_front/theme/gouvfr/templates/user/followers.html:28
 #, python-format
 msgid "%(num)d follower"
 msgid_plural "%(num)d followers"
@@ -2044,7 +2044,6 @@ msgstr ""
 
 #: udata_front/theme/gouvfr/templates/territories/home.html:22
 #: udata_front/theme/gouvfr/templates/territories/territory.html:10
-#: udata_front/theme/gouvfr/templates/user/base.html:46
 msgid "Work in progress"
 msgstr ""
 
@@ -2136,50 +2135,39 @@ msgid "%(site)s %(topic)s related reuses"
 msgstr ""
 
 #: udata_front/theme/gouvfr/templates/user/activity.html:16
-#: udata_front/theme/gouvfr/templates/user/base.html:75
 msgid "Activity"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:8
+#: udata_front/theme/gouvfr/templates/user/base.html:10
 #, python-format
 msgid "%(site)s %(username)s profile"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:9
+#: udata_front/theme/gouvfr/templates/user/base.html:11
 msgid "user"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:9
+#: udata_front/theme/gouvfr/templates/user/base.html:11
 msgid "profile"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:47
-msgid "The user page is currently being renovated to improve user experience."
+#: udata_front/theme/gouvfr/templates/user/base.html:38
+msgid "User's admin profile"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:86
+#: udata_front/theme/gouvfr/templates/user/base.html:95
 #, python-format
 msgid "%(num)d followed"
 msgid_plural "%(num)d followed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:92
-#, python-format
-msgid "%(num)d dataset"
-msgid_plural "%(num)d datasets"
-msgstr[0] ""
-msgstr[1] ""
-
-#: udata_front/theme/gouvfr/templates/user/base.html:98
-#, python-format
-msgid "%(num)d reuse"
-msgid_plural "%(num)d reuses"
-msgstr[0] ""
-msgstr[1] ""
+#: udata_front/theme/gouvfr/templates/user/base.html:140
+msgid "Browse the datasets of this user."
+msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/base.html:116
-msgid "User's admin profile"
+#: udata_front/theme/gouvfr/templates/user/base.html:145
+msgid "Browse the reuses of this user."
 msgstr ""
 
 #: udata_front/theme/gouvfr/templates/user/datasets.html:40
@@ -2187,44 +2175,44 @@ msgstr ""
 msgid "%(user)s has not yet published any dataset"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/followers.html:44
+#: udata_front/theme/gouvfr/templates/user/followers.html:38
 #, python-format
 msgid "%(user)s has no follower"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:18
+#: udata_front/theme/gouvfr/templates/user/following.html:21
 msgid "Following"
 msgstr ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:54
+#: udata_front/theme/gouvfr/templates/user/following.html:41
 #, python-format
 msgid "Follow %(num)d dataset"
 msgid_plural "Follow %(num)d datasets"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:74
+#: udata_front/theme/gouvfr/templates/user/following.html:58
 #, python-format
 msgid "Follow %(num)d reuse"
 msgid_plural "Follow %(num)d reuses"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:94
+#: udata_front/theme/gouvfr/templates/user/following.html:75
 #, python-format
 msgid "Follow %(num)d organization"
 msgid_plural "Follow %(num)d organizations"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:114
+#: udata_front/theme/gouvfr/templates/user/following.html:92
 #, python-format
 msgid "Follow %(num)d user"
 msgid_plural "Follow %(num)d users"
 msgstr[0] ""
 msgstr[1] ""
 
-#: udata_front/theme/gouvfr/templates/user/following.html:140
+#: udata_front/theme/gouvfr/templates/user/following.html:110
 #, python-format
 msgid "%(user)s is not following anyone"
 msgstr ""
diff --git a/udata_front/views/user.py b/udata_front/views/user.py
index f7ebc55a4..cf8a75665 100644
--- a/udata_front/views/user.py
+++ b/udata_front/views/user.py
@@ -1,13 +1,12 @@
 import logging
 
-from flask import abort, g
+from flask import url_for, redirect, abort, g
 from flask_security import current_user
 
 from udata_front.views.base import DetailView
-from udata.models import User, Activity, Organization, Dataset, Reuse, Follow
+from udata.core.user.permissions import sysadmin, UserEditPermission
 from udata.i18n import I18nBlueprint
-
-from udata.core.user.permissions import sysadmin
+from udata.models import User, Organization, Dataset, Reuse, Follow
 
 
 blueprint = I18nBlueprint('users', __name__, url_prefix='/users')
@@ -40,46 +39,37 @@ def get_context(self):
         return context
 
 
-@blueprint.route('/<user:user>/datasets/', endpoint='datasets')
-class UserDatasetsView(UserView, DetailView):
-    template_name = 'user/datasets.html'
-
-    def get_context(self):
-        context = super(UserDatasetsView, self).get_context()
-        context['datasets'] = Dataset.objects(owner=self.user).visible()
-        return context
-
-
-@blueprint.route('/<user:user>/reuses/', endpoint='reuses')
-class UserReusesView(UserView, DetailView):
-    template_name = 'user/reuses.html'
-
-    def get_context(self):
-        context = super(UserReusesView, self).get_context()
-        context['reuses'] = Reuse.objects(owner=self.user).visible()
-        return context
-
-
 @blueprint.route('/<user:user>/', endpoint='show')
-class UserActivityView(UserView, DetailView):
-    template_name = 'user/activity.html'
+class UserDetailView(UserView, DetailView):
+    template_name = 'user/base.html'
 
     def get_context(self):
         if current_user.is_anonymous or not current_user.sysadmin:
             if not self.user.active:
                 abort(410, 'User is not active')
-        context = super(UserActivityView, self).get_context()
-        context['activities'] = (Activity.objects(actor=self.object)
-                                         .order_by('-created_at').limit(15))
+        context = super(UserDetailView, self).get_context()
+        context['can_edit'] = UserEditPermission(self.user)
+
         return context
 
 
+@blueprint.route('/<user:user>/datasets/', endpoint='datasets')
+def redirect_datasets(user):
+    return redirect(url_for('users.show', user=user))
+
+
+@blueprint.route('/<user:user>/reuses/', endpoint='reuses')
+def redirect_reuses(user):
+    return redirect(url_for('users.show', user=user))
+
+
 @blueprint.route('/<user:user>/following/', endpoint='following')
 class UserFollowingView(UserView, DetailView):
     template_name = 'user/following.html'
 
     def get_context(self):
         context = super(UserFollowingView, self).get_context()
+        context['can_edit'] = UserEditPermission(self.user)
         datasets, reuses, organizations, users = [], [], [], []
 
         for follow in Follow.objects.following(self.user).select_related():
@@ -113,6 +103,7 @@ class UserFollowersView(UserView, DetailView):
 
     def get_context(self):
         context = super(UserFollowersView, self).get_context()
+        context['can_edit'] = UserEditPermission(self.user)
         context['followers'] = (Follow.objects.followers(self.user)
                                               .order_by('follower.fullname'))
         return context


Test patch:
diff --git a/udata_front/tests/views/test_user.py b/udata_front/tests/views/test_user.py
index 21f177d30..4596db824 100644
--- a/udata_front/tests/views/test_user.py
+++ b/udata_front/tests/views/test_user.py
@@ -2,11 +2,12 @@
 
 from udata.models import Follow
 from udata.core.user.factories import UserFactory, AdminFactory
-from udata.core.dataset.factories import DatasetFactory, ResourceFactory
+from udata.core.dataset.factories import DatasetFactory
 from udata.core.reuse.factories import ReuseFactory
 from udata.core.organization.factories import OrganizationFactory
 from udata_front.tests import GouvFrSettings
 from udata_front.tests.frontend import GouvfrFrontTestCase
+from udata.tests.helpers import assert_redirects
 
 
 class UserBlueprintTest(GouvfrFrontTestCase):
@@ -44,26 +45,14 @@ def test_render_profile_of_an_inactive_user_when_admin(self):
     def test_render_profile_datasets(self):
         '''It should render the user profile datasets page'''
         user = UserFactory()
-        datasets = [DatasetFactory(owner=user, resources=[ResourceFactory()])
-                    for _ in range(3)]
-        for _ in range(2):
-            DatasetFactory(resources=[ResourceFactory()])
         response = self.get(url_for('users.datasets', user=user))
-        self.assert200(response)
-        rendered_datasets = self.get_context_variable('datasets')
-        self.assertEqual(len(rendered_datasets), len(datasets))
+        assert_redirects(response, url_for('users.show', user=user))
 
     def test_render_profile_reuses(self):
         '''It should render the user profile reuses page'''
         user = UserFactory()
-        reuses = [ReuseFactory(owner=user, datasets=[DatasetFactory()])
-                  for _ in range(3)]
-        for _ in range(2):
-            ReuseFactory(datasets=[DatasetFactory()])
         response = self.get(url_for('users.reuses', user=user))
-        self.assert200(response)
-        rendered_reuses = self.get_context_variable('reuses')
-        self.assertEqual(len(rendered_reuses), len(reuses))
+        assert_redirects(response, url_for('users.show', user=user))
 
     def test_render_profile_following(self):
         '''It should render the user profile following page'''