From f77c8f9e0f407062e1d47a99ef0ef50d72843854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20Caux?= <git@jscaux.org>
Date: Sat, 12 Feb 2022 20:55:50 +0100
Subject: [PATCH] htmx-driven version of personal page

---
 scipost_django/SciPost_v1/settings/base.py    |   1 +
 scipost_django/scipost/context_processors.py  |  62 +++++
 .../static/scipost/assets/css/_tablist.scss   |  13 +
 .../static/scipost/assets/css/style.scss      |   1 +
 .../scipost/personal_page/_hx_account.html    | 219 +++++++++++++++++
 .../scipost/personal_page/_hx_admin.html      |  95 ++++++++
 .../personal_page/_hx_author_replies.html     |  25 ++
 .../personal_page/_hx_commentaries.html       |  35 +++
 .../scipost/personal_page/_hx_comments.html   |  25 ++
 .../scipost/personal_page/_hx_edadmin.html    | 125 ++++++++++
 .../personal_page/_hx_publications.html       |  41 ++++
 .../scipost/personal_page/_hx_refereeing.html | 144 +++++++++++
 .../personal_page/_hx_submissions.html        |  62 +++++
 .../scipost/personal_page/_hx_tablist.html    |  71 ++++++
 .../scipost/personal_page/_hx_theses.html     |  34 +++
 .../personal_page/personal_page_htmx.html     |  34 +++
 scipost_django/scipost/urls.py                | 169 +++++++++++--
 scipost_django/scipost/views.py               | 228 ++++++++++++++++++
 18 files changed, 1367 insertions(+), 17 deletions(-)
 create mode 100644 scipost_django/scipost/context_processors.py
 create mode 100644 scipost_django/scipost/static/scipost/assets/css/_tablist.scss
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_account.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_admin.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_author_replies.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_commentaries.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_comments.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_edadmin.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_publications.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_tablist.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/_hx_theses.html
 create mode 100644 scipost_django/scipost/templates/scipost/personal_page/personal_page_htmx.html

diff --git a/scipost_django/SciPost_v1/settings/base.py b/scipost_django/SciPost_v1/settings/base.py
index b84f985ca..310bd20ff 100644
--- a/scipost_django/SciPost_v1/settings/base.py
+++ b/scipost_django/SciPost_v1/settings/base.py
@@ -318,6 +318,7 @@ TEMPLATES = [
                 "django.template.context_processors.media",
                 "django.contrib.auth.context_processors.auth",
                 "django.contrib.messages.context_processors.messages",
+                "scipost.context_processors.roles_processor",
                 "journals.context_processors.journals_processor",
                 "ontology.context_processors.ontology_processor",
             ],
diff --git a/scipost_django/scipost/context_processors.py b/scipost_django/scipost/context_processors.py
new file mode 100644
index 000000000..cf6f2ed8d
--- /dev/null
+++ b/scipost_django/scipost/context_processors.py
@@ -0,0 +1,62 @@
+__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.contrib.auth.models import Group
+
+
+def roles_processor(request):
+    """
+    Add list of roles a User has to the context.
+    """
+    context = {
+        "user_roles": []
+    }
+    group_names = [g.name for g in Group.objects.filter(user__pk=request.user.id)]
+    # Groups-based roles
+    # if groups.filter(name="Registered Contributors").exists():
+    #     context["user_roles"].append("registered_contributor")
+    # if groups.filter(name="SciPost Administrators").exists():
+    #     context["user_roles"].append("scipost_admin")
+    # if groups.filter(name="Editorial Administrators").exists():
+    #     context["user_roles"].append("edadmin")
+    # if groups.filter(name="Financial Administrators").exists():
+    #     context["user_roles"].append("finadmin")
+    # if groups.filter(name="Advisory Board").exists():
+    #     context["user_roles"].append("advisory_board")
+    # if groups.filter(name="Vetting Editors").exists():
+    #     context["user_roles"].append("vetting_editor")
+    # if groups.filter(name="Ambassadors").exists():
+    #     context["user_roles"].append("ambassador")
+    # if groups.filter(name="Junior Ambassadors").exists():
+    #     context["user_roles"].append("junior_ambassador")
+    # if groups.filter(name="Production Officers").exists():
+    #     context["user_roles"].append("production_officer")
+    if "Registered Contributors" in group_names:
+        context["user_roles"].append("registered_contributor")
+    if "SciPost Administrators" in group_names:
+        context["user_roles"].append("scipost_admin")
+    if "Editorial Administrators" in group_names:
+        context["user_roles"].append("edadmin")
+    if "Financial Administrators" in group_names:
+        context["user_roles"].append("finadmin")
+    if "Advisory Board" in group_names:
+        context["user_roles"].append("advisory_board")
+    if "Vetting Editors" in group_names:
+        context["user_roles"].append("vetting_editor")
+    if "Ambassadors" in group_names:
+        context["user_roles"].append("ambassador")
+    if "Junior Ambassadors" in group_names:
+        context["user_roles"].append("junior_ambassador")
+    if "Production Officers" in group_names:
+        context["user_roles"].append("production_officer")
+    # Contributor-based roles
+    try:
+        active_fellowships = request.user.contributor.fellowships.active()
+        if active_fellowships.exists():
+            context["user_roles"].append("active_fellow")
+        if active_fellowships.senior().exists():
+            context["user_roles"].append("active_senior_fellow")
+    except AttributeError:
+        pass
+    return context
diff --git a/scipost_django/scipost/static/scipost/assets/css/_tablist.scss b/scipost_django/scipost/static/scipost/assets/css/_tablist.scss
new file mode 100644
index 000000000..a444fbae6
--- /dev/null
+++ b/scipost_django/scipost/static/scipost/assets/css/_tablist.scss
@@ -0,0 +1,13 @@
+.tablist {
+    border-bottom: 3px solid #eee;
+    margin-bottom: 1rem;
+}
+.tablist a {
+    display: inline-block;
+    padding: 0.5rem 1rem;
+    cursor: pointer;
+}
+.tablist a.selected {
+    background-color: #eee;
+    font-weight: bold;
+}
diff --git a/scipost_django/scipost/static/scipost/assets/css/style.scss b/scipost_django/scipost/static/scipost/assets/css/style.scss
index 604b806a4..0d2964b21 100644
--- a/scipost_django/scipost/static/scipost/assets/css/style.scss
+++ b/scipost_django/scipost/static/scipost/assets/css/style.scss
@@ -54,6 +54,7 @@
 @import "personal_page";
 @import "reports";
 @import "submissions";
+@import "tablist";
 
 
 /**
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_account.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_account.html
new file mode 100644
index 000000000..2d3eb1721
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_account.html
@@ -0,0 +1,219 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='account' %}
+
+
+{% load bootstrap %}
+{% load user_groups %}
+
+{% recommend_new_totp_device request.user as recommend_totp %}
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title mb-0">Your Account</h2>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-md-6">
+    <h3>Your personal details:</h3>
+    {% include "scipost/_private_info_as_table.html" with contributor=contributor %}
+
+    {% if contributor %}
+      {# Scientist fields #}
+      <h3 class="mt-3">Your main academic field:</h3>
+      <ul><li>{{ contributor.profile.acad_field }}</li></ul>
+
+      <h3 class="mt-3">Your specialties:</h3>
+      <ul>
+	{% for specialty in contributor.profile.specialties.all %}
+	  <li>{{ specialty }}</li>
+	{% empty %}
+          <li>You haven't listed your specialties yet.</li>
+	{% endfor %}
+      </ul>
+      <p>You can add/remove specialties by <a href="{% url 'scipost:update_personal_data' %}">updating your personal data</a>.</p>
+    {% endif %}
+  </div>
+
+  <div class="col-md-6">
+    {% if contributor %}
+      {# Scientist fields #}
+      {% if not contributor.is_currently_available %}
+        <h3 class="text-warning">You are currently unavailable</h3>
+        <p>Check your availability underneath if this should not be the case.</p>
+        <hr>
+      {% endif %}
+      {# END: Scientist fields #}
+    {% endif %}
+
+    {% if recommend_totp %}
+      <div class="border border-danger p-2 mb-3">
+        <h3>
+          <span class="text-danger">{% include 'bi/exclamation-triangle-fill.html' %}</span>
+          Please increase your account's security</h3>
+        <div>
+          Your account grants access to sensitive, confidential information.
+	  Therefore we strongly recommend to use two factor authentication, which adds
+	  an extra layer of protection to your SciPost account.
+          <br><br>
+          <a href="{% url 'scipost:totp_create' %}">Set up two factor authentication here</a>.
+        </div>
+      </div>
+    {% endif %}
+
+    <div class="border border-danger p-2">
+      <h3 class="text-danger">Scientists, please help us out!</h3>
+      <p class="mb-1">If it is not listed on our
+	<a href="{% url 'sponsors:sponsors' %}" target="_blank">Sponsors page</a>,
+	please encourage your institution (through a librarian, director, ...) to join.
+	You can use this email <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'sponsors/sponsor_petition_email.html' %}{% endautoescape %}&cc=sponsors@{{ request.get_host }}">template</a>.</p>
+    </div>
+    <hr>
+
+    {% if "scipost_admin" in user_roles %}
+      <h3>You are a SciPost Administrator.</h3>
+    {% endif %}
+
+    {% if "edadmin" in user_roles %}
+      <h3>You are a SciPost Editorial Administrator.</h3>
+    {% endif %}
+
+    {% if "advisory_board" in user_roles %}
+      <h3>You are a member of the Advisory Board.</h3>
+    {% endif %}
+
+    {% if "vetting_editor" in user_roles %}
+      <h3>You are a SciPost Vetting Editor.</h3>
+    {% endif %}
+
+    {% if "registered_contributor" in user_roles %}
+      <h3>You are a Registered Contributor.</h3>
+    {% endif %}
+
+    {% if "tester" in user_roles %}
+      <h3>You are a SciPost Tester.</h3>
+    {% endif %}
+
+    {% if "ambassador" in user_roles %}
+      <h3>You are a SciPost Ambassador.</h3>
+    {% endif %}
+
+    {% if "junior_ambassador" in user_roles %}
+      <h3>You are a SciPost Junior Ambassador.</h3>
+    {% endif %}
+
+    {% if "production_officer" in user_roles %}
+      <h3>You are a SciPost Production Officer.</h3>
+    {% endif %}
+
+    {% if contributor.fellowships.all %}
+      <h3>Your Fellowships:</h3>
+      <ul class="mb-2">
+        {% for fellowship in contributor.fellowships.all %}
+          <li class="pt-1">
+            {{ fellowship.college }}
+
+            {% if fellowship.guest %}
+	      (Guest Fellowship)
+	      <br>
+	      Your Proceedings:
+	      <ul>
+		{% for proc in fellowship.proceedings.all %}
+		  <li>{{ proc }}</li>
+		{% empty %}
+		  <li><em>No proceedings assigned yet.</em></li>
+		{% endfor %}
+	      </ul>
+	    {% elif fellowship.senior %}
+	      (Senior Fellowship)
+            {% else %}
+	      (Regular Fellowship)
+            {% endif %}
+
+            {% if not fellowship.is_active %}
+              <span class="label label-outline-warning label-sm">Inactive</span>
+            {% endif %}
+
+            {% if fellowship.start_date or fellowship.until_date %}
+              <div class="text-muted">
+                {% if fellowship.start_date %}
+                  from {{ fellowship.start_date }}
+                {% endif %}
+                {% if fellowship.until_date %}
+                  until {{ fellowship.until_date }}
+                {% endif %}
+              </div>
+            {% endif %}
+          </li>
+        {% endfor %}
+      </ul>
+      <a href="{% url 'submissions:pool' %}" class="h3 text-primary ms-4 px-3 d-block-inline">Go to the Submissions Pool</a>
+    {% endif %}
+
+    <h3 class="mt-3">Update your personal data or password</h3>
+    <ul>
+      <li><a href="{% url 'scipost:update_personal_data' %}">Update your personal data</a></li>
+      <li><a href="{% url 'scipost:password_change' %}">Change your password</a></li>
+      <li><a href="{% url 'scipost:totp' %}">Two factor authentication</a></li>
+    </ul>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-12">
+    <h3>Your Affiliations:</h3>
+    <ul>
+      <li><a href="{% url 'profiles:affiliation_create' profile_id=contributor.profile.id %}">Add a new Affiliation</a></li>
+    </ul>
+    {% include 'profiles/_affiliations_table.html' with profile=contributor.profile actions=True %}
+  </div>
+</div>
+
+{% if unavailability_form %}
+  <hr>
+  <div class="row">
+    <div class="col">
+      <h2 class="highlight">Your Availability</h2>
+    </div>
+  </div>
+  <div class="row justify-content-center">
+    <div class="col-md-4 me-md-5">
+      <p>To help with the editorial workflow, you can inform us of any periods during which you are unavailable. We will do our best to respect these.</p>
+      <h3 class="mb-3">Mark a period as unavailable:</h3>
+      <form action="{% url 'scipost:mark_unavailable_period' %}" method="post">
+        {% csrf_token %}
+        {{ unavailability_form|bootstrap }}
+        <input class="btn btn-outline-secondary" type="submit" value="Submit" />
+      </form>
+    </div>
+    <div class="col-md-4 ms-md-5">
+      {% if unavailabilities %}
+        <h3>Your unavailability periods in our records</h3>
+        <p class="text-muted">(YYYY-MM-DD)</p>
+        <table class="table">
+          <tr>
+            <th>Start</th>
+            <th colspan="2">End</th>
+          </tr>
+          {% for unav in unavailabilities %}
+            <tr>
+              <td>{{ unav.start }}</td>
+              <td>{{ unav.end }}</td>
+              <td>
+                <form action="{% url 'scipost:delete_unavailable_period' unav.id %}" method="post">
+                  {% csrf_token %}
+                  <input class="btn btn-danger" type="submit" value="Delete" />
+                </form>
+              </td>
+            </tr>
+          {% endfor %}
+        </table>
+      {% else %}
+        <p>You don't have any upcoming unavailability periods on record.</p>
+      {% endif %}
+    </div>
+  </div>
+{% endif %}
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_admin.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_admin.html
new file mode 100644
index 000000000..621e43872
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_admin.html
@@ -0,0 +1,95 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='admin' %}
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title mb-0">Admin Actions</h2>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row">
+  {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_create_registration_invitations or perms.scipost.can_resend_registration_requests or perms.scipost.can_manage_news %}
+    <div class="col-md-4">
+      <h3>Registration actions</h3>
+      <ul>
+        {% if perms.scipost.can_vet_registration_requests %}
+          <li><a href="{% url 'scipost:vet_registration_requests' %}">Vet Registration requests</a> ({{ nr_reg_to_vet }})</li>
+        {% endif %}
+        {% if perms.scipost.can_resend_registration_requests %}
+          <li><a href="{% url 'scipost:registration_requests' %}">Awaiting validation</a> ({{ nr_reg_awaiting_validation }})</li>
+        {% endif %}
+        {% if perms.scipost.can_create_registration_invitations %}
+          <li><a href="{% url 'invitations:list' %}">Manage Registration Invitations</a></li>
+        {% endif %}
+      </ul>
+
+      {% if perms.scipost.can_manage_news %}
+	<h3>News management</h3>
+	<ul>
+	  <li><a href="{% url 'news:manage' %}">Manage News Items and Newsletters</a></li>
+	</ul>
+      {% endif %}
+
+      {% if perms.scipost.can_manage_registration_invitations %}
+        <h3>Notifications</h3>
+        <ul>
+          <li><a href="{% url 'invitations:citation_notification_list' %}">Manage citation notifications</a></li>
+        </ul>
+      {% endif %}
+
+      {% if "scipost_admin" in user_roles %}
+        <h3>Email communications</h3>
+        <ul>
+          {% if perms.scipost.can_email_group_members %}
+            <li><a href="{% url 'scipost:email_group_members' %}">Email Group Members</a></li>
+          {% endif %}
+          {% if perms.scipost.can_email_particulars %}
+            <li><a href="{% url 'scipost:send_precooked_email' %}">Send a precooked email</a></li>
+            <li><a href="{% url 'scipost:email_particular' %}">Email a particular individual/address</a></li>
+          {% endif %}
+          {% if perms.scipost.can_manage_mailchimp %}
+            <li><a href="{% url 'mailing_lists:overview' %}">Manage mailing lists</a></li>
+          {% endif %}
+        </ul>
+      {% endif %}
+
+    </div>
+  {% endif %}
+
+  <div class="col-md-4">
+    {% if perms.scipost.can_view_profiles %}
+      <h3>Profiles</h3>
+      <ul>
+	<li><a href="{% url 'profiles:profiles' %}">List/Manage Profiles</a></li>
+      </ul>
+    {% endif %}
+
+    <h3>Ontology</h3>
+    <ul>
+      <li><a href="{% url 'ontology:ontology' %}">View/Manage the Ontology</a></li>
+    </ul>
+
+    {% if perms.scipost.can_manage_organizations %}
+      <h3>Organizations</h3>
+      <ul>
+	<li><a href="{% url 'organizations:organizations' %}">Manage Organizations</a></li>
+      </ul>
+    {% endif %}
+  </div>
+
+  <div class="col-md-4">
+    <h3>Finances</h3>
+    <ul>
+      {% if perms.scipost.can_manage_subsidies %}
+	<li><a href="{% url 'finances:subsidies' %}">Manage Subsidies</a></li>
+      {% endif %}
+      {% if perms.scipost.can_view_timesheets %}
+        <li><a href="{% url 'finances:timesheets' %}">Production Team Timesheets</a></li>
+      {% endif %}
+    </ul>
+  </div>
+
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_author_replies.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_author_replies.html
new file mode 100644
index 000000000..6e33291de
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_author_replies.html
@@ -0,0 +1,25 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='author_replies' %}
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title mb-0">Your Author Replies</h2>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row" id="myauthorreplieslist">
+  <div class="col-12">
+    <ul class="list-group list-group-flush">
+      {% for own_reply in own_authorreplies %}
+        <li class="list-group-item">
+          {% include 'comments/_comment_card_extended_for_author.html' with comment=own_reply %}
+        </li>
+      {% empty %}
+        <li class="list-group-item"><em>You do not have Author Replies yet.</em></li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_commentaries.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_commentaries.html
new file mode 100644
index 000000000..73b20b63f
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_commentaries.html
@@ -0,0 +1,35 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='commentaries' %}
+
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title">Commentaries</h2>
+        <ul class="mb-0">
+          {% if nr_commentary_authorships_to_claim > 0 %}
+            <li><a href="{% url 'scipost:claim_authorships' %}">Potential authorships to claim (auto-detected: {{ nr_commentary_authorships_to_claim}})</a></li>
+          {% endif %}
+          <li><a href="{% url 'commentaries:request_commentary' %}">Request opening a SciPost Commentary Page</a></li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row" id="mycommentarieslist">
+  <div class="col-12">
+    <h3>Commentaries for which you are identified as an author:</h3>
+  </div>
+  <div class="col-12">
+    <ul class="list-group list-group-flush">
+      {% for com in own_commentaries %}
+        <li class="list-group-item">
+          {% include 'commentaries/_commentary_card_content.html' with commentary=com %}
+        </li>
+      {% empty %}
+        <li class="list-group-item"><em>No Commentaries found</em></li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_comments.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_comments.html
new file mode 100644
index 000000000..e4fb89a40
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_comments.html
@@ -0,0 +1,25 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='comments' %}
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title mb-0">Your Comments</h2>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row" id="mycommentslist">
+  <div class="col-12">
+    <ul class="list-group list-group-flush">
+      {% for own_comment in own_comments %}
+        <li class="list-group-item">
+          {% include 'comments/_comment_card_extended_for_author.html' with comment=own_comment %}
+        </li>
+      {% empty %}
+        <li class="list-group-item"><em>You have not commented yet.</em></li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_edadmin.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_edadmin.html
new file mode 100644
index 000000000..e26e61ba5
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_edadmin.html
@@ -0,0 +1,125 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='edadmin' %}
+
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title mb-0">Pending Editorial Actions</h2>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row">
+
+  <div class="col-md-4">
+    {% if perms.scipost.can_vet_comments or perms.scipost.can_vet_submitted_reports %}
+      <h3>Vetting actions</h3>
+      <ul>
+        {% if perms.scipost.can_vet_commentary_requests %}
+          <li><a href="{% url 'commentaries:vet_commentary_requests' %}">Vet Commentary Page requests</a> ({{ nr_commentary_page_requests_to_vet }})</li>
+        {% endif %}
+        {% if perms.scipost.can_vet_comments %}
+          <li><a href="{% url 'comments:vet_submitted_comments_list' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li>
+        {% endif %}
+        {% if perms.scipost.can_vet_thesislink_requests %}
+          <li><a href="{% url 'theses:unvetted_thesislinks' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li>
+        {% endif %}
+        {% if perms.scipost.can_vet_authorship_claims %}
+          <li><a href="{% url 'scipost:vet_authorship_claims' %}">Vet Authorship Claims</a> ({{ nr_authorship_claims_to_vet }})</li>
+        {% endif %}
+        {% if perms.scipost.can_vet_submitted_reports %}
+          <li><a href="{% url 'submissions:vet_submitted_reports_list' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li>
+        {% endif %}
+      </ul>
+    {% endif %}
+
+    {% if perms.scipost.can_oversee_refereeing %}
+      <h3>Editorial Admin actions</h3>
+      <ul>
+        <li><a href="{% url 'submissions:reports_accepted_list' %}">Accepted Reports</a>{% if nr_reports_without_pdf %} ({{nr_reports_without_pdf}} unfinished){% endif %}</li>
+        <li><a href="{% url 'submissions:pool' %}">Submissions Pool</a></li>
+        <li><a href="{% url 'submissions:treated_submissions_list' %}">Fully treated Submissions</a>{% if nr_treated_submissions_without_pdf %} ({{nr_treated_submissions_without_pdf}} unfinished){% endif %}</li>
+        <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li>
+        <li><a href="{% url 'journals:manage_comment_metadata' %}">Manage Comment metadata</a></li>
+        <li><a href="{% url 'journals:admin_volumes_list' %}">Manage Volumes</a></li>
+        <li><a href="{% url 'journals:admin_issue_list' %}">Manage Issues</a></li>
+        <li><a href="{% url 'proceedings:proceedings' %}">Manage Proceedings Issues</a></li>
+        <li><a href="{% url 'journals:manage_metadata' %}">Manage Publication metadata</a></li>
+        <li><a href="{% url 'journals:manage_report_metadata' %}">Manage Report metadata</a></li>
+        <li><a href="{% url 'journals:manage_update_metadata' %}">Manage PublicationUpdate metadata</a></li>
+      </ul>
+    {% endif %}
+
+  </div>
+
+  {% if perms.scipost.can_oversee_refereeing or request.user.contributor.is_active_fellow %}
+    <div class="col-md-4">
+      <h3>Info</h3>
+      <ul>
+        <li><a href="{% url 'submissions:editorial_workflow' %}">How-to guide: summary of the editorial workflow</a></li>
+	<li><a href="{% url 'submissions:monitor' %}">Submissions monitor (current workflow timescales)</a></li>
+      </ul>
+
+      <h3>Submissions assignments</h3>
+      <ul>
+        <li><a href="{% url 'submissions:assignments' %}">Your assignments</a></li>
+        <li><a href="{% url 'colleges:fellowships' %}">Assignments overview</a></li>
+
+        {% if perms.scipost.can_assign_submissions %}
+          <li>Assign Submissions via the <a href="{% url 'submissions:pool' %}">Submissions Pool</a> ({{ nr_submissions_to_assign }})</li>
+        {% endif %}
+        {% if request.user.contributor.is_active_fellow %}
+          <li>Accept or decline assignments via the <a href="{% url 'submissions:pool' %}">Submissions Pool</a> ({{ nr_assignments_to_consider }})</li>
+        {% endif %}
+      </ul>
+
+      {% if perms.scipost.can_oversee_refereeing %}
+        <h3>Refereeing overview</h3>
+        <ul>
+          <li>View (and act on) outstanding refereeing invitations in the <a href="{% url 'submissions:refereeing_overview' %}">refereeing overview</a></li>
+          <li><a href="{% url 'stats:statistics' %}">View statistics</a> for submissions, refereeing, publishing</li>
+        </ul>
+        <h3>Voting</h3>
+        <ul>
+          <li>Prepare Editorial Recommendations for voting via the <a href="{% url 'submissions:pool' %}">Submissions Pool</a> ({{ nr_recommendations_to_prepare_for_voting }})</li>
+        </ul>
+      {% endif %}
+    </div>
+  {% endif %}
+
+  <div class="col-md-4">
+    {% if perms.scipost.can_manage_college_composition or perms.scipost.can_view_potentialfellowship_list %}
+      <h3>Colleges and Fellowships</h3>
+      <ul>
+	<li><a href="{% url 'colleges:colleges' %}">Editorial Colleges</a></li>
+	{% if perms.scipost.can_manage_college_composition %}
+          <li><a href="{% url 'colleges:fellowships' %}">Fellowships</a></li>
+	{% endif %}
+	<li><a href="{% url 'colleges:potential_fellowships' %}">Potential Fellowships: view{% if perms.scipost.can_manage_college_composition %} and manage{% endif %}</a></li>
+      </ul>
+    {% endif %}
+  </div>
+</div>
+
+{% if active_assignments %}
+  <div class="row">
+    <div class="col-12">
+      <h3 class="highlight">Submissions for which you are Editor-in-charge</h3>
+    </div>
+    <div class="col-12">
+      <ul class="list-group list-group-flush">
+        {% for assignment in active_assignments %}
+          <li class="list-group-item">
+            <div class="card-body px-0">
+              {% include 'submissions/_submission_card_content.html' with submission=assignment.submission %}
+              {% include 'submissions/_submission_status.html' with submission=assignment.submission %}
+              <p class="card-text mt-2">Manage this Submission from its <a href="{% url 'submissions:editorial_page' assignment.submission.preprint.identifier_w_vn_nr %}">Editorial Page</a>.</p>
+            </div>
+          </li>
+        {% endfor %}
+      </ul>
+    </div>
+  </div>
+{% endif %}
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_publications.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_publications.html
new file mode 100644
index 000000000..e3af784dd
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_publications.html
@@ -0,0 +1,41 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='publications' %}
+
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title">Publications</h2>
+        <ul class="mb-0">
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+
+
+<div class="row" id="mypublicationslist">
+  <div class="col-12">
+    <h3 class="mb-3">Publications for which you are identified as an author:</h3>
+  </div>
+  <div class="col-12">
+    <ul class="list-unstyled">
+      {% for pub in own_publications %}
+        <li>
+          <div class="card bg-light card-publication" id="{{pub.doi_label}}">
+            {% include 'journals/_publication_card_content.html' with publication=pub current_user=request.user %}
+            {% if request.user == pub.accepted_submission.submitted_by.user %}
+              {% if not pub.pubfractions_confirmed_by_authors or not pub.pubfractions_sum_to_1 %}
+                <h4 class="m-2"><a href="{% url 'journals:allocate_orgpubfractions' doi_label=pub.doi_label %}"><span class="text-danger">Intervention needed:</span> review support fractions</a></h4>
+              {% endif %}
+            {% endif %}
+          </div>
+        </li>
+      {% empty %}
+        <li>
+          <em>No Publications found</em>
+        </li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
new file mode 100644
index 000000000..5de492e1a
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
@@ -0,0 +1,144 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='refereeing' %}
+
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title">Refereeing Tasks</h2>
+        <ul class="mb-0">
+          <li><a href="{% url 'submissions:accept_or_decline_ref_invitations' %}">Accept/decline refereeing invitations</a> ({{ contributor.referee_invitations.open.count }})</li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+
+{% if contributor.reports.in_draft.all %}
+  <div class="row">
+    <div class="col-12">
+      <h3 class="highlight">Unfinished reports:</h3>
+      <ul class="list-group list-group-flush">
+        {% for report in contributor.reports.in_draft.all %}
+          <li class="list-group-item">
+            <div class="card-body px-0">
+              {% include 'submissions/_submission_li.html' with submission=report.submission %}
+              <a class="btn btn-outline-primary my-2" href="{% url 'submissions:submit_report' report.submission.preprint.identifier_w_vn_nr %}">Finish report</a>
+            </div>
+          </li>
+        {% endfor %}
+      </ul>
+    </div>
+  </div>
+{% endif %}
+
+
+<div class="row">
+  <div class="col-12">
+    <h3 class="highlight">Refereeing Invitations</h3>
+    {% if contributor.referee_invitations.all %}
+      <h3 class="mt-4">Pending Refereeing Invitations</h3>
+      {% if contributor.referee_invitations.in_process.all %}
+        <ul class="list-group list-group-flush">
+          {% for invitation in contributor.referee_invitations.in_process.all %}
+            <li class="list-group-item py-2">
+              {% include 'submissions/_submission_li.html' with submission=invitation.submission %}
+              <table>
+                <tr>
+                  <th style='min-width: 100px;'>Due:</th>
+                  <td>{{ invitation.submission.reporting_deadline|date:'d F Y' }}{% if invitation.submission.reporting_deadline_has_passed %} <span class="label label-sm label-danger ms-2 px-3">overdue</span> {% endif %}<td>
+                </tr>
+                <tr>
+                  <th>Status:</th>
+                  <td>{{ invitation.get_status_display }}</td>
+                </tr>
+                {% if invitation.accepted is not None %}
+                  <tr>
+                    <th>{{ invitation.accepted|yesno:'Accepted,Declined' }}:</th>
+                    <td>{{ invitation.date_responded }}</td>
+                  </tr>
+                {% endif %}
+                <tr>
+                  <td colspan="2">
+                    <a class="d-inline-block" href="{% url 'submissions:submit_report' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr %}">Submit your Report</a> <span class="text-blue">|</span>
+                    <a class="d-inline-block" href="{% url 'submissions:communication' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr comtype='RtoE' referee_id=request.user.contributor.id %}">Write to the Editor-in-charge</a>
+                  </td>
+                </tr>
+              </table>
+            </li>
+          {% endfor %}
+        </ul>
+      {% else %}
+        <p><em>You do not have any pending refereeing task</em></p>
+      {% endif %}
+
+      <br>
+      <h3><button type="button" class="btn btn-link p-0" data-bs-toggle="toggle" data-bs-target="#all-invitations"><small>+ See all Refereeing Invitations ({{ contributor.referee_invitations.all|length }})</small></button></h3>
+      <ul class="list-group list-group-flush ms-md-4" id="all-invitations" style="display: none;">
+        {% for invitation in contributor.referee_invitations.all %}
+          <li class="list-group-item py-2">
+            {% include 'submissions/_submission_li.html' with submission=invitation.submission %}
+            <table>
+              <tr>
+                <th style='min-width: 100px;'>Status:</th>
+                <td>{{ invitation.get_status_display }}</td>
+              </tr>
+              {% if invitation.accepted is not None %}
+                <tr>
+                  <th>{{ invitation.accepted|yesno:'Accepted,Declined' }}:</th>
+                  <td>{{ invitation.date_responded }}</td>
+                </tr>
+              {% endif %}
+              {% if invitation.related_report %}
+                <tr>
+                  <th>Report:</th>
+                  <td><a href="{{ invitation.related_report.get_absolute_url }}">{{ invitation.related_report.citation|default:'Link' }}</a></td>
+                </tr>
+              {% endif %}
+            </table>
+          </li>
+        {% endfor %}
+      </ul>
+    {% else %}
+      <p><em>You do not have any refereeing invitation</em></p>
+    {% endif %}
+  </div>
+</div>
+
+{% if contributor.reports.non_draft.all %}
+  <div class="row">
+    <div class="col-12">
+      <h3 class="highlight">Finished reports</h3>
+
+      <ul class="list-group list-group-flush">
+        {% for report in contributor.reports.non_draft.all %}
+          <li class="list-group-item">
+            {% comment %}
+            Temporary: There is already a template for a "Report summary" in a parallel (unmerged) branch. Awaiting merge to use that template.
+            {% endcomment %}
+            <div class="card-body px-0 {% block cardblock_class_block %}{% endblock %}">
+              <h3>Report on Submission <a href="{{ report.submission.get_absolute_url }}">{{ report.submission.title }}</a></h3>
+              <table>
+                <tr>
+                  <th style='min-width: 100px;'>Received:</th><td>{{ report.date_submitted|date:'Y-n-j' }}<td>
+                </tr>
+                <tr>
+                  <th>Status:</th><td {% if report.status == 'vetted' %}class="text-success"{% elif report.status == 'unvetted' %}class="text-danger"{% endif %}>{{report.get_status_display}}</td>
+                </tr>
+                {% if report.doi_label %}
+                  <tr>
+                    <th>DOI:</th>
+                    <td>{{ report.doi_string }}</td>
+                  </tr>
+                {% endif %}
+                <tr>
+                  <th>Anonymous:</th><td>{{report.anonymous|yesno:'Yes,No'}}</td>{% if report.anonymous %}<td>You can <a href="{% url 'journals:sign_existing_report' report_id=report.id %}">click here to sign this Report</a> (leads to confirmation page){% endif %}</td>
+                </tr>
+              </table>
+            </div>
+          </li>
+        {% endfor %}
+      </ul>
+    </div>
+  </div>
+{% endif %}
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
new file mode 100644
index 000000000..40351b2ff
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_submissions.html
@@ -0,0 +1,62 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='submissions' %}
+
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title">Submissions</h2>
+        <ul class="mb-0">
+          {% if nr_submission_authorships_to_claim > 0 %}
+            <li><a href="{% url 'scipost:claim_authorships' %}">Potential authorships to claim (auto-detected: {{ nr_submission_authorships_to_claim }})</a></li>
+          {% endif %}
+          <li><a href="{% url 'submissions:submit_manuscript' %}">Submit to a SciPost Journal</a></li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+
+
+<div class="row" id="mysubmissionslist">
+  <div class="col-12">
+    <h3>Submissions for which you are identified as an author:</h3>
+  </div>
+  <div class="col-12">
+    <ul class="list-group list-group-flush">
+      {% for sub in own_submissions %}
+        <li class="list-group-item">
+          <div class="card-body px-0">
+            {% include 'submissions/_submission_card_content.html' with submission=sub %}
+            {% include 'submissions/_submission_status.html' with submission=sub %}
+
+            {% if request.user.contributor == sub.submitted_by %}
+              <p class="card-text mt-1">
+		<ul>
+                  {% if sub.open_for_resubmission %}
+                    <li><a href="{% url 'submissions:submit_choose_journal' acad_field=sub.acad_field.slug %}?thread_hash={{ sub.thread_hash }}">{% include 'bi/arrow-right.html' %} resubmit</a></li>
+                  {% endif %}
+		  {% if sub.under_consideration %}
+                    {% if sub.editor_in_charge %}
+                      <li><a href="{% url 'submissions:communication' sub.preprint.identifier_w_vn_nr 'AtoE' %}">Write to the Editor-in-charge</a></li>
+                    {% endif %}
+		    {% if sub.editorial_decision %}
+		      {% if sub.editorial_decision.status == sub.editorial_decision.AWAITING_PUBOFFER_ACCEPTANCE %}
+			<li><a class="btn btn-primary my-1 px-1 py-0" href="{% url 'submissions:accept_puboffer' sub.preprint.identifier_w_vn_nr %}">Accept offer for publication in {{ sub.editorial_decision.for_journal }} (one-click action)</a></li>
+		      {% endif %}
+		    {% endif %}
+		    <li><a href="{% url 'submissions:withdraw_manuscript' sub.preprint.identifier_w_vn_nr %}"><span class="text-danger">Withdraw</span> (leads to confirmation page)</a></li>
+		  {% endif %}
+		</ul>
+              </p>
+            {% endif %}
+          </div>
+        </li>
+      {% empty %}
+        <li class="list-group-item">
+          <em>No Submissions found</em>
+        </li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_tablist.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_tablist.html
new file mode 100644
index 000000000..64904378e
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_tablist.html
@@ -0,0 +1,71 @@
+<div class="tablist">
+  <a hx-get="{% url 'scipost:personal_page_hx_account' %}"
+     {% if selected == 'account' %}class="selected"{% endif %}
+  >
+    Account
+  </a>
+  {% if "scipost_admin" in user_roles or "finadmin" in user_roles or perms.scipost.can_vet_registration_requests %}
+    <a hx-get="{% url 'scipost:personal_page_hx_admin' %}"
+       {% if selected == 'admin' %}class="selected"{% endif %}
+    >
+      Admin
+    </a>
+  {% endif %}
+  {% if "scipost_admin" in user_roles or "edadmin" in user_roles or "active_fellow" in user_roles or "advisory_board" in user_roles or "vetting_editor" in user_roles or "ambassador" in user_roles or "junior_ambassador" in user_roles %}
+    <a hx-get="{% url 'scipost:personal_page_hx_edadmin' %}"
+       {% if selected == 'edadmin' %}class="selected"{% endif %}
+    >
+      EdAdmin
+    </a>
+  {% endif %}
+  {% if perms.scipost.can_referee %}
+    <a hx-get="{% url 'scipost:personal_page_hx_refereeing' %}"
+       {% if selected == 'refereeing' %}class="selected"{% endif %}
+    >
+      Refereeing
+    </a>
+  {% endif %}
+  <a hx-get="{% url 'scipost:personal_page_hx_publications' %}"
+     {% if selected == 'publications' %}class="selected"{% endif %}
+  >
+    Publications
+  </a>
+  <a hx-get="{% url 'scipost:personal_page_hx_submissions' %}"
+     {% if selected == 'submissions' %}class="selected"{% endif %}
+  >
+    Submissions
+  </a>
+  <a hx-get="{% url 'scipost:personal_page_hx_commentaries' %}"
+     {% if selected == 'commentaries' %}class="selected"{% endif %}
+  >
+    Commentaries
+  </a>
+  <a hx-get="{% url 'scipost:personal_page_hx_theses' %}"
+     {% if selected == 'theses' %}class="selected"{% endif %}
+  >
+    Theses
+  </a>
+  {% with contributor.comments.regular_comments as regular_comments %}
+    {% if regular_comments %}
+      {% with regular_comments.awaiting_vetting.count as count %}
+	<a hx-get="{% url 'scipost:personal_page_hx_comments' %}"
+       {% if selected == 'comments' %}class="selected"{% endif %}
+	>
+	  Comments{% if count %} ({{count}} unvetted){% endif %}
+	</a>
+      {% endwith %}
+    {% endif %}
+  {% endwith %}
+  {% with contributor.comments.author_replies as replies %}
+    {% if replies %}
+      {% with replies.awaiting_vetting.count as count %}
+	<a hx-get="{% url 'scipost:personal_page_hx_author_replies' %}"
+	   {% if selected == 'author_replies' %}class="selected"{% endif %}
+	>
+	  Author Replies{% if count %} ({{count}} unvetted){% endif %}
+	</a>
+      {% endwith %}
+    {% endif %}
+  {% endwith %}
+
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_theses.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_theses.html
new file mode 100644
index 000000000..8e5fb6586
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_theses.html
@@ -0,0 +1,34 @@
+{% include 'scipost/personal_page/_hx_tablist.html' with selected='theses' %}
+
+<div class="row">
+  <div class="col-12">
+    <div class="card bg-light">
+      <div class="card-body">
+        <h2 class="card-title">Theses</h2>
+        <ul class="mb-0">
+          {% if nr_thesis_authorships_to_claim > 0 %}
+            <li><a href="{% url 'scipost:claim_authorships' %}">Potential authorships to claim (auto-detected: {{ nr_thesis_authorships_to_claim}})</a></li>
+          {% endif %}
+          <li><a href="{% url 'theses:request_thesislink' %}">Request a SciPost ThesisLink</a></li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="row" id="mytheseslist">
+  <div class="col-12">
+    <h3>Theses for which you are identified as an author:</h3>
+  </div>
+  <div class="col-12">
+    <ul class="list-group list-group-flush">
+      {% for thesis in own_thesislinks %}
+        <li class="list-group-item">
+          {% include 'theses/_thesislink_card_content.html' with thesislink=thesis %}
+        </li>
+      {% empty %}
+        <li class="list-group-item"><em>No Theses found</em></li>
+      {% endfor %}
+    </ul>
+  </div>
+</div>
diff --git a/scipost_django/scipost/templates/scipost/personal_page/personal_page_htmx.html b/scipost_django/scipost/templates/scipost/personal_page/personal_page_htmx.html
new file mode 100644
index 000000000..a74383e2b
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/personal_page/personal_page_htmx.html
@@ -0,0 +1,34 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: personal page{% endblock pagetitle %}
+
+{% block navbar %}
+  {% include 'scipost/navbar.html' %}
+{% endblock navbar %}
+
+{% block content %}
+
+  <h1 class="highlight">Welcome to your SciPost Personal Page, {{ appellation }}</h1>
+
+  {% if needs_validation %}
+    <div class="row">
+      <div class="col-12">
+        <hr>
+        <h3>Your credentials will soon be verified by an Editor.</h3>
+        <p>If accepted, you will become a registered Contributor, enabling you to submit, comment and vote.</p>
+      </div>
+    </div>
+
+  {% else %}
+
+    <div id="tablist"
+	 hx-get="{% url 'scipost:personal_page_hx_account' %}"
+	 hx-trigger="load after:100ms"
+	 hx-target="this"
+	 hx-swap="innerHTML"
+    >
+    </div>
+
+  {% endif %}
+
+{% endblock content %}
diff --git a/scipost_django/scipost/urls.py b/scipost_django/scipost/urls.py
index ef7715836..419ff30db 100644
--- a/scipost_django/scipost/urls.py
+++ b/scipost_django/scipost/urls.py
@@ -39,12 +39,24 @@ app_name = "scipost"
 
 
 urlpatterns = [
+    #
+    #######################
     # redirect for favicon
+    #######################
     path("favicon\.ico", favicon_view),
+    #
+    #############
     # Utilities:
+    #############
+    #
+    ###########################
     # Test Sentry installation
+    ###########################
     path("sentry-debug/", views.trigger_error, name="trigger_error"),
+    #
+    ###############
     # Autocomplete
+    ###############
     path(
         "group-autocomplete",
         views.GroupAutocompleteView.as_view(),
@@ -55,20 +67,29 @@ urlpatterns = [
         views.UserAutocompleteView.as_view(),
         name="user-autocomplete",
     ),
+    #
+    #########
     # Search
+    #########
     path(
         "search",
         TemplateView.as_view(template_name="search/search.html"),
         name="search",
     ),
+    #
+    ###########
     # Homepage
+    ###########
     path(
         "",  #'index_pre202202',
         views.index_pre202202,
         name="index",  # name='index_pre202202'
     ),
     path("portal", views.portal, name="portal"),
-    # HTMX-delivered fragments
+    #
+    ####################################
+    # HTMX-delivered homepage fragments
+    ####################################
     path("portal/_hx_home", views.portal_hx_home, name="portal_hx_home"),
     path("portal/_hx_journals", views.portal_hx_journals, name="portal_hx_journals"),
     path(
@@ -110,8 +131,11 @@ urlpatterns = [
         name="_hx_participates_in",
     ),
     path("_hx_sponsors", views._hx_sponsors, name="_hx_sponsors"),
-    path("files/secure/<path:path>", views.protected_serve, name="secure_file"),
+    #
+    ####################
     # General use pages
+    ####################
+    path("files/secure/<path:path>", views.protected_serve, name="secure_file"),
     path(
         "error", TemplateView.as_view(template_name="scipost/error.html"), name="error"
     ),
@@ -120,7 +144,10 @@ urlpatterns = [
         TemplateView.as_view(template_name="scipost/acknowledgement.html"),
         name="acknowledgement",
     ),
+    #
+    #######
     # Info
+    #######
     path(
         "about", TemplateView.as_view(template_name="scipost/about.html"), name="about"
     ),
@@ -166,7 +193,10 @@ urlpatterns = [
         TemplateView.as_view(template_name="scipost/privacy_policy.html"),
         name="privacy_policy",
     ),
+    #
+    ########
     # Feeds
+    ########
     path("feeds", views.feeds, name="feeds"),
     path("rss/news/", LatestNewsFeedRSS(), name="feeds_rss_news"),
     path("atom/news/", LatestNewsFeedAtom(), name="feeds_atom_news"),
@@ -209,16 +239,23 @@ urlpatterns = [
         LatestPublicationsFeedAtom(),
         name="pub_feed_spec_atom",
     ),
+    #
     ################
     # Contributors:
     ################
+    #
+    #################################
     # Contributor info (public view)
+    #################################
     path(
         "contributor/<int:contributor_id>",
         views.contributor_info,
         name="contributor_info",
     ),
+    #
+    ###############
     # Registration
+    ###############
     path("register", views.register, name="register"),
     path(
         "thanks_for_registering",
@@ -260,9 +297,15 @@ urlpatterns = [
         views.registration_requests_reset,
         name="registration_requests_reset",
     ),
+    #
+    #################################################################
     # Registration invitations (Never change this route! Thank you.)
+    #################################################################
     path("invitation/<str:key>", views.invitation, name="invitation"),
+    #
+    #################
     # Authentication
+    #################
     path("login/", views.SciPostLoginView.as_view(), name="login"),
     path("login/info/", views.raw_user_auth_info, name="login_info"),
     path("logout/", views.SciPostLogoutView.as_view(), name="logout"),
@@ -291,9 +334,66 @@ urlpatterns = [
         views.TOTPDeviceDeleteView.as_view(),
         name="totp_delete",
     ),
+    #
+    ############################################
     # Single sign-on [for GitLab: see api/urls]
+    ############################################
     path("sso_discourse", sso.discourse, name="sso_discourse"),
+    #
+    ################
     # Personal Page
+    ################
+    path("personal_page_htmx", views.personal_page_htmx, name="personal_page_htmx"),
+    path(
+        "personal_page_htmx/_hx_account",
+        views.personal_page_hx_account,
+        name="personal_page_hx_account",
+    ),
+    path(
+        "personal_page_htmx/_hx_admin",
+        views.personal_page_hx_admin,
+        name="personal_page_hx_admin",
+    ),
+    path(
+        "personal_page_htmx/_hx_edadmin",
+        views.personal_page_hx_edadmin,
+        name="personal_page_hx_edadmin",
+    ),
+    path(
+        "personal_page_htmx/_hx_refereeing",
+        views.personal_page_hx_refereeing,
+        name="personal_page_hx_refereeing",
+    ),
+    path(
+        "personal_page_htmx/_hx_publications",
+        views.personal_page_hx_publications,
+        name="personal_page_hx_publications",
+    ),
+    path(
+        "personal_page_htmx/_hx_submissions",
+        views.personal_page_hx_submissions,
+        name="personal_page_hx_submissions",
+    ),
+    path(
+        "personal_page_htmx/_hx_commentaries",
+        views.personal_page_hx_commentaries,
+        name="personal_page_hx_commentaries",
+    ),
+    path(
+        "personal_page_htmx/_hx_theses",
+        views.personal_page_hx_theses,
+        name="personal_page_hx_theses",
+    ),
+    path(
+        "personal_page_htmx/_hx_comments",
+        views.personal_page_hx_comments,
+        name="personal_page_hx_comments",
+    ),
+    path(
+        "personal_page_htmx/_hx_author_replies",
+        views.personal_page_hx_author_replies,
+        name="personal_page_hx_author_replies",
+    ),
     path("personal_page/", views.personal_page, name="personal_page"),
     path(
         "personal_page/account",
@@ -355,7 +455,10 @@ urlpatterns = [
         name="personal_page_author_replies",
         kwargs={"tab": "author_replies"},
     ),
+    #
+    ###################
     # Unavailabilities
+    ###################
     path(
         "unavailable_period",
         views.mark_unavailable_period,
@@ -366,7 +469,10 @@ urlpatterns = [
         views.delete_unavailable_period,
         name="delete_unavailable_period",
     ),
+    #
+    ####################
     # Authorship claims
+    ####################
     path("claim_authorships", views.claim_authorships, name="claim_authorships"),
     path(
         "claim_sub_authorship/<int:submission_id>/<int:claim>",
@@ -393,52 +499,70 @@ urlpatterns = [
         views.vet_authorship_claim,
         name="vet_authorship_claim",
     ),
+    #
+    #######################
     # Potential duplicates
+    #######################
     path(
         "contributor_duplicates/",
         views.ContributorDuplicateListView.as_view(),
         name="contributor_duplicates",
     ),
     path("contributor_merge/", views.contributor_merge, name="contributor_merge"),
-    ####################
-    # Email facilities #
-    ####################
+    #
+    ###################
+    # Email facilities
+    ###################
     path("email_group_members", views.email_group_members, name="email_group_members"),
     path("email_particular", views.email_particular, name="email_particular"),
     path(
         "send_precooked_email", views.send_precooked_email, name="send_precooked_email"
     ),
-    #####################
-    # Editorial College #
-    #####################
+    #
+    ####################
+    # Editorial College
+    ####################
     path("EdCol_by-laws", views.EdCol_bylaws, name="EdCol_by-laws"),
     path(
         "EdCol_by-laws_Changes_2021_04",
         views.EdCol_bylaws_Changes_2021_04,
         name="EdCol_by-laws_Changes_2021_04",
     ),
-    ################
-    # Publications #
-    ################
+    #
+    ###############
+    # Publications
+    ###############
+    #
+    ##########
     # Reports
+    ##########
     path(
         "<report_doi_label:doi_label>",
         journals_views.report_detail,
         name="report_detail",
     ),
+    #
+    ###########
     # Comments
+    ###########
     path(
         "<comment_doi_label:doi_label>",
         journals_views.comment_detail,
         name="comment_detail",
     ),
+    #
+    #################
     # Author Replies
+    #################
     path(
         "<author_reply_doi_label:doi_label>",
         journals_views.author_reply_detail,
         name="author_reply_detail",
     ),
+    #
+    ############################
     # Publication detail (+pdf)
+    ############################
     re_path(
         "^10.21468/{pattern}$".format(pattern=DOI_DISPATCH_PATTERN),
         journals_views.doi_dispatch,
@@ -469,7 +593,10 @@ urlpatterns = [
         journals_views.publication_detail_pdf,
         name="publication_pdf",
     ),
+    #
+    ######################
     # Publication updates
+    ######################
     path(
         "<publication_doi_label:doi_label>-update-<int:update_nr>",
         journals_views.publication_update_detail,
@@ -480,7 +607,10 @@ urlpatterns = [
         journals_views.publication_update_detail,
         name="publication_update_detail",
     ),
+    #
+    ################
     # Journal issue
+    ################
     path(
         "10.21468/<issue_doi_label:doi_label>",
         journals_views.issue_detail,
@@ -489,7 +619,10 @@ urlpatterns = [
     path(
         "<issue_doi_label:doi_label>", journals_views.issue_detail, name="issue_detail"
     ),
+    #
+    #######################
     # Journal landing page
+    #######################
     path(
         "10.21468/<journal_doi_label:doi_label>",
         journals_views.landing_page,
@@ -505,9 +638,10 @@ urlpatterns = [
         journals_views.arxiv_doi_feed,
         name="arxiv_doi_feed",
     ),
-    ################
-    # Howto guides #
-    ################
+    #
+    ###############
+    # Howto guides
+    ###############
     path(
         "howto", TemplateView.as_view(template_name="scipost/howto.html"), name="howto"
     ),
@@ -516,9 +650,10 @@ urlpatterns = [
         TemplateView.as_view(template_name="scipost/howto_production.html"),
         name="howto_production",
     ),
-    ########################
-    # Pwning verification #
-    ########################
+    #
+    ######################
+    # Pwning verification
+    ######################
     path(
         "have-i-been-pwned-verification.txt",
         views.have_i_been_pwned,
diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py
index 71ff52b39..6a9f82732 100644
--- a/scipost_django/scipost/views.py
+++ b/scipost_django/scipost/views.py
@@ -1192,6 +1192,234 @@ def _personal_page_author_replies(request):
     return render(request, "scipost/_personal_page_author_replies.html", context)
 
 
+@is_contributor_user()
+def personal_page_htmx(request):
+    context = {
+        "appellation": str(request.user),
+        "needs_validation": False,
+    }
+    try:
+        contributor = request.user.contributor
+        context["needs_validation"] = contributor.status != NORMAL_CONTRIBUTOR
+    except (Contributor.DoesNotExist, AttributeError):
+        if has_contact(request.user):
+            return redirect(reverse("organizations:dashboard"))
+        contributor = None
+
+    if contributor:
+        context["appellation"] = (
+            contributor.profile.get_title_display() + " " + contributor.user.last_name
+        )
+    return render(request, "scipost/personal_page/personal_page_htmx.html", context)
+
+
+@login_required
+def personal_page_hx_account(request):
+    """Personal Page tab: Account."""
+    contributor = request.user.contributor
+    context = {
+        "contributor": contributor,
+        "unavailability_form": UnavailabilityPeriodForm(),
+        "unavailabilities": contributor.unavailability_periods.future().order_by(
+            "start"
+        ),
+    }
+    return render(request, "scipost/personal_page/_hx_account.html", context)
+
+
+@login_required
+def personal_page_hx_admin(request):
+    """Personal Page tab: Admin Actions."""
+    permission = (
+        request.user.groups.filter(
+            name__in=["SciPost Administrators", "Financial Administrators"]
+        ).exists()
+        or request.user.is_superuser
+    )
+    if not permission:
+        raise PermissionDenied
+    context = {}
+    contributor = request.user.contributor
+    if contributor.is_scipost_admin:
+        # count the number of pending registration requests
+        context["nr_reg_to_vet"] = Contributor.objects.awaiting_vetting().count()
+        context[
+            "nr_reg_awaiting_validation"
+        ] = Contributor.objects.awaiting_validation().count()
+    return render(request, "scipost/personal_page/_hx_admin.html", context)
+
+
+@login_required
+def personal_page_hx_edadmin(request):
+    """
+    Personal Page tab: Editorial Actions.
+    """
+    permission = (
+        request.user.groups.filter(
+            name__in=[
+                "Ambassadors",
+                "Advisory Board",
+                "Editorial Administrators",
+                "Editorial College",
+                "Vetting Editors",
+                "Junior Ambassadors",
+            ]
+        ).exists()
+        or request.user.is_superuser
+    )
+    permission = permission or request.user.contributor.is_active_fellow()
+    if not permission:
+        raise PermissionDenied
+    context = {}
+    contributor = request.user.contributor
+    if contributor.is_scipost_admin:
+        context["nr_submissions_to_assign"] = Submission.objects.prescreening().count()
+        context[
+            "nr_recommendations_to_prepare_for_voting"
+        ] = EICRecommendation.objects.voting_in_preparation().count()
+    if contributor.is_vetting_editor:
+        context["nr_commentary_page_requests_to_vet"] = (
+            Commentary.objects.awaiting_vetting()
+            .exclude(requested_by=contributor)
+            .count()
+        )
+        context["nr_comments_to_vet"] = Comment.objects.awaiting_vetting().count()
+        context[
+            "nr_thesislink_requests_to_vet"
+        ] = ThesisLink.objects.awaiting_vetting().count()
+        context[
+            "nr_authorship_claims_to_vet"
+        ] = AuthorshipClaim.objects.awaiting_vetting().count()
+    if contributor.is_active_fellow:
+        context[
+            "nr_assignments_to_consider"
+        ] = contributor.editorial_assignments.invited().count()
+        context["active_assignments"] = contributor.editorial_assignments.ongoing()
+        context["nr_reports_to_vet"] = (
+            Report.objects.awaiting_vetting()
+            .filter(submission__editor_in_charge=contributor)
+            .count()
+        )
+    if contributor.is_ed_admin:
+        context["nr_reports_without_pdf"] = (
+            Report.objects.accepted().filter(pdf_report="").count()
+        )
+        context["nr_treated_submissions_without_pdf"] = (
+            Submission.objects.treated().public().filter(pdf_refereeing_pack="").count()
+        )
+    return render(request, "scipost/personal_page/_hx_edadmin.html", context)
+
+
+@login_required
+def personal_page_hx_refereeing(request):
+    context = {"contributor": request.user.contributor}
+    return render(request, "scipost/personal_page/_hx_refereeing.html", context)
+
+
+@login_required
+def personal_page_hx_publications(request):
+    """
+    Personal Page tab: Publications.
+    """
+    contributor = request.user.contributor
+    context = {
+        "contributor": contributor,
+        "own_publications": contributor.profile.publications()
+        .published()
+        .order_by("-publication_date"),
+    }
+    return render(request, "scipost/personal_page/_hx_publications.html", context)
+
+
+@login_required
+def personal_page_hx_submissions(request):
+    """
+    Personal Page tab: Submissions.
+    """
+    contributor = request.user.contributor
+    context = {"contributor": contributor}
+
+    context["nr_submission_authorships_to_claim"] = (
+        Submission.objects.filter(author_list__contains=request.user.last_name)
+        .exclude(authors=contributor)
+        .exclude(authors_claims=contributor)
+        .exclude(authors_false_claims=contributor)
+        .count()
+    )
+    context["own_submissions"] = contributor.submissions.filter(
+        is_current=True
+    ).order_by("-submission_date")
+    return render(request, "scipost/personal_page/_hx_submissions.html", context)
+
+
+@login_required
+def personal_page_hx_commentaries(request):
+    """
+    Personal Page tab: Commentaries.
+    """
+    contributor = request.user.contributor
+    context = {"contributor": contributor}
+
+    context["nr_commentary_authorships_to_claim"] = (
+        Commentary.objects.filter(author_list__contains=request.user.last_name)
+        .exclude(authors=contributor)
+        .exclude(authors_claims=contributor)
+        .exclude(authors_false_claims=contributor)
+        .count()
+    )
+    context["own_submissions"] = contributor.commentaries.order_by("-latest_activity")
+    return render(request, "scipost/personal_page/_hx_commentaries.html", context)
+
+
+@login_required
+def personal_page_hx_theses(request):
+    """
+    Personal Page tab: Theses.
+    """
+    contributor = request.user.contributor
+    context = {"contributor": contributor}
+
+    context["nr_thesis_authorships_to_claim"] = (
+        ThesisLink.objects.filter(author__contains=request.user.last_name)
+        .exclude(author_as_cont=contributor)
+        .exclude(author_claims=contributor)
+        .exclude(author_false_claims=contributor)
+        .count()
+    )
+    context["own_thesislinks"] = contributor.theses.all()
+    return render(request, "scipost/personal_page/_hx_theses.html", context)
+
+
+@login_required
+def personal_page_hx_comments(request):
+    """
+    Personal Page tab: Comments.
+    """
+    contributor = request.user.contributor
+    context = {
+        "contributor": contributor,
+        "own_comments": contributor.comments.regular_comments().order_by(
+            "-date_submitted"
+        ),
+    }
+    return render(request, "scipost/personal_page/_hx_comments.html", context)
+
+
+@login_required
+def personal_page_hx_author_replies(request):
+    """
+    Personal Page tab: Author Replies.
+    """
+    contributor = request.user.contributor
+    context = {
+        "contributor": contributor,
+        "own_authorreplies": contributor.comments.author_replies().order_by(
+            "-date_submitted"
+        ),
+    }
+    return render(request, "scipost/personal_page/_hx_author_replies.html", context)
+
+
 @login_required
 def personal_page(request, tab="account"):
     """
-- 
GitLab