diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 1080c51c757975e2b6ef8e3a9e05b1fc02f14818..ed4b7571de273d1c687b568c076f6b2ae164c8cb 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,3 +1,24 @@
-Fixes #ISSUEID
+<!--
+Thanks for contributing to Hypha!
 
-Add description here.
+Please ensure your contributions pass all necessary linting/testing and that the appropriate documentation has been updated.
+-->
+
+## Description
+<!--
+Describe briefly what your pull request changes. If this is resoving an issue, please specify below via "Fixes #<Github Issue ID>"
+-->
+Fixes #ISSUEID.
+
+
+## Test Steps
+<!-- 
+If step does not require manual testing, skip/remove this section.
+
+Give a brief overview of the steps required for a user/dev to test this contribution. Important things to include:
+ - Required user roles for where neccesary (ie. "As a Staff Admin...")
+ - Clear & validatable expected results (ie. "Confirm the submit button is now not clickable")
+ - Langauge that can be understood by non-technical testers if being tested by users
+-->
+
+ - [ ] ...
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index fc0e89bc604c2a0adc1c76f2dc897a2397011149..9b6212a2e105eee09b489111e2c42921caa0c076 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -18,6 +18,8 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
 
       - name: Deploy docs
         uses: mhausenblas/mkdocs-deploy-gh-pages@master
diff --git a/.github/workflows/hypha-ci.yml b/.github/workflows/hypha-ci.yml
index fbbc17b6e96f51a850b5ad8bc60e715243c46eaf..e3d0378f0eb053be1cd24bbbffe6a695c954e8d9 100644
--- a/.github/workflows/hypha-ci.yml
+++ b/.github/workflows/hypha-ci.yml
@@ -67,11 +67,11 @@ jobs:
         with:
           python-version-file: ".python-version"
       - name: Install python dependencies
-        run: pip install `grep -E "ruff|djhtml|black" requirements-dev.txt`
-      - name: Run ruff
+        run: pip install `grep -E "ruff|djhtml" requirements-dev.txt`
+      - name: Run linting
         run: ruff check --output-format=github .
-      - name: Run black
-        run: black . --check
+      - name: Run formating check
+        run: ruff format --check .
       - name: Run djhtml
         run: djhtml hypha/ --check
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c5bee231369855d6f829fbdf1abd3242627890b9..da61f5362f13dd2fb2736d6787230debf546d937 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,22 +2,18 @@ repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
     rev: v0.1.7
     hooks:
+      # Run the linter.
       - id: ruff
         args: [--fix, --exit-non-zero-on-fix]
-  - repo: https://github.com/psf/black
-    rev: 23.11.0
-    hooks:
-      - id: black
-        # It is recommended to specify the latest version of Python
-        # supported by your project here, or alternatively use
-        # pre-commit's default_language_version, see
-        # https://pre-commit.com/#top_level-default_language_version
-        language_version: python3.11
+      # Run the formatter.
+      - id: ruff-format
+
   - repo: https://github.com/rtts/djhtml
     rev: "3.0.6"
     hooks:
       - id: djhtml
         files: .*/templates/.*\.html$
+
   - repo: https://github.com/pre-commit/mirrors-prettier
     rev: v3.1.0
     hooks:
diff --git a/.prettierignore b/.prettierignore
index ddc6779e198b24ff29a16d9e2cb751bebae81364..bed37e35bd97313835f446430954d0fe107a9aaf 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -11,10 +11,12 @@
 # Ignore all files in the static & media directory.
 static_compiled/**
 media/**
+/static/**
 
 # Ignore all files in the virtualenv directory.
 .venv/**
 venv/**
+.ruff_cache/**
 
 # Ignore node files
 node_modules/**
diff --git a/Makefile b/Makefile
index e9c5c50a18097b855c342dee89ec0c3e2f724ce7..3b313d99659031d94ca5a3e37af05e217da574a3 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ build:
 fmt:
 	@echo "run code formatters on all code."
 	python -m ruff --fix .
-	python -m black .
+	python -m ruff format .
 	npx prettier . --write
 	djhtml hypha/
 
@@ -52,7 +52,7 @@ endif
 lint:
 	@echo "Checking python code style with ruff"
 	ruff check .
-	black . --check
+	ruff format --check .
 	@echo "Checking html file indendation."
 	djhtml hypha/ --check
 	@echo "Checking js and css code style."
diff --git a/docs/references/workflows.md b/docs/references/workflows.md
index f84975ed04b72500075e0fb8da5f36bf1aaefde7..a5f1f84749d5cd8b56a2e1bba1003112509b5d40 100644
--- a/docs/references/workflows.md
+++ b/docs/references/workflows.md
@@ -1,22 +1,31 @@
-A workflow describes the process from a submitted application, through various stages of review, to acceptance of an application.
+The **Workflow** defines the stages and processes that the application should undertake. Creating a new Fund or Lab requires you to select one of these workflows.
 
-> ℹ️ _Hypha covers more than just the application phase, but workflows are used in the application process only._
+Each workflow has a predetermined amount of stages (e.g. request, proposal), application forms, review forms, and determination forms associated with this Fund or Lab.
+
+Each workflow offers different statuses (e.g. External Review, Ready for Determination), and different actions (e.g Invite to proposal).
+
+!!! info
+    Hypha covers more than just the application phase, but workflows are used in the application process only.
 
 
 ## What are the 4 workflows?
 
-1. Request
-2. Request with external review
-3. Request with community review
-4. Concept and proposal
+1. [Request](#request)
+2. [Request with external review](#request-with-external-review)
+3. [Request with community review](#request-with-community-review)
+4. [Concept and Proposal](#concept-and-proposal)
 
 All workflows begin with applicant drafting, revising and submitting an application (`DRAFT_STATE`) — only transition available is to `INITIAL_STATE`, upon applicant taking the action to submit their application/request.
 
-### Request
+### 💁 Request
 
-The request workflow is a single stage process with no advisory council review.
+The request workflow is a single stage process with no advisory council review. This application process requires less time and effort than the other workflow processes.
 
-Proposal Persona: Funding organization offers a rapid response fund or another type of grantmaking that requires a streamline process that does not require an external review process. This application process could also be used for in-kind services like coaching, security audits, etc.
+**Proposal Persona:**
+
+Funding organization offers a rapid response fund or another type of grantmaking that requires a streamline process that does not require an external review process. This application process could also be used for in-kind services like coaching, security audits, etc.
+
+![Screenshot 2022-09-06 8 10 17 PM](https://user-images.githubusercontent.com/20019656/188733678-a926ebdd-8b8d-46a6-bfb7-a35dbe376ec1.png)
 
 ![Request workflow flowchart](../assets/workflow1.png)
 
@@ -31,13 +40,15 @@ Once an application is submitted (`INITIAL_STATE`) — it can transition into th
 - Accepted (`accepted`) — application accepted. Staff can still edit this submission.
 - Rejected (`rejected`) — application rejected. Permissions removed from all roles.
 
-### Request with external review
+### 👳 Request with external review
 
 This workflow is a single stage process with an advisory council review or external review stage -- includes functionalties for external reviewers like advisory board members to access applications and submit reviews.
 
 Proposal Persona: This funding organization relies on external partners for evaluations. Proposals submitted to this workflow are reviewed by staff members and an advisory board that is made up of trusted community members.
 
-![Request workflow flowchart](../assets/workflow2.png)
+![Screenshot 2022-09-06 10 40 41 PM](https://user-images.githubusercontent.com/20019656/188733916-d2133858-ee47-49d3-a63c-a78001be75c5.png)
+
+![Flow chart of Request with external review workflow](../assets/workflow2.png)
 
 Once an application is submitted (`INITIAL_STATE`) — it can transition into the following:
 
@@ -53,11 +64,15 @@ Once an application is submitted (`INITIAL_STATE`) — it can transition into th
 - Accepted (`ext_accepted`) — application accepted. Staff can still edit this submission.
 - Rejected (`ext_rejected`) — application rejected. Permissions removed from all roles.
 
-### Request with community review
+### 👪 Request with community review
+
+This workflow is a single stage application process with functionalties for external reviewers, including applicants to carry out peer review of each other applications.
 
-This workflow is a single stage process with an advisory council review or external review stage.
+**Proposal Persona:** 
+
+This funding organization works with the community to co-design a meaningful definition of success. Applications are reviewed by staff members and an advisory board that is made up of trusted community members.
 
-Proposal Persona: This funding organization works with the community to co-design a meaningful definition of success. Applications are reviewed by staff members and an advisory board that is made up of trusted community members.
+![Screenshot 2022-09-06 10 41 24 PM](https://user-images.githubusercontent.com/20019656/188734015-69b4890c-d9f9-4b60-b326-88acedff3f76.png)
 
 ![Request workflow flowchart](../assets/workflow3.png)
 
@@ -77,16 +92,28 @@ Once an application is submitted (`INITIAL_STATE`) — it can transition into th
 - Accept application but additional information is required (`com_almost`) — can transition to accepting application (`com_accepted`) or revert back to ready for discussion (`com_post_external_review_discussion`)
 - Reject application (`com_rejected`)
 
-### Concept and Proposal
+### 💡 Concept and Proposal
 
 This workflow is a two-stage process: the first stage is the request and the second stage includes an advisory council review or external review stage.
 
-Proposal Persona: This application process is continually informed by feedback from grantee partners and community members. Applicants could use the workflow to follow the trajectory of the submission process as this workflow is transparent from the concept note (first stage) all the way to the proposal (second stage) with prospective and current applicants about funding priorities and decisions.
+**Proposal Persona:** 
+
+This application process is continually informed by feedback from grantee partners and community members. Applicants could use the workflow to follow the trajectory of the submission process as this workflow is transparent from the concept note (first stage) all the way to the proposal (second stage) with prospective and current applicants about funding priorities and decisions.
 The proposal stage has functionalities for applications to be reviewed by staff members and an advisory board that is made up of trusted community members.
 
+
 ![Request workflow flowchart](../assets/workflow4.1.png)
 ![Request workflow flowchart](../assets/workflow4.2.png)
 
+**Stage 1**
+
+![Screenshot 2022-09-06 10 41 55 PM](https://user-images.githubusercontent.com/20019656/188734145-34091645-7d43-4c04-bbae-85aaec84fa44.png)
+
+**Stage 2**
+
+![Screenshot 2022-09-06 10 42 11 PM](https://user-images.githubusercontent.com/20019656/188734136-aca2acae-8d42-4c39-8dd0-b77b58bb5b9e.png)
+
+
 Once an application is submitted (`INITIAL_STATE`) — it can transition into the following:
 
 - A request for more information (`concept_more_info`) — opens editing permissions to applicant again to revise their application to provide the information requested by the screeners
@@ -109,59 +136,3 @@ Once an application is submitted (`INITIAL_STATE`) — it can transition into th
 - Proposal accepted (`proposal_accepted`)
 - Proposal accepted but additional info required (`proposal_almost`)
 - Proposal rejected (`proposal_rejected`)
-
-
-# Select a Workflow
-
-The **Workflow** defines the stages and processes that the application should undertake with OTF. Creating a new Fund or Lab requires you to select one of these workflows.
-
-Each workflow has a predetermined amount of stages (e.g. request, proposal), application forms, review forms, and determination forms associated with this Fund or Lab.
-
-Each workflow offers different statuses (e.g. External Review, Ready for Determination), and different actions (e.g Invite to proposal).
-
-The four hard-coded workflows are: Request, Request with external review, Request with community review, Concept & Proposal.
-
-### 💁 Request
-
-This application process requires less time and effort than the other workflow processes. This workflow has only a single stage with no external review. 
-
-**Proposal Persona**
-Funding organization offers a rapid response fund or another type of grantmaking that requires a streamline process that does not require an external review process. This application process could also be used for in-kind services like coaching, security audits, etc. 
-
-
-![Screenshot 2022-09-06 8 10 17 PM](https://user-images.githubusercontent.com/20019656/188733678-a926ebdd-8b8d-46a6-bfb7-a35dbe376ec1.png)
-
-
-### 👳 Request with External Reviewer
-
-The Request - External Reviewer workflow is a single stage application process with functionalties for external reviewers like advisory board members to access applications and submit reviews.
-
-**Proposal Persona**
-This funding organization relies on external partners for evaluations. Proposals submitted to this workflow are reviewed by staff members and an advisory board that is made up of trusted community members. 
-
-![Screenshot 2022-09-06 10 40 41 PM](https://user-images.githubusercontent.com/20019656/188733916-d2133858-ee47-49d3-a63c-a78001be75c5.png)
-
-
-### 👪 Request with Community Review
-
-The Request with Community Review workflow is a single stage application process with functionalties for external reviewers, including applicants to carry out peer review of each other applications.
-
-**Proposal Persona**
-This funding organization works with the community to co-design a meaningful definition of success. Applications are reviewed by staff members and an advisory board that is made up of trusted community members.
-
-![Screenshot 2022-09-06 10 41 24 PM](https://user-images.githubusercontent.com/20019656/188734015-69b4890c-d9f9-4b60-b326-88acedff3f76.png)
-
-
-### 💡Concept and Proposal
-
-**Proposal Persona**
-Applicants could use this workflow to follow the trajectory of the submission process as this workflow is transparent from the concept note (first stage) all the way to the proposal (second stage) with prospective and current applicants about funding priorities and decisions. The proposal stage has functionalities for applications to be reviewed by staff members and an advisory board that is made up of trusted community members.
-
-**Stage 1**
-
-![Screenshot 2022-09-06 10 41 55 PM](https://user-images.githubusercontent.com/20019656/188734145-34091645-7d43-4c04-bbae-85aaec84fa44.png)
-
-**Stage 2**
-
-![Screenshot 2022-09-06 10 42 11 PM](https://user-images.githubusercontent.com/20019656/188734136-aca2acae-8d42-4c39-8dd0-b77b58bb5b9e.png)
-
diff --git a/docs/setup/administrators/configuration.md b/docs/setup/administrators/configuration.md
index 0e13424a6c33f47047f47074e8f80d1cd3095ea8..bf9301b48d9f2d6c457985b3802d2b260e7bc892 100644
--- a/docs/setup/administrators/configuration.md
+++ b/docs/setup/administrators/configuration.md
@@ -160,6 +160,14 @@ Possible values are: fund, round, status, lead, reviewers, screening_statuses, c
 
     SUBMISSIONS_TABLE_EXCLUDED_FIELDS = env.list('SUBMISSIONS_TABLE_EXCLUDED_FIELDS', [])
 
+### Default visibility for reviews.
+
+Possible values are: 'reviewers' or 'private'. 
+Private: Visible only to staff.
+Reviewers: Visible to other reviewers and staff.
+
+    REVIEW_VISIBILITY_DEFAULT = env.str('REVIEW_VISIBILITY_DEFAULT', 'private')
+
 ### Should submission automatically transition after all reviewer roles are assigned.
 
     TRANSITION_AFTER_ASSIGNED = env.bool('TRANSITION_AFTER_ASSIGNED', False)
diff --git a/docs/setup/deployment/email-dns.md b/docs/setup/deployment/email-dns.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ab650367af69d7575f43ece8fa040debf6d2b64
--- /dev/null
+++ b/docs/setup/deployment/email-dns.md
@@ -0,0 +1,49 @@
+# DNS
+
+In order to operate, Hypha presents two different interfaces.  One is a general
+website frontend, and the other is the application interface. A future version
+of Hypha will remove that general website frontend, so we ignore it in setting
+up new Hypha deploys.
+
+The application interface operates on its own subdomain.  Typically, this is
+`apply.example.com`.  We will provide you with an IP address.  We ask that you
+add an A record to your DNS configuration so that requests to
+`apply.example.com` get routed to that provided IP address.  If you prefer,
+'apply' can be replaced with another subdomain.
+
+# Email
+
+Hypha sends email to its users for password management and other purposes.  In
+order to accomplish this, it needs to be able to send email from a valid email
+address at a valid domain.  There are three possible domains for our purposes.
+We present them from easiest and most favorable to least desirable.
+
+ * `example.com` - we can send mail from your main domain.  The big advantage of
+   this is that you probably already have mail working for your domain.  All you
+   need to do is create a user account (e.g. `apply@example.com` or
+   `bot@example.com`) and give us password access to that account.  Note that
+   for gmail-backed email with 2FA enabled, we would need an "App-specific
+   password" because Hypha cannot do two-factor login into your email.
+
+ * `apply.example.com` - we can send mail from the application interface's
+   domain.  This is clear enogh to users but might require significant setup for
+   you.  We would need you to configure your mail server to send mail from
+   `apply.example.com` and configure your DNS to validate that mail (via DKIM
+   and SPF).
+
+ * `opentechstrategies.com` - we can send email from Open Tech Strategies.  This
+   has the benefit of requiring no work on your side to create a valid email
+   address and provide us with credentials.  This has the downside of hypha
+   emails coming from an unexpected domain, which will confuse users.
+
+In addition to the above, hypha has support for mailgun.  We can delegate
+sending mail to mailgun fairly easily.  If you are already using mailgun, we'll
+hook into it.  If not, the additional work of setting up mailgun might not be
+worth the effort here.
+
+Note that no matter what domain Hypha sends from, it is important that email be
+configured correctly so it does not often get mis-identified as spam.  This
+usually requires setting DKIM and SPF records.  If you are using gmail-based
+email, you probably already have this set, but please check your DNS settings to
+make sure.
+
diff --git a/docs/setup/deployment/questionaire.md b/docs/setup/deployment/questionaire.md
new file mode 100644
index 0000000000000000000000000000000000000000..03a0a25a851571669d049bc6c4e0f59d6be1602d
--- /dev/null
+++ b/docs/setup/deployment/questionaire.md
@@ -0,0 +1,9 @@
+# Deadlines
+
+By when do you need this site live?  Our team needs a minimum of three weeks to
+deploy a new Hypha instance, assuming all goes smoothly.
+
+# Appearance
+
+Does your site include any right-to-left fonts or text?  If so, please allow extra setup time for adjusting Hypha.
+
diff --git a/docs/setup/deployment/stand-alone.md b/docs/setup/deployment/stand-alone.md
index cda69916bdee03fc7500313f5cdc54b9eea14ca4..d35e01461e640f7b8bb68079519ed9eabf12a158 100644
--- a/docs/setup/deployment/stand-alone.md
+++ b/docs/setup/deployment/stand-alone.md
@@ -201,8 +201,6 @@ BASIC_AUTH_PASSWORD:                           [PASS]
 BASIC_AUTH_WHITELISTED_HTTP_HOSTS:             www.example.org,apply.example.org
 CLOUDFLARE_API_ZONEID:                         [KEY]
 CLOUDFLARE_BEARER_TOKEN:                       [KEY]
-MAILCHIMP_API_KEY:                             [KEY]-us10
-MAILCHIMP_LIST_ID:                             [ID]
 MAILGUN_API_KEY:                               [KEY]
 SEND_READY_FOR_REVIEW:                         false
 SLACK_DESTINATION_ROOM:                        #notify
diff --git a/hypha/apply/activity/forms.py b/hypha/apply/activity/forms.py
index ec7fc37632b2081c94c99de72e7aa55e006b855d..5d07d9c4d427787281021251902c2599f12550d2 100644
--- a/hypha/apply/activity/forms.py
+++ b/hypha/apply/activity/forms.py
@@ -1,10 +1,17 @@
 from django import forms
+from django.db import transaction
+from django.utils.translation import gettext_lazy as _
+from django_file_form.forms import FileFormMixin
 from pagedown.widgets import PagedownWidget
 
-from .models import Activity
+from hypha.apply.stream_forms.fields import MultiFileField
 
+from .models import Activity, ActivityAttachment
+
+
+class CommentForm(FileFormMixin, forms.ModelForm):
+    attachments = MultiFileField(label=_("Attachments"), required=False)
 
-class CommentForm(forms.ModelForm):
     class Meta:
         model = Activity
         fields = ("message", "visibility")
@@ -33,3 +40,14 @@ class CommentForm(forms.ModelForm):
             visibility.choices = self.visibility_choices
             visibility.initial = visibility.initial[0]
             visibility.widget = forms.HiddenInput()
+
+    @transaction.atomic
+    def save(self, commit=True):
+        instance = super().save(commit=True)
+        added_files = self.cleaned_data["attachments"]
+        if added_files:
+            ActivityAttachment.objects.bulk_create(
+                ActivityAttachment(activity=instance, file=file) for file in added_files
+            )
+
+        return instance
diff --git a/hypha/apply/activity/messaging.py b/hypha/apply/activity/messaging.py
index 31e69cf7a0e90430b054ffdbbe4c62b714b3f567..10606c97ba74d8ef4c4a96b511680b1626b2bead 100644
--- a/hypha/apply/activity/messaging.py
+++ b/hypha/apply/activity/messaging.py
@@ -35,7 +35,7 @@ class MessengerBackend:
                     user=user,
                     source=source,
                     related=related,
-                    **kwargs
+                    **kwargs,
                 )
 
         elif sources:
@@ -51,7 +51,7 @@ class MessengerBackend:
                     user=user,
                     sources=sources,
                     related=related,
-                    **kwargs
+                    **kwargs,
                 )
 
 
diff --git a/hypha/apply/activity/migrations/0078_activityattachment.py b/hypha/apply/activity/migrations/0078_activityattachment.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8f5903a2530d190b4f899b2132e20d09e821dee
--- /dev/null
+++ b/hypha/apply/activity/migrations/0078_activityattachment.py
@@ -0,0 +1,49 @@
+# Generated by Django 4.2.8 on 2023-12-18 09:35
+
+import django.core.files.storage
+from django.db import migrations, models
+import django.db.models.deletion
+import hypha.apply.activity.models
+import uuid
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("activity", "0077_remove_deleted_submission_actions"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="ActivityAttachment",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "uuid",
+                    models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
+                ),
+                (
+                    "file",
+                    models.FileField(
+                        storage=django.core.files.storage.FileSystemStorage(),
+                        upload_to=hypha.apply.activity.models.get_attachment_upload_path,
+                    ),
+                ),
+                (
+                    "activity",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="attachments",
+                        to="activity.activity",
+                    ),
+                ),
+            ],
+        ),
+    ]
diff --git a/hypha/apply/activity/models.py b/hypha/apply/activity/models.py
index 0e438c7061a0eb791c3be794c7ecf62b970ffa44..321d078ae0c8452714741635eac22b9603ad615f 100644
--- a/hypha/apply/activity/models.py
+++ b/hypha/apply/activity/models.py
@@ -1,12 +1,19 @@
+import os
+import uuid
+
 from django.conf import settings
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.db.models import Case, Value, When
 from django.db.models.functions import Concat
+from django.urls import reverse
 from django.utils import timezone
+from django.utils.text import get_valid_filename
 from django.utils.translation import gettext as _
 
+from hypha.apply.utils.storage import PrivateStorage
+
 from .options import MESSAGES
 
 COMMENT = "comment"
@@ -95,6 +102,33 @@ class ActionManager(ActivityBaseManager):
     type = ACTION
 
 
+def get_attachment_upload_path(instance, filename):
+    return f"activity/attachments/{instance.id}/{get_valid_filename(filename)}"
+
+
+class ActivityAttachment(models.Model):
+    wagtail_reference_index_ignore = True
+
+    uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
+
+    activity = models.ForeignKey(
+        "Activity", on_delete=models.CASCADE, related_name="attachments"
+    )
+    file = models.FileField(
+        upload_to=get_attachment_upload_path, storage=PrivateStorage()
+    )
+
+    @property
+    def filename(self):
+        return os.path.basename(self.file.name)
+
+    def __str__(self):
+        return self.filename
+
+    def get_absolute_url(self):
+        return reverse("activity:attachment", kwargs={"file_pk": str(self.uuid)})
+
+
 class Activity(models.Model):
     timestamp = models.DateTimeField()
     type = models.CharField(choices=ACTIVITY_TYPES.items(), max_length=30)
diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py
index 117727d85734e3a18584fd615a3a83d8dffa9716..5aa17b9d57e67ebabefad81f7c684f1cadc64630 100644
--- a/hypha/apply/activity/options.py
+++ b/hypha/apply/activity/options.py
@@ -14,8 +14,9 @@ class MESSAGES(TextChoices):
     TRANSITION = "TRANSITION", _("transitioned")
     BATCH_TRANSITION = "BATCH_TRANSITION", _("batch transitioned")
     DETERMINATION_OUTCOME = "DETERMINATION_OUTCOME", _("sent determination outcome")
-    BATCH_DETERMINATION_OUTCOME = "BATCH_DETERMINATION_OUTCOME", _(
-        "sent batch determination outcome"
+    BATCH_DETERMINATION_OUTCOME = (
+        "BATCH_DETERMINATION_OUTCOME",
+        _("sent batch determination outcome"),
     )
     INVITED_TO_PROPOSAL = "INVITED_TO_PROPOSAL", _("invited to proposal")
     REVIEWERS_UPDATED = "REVIEWERS_UPDATED", _("updated reviewers")
@@ -23,8 +24,9 @@ class MESSAGES(TextChoices):
     PARTNERS_UPDATED = "PARTNERS_UPDATED", _("updated partners")
     PARTNERS_UPDATED_PARTNER = "PARTNERS_UPDATED_PARTNER", _("partners updated partner")
     READY_FOR_REVIEW = "READY_FOR_REVIEW", _("marked ready for review")
-    BATCH_READY_FOR_REVIEW = "BATCH_READY_FOR_REVIEW", _(
-        "marked batch ready for review"
+    BATCH_READY_FOR_REVIEW = (
+        "BATCH_READY_FOR_REVIEW",
+        _("marked batch ready for review"),
     )
     NEW_REVIEW = "NEW_REVIEW", _("added new review")
     COMMENT = "COMMENT", _("added comment")
@@ -44,8 +46,9 @@ class MESSAGES(TextChoices):
     APPROVE_PAF = "APPROVE_PAF", _("approved paf")
     PROJECT_TRANSITION = "PROJECT_TRANSITION", _("transitioned project")
     REQUEST_PROJECT_CHANGE = "REQUEST_PROJECT_CHANGE", _("requested project change")
-    SUBMIT_CONTRACT_DOCUMENTS = "SUBMIT_CONTRACT_DOCUMENTS", _(
-        "submitted contract documents"
+    SUBMIT_CONTRACT_DOCUMENTS = (
+        "SUBMIT_CONTRACT_DOCUMENTS",
+        _("submitted contract documents"),
     )
     UPLOAD_DOCUMENT = "UPLOAD_DOCUMENT", _("uploaded document to project")
     REMOVE_DOCUMENT = "REMOVE_DOCUMENT", _("removed document from project")
@@ -66,8 +69,9 @@ class MESSAGES(TextChoices):
     DELETE_REMINDER = "DELETE_REMINDER", _("deleted reminder")
     REVIEW_REMINDER = "REVIEW_REMINDER", _("reminder to review")
     BATCH_DELETE_SUBMISSION = "BATCH_DELETE_SUBMISSION", _("batch deleted submissions")
-    BATCH_ARCHIVE_SUBMISSION = "BATCH_ARCHIVE_SUBMISSION", _(
-        "batch archive submissions"
+    BATCH_ARCHIVE_SUBMISSION = (
+        "BATCH_ARCHIVE_SUBMISSION",
+        _("batch archive submissions"),
     )
     STAFF_ACCOUNT_CREATED = "STAFF_ACCOUNT_CREATED", _("created new account")
     STAFF_ACCOUNT_EDITED = "STAFF_ACCOUNT_EDITED", _("edited account")
diff --git a/hypha/apply/activity/templates/activity/include/comment_form.html b/hypha/apply/activity/templates/activity/include/comment_form.html
index 4dc4dcffc41d17affb87e52f9f0f4b512ca6bd6e..3d7e8f78a247b7b2dd834eab62072dae744382ca 100644
--- a/hypha/apply/activity/templates/activity/include/comment_form.html
+++ b/hypha/apply/activity/templates/activity/include/comment_form.html
@@ -1,6 +1,5 @@
 {% load i18n %}
 
-<h4 class="m-0">Add communication</h4>
 <div class="wrapper wrapper--comments">
     {% trans "Submit" as submit %}
     {% include "funds/includes/delegated_form_base.html" with form=comment_form value=submit extra_classes="form__comments" %}
diff --git a/hypha/apply/activity/templates/activity/include/listing_base.html b/hypha/apply/activity/templates/activity/include/listing_base.html
index 450b1dc5b7e154cf32848f485238b35a2b273920..de7994bd854cb033c97306c466b96758f68adcd3 100644
--- a/hypha/apply/activity/templates/activity/include/listing_base.html
+++ b/hypha/apply/activity/templates/activity/include/listing_base.html
@@ -1,60 +1,103 @@
-{% load i18n activity_tags bleach_tags markdown_tags submission_tags apply_tags %}
-<div class="feed__item feed__item--{{ activity.type }}" id="communications#{{ activity.id }}">
-    <div class="feed__pre-content">
-        <p class="feed__label feed__label--{{ activity.type }}">{{ activity.type|capfirst }}</p>
+{% load i18n activity_tags bleach_tags markdown_tags submission_tags apply_tags heroicons %}
+<div class="feed__item feed__item--{{ activity.type }} border shadow-sm rounded-sm pb-2 " id="communications#{{ activity.id }}">
+    <div class="feed__pre-content hidden lg:block">
+        <p class="feed__label lg:py-2 feed__label--{{ activity.type }}">
+            {% if  activity.type == 'comment' %}
+                {% heroicon_mini "chat-bubble-left" class="inline align-text-bottom mr-2" aria_hidden=true %}
+            {% else %}
+                {{ activity.type|capfirst }}
+            {% endif %}
+        </p>
     </div>
     <div class="feed__content js-feed-content">
-        <div class="feed__meta js-feed-meta">
-            <p class="feed__label feed__label--{{ activity.type }} feed__label--mobile">{{ activity.type|capfirst }}</p>
-            <p class="feed__meta-item"><span>{{ activity|display_author:request.user }}</span> <relative-time class="text-fg-muted text-sm" datetime={{ activity.timestamp|date:"c" }}>{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}</relative-time> </p>
-
-            {% if editable %}
-                {% if activity.user == request.user %}
-                    <p class="feed__meta-item feed__meta-item--edit-button">
-                        <a class="link link--edit-submission is-active js-edit-comment" href="#">
-                            {% trans "Edit" %}
-                            <svg class="icon icon--pen"><use xlink:href="#pen"></use></svg>
-                        </a>
-                    </p>
+        <div class="feed__meta js-feed-meta py-2 bg-slate-50 shadow-sm">
+            <p class="feed__meta-item pl-3">
+                <span>{{ activity|display_author:request.user }}</span>
+                <relative-time class="text-fg-muted text-sm" data-tippy-content="{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}" datetime={{ activity.timestamp|date:"c" }}>{{ activity.timestamp|date:"SHORT_DATETIME_FORMAT" }}</relative-time>
+                {% if activity.edited %}
+                    •
+                    <span class="js-last-edited text-fg-muted text-sm" data-tippy-content="{{ activity.edited|date:"SHORT_DATETIME_FORMAT" }}">{% trans "edited" %}</span>
                 {% endif %}
-                <p class="feed__meta-item feed__meta-item--last-edited" {% if not activity.edited %} hidden {% endif %}>
-                    ({% trans "Last edited" %}: <span class="js-last-edited">{{ activity.edited|date:"SHORT_DATETIME_FORMAT" }}</span>)
+            </p>
+
+            {% if editable and activity.user == request.user %}
+                <p class="feed__meta-item feed__meta-item--edit-button">
+                    <a class="link link--edit-submission is-active js-edit-comment" href="#">
+                        {% trans "Edit" %}
+                        <svg class="icon icon--pen"><use xlink:href="#pen"></use></svg>
+                    </a>
                 </p>
             {% endif %}
 
             <p class="feed__meta-item feed__meta-item--right" {% if not activity.private %} hidden {% endif %}>
-                <svg class="icon icon--eye"><use xlink:href="#eye"></use></svg>
+                {% heroicon_mini "eye" size=17 class="inline align-text-bottom fill-fg-muted mr-1" aria_hidden=true %}
                 <span class="js-comment-visibility">{{ activity.visibility|visibility_display:request.user }}</span>
             </p>
         </div>
 
-        <p class="feed__heading">
+        <div class="feed__heading">
             {% if submission_title %}
                 {% trans "updated" %} <a href="{{ activity.source.get_absolute_url }}">{{ activity.source.title }}</a>
             {% endif %}
 
             {% if editable %}
-                <div class="feed__comment js-comment" data-id="{{activity.id}}" data-comment="{{activity|display_for:request.user|to_markdown}}"
+                <div class="feed__comment js-comment px-3 prose"
+                     data-id="{{activity.id}}" data-comment="{{activity|display_for:request.user|to_markdown}}"
                      data-visibility-options="{{activity|visibility_options:activity.user}}"
                      data-visibility="{{activity.visibility}}"
-                     data-edit-url="{% url 'api:v1:comments-edit' pk=activity.pk %}">
+                     data-edit-url="{% url 'api:v1:comments-edit' pk=activity.pk %}"
+                >
                     {{ activity|display_for:request.user|submission_links|markdown|bleach }}
                 </div>
-
-                <div class="js-edit-block" aria-live="polite"></div>
+                <style>
+                    @media only screen and (min-width: 1024px){
+                        .js-edit-block .form .wmd-preview, .js-edit-block .form .wmd-input {
+                            max-width: 70%;
+                        }
+                    }
+                </style>
+                <div class="js-edit-block pr-3" aria-live="polite"></div>
             {% else %}
-                {{ activity|display_for:request.user|submission_links|markdown|bleach }}
+                <div class="px-3 prose">
+                    {{ activity|display_for:request.user|submission_links|markdown|bleach }}
+                </div>
             {% endif %}
 
+
             {% if not submission_title and activity|user_can_see_related:request.user %}
-                {% with url=activity.related_object.get_absolute_url %}
-                    {% if url %}
-                        <a href="{{ url }}" target="_blank" class="feed__related-item">
-                            {% trans "View " %}{{ activity.related_object|model_verbose_name }} <svg class="icon"><use xlink:href="#arrow-head-pixels--solid"></use></svg>
+                <div class="px-3">
+                    {% with url=activity.related_object.get_absolute_url %}
+                        {% if url %}
+                            <a href="{{ url }}" target="_blank" class="feed__related-item">
+                                {% trans "View " %}{{ activity.related_object|model_verbose_name }} <svg class="icon"><use xlink:href="#arrow-head-pixels--solid"></use></svg>
+                            </a>
+                        {% endif %}
+                    {% endwith %}
+                </div>
+            {% endif %}
+
+        </div>
+        {% with activity.attachments.all as attachments %}
+            {% if attachments %}
+                <div class="section-attachments flex gap-2 flex-col max-w-xl mt-4 px-3 pb-2">
+                    {% for attachment in attachments %}
+                        <a href="{{attachment.get_absolute_url }}"
+                           class="flex justify-between border rounded px-3 py-2 font-medium bg-slate-50 hover:bg-slate-200 transition-colors"
+                           target="_blank"
+                           rel="noopener noreferrer"
+                           title="{{ attachment.filename }}"
+                        >
+                            <span class="truncate text-sm">
+                                {% heroicon_mini "paper-clip" class="inline align-text-bottom" aria_hidden=true %}
+                                {{ attachment.filename|truncatechars_middle:45 }}
+                            </span>
+                            <span>
+                                {% heroicon_mini "arrow-small-down" class="inline align-text-bottom rounded" aria_hidden=true %}
+                            </span>
                         </a>
-                    {% endif %}
-                {% endwith %}
+                    {% endfor %}
+                </div>
             {% endif %}
-        </p>
+        {% endwith %}
     </div>
 </div>
diff --git a/hypha/apply/activity/urls.py b/hypha/apply/activity/urls.py
index eebe907ae055791f9df4a4e3068325ae2911ecab..4ec83895229310fb7344edade816b52789075519 100644
--- a/hypha/apply/activity/urls.py
+++ b/hypha/apply/activity/urls.py
@@ -1,6 +1,6 @@
 from django.urls import include, path
 
-from .views import NotificationsView
+from .views import AttachmentView, NotificationsView
 
 app_name = "activity"
 
@@ -8,4 +8,9 @@ app_name = "activity"
 urlpatterns = [
     path("anymail/", include("anymail.urls")),
     path("notifications/", NotificationsView.as_view(), name="notifications"),
+    path(
+        "activities/attachment/<uuid:file_pk>/download/",
+        AttachmentView.as_view(),
+        name="attachment",
+    ),
 ]
diff --git a/hypha/apply/activity/views.py b/hypha/apply/activity/views.py
index ec106f4224533ac884f0ace33d2a69507a97da61..52b125342721e55d0b2e9d85ea4e8ecbb09735dc 100644
--- a/hypha/apply/activity/views.py
+++ b/hypha/apply/activity/views.py
@@ -1,16 +1,18 @@
 from django.conf import settings
+from django.shortcuts import get_object_or_404
 from django.utils import timezone
 from django.utils.decorators import method_decorator
 from django.views.generic import CreateView, ListView
 from django_ratelimit.decorators import ratelimit
 
 from hypha.apply.users.decorators import staff_required
+from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegatedViewMixin
 
 from .filters import NotificationFilter
 from .forms import CommentForm
 from .messaging import MESSAGES, messenger
-from .models import COMMENT, Activity
+from .models import COMMENT, Activity, ActivityAttachment
 from .services import get_related_comments_for_user
 
 
@@ -60,6 +62,19 @@ class CommentFormView(DelegatedViewMixin, CreateView):
         return kwargs
 
 
+class AttachmentView(PrivateMediaView):
+    model = ActivityAttachment
+
+    def dispatch(self, *args, **kwargs):
+        file_pk = kwargs.get("file_pk")
+        self.instance = get_object_or_404(ActivityAttachment, uuid=file_pk)
+
+        return super().dispatch(*args, **kwargs)
+
+    def get_media(self, *args, **kwargs):
+        return self.instance.file
+
+
 @method_decorator(staff_required, name="dispatch")
 class NotificationsView(ListView):
     model = Activity
diff --git a/hypha/apply/categories/admin_helpers.py b/hypha/apply/categories/admin_helpers.py
index addea237ab42f4a7fb5021ee472a01031808632c..7afbcfe26d20dc5746586bb9e21802ca47adb46d 100644
--- a/hypha/apply/categories/admin_helpers.py
+++ b/hypha/apply/categories/admin_helpers.py
@@ -46,7 +46,7 @@ class MetaTermButtonHelper(ButtonHelper):
         add_child_button = self.add_child_button(
             pk=getattr(obj, self.opts.pk.attname),
             child_verbose_name=obj.node_child_verbose_name,
-            **kwargs
+            **kwargs,
         )
         buttons.append(add_child_button)
 
diff --git a/hypha/apply/dashboard/services.py b/hypha/apply/dashboard/services.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f9ad5bfca8c1af39e42fc690a56d30d89396a98
--- /dev/null
+++ b/hypha/apply/dashboard/services.py
@@ -0,0 +1,28 @@
+from django.db.models import Count
+
+from hypha.apply.projects.models import PAFApprovals
+
+
+def get_paf_for_review(user, is_paf_approval_sequential):
+    """Return a list of paf approvals ready for user's review"""
+
+    paf_approvals = PAFApprovals.objects.annotate(
+        roles_count=Count("paf_reviewer_role__user_roles")
+    ).filter(
+        roles_count=len(list(user.groups.all())),
+        approved=False,
+    )
+
+    for role in user.groups.all():
+        paf_approvals = paf_approvals.filter(paf_reviewer_role__user_roles__id=role.id)
+
+    if is_paf_approval_sequential:
+        all_matched_paf_approvals = list(paf_approvals)
+        for matched_paf_approval in all_matched_paf_approvals:
+            if matched_paf_approval.project.paf_approvals.filter(
+                paf_reviewer_role__sort_order__lt=matched_paf_approval.paf_reviewer_role.sort_order,
+                approved=False,
+            ).exists():
+                paf_approvals = paf_approvals.exclude(id=matched_paf_approval.id)
+
+    return paf_approvals
diff --git a/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html b/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html
index a85cacc7874e2f6ea15758e7b6549420733fd8d6..d9c99e91c518a0c0a7d199474d60ef9d09d310c4 100644
--- a/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/applicant_dashboard.html
@@ -1,6 +1,6 @@
 {% extends "base-apply.html" %}
 {% load render_table from django_tables2 %}
-{% load i18n static wagtailcore_tags workflow_tags statusbar_tags heroicons dashboard_statusbar_tags apply_tags invoice_tools %}
+{% load i18n static wagtailcore_tags workflow_tags statusbar_tags heroicons dashboard_statusbar_tags apply_tags invoice_tools markdown_tags bleach_tags %}
 {% block body_class %}bg-light-grey{% endblock %}
 
 {% block title %}{% trans "Dashboard" %}{% endblock %}
@@ -25,6 +25,19 @@
     </div>
 
     <div class="wrapper wrapper--large wrapper--inner-space-medium">
+        {% if my_tasks.count %}
+            <div class="w-8/12 m-auto mb-10">
+                <h2 class="text-center font-light">{% trans "My tasks" %}</h2>
+                {% for task in my_tasks.data %}
+                    <div class="bg-white p-1 flex mb-1 items-center">
+                        <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg>
+                        <div class="flex-1">{{ task.text|markdown|bleach }}</div>
+                        <a class="button button-primary m-2" href="{{ task.url }}">View</a>
+                    </div>
+                {% endfor %}
+            </div>
+        {% endif %}
+
         {% if my_active_submissions %}
             <div class="mb-10">
                 <div class="flex">
diff --git a/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html b/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html
index a382b38822cd77d49a028280610e5a2a916d30a5..533ee6b65138bbfacc272a2bedf4375084ccd526 100644
--- a/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/contracting_dashboard.html
@@ -1,6 +1,6 @@
 {% extends "base-apply.html" %}
 {% load render_table from django_tables2 %}
-{% load i18n static %}
+{% load i18n static markdown_tags bleach_tags %}
 
 {% block title %}{% trans "Dashboard" %}{% endblock %}
 
@@ -15,14 +15,23 @@
     {% endadminbar %}
 
     <div class="wrapper wrapper--large wrapper--inner-space-medium">
-        {% if paf_waiting_for_approval.count %}
-            {% include "dashboard/includes/paf_waiting_for_approval.html" with paf_waiting_for_approval=paf_waiting_for_approval %}
+        {% if my_tasks.count %}
+            <div class="w-8/12 m-auto mb-10">
+                <h2 class="text-center font-light">{% trans "My tasks" %}</h2>
+                {% for task in my_tasks.data %}
+                    <div class="bg-white p-1 flex mb-1 items-center">
+                        <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg>
+                        <div class="flex-1">{{ task.text|markdown|bleach }}</div>
+                        <a class="button button-primary m-2" href="{{ task.url }}">View</a>
+                    </div>
+                {% endfor %}
+            </div>
         {% endif %}
 
-        {% if paf_waiting_for_assignment.count %}
-            <div id="paf_waiting_for_assignment" class="wrapper wrapper--bottom-space">
-                <h4 class="heading heading--normal">{% trans "PAF waiting for assignee" %}</h4>
-                {% render_table paf_waiting_for_assignment.table %}
+        {% if paf_for_review.count %}
+            <div id="paf_for_review" class="wrapper wrapper--bottom-space">
+                <h4 class="heading heading--normal">{% trans "PAFs for review" %}</h4>
+                {% render_table paf_for_review.table %}
             </div>
         {% endif %}
 
diff --git a/hypha/apply/dashboard/templates/dashboard/dashboard.html b/hypha/apply/dashboard/templates/dashboard/dashboard.html
index 006d7812ef70475f7c1f68212cc3a16be6086925..3f5732864c07de26f7e49a2396bac01ab27ab04b 100644
--- a/hypha/apply/dashboard/templates/dashboard/dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/dashboard.html
@@ -1,6 +1,6 @@
 {% extends "base-apply.html" %}
 {% load render_table from django_tables2 %}
-{% load i18n static %}
+{% load i18n static bleach_tags markdown_tags %}
 
 {% block extra_css %}
     {{ my_reviewed.filterset.form.media.css }}
@@ -24,6 +24,19 @@
 
     <div class="wrapper wrapper--large wrapper--inner-space-medium">
         <div class="wrapper wrapper--bottom-space">
+            {% if my_tasks.count %}
+                <div class="w-8/12 m-auto mb-10">
+                    <h2 class="text-center font-light">{% trans "My tasks" %}</h2>
+                    {% for task in my_tasks.data %}
+                        <div class="bg-white p-1 flex mb-1 items-center">
+                            <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg>
+                            <div class="flex-1">{{ task.text|markdown|bleach }}</div>
+                            <a class="button button-primary m-2" href="{{ task.url }}">View</a>
+                        </div>
+                    {% endfor %}
+                </div>
+            {% endif %}
+
             <div class="stat-block">
                 <a href="#submissions-awaiting-review" class="stat-block__item border">
                     <p class="stat-block__number">{{ awaiting_reviews.count }}</p>
@@ -39,15 +52,6 @@
                         <div class="stat-block__view">{% trans "View" %}</div>
                     {% endif %}
                 </a>
-                {% if not paf_waiting_for_approval.count is None%}
-                    <a href="#paf-awaiting-approval" class="stat-block__item border">
-                        <p class="stat-block__number">{{ paf_waiting_for_approval.count }}</p>
-                        <p class="stat-block__text">{% trans "Projects awaiting approval" %}</p>
-                        {% if paf_waiting_for_approval.count %}
-                            <div class="stat-block__view">{% trans "View" %}</div>
-                        {% endif %}
-                    </a>
-                {% endif %}
                 <a href="#active-invoices" class="stat-block__item border">
                     <p class="stat-block__number">{{ active_invoices.count }}</p>
                     <p class="stat-block__text">{% trans "Requests for invoices requiring your attention" %}</p>
@@ -72,14 +76,10 @@
             {% include "funds/includes/round-block.html" with can_export=can_export closed_rounds=rounds.closed open_rounds=rounds.open title="Your rounds and labs" page_type='dashboard' %}
         {% endif %}
 
-        {% if paf_waiting_for_approval.count %}
-            {% include "dashboard/includes/paf_waiting_for_approval.html" with paf_waiting_for_approval=paf_waiting_for_approval %}
-        {% endif %}
-
-        {% if paf_waiting_for_assignment.count %}
-            <div id="paf_waiting_for_assignment" class="wrapper wrapper--bottom-space">
-                <h4 class="heading heading--normal">{% trans "PAF waiting for assignee" %}</h4>
-                {% render_table paf_waiting_for_assignment.table %}
+        {% if paf_for_review.count %}
+            <div id="paf_for_review" class="wrapper wrapper--bottom-space">
+                <h4 class="heading heading--normal">{% trans "PAFs for review" %}</h4>
+                {% render_table paf_for_review.table %}
             </div>
         {% endif %}
 
@@ -125,6 +125,7 @@
 {% block extra_js %}
     {{ my_reviewed.filterset.form.media.js }}
     <script src="{% static 'js/apply/url-search-params.js' %}"></script>
+    <script src="{% static 'js/apply/all-submissions-table.js' %}"></script>
     <script src="{% static 'js/apply/submission-filters.js' %}"></script>
     <script src="{% static 'js/apply/tabs.js' %}"></script>
     <script src="{% static 'js/apply/flag.js' %}"></script>
diff --git a/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html b/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html
index 2b0e6f9a5dd3f98e8a49a34b22c8b55f9b5de6a8..5510e4e4c4142ba5f4b991f7a8386f6862ff20ba 100644
--- a/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/finance_dashboard.html
@@ -1,6 +1,6 @@
 {% extends "base-apply.html" %}
 {% load render_table from django_tables2 %}
-{% load i18n static %}
+{% load i18n static markdown_tags bleach_tags %}
 
 {% block title %}{% trans "Dashboard" %}{% endblock %}
 
@@ -15,6 +15,19 @@
     {% endadminbar %}
 
     <div class="wrapper wrapper--large wrapper--inner-space-medium">
+        {% if my_tasks.count %}
+            <div class="w-8/12 m-auto mb-10">
+                <h2 class="text-center font-light">{% trans "My tasks" %}</h2>
+                {% for task in my_tasks.data %}
+                    <div class="bg-white p-1 flex mb-1 items-center">
+                        <svg class="icon icon--dashboard-tasks"><use xlink:href="#{{ task.icon }}"></use></svg>
+                        <div class="flex-1">{{ task.text|markdown|bleach }}</div>
+                        <a class="button button-primary m-2" href="{{ task.url }}">View</a>
+                    </div>
+                {% endfor %}
+            </div>
+        {% endif %}
+
         <div class="wrapper wrapper--bottom-space"
              role="tablist" aria-label="Invoice Tabs"
              x-data="{ tab: '{% trans "Active" %}' }"
@@ -84,16 +97,13 @@
 
         </div>
 
-        {% if paf_waiting_for_approval.count %}
-            {% include "dashboard/includes/paf_waiting_for_approval.html" with paf_waiting_for_approval=paf_waiting_for_approval %}
-        {% endif %}
-
-        {% if paf_waiting_for_assignment.count %}
-            <div id="paf_waiting_for_assignment" class="wrapper wrapper--bottom-space">
-                <h4 class="heading heading--normal">{% trans "PAF waiting for assignee" %}</h4>
-                {% render_table paf_waiting_for_assignment.table %}
+        {% if paf_for_review.count %}
+            <div id="paf_for_review" class="wrapper wrapper--bottom-space">
+                <h4 class="heading heading--normal">{% trans "PAFs for review" %}</h4>
+                {% render_table paf_for_review.table %}
             </div>
         {% endif %}
+
     </div>
 {% endblock %}
 
diff --git a/hypha/apply/dashboard/templates/dashboard/includes/paf_waiting_for_approval.html b/hypha/apply/dashboard/templates/dashboard/includes/paf_waiting_for_approval.html
deleted file mode 100644
index 0fdfcaa1a55c3617e701533744494a2ed0bd6301..0000000000000000000000000000000000000000
--- a/hypha/apply/dashboard/templates/dashboard/includes/paf_waiting_for_approval.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% load render_table from django_tables2 %}
-{% load i18n %}
-
-<div id="paf-awaiting-approval" class="wrapper wrapper--bottom-space" x-data="{ tab: '{% trans "Awaiting your approval" %}' }">
-    <section class="section section--with-options">
-        <h2 class="text-xl mb-2">
-            {% trans 'PAF waiting for internal approval' %}
-        </h2>
-        <nav>
-            <a class="tab__item tab__item--alt"
-               role="tab"
-               href="#"
-               id="tab-paf-awaiting"
-               aria-controls="panel-paf-awaiting"
-               :class="{ 'tab__item--active': tab === '{% trans "Awaiting your approval" %}' }"
-               @click.prevent="tab = '{% trans "Awaiting your approval" %}'"
-            >{% trans "Awaiting your approval" %}</a>
-            <a class="tab__item tab__item--alt"
-               role="tab"
-               href="#"
-               id="tab-paf-approved"
-               aria-controls="panel-paf-approved"
-               :class="{ 'tab__item--active': tab === '{% trans "Approved by you" %}' }"
-               @click.prevent="tab = '{% trans "Approved by you" %}'"
-            >{% trans "Approved by you" %}</a>
-        </nav>
-    </section>
-
-    <div x-show="tab === '{% trans "Awaiting your approval" %}'" role="tabpanel" tabindex="0" aria-labelledby="tab-paf-awaiting" id="panel-paf-awaiting">
-        {% if paf_waiting_for_approval.awaiting_your_approval.count %}
-            {% render_table paf_waiting_for_approval.awaiting_your_approval.table %}
-        {% else %}
-            <div class="border px-2 py-4">
-                {% trans "You have approved all PAFs, no PAF is waiting for your Approval " %}
-            </div>
-        {% endif %}
-    </div>
-    <div x-show="tab === '{% trans "Approved by you" %}'" role="tabpanel" tabindex="0" aria-labelledby="tab-paf-approved" id="panel-paf-approved">
-        {% if paf_waiting_for_approval.approved_by_you.count %}
-            {% render_table paf_waiting_for_approval.approved_by_you.table %}
-        {% else %}
-            <div class="border px-2 py-4">
-                {% trans "No PAF is approved by you yet " %}
-            </div>
-        {% endif %}
-    </div>
-
-</div>
diff --git a/hypha/apply/dashboard/templates/dashboard/reviewer_dashboard.html b/hypha/apply/dashboard/templates/dashboard/reviewer_dashboard.html
index 1d6d0c05b667b8c308a190c3bc01c27d4cc8f564..444531c53772cd335eeaf536dbb6346a32174ba6 100644
--- a/hypha/apply/dashboard/templates/dashboard/reviewer_dashboard.html
+++ b/hypha/apply/dashboard/templates/dashboard/reviewer_dashboard.html
@@ -69,15 +69,6 @@
             </div>
         {% endif %}
 
-        {% if paf_waiting_for_assignment.count %}
-            <div id="paf_waiting_for_assignment" class="wrapper wrapper--bottom-space">
-                <h2 class="text-xl mb-2">
-                    {% trans "PAF waiting for assignee" %}
-                </h2>
-                {% render_table paf_waiting_for_assignment.table %}
-            </div>
-        {% endif %}
-
         {% if my_inactive_submissions.data %}
             <div class="wrapper wrapper--bottom-space">
                 <h2 class="text-xl mb-2">
diff --git a/hypha/apply/dashboard/views.py b/hypha/apply/dashboard/views.py
index 41823412a5cee38bef482c76c4e89995c5626ffd..bb5611808bfff45d80d72109a29fc22edec712ba 100644
--- a/hypha/apply/dashboard/views.py
+++ b/hypha/apply/dashboard/views.py
@@ -1,9 +1,9 @@
 from django.conf import settings
-from django.db.models import Count
 from django.http import HttpResponseForbidden, HttpResponseRedirect
 from django.shortcuts import render
 from django.urls import reverse, reverse_lazy
 from django.views.generic import TemplateView
+from django_tables2 import RequestConfig
 
 from hypha.apply.funds.models import (
     ApplicationSubmission,
@@ -21,17 +21,18 @@ from hypha.apply.funds.tables import (
     review_filter_for_user,
 )
 from hypha.apply.projects.filters import ProjectListFilter
-from hypha.apply.projects.models import Invoice, PAFApprovals, Project, ProjectSettings
+from hypha.apply.projects.models import Invoice, Project, ProjectSettings
 from hypha.apply.projects.models.payment import DECLINED, PAID
-from hypha.apply.projects.models.project import INTERNAL_APPROVAL
-from hypha.apply.projects.permissions import has_permission
 from hypha.apply.projects.tables import (
     InvoiceDashboardTable,
-    ProjectsAssigneeDashboardTable,
+    PAFForReviewDashboardTable,
     ProjectsDashboardTable,
 )
+from hypha.apply.todo.views import render_task_templates_for_user
 from hypha.apply.utils.views import ViewDispatcher
 
+from .services import get_paf_for_review
+
 
 class MySubmissionContextMixin:
     def get_context_data(self, **kwargs):
@@ -91,15 +92,41 @@ class AdminDashboardView(MyFlaggedMixin, TemplateView):
                 "can_export": can_export_submissions(self.request.user),
                 "my_reviewed": self.my_reviewed(submissions),
                 "projects": self.projects(),
-                "paf_waiting_for_approval": self.paf_waiting_for_approval(),
                 "rounds": self.rounds(),
                 "my_flagged": self.my_flagged(submissions),
-                "paf_waiting_for_assignment": self.paf_waiting_for_approver_assignment(),
+                "paf_for_review": self.paf_for_review(),
+                "my_tasks": self.my_tasks(),
             }
         )
 
         return context
 
+    def paf_for_review(self):
+        if not self.request.user.is_apply_staff:
+            return {"count": None, "table": None}
+        project_settings = ProjectSettings.for_request(self.request)
+
+        paf_approvals = get_paf_for_review(
+            user=self.request.user,
+            is_paf_approval_sequential=project_settings.paf_approval_sequential,
+        )
+        paf_table = PAFForReviewDashboardTable(
+            paf_approvals, prefix="paf-review-", order_by="-date_requested"
+        )
+        RequestConfig(self.request, paginate=False).configure(paf_table)
+
+        return {
+            "count": paf_approvals.count(),
+            "table": paf_table,
+        }
+
+    def my_tasks(self):
+        tasks = render_task_templates_for_user(self.request, self.request.user)
+        return {
+            "count": len(tasks),
+            "data": tasks,
+        }
+
     def awaiting_reviews(self, submissions):
         submissions = submissions.in_review_for(self.request.user).order_by(
             "-submit_time"
@@ -141,109 +168,11 @@ class AdminDashboardView(MyFlaggedMixin, TemplateView):
         return {
             "count": projects.count(),
             "filterset": filterset,
-            "table": ProjectsDashboardTable(projects[:limit]),
+            "table": ProjectsDashboardTable(data=projects[:limit], prefix="project-"),
             "display_more": projects.count() > limit,
             "url": reverse("apply:projects:all"),
         }
 
-    def paf_waiting_for_approver_assignment(self):
-        project_settings = ProjectSettings.for_request(self.request)
-
-        paf_approvals = PAFApprovals.objects.annotate(
-            roles_count=Count("paf_reviewer_role__user_roles")
-        ).filter(
-            roles_count=len(list(self.request.user.groups.all())),
-            approved=False,
-            user__isnull=True,
-        )
-
-        for role in self.request.user.groups.all():
-            paf_approvals = paf_approvals.filter(
-                paf_reviewer_role__user_roles__id=role.id
-            )
-
-        paf_approvals_ids = paf_approvals.values_list("id", flat=True)
-        projects = Project.objects.filter(
-            paf_approvals__id__in=paf_approvals_ids
-        ).for_table()
-
-        if project_settings.paf_approval_sequential:
-            all_projects = list(projects)
-            for project in all_projects:
-                matched_paf_approval = (
-                    paf_approvals.filter(project=project)
-                    .order_by("paf_reviewer_role__sort_order")
-                    .first()
-                )
-                if project.paf_approvals.filter(
-                    paf_reviewer_role__sort_order__lt=matched_paf_approval.paf_reviewer_role.sort_order,
-                    approved=False,
-                ).exists():
-                    projects = projects.exclude(id=project.id)
-
-        return {
-            "count": projects.count(),
-            "table": ProjectsAssigneeDashboardTable(projects),
-        }
-
-    def paf_waiting_for_approval(self):
-        if (
-            not self.request.user.is_apply_staff
-            or not PAFApprovals.objects.filter(
-                project__status=INTERNAL_APPROVAL,
-                user=self.request.user,
-            ).exists()
-        ):
-            return {
-                "count": None,
-                "awaiting_your_approval": {
-                    "count": None,
-                    "table": None,
-                },
-                "approved_by_you": {
-                    "count": None,
-                    "table": None,
-                },
-            }
-
-        waiting_paf_approval = Project.objects.internal_approval().for_table()
-        project_settings = ProjectSettings.for_request(self.request)
-        if project_settings.paf_approval_sequential:
-            awaiting_user_approval = []
-            for waiting_project in waiting_paf_approval.filter(
-                paf_approvals__approved=False
-            ):
-                permission, _ = has_permission(
-                    "paf_status_update",
-                    self.request.user,
-                    object=waiting_project,
-                    raise_exception=False,
-                    request=self.request,
-                )
-                if permission:
-                    awaiting_user_approval.append(waiting_project)
-        else:
-            awaiting_user_approval = waiting_paf_approval.filter(
-                paf_approvals__user=self.request.user,
-                paf_approvals__approved=False,
-            )
-        approved_by_user = waiting_paf_approval.filter(
-            paf_approvals__user=self.request.user,
-            paf_approvals__approved=True,
-        )
-
-        return {
-            "count": len(awaiting_user_approval) + len(approved_by_user),
-            "awaiting_your_approval": {
-                "count": len(awaiting_user_approval),
-                "table": ProjectsDashboardTable(data=awaiting_user_approval),
-            },
-            "approved_by_you": {
-                "count": len(approved_by_user),
-                "table": ProjectsDashboardTable(data=approved_by_user),
-            },
-        }
-
     def my_reviewed(self, submissions):
         """Staff reviewer's reviewed submissions for 'Previous reviews' block"""
         submissions = submissions.reviewed_by(self.request.user).order_by(
@@ -289,13 +218,39 @@ class FinanceDashboardView(MyFlaggedMixin, TemplateView):
                 "active_invoices": self.active_invoices(),
                 "invoices_for_approval": self.invoices_for_approval(),
                 "invoices_to_convert": self.invoices_to_convert(),
-                "paf_waiting_for_approval": self.paf_waiting_for_approval(),
-                "paf_waiting_for_assignment": self.paf_waiting_for_approver_assignment(),
+                "paf_for_review": self.paf_for_review(),
+                "my_tasks": self.my_tasks(),
             }
         )
 
         return context
 
+    def paf_for_review(self):
+        if not self.request.user.is_finance:
+            return {"count": None, "table": None}
+        project_settings = ProjectSettings.for_request(self.request)
+
+        paf_approvals = get_paf_for_review(
+            user=self.request.user,
+            is_paf_approval_sequential=project_settings.paf_approval_sequential,
+        )
+        paf_table = PAFForReviewDashboardTable(
+            paf_approvals, prefix="paf-review-", order_by="-date_requested"
+        )
+        RequestConfig(self.request, paginate=False).configure(paf_table)
+
+        return {
+            "count": paf_approvals.count(),
+            "table": paf_table,
+        }
+
+    def my_tasks(self):
+        tasks = render_task_templates_for_user(self.request, self.request.user)
+        return {
+            "count": len(tasks),
+            "data": tasks,
+        }
+
     def active_invoices(self):
         if self.request.user.is_finance_level_2:
             invoices = Invoice.objects.for_finance_2()
@@ -307,46 +262,6 @@ class FinanceDashboardView(MyFlaggedMixin, TemplateView):
             "table": InvoiceDashboardTable(invoices),
         }
 
-    def paf_waiting_for_approver_assignment(self):
-        project_settings = ProjectSettings.for_request(self.request)
-
-        paf_approvals = PAFApprovals.objects.annotate(
-            roles_count=Count("paf_reviewer_role__user_roles")
-        ).filter(
-            roles_count=len(list(self.request.user.groups.all())),
-            approved=False,
-            user__isnull=True,
-        )
-
-        for role in self.request.user.groups.all():
-            paf_approvals = paf_approvals.filter(
-                paf_reviewer_role__user_roles__id=role.id
-            )
-
-        paf_approvals_ids = paf_approvals.values_list("id", flat=True)
-        projects = Project.objects.filter(
-            paf_approvals__id__in=paf_approvals_ids
-        ).for_table()
-
-        if project_settings.paf_approval_sequential:
-            all_projects = list(projects)
-            for project in all_projects:
-                matched_paf_approval = (
-                    paf_approvals.filter(project=project)
-                    .order_by("paf_reviewer_role__sort_order")
-                    .first()
-                )
-                if project.paf_approvals.filter(
-                    paf_reviewer_role__sort_order__lt=matched_paf_approval.paf_reviewer_role.sort_order,
-                    approved=False,
-                ).exists():
-                    projects = projects.exclude(id=project.id)
-
-        return {
-            "count": projects.count(),
-            "table": ProjectsAssigneeDashboardTable(projects),
-        }
-
     def invoices_for_approval(self):
         if self.request.user.is_finance_level_2:
             invoices = Invoice.objects.approved_by_finance_1()
@@ -367,64 +282,6 @@ class FinanceDashboardView(MyFlaggedMixin, TemplateView):
             "table": InvoiceDashboardTable(invoices),
         }
 
-    def paf_waiting_for_approval(self):
-        if (
-            not self.request.user.is_finance
-            or not PAFApprovals.objects.filter(
-                project__status=INTERNAL_APPROVAL,
-                user=self.request.user,
-            ).exists()
-        ):
-            return {
-                "count": None,
-                "awaiting_your_approval": {
-                    "count": None,
-                    "table": None,
-                },
-                "approved_by_you": {
-                    "count": None,
-                    "table": None,
-                },
-            }
-
-        waiting_paf_approval = Project.objects.internal_approval().for_table()
-        project_settings = ProjectSettings.for_request(self.request)
-        if project_settings.paf_approval_sequential:
-            awaiting_user_approval = []
-            for waiting_project in waiting_paf_approval.filter(
-                paf_approvals__approved=False
-            ):
-                permission, _ = has_permission(
-                    "paf_status_update",
-                    self.request.user,
-                    object=waiting_project,
-                    raise_exception=False,
-                    request=self.request,
-                )
-                if permission:
-                    awaiting_user_approval.append(waiting_project)
-        else:
-            awaiting_user_approval = waiting_paf_approval.filter(
-                paf_approvals__user=self.request.user,
-                paf_approvals__approved=False,
-            )
-        approved_by_user = waiting_paf_approval.filter(
-            paf_approvals__user=self.request.user,
-            paf_approvals__approved=True,
-        )
-
-        return {
-            "count": len(awaiting_user_approval) + len(approved_by_user),
-            "awaiting_your_approval": {
-                "count": len(awaiting_user_approval),
-                "table": ProjectsDashboardTable(data=awaiting_user_approval),
-            },
-            "approved_by_you": {
-                "count": len(approved_by_user),
-                "table": ProjectsDashboardTable(data=approved_by_user),
-            },
-        }
-
 
 class ReviewerDashboardView(MyFlaggedMixin, MySubmissionContextMixin, TemplateView):
     template_name = "dashboard/reviewer_dashboard.html"
@@ -464,7 +321,6 @@ class ReviewerDashboardView(MyFlaggedMixin, MySubmissionContextMixin, TemplateVi
                 "awaiting_reviews": self.awaiting_reviews(submissions),
                 "my_reviewed": self.my_reviewed(submissions),
                 "my_flagged": self.my_flagged(submissions),
-                "paf_waiting_for_assignment": self.paf_waiting_for_approver_assignment(),
             }
         )
 
@@ -487,46 +343,6 @@ class ReviewerDashboardView(MyFlaggedMixin, MySubmissionContextMixin, TemplateVi
             "table": ReviewerSubmissionsTable(submissions[:limit], prefix="my-review-"),
         }
 
-    def paf_waiting_for_approver_assignment(self):
-        project_settings = ProjectSettings.for_request(self.request)
-
-        paf_approvals = PAFApprovals.objects.annotate(
-            roles_count=Count("paf_reviewer_role__user_roles")
-        ).filter(
-            roles_count=len(list(self.request.user.groups.all())),
-            approved=False,
-            user__isnull=True,
-        )
-
-        for role in self.request.user.groups.all():
-            paf_approvals = paf_approvals.filter(
-                paf_reviewer_role__user_roles__id=role.id
-            )
-
-        paf_approvals_ids = paf_approvals.values_list("id", flat=True)
-        projects = Project.objects.filter(
-            paf_approvals__id__in=paf_approvals_ids
-        ).for_table()
-
-        if project_settings.paf_approval_sequential:
-            all_projects = list(projects)
-            for project in all_projects:
-                matched_paf_approval = (
-                    paf_approvals.filter(project=project)
-                    .order_by("paf_reviewer_role__sort_order")
-                    .first()
-                )
-                if project.paf_approvals.filter(
-                    paf_reviewer_role__sort_order__lt=matched_paf_approval.paf_reviewer_role.sort_order,
-                    approved=False,
-                ).exists():
-                    projects = projects.exclude(id=project.id)
-
-        return {
-            "count": projects.count(),
-            "table": ProjectsAssigneeDashboardTable(projects),
-        }
-
     def my_reviewed(self, submissions):
         """Staff reviewer's reviewed submissions for 'Previous reviews' block"""
         submissions = submissions.reviewed_by(self.request.user).order_by(
@@ -583,110 +399,38 @@ class ContractingDashboardView(MyFlaggedMixin, TemplateView):
         context = super().get_context_data(**kwargs)
         context.update(
             {
-                "paf_waiting_for_approval": self.paf_waiting_for_approval(),
                 "projects_in_contracting": self.projects_in_contracting(),
-                "paf_waiting_for_assignment": self.paf_waiting_for_approver_assignment(),
+                "paf_for_review": self.paf_for_review(),
+                "my_tasks": self.my_tasks(),
             }
         )
 
         return context
 
-    def paf_waiting_for_approval(self):
-        if (
-            not self.request.user.is_contracting
-            or not PAFApprovals.objects.filter(
-                project__status=INTERNAL_APPROVAL,
-                user=self.request.user,
-            ).exists()
-        ):
-            return {
-                "count": None,
-                "awaiting_your_approval": {
-                    "count": None,
-                    "table": None,
-                },
-                "approved_by_you": {
-                    "count": None,
-                    "table": None,
-                },
-            }
-
-        waiting_paf_approval = Project.objects.internal_approval().for_table()
+    def paf_for_review(self):
+        if not self.request.user.is_contracting:
+            return {"count": None, "table": None}
         project_settings = ProjectSettings.for_request(self.request)
-        if project_settings.paf_approval_sequential:
-            awaiting_user_approval = []
-            for waiting_project in waiting_paf_approval.filter(
-                paf_approvals__approved=False
-            ):
-                permission, _ = has_permission(
-                    "paf_status_update",
-                    self.request.user,
-                    object=waiting_project,
-                    raise_exception=False,
-                    request=self.request,
-                )
-                if permission:
-                    awaiting_user_approval.append(waiting_project)
-        else:
-            awaiting_user_approval = waiting_paf_approval.filter(
-                paf_approvals__user=self.request.user,
-                paf_approvals__approved=False,
-            )
-        approved_by_user = waiting_paf_approval.filter(
-            paf_approvals__user=self.request.user,
-            paf_approvals__approved=True,
+
+        paf_approvals = get_paf_for_review(
+            user=self.request.user,
+            is_paf_approval_sequential=project_settings.paf_approval_sequential,
         )
+        paf_table = PAFForReviewDashboardTable(
+            paf_approvals, prefix="paf-review-", order_by="-date_requested"
+        )
+        RequestConfig(self.request, paginate=False).configure(paf_table)
 
         return {
-            "count": len(awaiting_user_approval) + len(approved_by_user),
-            "awaiting_your_approval": {
-                "count": len(awaiting_user_approval),
-                "table": ProjectsDashboardTable(data=awaiting_user_approval),
-            },
-            "approved_by_you": {
-                "count": len(approved_by_user),
-                "table": ProjectsDashboardTable(data=approved_by_user),
-            },
+            "count": paf_approvals.count(),
+            "table": paf_table,
         }
 
-    def paf_waiting_for_approver_assignment(self):
-        project_settings = ProjectSettings.for_request(self.request)
-
-        paf_approvals = PAFApprovals.objects.annotate(
-            roles_count=Count("paf_reviewer_role__user_roles")
-        ).filter(
-            roles_count=len(list(self.request.user.groups.all())),
-            approved=False,
-            user__isnull=True,
-        )
-
-        for role in self.request.user.groups.all():
-            paf_approvals = paf_approvals.filter(
-                paf_reviewer_role__user_roles__id=role.id
-            )
-
-        paf_approvals_ids = paf_approvals.values_list("id", flat=True)
-        projects = Project.objects.filter(
-            paf_approvals__id__in=paf_approvals_ids
-        ).for_table()
-
-        if project_settings.paf_approval_sequential:
-            all_projects = list(projects)
-            for project in all_projects:
-                matched_paf_approval = (
-                    paf_approvals.filter(project=project)
-                    .order_by("paf_reviewer_role__sort_order")
-                    .first()
-                )
-                if project.paf_approvals.filter(
-                    paf_reviewer_role__sort_order__lt=matched_paf_approval.paf_reviewer_role.sort_order,
-                    approved=False,
-                ).exists():
-                    projects = projects.exclude(id=project.id)
-
+    def my_tasks(self):
+        tasks = render_task_templates_for_user(self.request, self.request.user)
         return {
-            "count": projects.count(),
-            "table": ProjectsAssigneeDashboardTable(projects),
+            "count": len(tasks),
+            "data": tasks,
         }
 
     def projects_in_contracting(self):
@@ -713,11 +457,16 @@ class ContractingDashboardView(MyFlaggedMixin, TemplateView):
             "count": projects_in_contracting.count(),
             "waiting_for_contract": {
                 "count": waiting_for_contract.count(),
-                "table": ProjectsDashboardTable(data=waiting_for_contract),
+                "table": ProjectsDashboardTable(
+                    data=waiting_for_contract, prefix="project-waiting-contract-"
+                ),
             },
             "waiting_for_contract_approval": {
                 "count": waiting_for_contract_approval.count(),
-                "table": ProjectsDashboardTable(data=waiting_for_contract_approval),
+                "table": ProjectsDashboardTable(
+                    data=waiting_for_contract_approval,
+                    prefix="project-waiting-approval-",
+                ),
             },
         }
 
@@ -776,8 +525,16 @@ class ApplicantDashboardView(TemplateView):
         context["active_invoices"] = self.active_invoices()
         context["historical_projects"] = self.historical_project_data()
         context["historical_submissions"] = self.historical_submission_data()
+        context["my_tasks"] = self.my_tasks()
         return context
 
+    def my_tasks(self):
+        tasks = render_task_templates_for_user(self.request, self.request.user)
+        return {
+            "count": len(tasks),
+            "data": tasks,
+        }
+
     def active_project_data(self):
         active_projects = (
             Project.objects.filter(user=self.request.user)
@@ -817,7 +574,9 @@ class ApplicantDashboardView(TemplateView):
         )
         return {
             "count": historical_projects.count(),
-            "table": ProjectsDashboardTable(data=historical_projects),
+            "table": ProjectsDashboardTable(
+                data=historical_projects, prefix="past-project-"
+            ),
         }
 
     def historical_submission_data(self):
diff --git a/hypha/apply/determinations/forms.py b/hypha/apply/determinations/forms.py
index 9a337cf29164fa0bd1808bc12c4e50117550fedc..1315c7d45ccc6c3fd790e8746e7dad78f8724ffd 100644
--- a/hypha/apply/determinations/forms.py
+++ b/hypha/apply/determinations/forms.py
@@ -520,8 +520,11 @@ class DeterminationModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMet
             self.fields[field].disabled = True
 
         if self.draft_button_name in self.data:
-            for field in self.fields.values():
-                field.required = False
+            # A determination must be set for saving a draft,
+            # this forces outcome to be validated.
+            unreq_fields = [name for name in self.fields if name != "outcome"]
+            for name in unreq_fields:
+                self.fields[name].required = False
 
         if edit:
             self.fields.pop("outcome")
@@ -548,8 +551,8 @@ class DeterminationModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMet
             self.instance.outcome = int(
                 self.cleaned_data[self.instance.determination_field.id]
             )
-            # Need to catch KeyError as outcome field would not exist in case of edit.
         except KeyError:
+            # Need to catch KeyError as outcome field would not exist in case of edit.
             pass
         self.instance.is_draft = self.draft_button_name in self.data
         self.instance.form_data = self.cleaned_data["form_data"]
diff --git a/hypha/apply/determinations/templates/determinations/base_determination_form.html b/hypha/apply/determinations/templates/determinations/base_determination_form.html
index d575ce5492f4e1639a4baafd3c34cf6cbff9d91d..6d59488bcc3cf860944fa0d7e5804dbda502712e 100644
--- a/hypha/apply/determinations/templates/determinations/base_determination_form.html
+++ b/hypha/apply/determinations/templates/determinations/base_determination_form.html
@@ -39,7 +39,7 @@
                 {% block form_buttons %}
                     <div class="form__group">
                         {% if form.draft_button_name %}
-                            <button class="button button--submit button--white" type="submit" name="{{ form.draft_button_name }}" formnovalidate>{% trans "Save draft" %}</button>
+                            <button class="button button--submit button--white" type="submit" name="{{ form.draft_button_name }}">{% trans "Save draft" %}</button>
                         {% endif %}
                         <button class="button button--submit button--primary" :disabled="isFormSubmitting" type="submit" name="submit">{% trans "Submit" %}</button>
                     </div>
diff --git a/hypha/apply/funds/blocks.py b/hypha/apply/funds/blocks.py
index 8b89f248783cdfdcfbf07ecc55806d0200da311a..4984c4823afbabe4f2d81c1c5d11e09573a360a3 100644
--- a/hypha/apply/funds/blocks.py
+++ b/hypha/apply/funds/blocks.py
@@ -51,6 +51,8 @@ class ValueBlock(ApplicationSingleIncludeFieldBlock):
         icon = "decimal"
 
     def prepare_data(self, value, data, serialize):
+        if not data:
+            return data
         return format_number_as_currency(str(data))
 
 
@@ -99,6 +101,8 @@ class AddressFieldBlock(ApplicationSingleIncludeFieldBlock):
         return ", ".join(data[field] for field in ADDRESS_FIELDS_ORDER if data[field])
 
     def prepare_data(self, value, data, serialize):
+        if not data:
+            return data
         data = json.loads(data)
         data = {field: data[field] for field in ADDRESS_FIELDS_ORDER}
 
@@ -193,6 +197,8 @@ class DurationBlock(ApplicationSingleIncludeFieldBlock):
         return field_kwargs
 
     def prepare_data(self, value, data, serialize):
+        if not data:
+            return data
         if value["duration_type"] == self.DAYS:
             return self.DURATION_DAY_OPTIONS[int(data)]
         if value["duration_type"] == self.WEEKS:
diff --git a/hypha/apply/funds/differ.py b/hypha/apply/funds/differ.py
index 322fd86895f0e2629699bb58058d5d70ab178145..66ccf9ffbd9db78fc104f2b7dad267ee1f4b42ca 100644
--- a/hypha/apply/funds/differ.py
+++ b/hypha/apply/funds/differ.py
@@ -6,25 +6,21 @@ from django.utils.html import format_html
 from django.utils.safestring import mark_safe
 
 
-def wrap_with_span(text, class_name):
+def wrap_deleted(text):
     return format_html(
-        '<span class="diff diff__{}">{}</span>', class_name, mark_safe(text)
+        '<span class="bg-red-200 line-through">{}</span>', mark_safe(text)
     )
 
 
-def wrap_deleted(text):
-    return wrap_with_span(text, "deleted")
-
-
 def wrap_added(text):
-    return wrap_with_span(text, "added")
+    return format_html('<span class="bg-green-200">{}</span>', mark_safe(text))
 
 
 def compare(answer_a, answer_b, should_bleach=True):
     if should_bleach:
         cleaner = Cleaner(tags=["h4"], attributes={}, strip=True)
-        answer_a = re.sub("(<li[^>]*>)", r"\1● ", answer_a)
-        answer_b = re.sub("(<li[^>]*>)", r"\1● ", answer_b)
+        answer_a = re.sub("(<li[^>]*>)", r"\1â—¦ ", answer_a)
+        answer_b = re.sub("(<li[^>]*>)", r"\1â—¦ ", answer_b)
         answer_a = cleaner.clean(answer_a)
         answer_b = cleaner.clean(answer_b)
 
@@ -37,20 +33,20 @@ def compare(answer_a, answer_b, should_bleach=True):
             to_diff.append(mark_safe(diff.b[b0:b1]))
         elif opcode == "insert":
             from_diff.append(mark_safe(diff.a[a0:a1]))
-            to_diff.append(wrap_with_span(diff.b[b0:b1], "added"))
+            to_diff.append(wrap_added(diff.b[b0:b1]))
         elif opcode == "delete":
-            from_diff.append(wrap_with_span(diff.a[a0:a1], "deleted"))
+            from_diff.append(wrap_deleted(diff.a[a0:a1]))
             to_diff.append(mark_safe(diff.b[b0:b1]))
         elif opcode == "replace":
-            from_diff.append(wrap_with_span(diff.a[a0:a1], "deleted"))
-            to_diff.append(wrap_with_span(diff.b[b0:b1], "added"))
+            from_diff.append(wrap_deleted(diff.a[a0:a1]))
+            to_diff.append(wrap_added(diff.b[b0:b1]))
 
     from_display = "".join(from_diff)
     to_display = "".join(to_diff)
-    from_display = re.sub(r"([●○]|[0-9]{1,2}[\)\.])", r"<br>\1", from_display)
-    to_display = re.sub(r"([●○]|[0-9]{1,2}[\)\.])", r"<br>\1", to_display)
     from_display = re.sub("(\\.\n)", r"\1<br><br>", from_display)
     to_display = re.sub("(\\.\n)", r"\1<br><br>", to_display)
+    from_display = re.sub(r"([â—¦])", r"<br>\1", from_display)
+    to_display = re.sub(r"([â—¦])", r"<br>\1", to_display)
     from_display = mark_safe(from_display)
     to_display = mark_safe(to_display)
 
diff --git a/hypha/apply/funds/migrations/0113_alter_assignedreviewers_reviewer.py b/hypha/apply/funds/migrations/0113_alter_assignedreviewers_reviewer.py
new file mode 100644
index 0000000000000000000000000000000000000000..1efafcb7f21b596f0e06529bf8939a31c8ffd153
--- /dev/null
+++ b/hypha/apply/funds/migrations/0113_alter_assignedreviewers_reviewer.py
@@ -0,0 +1,27 @@
+# Generated by Django 3.2.22 on 2023-11-01 15:18
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ("funds", "0112_add_organization_name"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="assignedreviewers",
+            name="reviewer",
+            field=models.ForeignKey(
+                limit_choices_to={
+                    "groups__name__in": ["Staff", "Reviewer", "Community Reviewer"],
+                    "is_active": True,
+                },
+                on_delete=django.db.models.deletion.CASCADE,
+                to=settings.AUTH_USER_MODEL,
+            ),
+        ),
+    ]
diff --git a/hypha/apply/funds/migrations/0114_alter_assignedreviewers_reviewer.py b/hypha/apply/funds/migrations/0114_alter_assignedreviewers_reviewer.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d07aadbe582ba649d1caadb93a3ae130282c312
--- /dev/null
+++ b/hypha/apply/funds/migrations/0114_alter_assignedreviewers_reviewer.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.7 on 2023-11-29 20:04
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ("funds", "0113_alter_assignedreviewers_reviewer"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="assignedreviewers",
+            name="reviewer",
+            field=models.ForeignKey(
+                limit_choices_to={
+                    "groups__name__in": ["Staff", "Reviewer", "Community reviewer"],
+                    "is_active": True,
+                },
+                on_delete=django.db.models.deletion.CASCADE,
+                to=settings.AUTH_USER_MODEL,
+            ),
+        ),
+    ]
diff --git a/hypha/apply/funds/tables.py b/hypha/apply/funds/tables.py
index cf45b752711bdaf9d0c86443d65ee14e0d42728f..d381df11fa0ba718f10d444c018f5d83cd957ce2 100644
--- a/hypha/apply/funds/tables.py
+++ b/hypha/apply/funds/tables.py
@@ -176,7 +176,19 @@ class BaseAdminSubmissionsTable(SubmissionsTable):
     organization_name = tables.Column()
 
     class Meta(SubmissionsTable.Meta):
-        fields = ("title", "phase", "stage", "fund", "round", "lead", "submit_time", "last_update", "screening_status", "reviews_stats", "organization_name")  # type: ignore
+        fields = (
+            "title",
+            "phase",
+            "stage",
+            "fund",
+            "round",
+            "lead",
+            "submit_time",
+            "last_update",
+            "screening_status",
+            "reviews_stats",
+            "organization_name",
+        )
         sequence = fields + ("comments",)
 
     def render_lead(self, value):
diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
index 183d9ab82f46ef1f81440a7d5e5d6d13af147bc9..d672e19ab0d144284d6069ca7387dc2191abf0a8 100644
--- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -177,6 +177,7 @@
         <div class="tabs__content" id="tab-2">
             <div class="feed">
                 {% if not object.is_archive %}
+                    <h4 class="m-0">{% trans "Add communication" %}</h4>
                     {% include "activity/include/comment_form.html" %}
                     {% include "activity/include/comment_list.html" with editable=True %}
                 {% else %}
diff --git a/hypha/apply/funds/templates/funds/includes/delegated_form_base.html b/hypha/apply/funds/templates/funds/includes/delegated_form_base.html
index fd2127f1e8e67db47267a2855921d5843742a8f3..33a7eb412caffe173f09fafbc3def9859a226ce2 100644
--- a/hypha/apply/funds/templates/funds/includes/delegated_form_base.html
+++ b/hypha/apply/funds/templates/funds/includes/delegated_form_base.html
@@ -12,13 +12,16 @@
     {% for hidden in form.hidden_fields %}
         {{ hidden }}
     {% endfor %}
-    {% for field in form.visible_fields %}
-        {% if field.field %}
-            {% include "forms/includes/field.html" %}
-        {% else %}
-            {{ field }}
-        {% endif %}
-    {% endfor %}
+
+    <div class="fields--visible">
+        {% for field in form.visible_fields %}
+            {% if field.field %}
+                {% include "forms/includes/field.html" %}
+            {% else %}
+                {{ field }}
+            {% endif %}
+        {% endfor %}
+    </div>
 
     <div class="form__group">
         {% if cancel %}
diff --git a/hypha/apply/funds/templates/funds/includes/revision_diff_table.html b/hypha/apply/funds/templates/funds/includes/revision_diff_table.html
deleted file mode 100644
index 6017274d367d9d3baeb7cffe56f9bd51fcdaa4d0..0000000000000000000000000000000000000000
--- a/hypha/apply/funds/templates/funds/includes/revision_diff_table.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% load i18n %}
-<table class="revision-diff-table">
-    <tr><td><h3>{% trans "Proposal Information" %}</h3></td><td><h3>{% trans "Proposal Information" %}</h3></td></tr>
-    <tr><td><h5>{% trans "Submitted" %}</h5>{{ timestamps.0|date:"DATETIME_FORMAT"}}</td><td><h5>{% trans "Submitted" %}</h5>{{ timestamps.1|date:"DATETIME_FORMAT"}}</td></tr>
-    {% for from_field, to_field in required_fields %}
-        {% if forloop.first %}
-            <tr><td><h4>{% trans "Title" %}</h4>{{ from_field }}</td><td><h4>{% trans "Title" %}</h4>{{ to_field }}</td></tr>
-        {% elif forloop.counter == 2 %}
-            <tr><td><h5>{% trans "Legal Name" %}</h5>{{ from_field }}</td><td><h5>{% trans "Legal Name" %}</h5>{{ to_field }}</td></tr>
-        {% elif forloop.counter == 3 %}
-            <tr><td><h5>{% trans "E-mail" %}</h5>{{ from_field }}</td><td><h5>{% trans "E-mail" %}</h5>{{ to_field }}</td></tr>
-        {% elif forloop.counter == 4 %}
-            <tr><td><h5>{% trans "Requested Funding" %}</h5>{{ from_field }}</td><td><h5>{% trans "Requested Funding" %}</h5>{{ to_field }}</td></tr>
-        {% elif forloop.counter == 5 %}
-            <tr><td><h5>{% trans "Project Duration" %}</h5>{{ from_field }}</td><td><h5>{% trans "Project Duration" %}</h5>{{ to_field }}</td></tr>
-        {% elif forloop.counter == 6 %}
-            <tr><td><h5>{% trans "Address" %}</h5>{{ from_field }}</td><td><h5>{% trans "Address" %}</h5>{{ to_field }}</td></tr>
-        {% else %}
-            <tr><td>{{ from_field }}</td><td>{{ to_field }}</td></tr>
-        {% endif %}
-    {% endfor %}
-    {% for from_field, to_field in stream_fields %}
-        <tr><td>{{ from_field }}</td><td>{{ to_field }}</td></tr>
-    {% endfor %}
-</table>
diff --git a/hypha/apply/funds/templates/funds/includes/status_bar.html b/hypha/apply/funds/templates/funds/includes/status_bar.html
index 3cdde65fe9d9748399b1cf90218d5eeb3fe6c233..beff7c317d8f0f282625c38d3696118496733383 100644
--- a/hypha/apply/funds/templates/funds/includes/status_bar.html
+++ b/hypha/apply/funds/templates/funds/includes/status_bar.html
@@ -1,5 +1,5 @@
 {% load statusbar_tags %}
-<div class="status-bar {{ class }}">
+<div class="status-bar my-6 {{ class }}">
     {% for phase in phases %}
         {% ifchanged phase.step %}
             {% status_display current_phase phase public as display_text %}
@@ -14,12 +14,12 @@
         {% endifchanged %}
     {% endfor %}
 </div>
-<div class="status-bar--mobile">
-    <h6 class="status-bar__subheading">
+<div class="status-bar--mobile my-2">
+    <div class="status-bar__subheading status-bar__text">
         {% if public %}
             {{ current_phase.public_name }}
         {% else %}
             {{ current_phase }}
         {% endif %}
-    </h6>
+    </div>
 </div>
diff --git a/hypha/apply/funds/templates/funds/includes/status_bar_item.html b/hypha/apply/funds/templates/funds/includes/status_bar_item.html
index 0925a321983639b740cacd67ebc5f5fb44c68458..97aaf8362d6a9648eee2006f188d7e00ba91bc33 100644
--- a/hypha/apply/funds/templates/funds/includes/status_bar_item.html
+++ b/hypha/apply/funds/templates/funds/includes/status_bar_item.html
@@ -4,9 +4,5 @@
             {% elif is_complete %}
                 status-bar__item--is-complete
             {% endif %}">
-    <span class="status-bar__tooltip"
-          data-title="{{ label }}" aria-label="{{ label }}"></span>
-    <svg class="status-bar__icon">
-        <use xlink:href="#tick-alt"></use>
-    </svg>
+    <div class="status-bar__tooltip status-bar__text" style="--tooltip-chars:{{ label|length }}ch;">{{ label }}</div>
 </div>
diff --git a/hypha/apply/funds/templates/funds/revisions_compare.html b/hypha/apply/funds/templates/funds/revisions_compare.html
index fbae8efe386da0951bd99f85dfd3fbb21318917b..f71d741389b46be1bd8d37f4abeaa4b6f470cc4c 100644
--- a/hypha/apply/funds/templates/funds/revisions_compare.html
+++ b/hypha/apply/funds/templates/funds/revisions_compare.html
@@ -17,7 +17,39 @@
 
     {% endadminbar %}
 
-    <div class="wrapper wrapper--large wrapper--tabs">
-        {% include "funds/includes/revision_diff_table.html" %}
-    </div>
+    <table class="revision-diff-table prose prose-h4:mt-0 max-w-none mb-8">
+        <tr>
+            <td>
+                <h3>{% trans "Proposal Information (Old)" %}</h3>
+                <p>{% trans "Submitted" %}: <strong>{{ timestamps.0|date:"DATETIME_FORMAT"}}</strong></p>
+            </td>
+            <td>
+                <h3>{% trans "Proposal Information (New)" %}</h3>
+                <p>{% trans "Submitted" %}: <strong>{{ timestamps.1|date:"DATETIME_FORMAT"}}</strong></p>
+            </td>
+        </tr>
+
+        {% for from_field, to_field in required_fields %}
+            {% if forloop.first %}
+                <tr><td><h4>{% trans "Title" %}</h4>{{ from_field }}</td><td><h4>{% trans "Title" %}</h4>{{ to_field }}</td></tr>
+            {% elif forloop.counter == 2 %}
+                <tr><td><h4>{% trans "Legal Name" %}</h4>{{ from_field }}</td><td><h4>{% trans "Legal Name" %}</h4>{{ to_field }}</td></tr>
+            {% elif forloop.counter == 3 %}
+                <tr><td><h4>{% trans "E-mail" %}</h4>{{ from_field }}</td><td><h4>{% trans "E-mail" %}</h4>{{ to_field }}</td></tr>
+            {% elif forloop.counter == 4 %}
+                <tr><td><h4>{% trans "Address" %}</h4>{{ from_field }}</td><td><h4>{% trans "Address" %}</h4>{{ to_field }}</td></tr>
+            {% elif forloop.counter == 5 %}
+                <tr><td><h4>{% trans "Project Duration" %}</h4>{{ from_field }}</td><td><h4>{% trans "Project Duration" %}</h4>{{ to_field }}</td></tr>
+            {% elif forloop.counter == 6 %}
+                <tr><td><h4>{% trans "Requested Funding" %}</h4>{{ from_field }}</td><td><h4>{% trans "Requested Funding" %}</h4>{{ to_field }}</td></tr>
+
+            {% else %}
+                <tr><td>{{ from_field }}</td><td>{{ to_field }}</td></tr>
+            {% endif %}
+        {% endfor %}
+        {% for from_field, to_field in stream_fields %}
+            <tr><td>{{ from_field }}</td><td>{{ to_field }}</td></tr>
+        {% endfor %}
+    </table>
+
 {% endblock %}
diff --git a/hypha/apply/funds/tests/test_models.py b/hypha/apply/funds/tests/test_models.py
index 530217bc472acf4eba123601e44356af0bbc69ea..9550a2cea07f4f2ad5474a70a1b24760ad011d09 100644
--- a/hypha/apply/funds/tests/test_models.py
+++ b/hypha/apply/funds/tests/test_models.py
@@ -314,9 +314,10 @@ class TestFormSubmission(TestCase):
         # Lead + 2 x applicant
         self.assertEqual(self.User.objects.count(), 3)
 
-        first_user, second_user = self.User.objects.get(
-            email=self.email
-        ), self.User.objects.get(email=email)
+        first_user, second_user = (
+            self.User.objects.get(email=self.email),
+            self.User.objects.get(email=email),
+        )
         self.assertEqual(ApplicationSubmission.objects.count(), 2)
         self.assertEqual(ApplicationSubmission.objects.first().user, first_user)
         self.assertEqual(ApplicationSubmission.objects.last().user, second_user)
diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py
index 18a8ca2cd4d1a515bb7c2b98d34dae2db64a149d..f0a75f95433bfce9fd63d018e0fae90cb5b9c8e5 100644
--- a/hypha/apply/funds/views.py
+++ b/hypha/apply/funds/views.py
@@ -9,6 +9,7 @@ from django.contrib import messages
 from django.contrib.auth import get_user_model
 from django.contrib.auth.decorators import login_required, permission_required
 from django.contrib.auth.mixins import UserPassesTestMixin
+from django.contrib.auth.models import Group
 from django.contrib.humanize.templatetags.humanize import intcomma
 from django.core.exceptions import PermissionDenied
 from django.db.models import Count, F, Q
@@ -52,7 +53,10 @@ from hypha.apply.projects.forms import CreateProjectForm
 from hypha.apply.projects.models import Project
 from hypha.apply.review.models import Review
 from hypha.apply.stream_forms.blocks import GroupToggleBlock
+from hypha.apply.todo.options import PROJECT_WAITING_PAF
+from hypha.apply.todo.views import add_task_to_user_group
 from hypha.apply.users.decorators import staff_or_finance_required, staff_required
+from hypha.apply.users.groups import STAFF_GROUP_NAME
 from hypha.apply.utils.models import PDFPageSettings
 from hypha.apply.utils.pdfs import draw_submission_content, make_pdf
 from hypha.apply.utils.storage import PrivateMediaView
@@ -787,6 +791,12 @@ class CreateProjectView(DelegatedViewMixin, CreateView):
             source=self.object,
             related=self.object.submission,
         )
+        # add task for staff to add PAF to the project
+        add_task_to_user_group(
+            code=PROJECT_WAITING_PAF,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=self.object,
+        )
         return response
 
     def get_context_data(self, **kwargs):
diff --git a/hypha/apply/funds/views_beta.py b/hypha/apply/funds/views_beta.py
index d5f2d24cacc0dff8d0a7bddf9d70155944f36e9f..a9a3984924818ae2888fa9fb8e2fc78d8c34e035 100644
--- a/hypha/apply/funds/views_beta.py
+++ b/hypha/apply/funds/views_beta.py
@@ -309,7 +309,9 @@ def bulk_update_submissions_status(request: HttpRequest) -> HttpResponse:
 
     submissions = ApplicationSubmission.objects.filter(id__in=submission_ids)
 
-    redirect: HttpResponse = BatchDeterminationCreateView.should_redirect(request, submissions, transitions)  # type: ignore
+    redirect: HttpResponse = BatchDeterminationCreateView.should_redirect(
+        request, submissions, transitions
+    )
     if redirect:
         return HttpResponseClientRedirect(redirect.url)
 
diff --git a/hypha/apply/projects/migrations/0081_alter_project_value.py b/hypha/apply/projects/migrations/0081_alter_project_value.py
new file mode 100644
index 0000000000000000000000000000000000000000..bde87b1c87c3983179d2cc672fd4770c60655a7b
--- /dev/null
+++ b/hypha/apply/projects/migrations/0081_alter_project_value.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.8 on 2023-12-11 20:16
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("application_projects", "0080_alter_invoice_status"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="project",
+            name="value",
+            field=models.DecimalField(
+                decimal_places=2,
+                default=0,
+                max_digits=20,
+                validators=[django.core.validators.MinValueValidator(limit_value=0)],
+            ),
+        ),
+    ]
diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py
index 6ab5d0ef7d7217997f25c9caeabdeb2a3da1baad..14aae7c1773049e6901f760983942c3085deba90 100644
--- a/hypha/apply/projects/models/project.py
+++ b/hypha/apply/projects/models/project.py
@@ -206,7 +206,12 @@ class Project(BaseStreamForm, AccessFormData, models.Model):
         blank=True,
         related_name="projects",
     )
-    value = models.PositiveIntegerField(default=0)
+    value = models.DecimalField(
+        default=0,
+        max_digits=20,
+        decimal_places=2,
+        validators=[MinValueValidator(limit_value=0)],
+    )
     proposed_start = models.DateTimeField(_("Proposed Start Date"), null=True)
     proposed_end = models.DateTimeField(_("Proposed End Date"), null=True)
 
@@ -388,9 +393,7 @@ class Project(BaseStreamForm, AccessFormData, models.Model):
         return False
 
     def get_absolute_url(self):
-        if settings.PROJECTS_ENABLED:
-            return reverse("apply:projects:detail", args=[self.id])
-        return "#"
+        return reverse("apply:projects:detail", args=[self.id])
 
     @property
     def can_make_approval(self):
diff --git a/hypha/apply/projects/services.py b/hypha/apply/projects/services.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1ae26e20159d7af306a92b9169232418283c300
--- /dev/null
+++ b/hypha/apply/projects/services.py
@@ -0,0 +1,154 @@
+from django.conf import settings
+from django.contrib.auth.models import Group
+
+from hypha.apply.todo.options import (
+    INVOICE_REQUIRED_CHANGES,
+    INVOICE_WAITING_APPROVAL,
+    INVOICE_WAITING_PAID,
+)
+from hypha.apply.todo.views import (
+    add_task_to_user,
+    add_task_to_user_group,
+    remove_tasks_for_user_group,
+)
+from hypha.apply.users.groups import (
+    APPROVER_GROUP_NAME,
+    FINANCE_GROUP_NAME,
+    STAFF_GROUP_NAME,
+)
+
+from .models.payment import (
+    APPROVED_BY_FINANCE,
+    APPROVED_BY_FINANCE_2,
+    APPROVED_BY_STAFF,
+    CHANGES_REQUESTED_BY_FINANCE,
+    CHANGES_REQUESTED_BY_FINANCE_2,
+    CHANGES_REQUESTED_BY_STAFF,
+    RESUBMITTED,
+    SUBMITTED,
+)
+
+
+def handle_tasks_on_invoice_update(old_status, invoice):
+    if old_status in [SUBMITTED, RESUBMITTED]:
+        # remove invoice waiting approval task for staff
+        remove_tasks_for_user_group(
+            code=INVOICE_WAITING_APPROVAL,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=invoice,
+        )
+        if invoice.status == CHANGES_REQUESTED_BY_STAFF:
+            # add invoice required changes task for applicant
+            add_task_to_user(
+                code=INVOICE_REQUIRED_CHANGES,
+                user=invoice.project.user,
+                related_obj=invoice,
+            )
+        elif invoice.status == APPROVED_BY_STAFF:
+            # add invoice waiting approval task for finance group
+            add_task_to_user_group(
+                code=INVOICE_WAITING_APPROVAL,
+                user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+                related_obj=invoice,
+            )
+    if old_status == APPROVED_BY_STAFF:
+        # remove invoice waiting approval task for finance group
+        remove_tasks_for_user_group(
+            code=INVOICE_WAITING_APPROVAL,
+            user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+            related_obj=invoice,
+        )
+        if invoice.status == CHANGES_REQUESTED_BY_FINANCE:
+            # add invoice required changes task for staff
+            add_task_to_user_group(
+                code=INVOICE_REQUIRED_CHANGES,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=invoice,
+            )
+        elif invoice.status == APPROVED_BY_FINANCE:
+            if settings.INVOICE_EXTENDED_WORKFLOW:
+                # add invoice waiting approval task for finance2 group
+                add_task_to_user_group(
+                    code=INVOICE_WAITING_APPROVAL,
+                    user_group=Group.objects.filter(name=FINANCE_GROUP_NAME).filter(
+                        name=APPROVER_GROUP_NAME
+                    ),
+                    related_obj=invoice,
+                )
+            else:
+                # add invoice waiting paid task for finance
+                add_task_to_user_group(
+                    code=INVOICE_WAITING_PAID,
+                    user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+                    related_obj=invoice,
+                )
+    if old_status == CHANGES_REQUESTED_BY_FINANCE:
+        # remove invoice required changes task for staff
+        remove_tasks_for_user_group(
+            code=INVOICE_REQUIRED_CHANGES,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=invoice,
+        )
+        if invoice.status == CHANGES_REQUESTED_BY_STAFF:
+            # add invoice required changes task for applicant
+            add_task_to_user(
+                code=INVOICE_REQUIRED_CHANGES,
+                user=invoice.project.user,
+                related_obj=invoice,
+            )
+    if not settings.INVOICE_EXTENDED_WORKFLOW and old_status == APPROVED_BY_FINANCE:
+        # remove invoice waiting paid task for finance group
+        remove_tasks_for_user_group(
+            code=INVOICE_WAITING_PAID,
+            user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+            related_obj=invoice,
+        )
+    if settings.INVOICE_EXTENDED_WORKFLOW:
+        if old_status == APPROVED_BY_FINANCE:
+            # remove invoice waiting approval task for finance2 group
+            remove_tasks_for_user_group(
+                code=INVOICE_WAITING_APPROVAL,
+                user_group=Group.objects.filter(name=FINANCE_GROUP_NAME).filter(
+                    name=APPROVER_GROUP_NAME
+                ),
+                related_obj=invoice,
+            )
+            if invoice.status == CHANGES_REQUESTED_BY_FINANCE_2:
+                # add invoice required changes task for finance
+                add_task_to_user_group(
+                    code=INVOICE_REQUIRED_CHANGES,
+                    user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+                    related_obj=invoice,
+                )
+            elif invoice.status == APPROVED_BY_FINANCE_2:
+                # add invoice waiting paid task for finance2
+                add_task_to_user_group(
+                    code=INVOICE_WAITING_PAID,
+                    user_group=Group.objects.filter(name=FINANCE_GROUP_NAME).filter(
+                        name=APPROVER_GROUP_NAME
+                    ),
+                    related_obj=invoice,
+                )
+        if old_status == CHANGES_REQUESTED_BY_FINANCE_2:
+            # remove invoice required changes task for finance
+            remove_tasks_for_user_group(
+                code=INVOICE_REQUIRED_CHANGES,
+                user_group=Group.objects.filter(name=FINANCE_GROUP_NAME),
+                related_obj=invoice,
+            )
+            if invoice.status == CHANGES_REQUESTED_BY_FINANCE:
+                # add invoice required changes task for staff
+                add_task_to_user_group(
+                    code=INVOICE_REQUIRED_CHANGES,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=invoice,
+                )
+        if old_status == APPROVED_BY_FINANCE_2:
+            # remove invoice waiting paid task for finance2
+            remove_tasks_for_user_group(
+                code=INVOICE_WAITING_PAID,
+                user_group=Group.objects.filter(name=FINANCE_GROUP_NAME).filter(
+                    name=APPROVER_GROUP_NAME
+                ),
+                related_obj=invoice,
+            )
diff --git a/hypha/apply/projects/tables.py b/hypha/apply/projects/tables.py
index 117d6db83bd59105cf473888bb3029e413ea1be9..2838b6792aa920b20b2ec80827ac34626403052b 100644
--- a/hypha/apply/projects/tables.py
+++ b/hypha/apply/projects/tables.py
@@ -4,7 +4,7 @@ import django_tables2 as tables
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 
-from .models import Invoice, Project, Report
+from .models import Invoice, PAFApprovals, Project, Report
 
 
 class BaseInvoiceTable(tables.Table):
@@ -32,6 +32,7 @@ class InvoiceDashboardTable(BaseInvoiceTable):
         ]
         model = Invoice
         order_by = ["-requested_at"]
+        template_name = "application_projects/tables/table.html"
         attrs = {"class": "invoices-table"}
 
 
@@ -51,6 +52,7 @@ class InvoiceListTable(BaseInvoiceTable):
         model = Invoice
         orderable = True
         order_by = ["-requested_at"]
+        template_name = "application_projects/tables/table.html"
         attrs = {"class": "invoices-table"}
 
 
@@ -104,6 +106,7 @@ class ProjectsDashboardTable(BaseProjectsTable):
             "end_date",
         ]
         model = Project
+        template_name = "application_projects/tables/table.html"
         orderable = False
         attrs = {"class": "projects-table"}
 
@@ -124,6 +127,50 @@ class ProjectsAssigneeDashboardTable(BaseProjectsTable):
         attrs = {"class": "projects-table"}
 
 
+class PAFForReviewDashboardTable(tables.Table):
+    date_requested = tables.DateColumn(
+        verbose_name=_("Date requested"),
+        accessor="created_at",
+        orderable=True,
+    )
+    title = tables.LinkColumn(
+        "funds:projects:detail",
+        text=lambda r: textwrap.shorten(r.project.title, width=30, placeholder="..."),
+        accessor="project__title",
+        args=[tables.utils.A("project__pk")],
+        orderable=False,
+    )
+    status = tables.Column(verbose_name=_("Status"), accessor="pk", orderable=False)
+    fund = tables.Column(
+        verbose_name=_("Fund"), accessor="project__submission__page", orderable=False
+    )
+
+    assignee = tables.Column(
+        verbose_name=_("Assignee"), accessor="user", orderable=False
+    )
+
+    class Meta:
+        fields = ["date_requested", "title", "fund", "status", "assignee"]
+        model = PAFApprovals
+        template_name = (
+            "funds/tables/table.html"  # todo: update it with Project table template
+        )
+        attrs = {"class": "paf-review-table"}
+
+    def order_date_requested(self, qs, is_descending):
+        direction = "-" if is_descending else ""
+
+        qs = qs.order_by(f"{direction}created_at")
+
+        return qs, True
+
+    def render_status(self, record):
+        if record.user:
+            return _("Waiting for approval")
+        else:
+            return _("Waiting for assignee")
+
+
 class ProjectsListTable(BaseProjectsTable):
     class Meta:
         fields = [
@@ -162,7 +209,8 @@ class ReportListTable(tables.Table):
         ]
         sequence = ["project", "report_period", "..."]
         model = Report
-        attrs = {"class": "responsive-table"}
+        template_name = "application_projects/tables/table.html"
+        attrs = {"class": "projects-table"}
 
     def render_report_period(self, record):
         return f"{record.start} to {record.end_date}"
diff --git a/hypha/apply/projects/templates/application_projects/project_detail.html b/hypha/apply/projects/templates/application_projects/project_detail.html
index a5380a29951c5d7c0b89a4c650eb72074c7e1601..549cae841d9ec2654401440d500eb9341571ea59 100644
--- a/hypha/apply/projects/templates/application_projects/project_detail.html
+++ b/hypha/apply/projects/templates/application_projects/project_detail.html
@@ -219,6 +219,7 @@
     {# Tab 2 #}
         <div class="tabs__content" id="tab-2">
             <div class="feed">
+                <h4 class="m-0">{% trans "Add communication" %}</h4>
                 {% include "activity/include/comment_form.html" %}
                 {% include "activity/include/comment_list.html" with editable=False %}
             </div>
diff --git a/hypha/apply/projects/templates/application_projects/tables/table.html b/hypha/apply/projects/templates/application_projects/tables/table.html
index 8c02c3bceff9ea2f76fa691411f208983a35dab4..457600b3ffb00d9a8ee4667fdbf09fb35ac8bddb 100644
--- a/hypha/apply/projects/templates/application_projects/tables/table.html
+++ b/hypha/apply/projects/templates/application_projects/tables/table.html
@@ -1,6 +1,19 @@
 {% extends 'django_tables2/table.html' %}
 {% load django_tables2 table_tags review_tags wagtailimages_tags i18n %}
 
+{% block table.tbody.row %}
+    <tr {{ row.attrs.as_html }}>
+        {% for column, cell in row.items %}
+            <td {{ column.attrs.td.as_html }}>
+                {% if column.name != "selected" %}
+                    <span class="mobile-label {{ column.attrs.td.class }}">{{ column.header }}: </span>
+                {% endif %}
+                {% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}
+            </td>
+        {% endfor %}
+    </tr>
+{% endblock %}
+
 {% block pagination %}
     {% if table.page and table.paginator.num_pages > 0 %}
         <div class="pagination--wrapper">
diff --git a/hypha/apply/projects/views/payment.py b/hypha/apply/projects/views/payment.py
index f767741dc3c1494103b0ef24e3c1fe88101e7581..1daae109f4da68456c3c44134991440ec6eb493d 100644
--- a/hypha/apply/projects/views/payment.py
+++ b/hypha/apply/projects/views/payment.py
@@ -2,6 +2,7 @@ from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.mixins import UserPassesTestMixin
+from django.contrib.auth.models import Group
 from django.core.exceptions import PermissionDenied
 from django.db import transaction
 from django.shortcuts import get_object_or_404, redirect
@@ -14,7 +15,19 @@ from django_tables2 import SingleTableMixin
 
 from hypha.apply.activity.messaging import MESSAGES, messenger
 from hypha.apply.activity.models import APPLICANT, COMMENT, Activity
+from hypha.apply.todo.options import (
+    INVOICE_REQUIRED_CHANGES,
+    INVOICE_WAITING_APPROVAL,
+    PROJECT_WAITING_INVOICE,
+)
+from hypha.apply.todo.views import (
+    add_task_to_user_group,
+    remove_tasks_for_user,
+    remove_tasks_for_user_group,
+    remove_tasks_of_related_obj,
+)
 from hypha.apply.users.decorators import staff_or_finance_required
+from hypha.apply.users.groups import STAFF_GROUP_NAME
 from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegateableView, DelegatedViewMixin, ViewDispatcher
 
@@ -23,10 +36,13 @@ from ..forms import ChangeInvoiceStatusForm, CreateInvoiceForm, EditInvoiceForm
 from ..models.payment import (
     APPROVED_BY_FINANCE,
     APPROVED_BY_STAFF,
+    CHANGES_REQUESTED_BY_FINANCE,
+    CHANGES_REQUESTED_BY_STAFF,
     INVOICE_TRANISTION_TO_RESUBMITTED,
     Invoice,
 )
 from ..models.project import PROJECT_ACTION_MESSAGE_TAG, Project
+from ..services import handle_tasks_on_invoice_update
 from ..tables import InvoiceListTable
 
 
@@ -58,6 +74,10 @@ class ChangeInvoiceStatusView(DelegatedViewMixin, InvoiceAccessMixin, UpdateView
     model = Invoice
 
     def form_valid(self, form):
+        invoice = get_object_or_404(
+            Invoice, pk=self.kwargs["invoice_pk"]
+        )  # to get the old status
+        old_status = invoice.status
         response = super().form_valid(form)
         if form.cleaned_data["comment"]:
             invoice_status_change = _(
@@ -100,6 +120,8 @@ class ChangeInvoiceStatusView(DelegatedViewMixin, InvoiceAccessMixin, UpdateView
             related=self.object,
         )
 
+        handle_tasks_on_invoice_update(old_status=old_status, invoice=self.object)
+
         return response
 
 
@@ -119,6 +141,9 @@ class DeleteInvoiceView(DeleteView):
 
     @transaction.atomic()
     def form_valid(self, form):
+        # remove all tasks related to this invoice irrespective of code and users/user_group
+        remove_tasks_of_related_obj(related_obj=self.object)
+
         response = super().form_valid(form)
 
         messenger(
@@ -223,6 +248,22 @@ class CreateInvoiceView(CreateView):
             source=self.project,
             related=self.object,
         )
+
+        if len(self.project.invoices.all()) == 1:
+            # remove Project waiting invoices task for applicant on first invoice
+            remove_tasks_for_user(
+                code=PROJECT_WAITING_INVOICE,
+                user=self.project.user,
+                related_obj=self.project,
+            )
+
+        # add Invoice waiting approval task for Staff group
+        add_task_to_user_group(
+            code=INVOICE_WAITING_APPROVAL,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=self.object,
+        )
+
         messages.success(
             self.request, _("Invoice added"), extra_tags=PROJECT_ACTION_MESSAGE_TAG
         )
@@ -274,7 +315,9 @@ class EditInvoiceView(InvoiceAccessMixin, UpdateView):
             return self.form_invalid(form)
 
     def form_valid(self, form):
+        old_status = self.object.status
         response = super().form_valid(form)
+
         if form.cleaned_data:
             if self.object.status in INVOICE_TRANISTION_TO_RESUBMITTED:
                 self.object.transition_invoice_to_resubmitted()
@@ -305,6 +348,38 @@ class EditInvoiceView(InvoiceAccessMixin, UpdateView):
             related=self.object,
         )
 
+        if self.request.user.is_applicant and old_status == CHANGES_REQUESTED_BY_STAFF:
+            # remove invoice required changes task for applicant
+            remove_tasks_for_user(
+                code=INVOICE_REQUIRED_CHANGES,
+                user=self.object.project.user,
+                related_obj=self.object,
+            )
+
+            # add invoice waiting approval task for staff group
+            add_task_to_user_group(
+                code=INVOICE_WAITING_APPROVAL,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=self.object,
+            )
+
+        if (
+            self.request.user.is_apply_staff
+            and old_status == CHANGES_REQUESTED_BY_FINANCE
+        ):
+            # remove invoice required changes task for staff group
+            remove_tasks_for_user_group(
+                code=INVOICE_REQUIRED_CHANGES,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=self.object,
+            )
+            # add invoice waiting approval task for staff group
+            add_task_to_user_group(
+                code=INVOICE_WAITING_APPROVAL,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=self.object,
+            )
+
         # Required for django-file-form: delete temporary files for the new files
         # that are uploaded.
         form.delete_temporary_files()
diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py
index eb200582d09bcb29c1605753eb783b1e3c1bfb5e..c8c03844ed1751985b669b34d53f4344a19e8875 100644
--- a/hypha/apply/projects/views/project.py
+++ b/hypha/apply/projects/views/project.py
@@ -6,6 +6,7 @@ from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.mixins import UserPassesTestMixin
+from django.contrib.auth.models import Group
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.db import transaction
 from django.db.models import Count, Q
@@ -40,10 +41,28 @@ from hypha.apply.activity.messaging import MESSAGES, messenger
 from hypha.apply.activity.models import ACTION, ALL, COMMENT, TEAM, Activity
 from hypha.apply.activity.views import ActivityContextMixin, CommentFormView
 from hypha.apply.stream_forms.models import BaseStreamForm
+from hypha.apply.todo.options import (
+    PAF_REQUIRED_CHANGES,
+    PAF_WAITING_APPROVAL,
+    PAF_WAITING_ASSIGNEE,
+    PROJECT_SUBMIT_PAF,
+    PROJECT_WAITING_CONTRACT,
+    PROJECT_WAITING_CONTRACT_DOCUMENT,
+    PROJECT_WAITING_CONTRACT_REVIEW,
+    PROJECT_WAITING_INVOICE,
+    PROJECT_WAITING_PAF,
+)
+from hypha.apply.todo.views import (
+    add_task_to_user,
+    add_task_to_user_group,
+    remove_tasks_for_user,
+    remove_tasks_for_user_group,
+)
 from hypha.apply.users.decorators import (
     staff_or_finance_or_contracting_required,
     staff_required,
 )
+from hypha.apply.users.groups import CONTRACTING_GROUP_NAME, STAFF_GROUP_NAME
 from hypha.apply.utils.models import PDFPageSettings
 from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegateableView, DelegatedViewMixin, ViewDispatcher
@@ -108,6 +127,20 @@ class SendForApprovalView(DelegatedViewMixin, UpdateView):
 
         response = super().form_valid(form)
 
+        # remove PAF submission task for staff group
+        remove_tasks_for_user_group(
+            code=PROJECT_SUBMIT_PAF,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=self.object,
+        )
+
+        # remove PAF rejection task for staff if exists
+        remove_tasks_for_user_group(
+            code=PAF_REQUIRED_CHANGES,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=self.object,
+        )
+
         project_settings = ProjectSettings.for_request(self.request)
 
         paf_approvals = self.object.paf_approvals.filter(approved=False)
@@ -130,6 +163,10 @@ class SendForApprovalView(DelegatedViewMixin, UpdateView):
                         source=self.object,
                         related=[paf_approvals.first()],
                     )
+                    # add PAF waiting approval task for paf_approval user
+                    add_task_to_user(
+                        code=PAF_WAITING_APPROVAL, user=user, related_obj=self.object
+                    )
                 else:
                     messenger(
                         MESSAGES.ASSIGN_PAF_APPROVER,
@@ -137,6 +174,12 @@ class SendForApprovalView(DelegatedViewMixin, UpdateView):
                         user=self.request.user,
                         source=self.object,
                     )
+                    # add PAF waiting assignee task for paf_approval reviewer_roles
+                    add_task_to_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=paf_approvals.first().paf_reviewer_role.user_roles.all(),
+                        related_obj=self.object,
+                    )
         else:
             if paf_approvals.filter(user__isnull=False).exists():
                 messenger(
@@ -152,6 +195,13 @@ class SendForApprovalView(DelegatedViewMixin, UpdateView):
                     source=self.object,
                     related=paf_approvals.filter(user__isnull=False),
                 )
+                # add PAF waiting approval task for paf_approvals users
+                for paf_approval in paf_approvals.filter(user__isnull=False):
+                    add_task_to_user(
+                        code=PAF_WAITING_APPROVAL,
+                        user=paf_approval.user,
+                        related_obj=self.object,
+                    )
             if paf_approvals.filter(user__isnull=True).exists():
                 messenger(
                     MESSAGES.ASSIGN_PAF_APPROVER,
@@ -159,6 +209,13 @@ class SendForApprovalView(DelegatedViewMixin, UpdateView):
                     user=self.request.user,
                     source=self.object,
                 )
+                # add PAF waiting assignee task for paf_approvals reviewer_roles
+                for paf_approval in paf_approvals.filter(user__isnull=True):
+                    add_task_to_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                        related_obj=self.object,
+                    )
 
         project.status = INTERNAL_APPROVAL
         project.save(update_fields=["status"])
@@ -407,6 +464,12 @@ class ApproveContractView(DelegatedViewMixin, UpdateView):
                 source=self.project,
                 related=self.object,
             )
+            # remove Project waiting contract review task for staff
+            remove_tasks_for_user_group(
+                code=PROJECT_WAITING_CONTRACT_REVIEW,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=self.project,
+            )
 
             self.project.status = INVOICING_AND_REPORTING
             self.project.save(update_fields=["status"])
@@ -418,6 +481,12 @@ class ApproveContractView(DelegatedViewMixin, UpdateView):
                 source=self.project,
                 related=old_stage,
             )
+            # add Project waiting invoice task for applicant
+            add_task_to_user(
+                code=PROJECT_WAITING_INVOICE,
+                user=self.project.user,
+                related_obj=self.project,
+            )
 
         messages.success(
             self.request,
@@ -468,6 +537,7 @@ class UploadContractView(DelegatedViewMixin, CreateView):
                 extra_tags=PROJECT_ACTION_MESSAGE_TAG,
             )
         elif self.request.user.is_contracting:
+            # :todo: update same date when staff uploads the contract(with STAFF_UPLOAD_CONTRACT setting)
             form.instance.uploaded_by_contractor_at = timezone.now()
             messages.success(
                 self.request,
@@ -485,6 +555,25 @@ class UploadContractView(DelegatedViewMixin, CreateView):
                 source=project,
                 related=form.instance,
             )
+            # remove Project waiting contract task for contracting/staff group
+            if settings.STAFF_UPLOAD_CONTRACT:
+                remove_tasks_for_user_group(
+                    code=PROJECT_WAITING_CONTRACT,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=project,
+                )
+            else:
+                remove_tasks_for_user_group(
+                    code=PROJECT_WAITING_CONTRACT,
+                    user_group=Group.objects.filter(name=CONTRACTING_GROUP_NAME),
+                    related_obj=project,
+                )
+            # add Project waiting contract document task for applicant
+            add_task_to_user(
+                code=PROJECT_WAITING_CONTRACT_DOCUMENT,
+                user=project.user,
+                related_obj=project,
+            )
 
         return response
 
@@ -524,6 +613,18 @@ class SubmitContractDocumentsView(DelegatedViewMixin, UpdateView):
             user=self.request.user,
             source=project,
         )
+        # remove project waiting contract documents task for applicant
+        remove_tasks_for_user(
+            code=PROJECT_WAITING_CONTRACT_DOCUMENT,
+            user=project.user,
+            related_obj=project,
+        )
+        # add project waiting contract review task for staff
+        add_task_to_user_group(
+            code=PROJECT_WAITING_CONTRACT_REVIEW,
+            user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+            related_obj=project,
+        )
 
         messages.success(
             self.request,
@@ -626,6 +727,35 @@ class ChangePAFStatusView(DelegatedViewMixin, UpdateView):
             self.object.save(update_fields=["status"])
             paf_approval.save()
 
+            # remove PAF waiting assignee/approval task for paf approval user/reviewer roles.
+            if project_settings.paf_approval_sequential:
+                if paf_approval.user:
+                    remove_tasks_for_user(
+                        code=PAF_WAITING_APPROVAL,
+                        user=paf_approval.user,
+                        related_obj=self.object,
+                    )
+                else:
+                    remove_tasks_for_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                        related_obj=self.object,
+                    )
+            else:
+                for approval in self.object.paf_approvals.filter(approved=False):
+                    if approval.user:
+                        remove_tasks_for_user(
+                            code=PAF_WAITING_APPROVAL,
+                            user=approval.user,
+                            related_obj=self.object,
+                        )
+                    else:
+                        remove_tasks_for_user_group(
+                            code=PAF_WAITING_ASSIGNEE,
+                            user_group=approval.paf_reviewer_role.user_roles.all(),
+                            related_obj=self.object,
+                        )
+
             if not paf_approval.user:
                 paf_approval.user = self.request.user
                 paf_approval.save(update_fields=["user"])
@@ -644,12 +774,32 @@ class ChangePAFStatusView(DelegatedViewMixin, UpdateView):
                 source=self.object,
                 related=old_stage,
             )
+            # add PAF required changes task to staff user group
+            add_task_to_user_group(
+                code=PAF_REQUIRED_CHANGES,
+                user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                related_obj=self.object,
+            )
+
             messages.success(
                 self.request,
                 _("PAF status has been updated"),
                 extra_tags=PROJECT_ACTION_MESSAGE_TAG,
             )
         elif paf_status == APPROVE:
+            # remove task for paf approval user/user_group related to this paf_approval of project
+            if paf_approval.user:
+                remove_tasks_for_user(
+                    code=PAF_WAITING_APPROVAL,
+                    user=paf_approval.user,
+                    related_obj=self.object,
+                )
+            else:
+                remove_tasks_for_user_group(
+                    code=PAF_WAITING_ASSIGNEE,
+                    user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                    related_obj=self.object,
+                )
             paf_approval.approved = True
             paf_approval.approved_at = timezone.now()
             paf_approval.user = self.request.user
@@ -668,6 +818,12 @@ class ChangePAFStatusView(DelegatedViewMixin, UpdateView):
                             source=self.object,
                             related=[next_paf_approval],
                         )
+                        # add PAF waiting approval task for next paf approval user
+                        add_task_to_user(
+                            code=PAF_WAITING_APPROVAL,
+                            user=next_paf_approval.user,
+                            related_obj=self.object,
+                        )
                     else:
                         messenger(
                             MESSAGES.ASSIGN_PAF_APPROVER,
@@ -675,6 +831,12 @@ class ChangePAFStatusView(DelegatedViewMixin, UpdateView):
                             user=self.request.user,
                             source=self.object,
                         )
+                        # add PAF waiting assignee task for nex paf approval reviewer roles
+                        add_task_to_user_group(
+                            code=PAF_WAITING_ASSIGNEE,
+                            user_group=next_paf_approval.paf_reviewer_role.user_roles.all(),
+                            related_obj=self.object,
+                        )
             messages.success(
                 self.request,
                 _("PAF has been approved"),
@@ -708,6 +870,19 @@ class ChangePAFStatusView(DelegatedViewMixin, UpdateView):
                 source=self.object,
                 related=old_stage,
             )
+            # add project waiting contract task to staff/contracting groups
+            if settings.STAFF_UPLOAD_CONTRACT:
+                add_task_to_user_group(
+                    code=PROJECT_WAITING_CONTRACT,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=self.object,
+                )
+            else:
+                add_task_to_user_group(
+                    code=PROJECT_WAITING_CONTRACT,
+                    user_group=Group.objects.filter(name=CONTRACTING_GROUP_NAME),
+                    related_obj=self.object,
+                )
         return response
 
 
@@ -782,8 +957,26 @@ class UpdateAssignApproversView(DelegatedViewMixin, UpdateView):
 
         project = self.kwargs["object"]
 
+        old_paf_approval = get_latest_project_paf_approval_via_roles(
+            project=project, roles=self.request.user.groups.all()
+        )
+
         response = super().form_valid(form)
 
+        # remove current task of user/user_group related to latest paf_approval of project
+        if old_paf_approval.user:
+            remove_tasks_for_user(
+                code=PAF_WAITING_APPROVAL,
+                user=old_paf_approval.user,
+                related_obj=project,
+            )
+        else:
+            remove_tasks_for_user_group(
+                code=PAF_WAITING_ASSIGNEE,
+                user_group=old_paf_approval.paf_reviewer_role.user_roles.all(),
+                related_obj=project,
+            )
+
         paf_approval = get_latest_project_paf_approval_via_roles(
             project=project, roles=self.request.user.groups.all()
         )
@@ -796,6 +989,12 @@ class UpdateAssignApproversView(DelegatedViewMixin, UpdateView):
                 source=self.object,
                 related=[paf_approval],
             )
+            # add PAF waiting approval task to updated paf_approval user
+            add_task_to_user(
+                code=PAF_WAITING_APPROVAL,
+                user=paf_approval.user,
+                related_obj=self.object,
+            )
         else:
             messenger(
                 MESSAGES.ASSIGN_PAF_APPROVER,
@@ -803,6 +1002,12 @@ class UpdateAssignApproversView(DelegatedViewMixin, UpdateView):
                 user=self.request.user,
                 source=self.object,
             )
+            # add paf waiting for assignee task
+            add_task_to_user_group(
+                code=PAF_WAITING_ASSIGNEE,
+                user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                related_obj=self.object,
+            )
 
         return response
 
@@ -834,6 +1039,35 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                     "user__id", flat=True
                 )
             )
+        # remove PAF waiting assignee/approval task for paf approval user/reviewer roles.
+        if project_settings.paf_approval_sequential:
+            paf_approval = project.paf_approvals.filter(approved=False).first()
+            if paf_approval.user:
+                remove_tasks_for_user(
+                    code=PAF_WAITING_APPROVAL,
+                    user=paf_approval.user,
+                    related_obj=project,
+                )
+            else:
+                remove_tasks_for_user_group(
+                    code=PAF_WAITING_ASSIGNEE,
+                    user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                    related_obj=project,
+                )
+        else:
+            for approval in project.paf_approvals.filter(approved=False):
+                if approval.user:
+                    remove_tasks_for_user(
+                        code=PAF_WAITING_APPROVAL,
+                        user=approval.user,
+                        related_obj=project,
+                    )
+                else:
+                    remove_tasks_for_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=approval.paf_reviewer_role.user_roles.all(),
+                        related_obj=project,
+                    )
 
         response = super().form_valid(form)
 
@@ -852,6 +1086,10 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                         source=self.object,
                         related=[paf_approvals.first()],
                     )
+                    # add PAF waiting approval task to paf_approval user
+                    add_task_to_user(
+                        code=PAF_WAITING_APPROVAL, user=user, related_obj=self.object
+                    )
                 elif not user:
                     messenger(
                         MESSAGES.ASSIGN_PAF_APPROVER,
@@ -859,6 +1097,12 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                         user=self.request.user,
                         source=self.object,
                     )
+                    # add PAF waiting assignee to paf_approvals reviewer roles
+                    add_task_to_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=paf_approvals.first().paf_reviewer_role.user_roles.all(),
+                        related_obj=self.object,
+                    )
             else:
                 if paf_approvals.filter(user__isnull=False).exists():
                     messenger(
@@ -868,6 +1112,13 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                         source=self.object,
                         related=paf_approvals.filter(user__isnull=False),
                     )
+                    # add PAF waiting approval task for paf_approvals users
+                    for paf_approval in paf_approvals.filter(user__isnull=False):
+                        add_task_to_user(
+                            code=PAF_WAITING_APPROVAL,
+                            user=paf_approval.user,
+                            related_obj=self.object,
+                        )
                 if paf_approvals.filter(user__isnull=True).exists():
                     messenger(
                         MESSAGES.ASSIGN_PAF_APPROVER,
@@ -875,7 +1126,15 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                         user=self.request.user,
                         source=self.object,
                     )
+                    # add PAF waiting assignee task for paf_approvals reviewer_roles
+                    for paf_approval in paf_approvals.filter(user__isnull=True):
+                        add_task_to_user_group(
+                            code=PAF_WAITING_ASSIGNEE,
+                            user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                            related_obj=self.object,
+                        )
         elif paf_approvals:
+            # :todo: check if this is covering any case(might be a duplicate of SendForApprovalView)
             if paf_approvals.filter(user__isnull=False).exists():
                 messenger(
                     MESSAGES.APPROVE_PAF,
@@ -884,6 +1143,13 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                     source=self.object,
                     related=paf_approvals.filter(user__isnull=False),
                 )
+                # add PAF waiting approval task for paf_approvals users
+                for paf_approval in paf_approvals.filter(user__isnull=False):
+                    add_task_to_user(
+                        code=PAF_WAITING_APPROVAL,
+                        user=paf_approval.user,
+                        related_obj=self.object,
+                    )
             if paf_approvals.filter(user__isnull=True).exists():
                 messenger(
                     MESSAGES.ASSIGN_PAF_APPROVER,
@@ -891,6 +1157,13 @@ class UpdatePAFApproversView(DelegatedViewMixin, UpdateView):
                     user=self.request.user,
                     source=self.object,
                 )
+                # add PAF waiting assignee task for paf_approvals reviewer_roles
+                for paf_approval in paf_approvals.filter(user__isnull=True):
+                    add_task_to_user_group(
+                        code=PAF_WAITING_ASSIGNEE,
+                        user_group=paf_approval.paf_reviewer_role.user_roles.all(),
+                        related_obj=self.object,
+                    )
 
         messages.success(
             self.request,
@@ -1542,6 +1815,18 @@ class ProjectApprovalFormEditView(BaseStreamForm, UpdateView):
                 self.sow_form.save(sow_form_fields=sow_form_fields, project=self.object)
                 self.paf_form.delete_temporary_files()
                 self.sow_form.delete_temporary_files()
+                # remove PAF addition task for staff group
+                remove_tasks_for_user_group(
+                    code=PROJECT_WAITING_PAF,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=self.object,
+                )
+                # add PAF submission task for staff group
+                add_task_to_user_group(
+                    code=PROJECT_SUBMIT_PAF,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=self.object,
+                )
                 return HttpResponseRedirect(self.get_success_url())
             else:
                 if not self.paf_form.is_valid():
@@ -1556,6 +1841,18 @@ class ProjectApprovalFormEditView(BaseStreamForm, UpdateView):
                     paf_form_fields = []
                 self.paf_form.save(paf_form_fields=paf_form_fields)
                 self.paf_form.delete_temporary_files()
+                # remove PAF addition task for staff group
+                remove_tasks_for_user_group(
+                    code=PROJECT_WAITING_PAF,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=self.object,
+                )
+                # add PAF submission task for staff group
+                add_task_to_user_group(
+                    code=PROJECT_SUBMIT_PAF,
+                    user_group=Group.objects.filter(name=STAFF_GROUP_NAME),
+                    related_obj=self.object,
+                )
                 return HttpResponseRedirect(self.get_success_url())
             else:
                 return self.form_invalid(self.paf_form)
diff --git a/hypha/apply/review/blocks.py b/hypha/apply/review/blocks.py
index 988bdfc44b45e37bee2ee735eefa5f8b7dd01c0c..2bc25ca696e89b2d0639178dcdaea20fd4abb6ea 100644
--- a/hypha/apply/review/blocks.py
+++ b/hypha/apply/review/blocks.py
@@ -1,6 +1,7 @@
 import json
 
 from django import forms
+from django.conf import settings
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 from wagtail.blocks import RichTextBlock
@@ -8,7 +9,6 @@ from wagtail.blocks import RichTextBlock
 from hypha.apply.review.fields import ScoredAnswerField
 from hypha.apply.review.options import (
     NA,
-    PRIVATE,
     RATE_CHOICE_NA,
     RATE_CHOICES,
     RATE_CHOICES_DICT,
@@ -152,7 +152,7 @@ class VisibilityBlock(ReviewMustIncludeFieldBlock):
     def get_field_kwargs(self, struct_value):
         kwargs = super(VisibilityBlock, self).get_field_kwargs(struct_value)
         kwargs["choices"] = VISIBILITY.items()
-        kwargs["initial"] = PRIVATE
+        kwargs["initial"] = settings.REVIEW_VISIBILITY_DEFAULT
         kwargs["help_text"] = mark_safe(
             "<br>".join(
                 [
diff --git a/hypha/apply/stream_forms/blocks.py b/hypha/apply/stream_forms/blocks.py
index e67c7e57d859b3beb7eaf7ff1cc9814d68ef573f..70d264dd58cc49614085e6357ffd758c776ea60c 100644
--- a/hypha/apply/stream_forms/blocks.py
+++ b/hypha/apply/stream_forms/blocks.py
@@ -285,6 +285,8 @@ class CheckboxesFieldBlock(OptionalFormFieldBlock):
         return kwargs
 
     def prepare_data(self, value, data, serialize=False):
+        if not data:
+            return data
         base_prepare = super().prepare_data
         return [base_prepare(value, item, serialize) for item in data]
 
@@ -443,7 +445,9 @@ class MultiFileFieldBlock(UploadableMediaBlock):
 
     def prepare_data(self, value, data, serialize):
         if serialize:
-            return [file.serialize() for file in data]
+            if data:
+                return [file.serialize() for file in data]
+            return None
         return data
 
     def no_response(self):
diff --git a/hypha/public/mailchimp/__init__.py b/hypha/apply/todo/__init__.py
similarity index 100%
rename from hypha/public/mailchimp/__init__.py
rename to hypha/apply/todo/__init__.py
diff --git a/hypha/apply/todo/apps.py b/hypha/apply/todo/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..b96e45e77612f0a7f8d25a48a70c0732795db266
--- /dev/null
+++ b/hypha/apply/todo/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class TodoConfig(AppConfig):
+    name = "hypha.apply.todo"
diff --git a/hypha/apply/todo/migrations/0001_initial.py b/hypha/apply/todo/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a6f39fc8162a8f1236c9b8bdc8920017d46bb7c
--- /dev/null
+++ b/hypha/apply/todo/migrations/0001_initial.py
@@ -0,0 +1,93 @@
+# Generated by Django 4.1.13 on 2023-11-22 10:41
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ("contenttypes", "0002_remove_content_type_name"),
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Task",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "code",
+                    models.CharField(
+                        choices=[
+                            ("project_waiting_paf", "Project waiting PAF"),
+                            ("project_submit_paf", "Project submit PAF"),
+                            ("paf_required_changes", "PAF required changes"),
+                            ("paf_waiting_assignee", "PAF waiting assignee"),
+                            ("paf_waiting_approval", "PAF waiting approval"),
+                            ("project_waiting_contract", "Project waiting contract"),
+                            (
+                                "project_waiting_contract_document",
+                                "Project waiting contract document",
+                            ),
+                            (
+                                "project_waiting_contract_review",
+                                "Project waiting contract review",
+                            ),
+                            ("project_waiting_invoice", "Project waiting invoice"),
+                            ("invoice_required_changes", "Invoice required changes"),
+                            ("invoice_waiting_approval", "Invoice waiting approval"),
+                            ("invoice_waiting_paid", "Invoice waiting paid"),
+                            ("report_due", "Report due"),
+                        ],
+                        max_length=50,
+                    ),
+                ),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "related_object_id",
+                    models.PositiveIntegerField(blank=True, null=True),
+                ),
+                (
+                    "related_content_type",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="task_related",
+                        to="contenttypes.contenttype",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="task",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+                (
+                    "user_group",
+                    models.ManyToManyField(
+                        blank=True, related_name="task", to="auth.group"
+                    ),
+                ),
+            ],
+            options={
+                "ordering": ("-created_at",),
+            },
+        ),
+    ]
diff --git a/hypha/public/mailchimp/migrations/__init__.py b/hypha/apply/todo/migrations/__init__.py
similarity index 100%
rename from hypha/public/mailchimp/migrations/__init__.py
rename to hypha/apply/todo/migrations/__init__.py
diff --git a/hypha/apply/todo/models.py b/hypha/apply/todo/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9dab5ded9898c605ca6d01cca8792296fba08df
--- /dev/null
+++ b/hypha/apply/todo/models.py
@@ -0,0 +1,36 @@
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+
+from hypha.apply.users.models import User
+
+from .options import TASKS_CODE_CHOICES
+
+
+class Task(models.Model):
+    code = models.CharField(choices=TASKS_CODE_CHOICES, max_length=50)
+    user = models.ForeignKey(
+        User, blank=True, null=True, on_delete=models.CASCADE, related_name="task"
+    )
+    user_group = models.ManyToManyField(
+        Group,
+        related_name="task",
+        blank=True,
+    )
+    created_at = models.DateTimeField(auto_now_add=True)
+    related_content_type = models.ForeignKey(
+        ContentType,
+        blank=True,
+        null=True,
+        on_delete=models.CASCADE,
+        related_name="task_related",
+    )
+    related_object_id = models.PositiveIntegerField(blank=True, null=True)
+    related_object = GenericForeignKey("related_content_type", "related_object_id")
+
+    class Meta:
+        ordering = ("-created_at",)
+
+    def save(self, **kwargs):
+        return super().save(**kwargs)
diff --git a/hypha/apply/todo/options.py b/hypha/apply/todo/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..1fa7cb5f42ad031d0db4efc1182e33488a6e2302
--- /dev/null
+++ b/hypha/apply/todo/options.py
@@ -0,0 +1,152 @@
+import copy
+
+from django.utils.translation import gettext as _
+
+from hypha.apply.activity.adapters.utils import link_to
+
+PROJECT_WAITING_PAF = "project_waiting_paf"
+PROJECT_SUBMIT_PAF = "project_submit_paf"
+PAF_REQUIRED_CHANGES = "paf_required_changes"
+PAF_WAITING_ASSIGNEE = "paf_waiting_assignee"
+PAF_WAITING_APPROVAL = "paf_waiting_approval"
+PROJECT_WAITING_CONTRACT = "project_waiting_contract"
+PROJECT_WAITING_CONTRACT_DOCUMENT = "project_waiting_contract_document"
+PROJECT_WAITING_CONTRACT_REVIEW = "project_waiting_contract_review"
+PROJECT_WAITING_INVOICE = "project_waiting_invoice"
+INVOICE_REQUIRED_CHANGES = "invoice_required_changes"
+INVOICE_WAITING_APPROVAL = "invoice_waiting_approval"
+INVOICE_WAITING_PAID = "invoice_waiting_paid"
+REPORT_DUE = "report_due"
+
+TASKS_CODE_CHOICES = (
+    (PROJECT_WAITING_PAF, "Project waiting PAF"),
+    (PROJECT_SUBMIT_PAF, "Project submit PAF"),
+    (PAF_REQUIRED_CHANGES, "PAF required changes"),
+    (PAF_WAITING_ASSIGNEE, "PAF waiting assignee"),
+    (PAF_WAITING_APPROVAL, "PAF waiting approval"),
+    (PROJECT_WAITING_CONTRACT, "Project waiting contract"),
+    (PROJECT_WAITING_CONTRACT_DOCUMENT, "Project waiting contract document"),
+    (PROJECT_WAITING_CONTRACT_REVIEW, "Project waiting contract review"),
+    (PROJECT_WAITING_INVOICE, "Project waiting invoice"),
+    (INVOICE_REQUIRED_CHANGES, "Invoice required changes"),
+    (INVOICE_WAITING_APPROVAL, "Invoice waiting approval"),
+    (INVOICE_WAITING_PAID, "Invoice waiting paid"),
+    (REPORT_DUE, "Report due"),
+)
+
+
+template_map = {
+    # SUBMISSIONS ACTIONS
+    # :todo: actions for mupltiple stages of submission
+    # PROJECT actions
+    # draft state (staff action)
+    PROJECT_WAITING_PAF: {
+        "text": _("Project [{related.title}]({link}) is waiting for PAF"),
+        "icon": "dashboard-paf",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    PROJECT_SUBMIT_PAF: {
+        "text": _("Project [{related.title}]({link}) is waiting for PAF submission"),
+        "icon": "dashboard-paf",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    PAF_REQUIRED_CHANGES: {
+        "text": _(
+            "PAF for project [{related.title}]({link}) required changes or more information"
+        ),
+        "icon": "dashboard-paf",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    # internal approval state (approvers/finance... action)
+    PAF_WAITING_ASSIGNEE: {
+        "text": _("PAF for project [{related.title}]({link}) is waiting for assignee"),
+        "icon": "dashboard-paf",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    PAF_WAITING_APPROVAL: {
+        "text": _(
+            "PAF for project [{related.title}]({link}) is waiting for your approval"
+        ),
+        "icon": "dashboard-paf",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    # contracting state (vendor/staff/contracting team action)
+    PROJECT_WAITING_CONTRACT: {
+        "text": _("Project [{related.title}]({link}) is waiting for contract"),
+        "icon": "dashboard-contract",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    PROJECT_WAITING_CONTRACT_DOCUMENT: {
+        "text": _(
+            "Project [{related.title}]({link}) is waiting for contracting documents"
+        ),
+        "icon": "dashboard-document",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    PROJECT_WAITING_CONTRACT_REVIEW: {
+        "text": _(
+            "Contract for project [{related.title}]({link}) is waiting for review"
+        ),
+        "icon": "dashboard-contract",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    # invoicing and reporting (vendor/staff/finance team action)
+    PROJECT_WAITING_INVOICE: {
+        "text": _("Project [{related.title}]({link}) is waiting for invoice"),
+        "icon": "dashboard-invoice",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    INVOICE_REQUIRED_CHANGES: {
+        "text": _(
+            "Invoice [{related.invoice_number}]({link}) required changes or more information"
+        ),
+        "icon": "dashboard-invoice",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    INVOICE_WAITING_APPROVAL: {
+        "text": _(
+            "Invoice [{related.invoice_number}]({link}) is waiting for your approval"
+        ),
+        "icon": "dashboard-invoice",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    INVOICE_WAITING_PAID: {
+        "text": _("Invoice [{related.invoice_number}]({link}) is waiting to be paid"),
+        "icon": "dashboard-invoice",
+        "url": "{link}",
+        "type": _("project"),
+    },
+    REPORT_DUE: {
+        "text": _("Report for project [{related.title}]({link}) is due"),
+        "icon": "dashboard-report",
+        "url": "{link}",
+        "type": _("project"),
+    },
+}
+
+
+def get_task_template(request, code, related_obj, **kwargs):
+    templates = copy.deepcopy(template_map)
+    try:
+        template = templates[code]
+    except KeyError:
+        # Unregistered code
+        return
+    template_kwargs = {
+        "related": related_obj,
+        "link": link_to(related_obj, request),
+    }
+    template["text"] = template["text"].format(**template_kwargs)
+    template["url"] = template["url"].format(**template_kwargs)
+    return template
diff --git a/hypha/apply/todo/services.py b/hypha/apply/todo/services.py
new file mode 100644
index 0000000000000000000000000000000000000000..47b85fe7b76f460a3b471577849b00df94806b01
--- /dev/null
+++ b/hypha/apply/todo/services.py
@@ -0,0 +1,68 @@
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Count
+
+from hypha.apply.activity.adapters.utils import get_users_for_groups
+
+from .models import Task
+
+
+def validate_user_uniquness(code, user, related_obj):
+    """
+    code + related_object + user should be unique together.
+    """
+    matching_tasks = Task.objects.filter(
+        code=code,
+        related_content_type=ContentType.objects.get_for_model(related_obj).id,
+        related_object_id=related_obj.id,
+    )
+    if matching_tasks.filter(user=user).exists():
+        # if same task already assigned to the same user
+        # raise ValidationError("Task is already assigned to the user") # :todo: add validation msg as a log msg?
+        return False
+    else:
+        # if same task is already assigned to user's user_group
+        user_group_matching_tasks = matching_tasks.annotate(
+            group_count=Count("user_group")
+        ).filter(group_count=len(user.groups.all()))
+        for group in user.groups.all():
+            user_group_matching_tasks = user_group_matching_tasks.filter(
+                user_group__id=group.id
+            )
+        if user_group_matching_tasks.exists():
+            # raise ValidationError("Task is already assigned to user's group")
+            return False
+        return True
+
+
+def validate_user_groups_uniqueness(code, user_groups, related_obj):
+    """
+    code + related_object + user_group should be unique together.
+    """
+    matching_tasks = Task.objects.filter(
+        code=code,
+        related_content_type=ContentType.objects.get_for_model(related_obj).id,
+        related_object_id=related_obj.id,
+    )
+    user_group_matching_tasks = matching_tasks.annotate(
+        group_count=Count("user_group")
+    ).filter(group_count=len(user_groups))
+    for group in user_groups:
+        user_group_matching_tasks = user_group_matching_tasks.filter(
+            user_group__id=group.id
+        )
+    if user_group_matching_tasks.exists():
+        # same task with same user_group already exists
+        # :todo: add validation msg as a log msg?
+        return False
+
+    # user with exact user group already assigned for same task
+    users = get_users_for_groups(
+        list(user_groups), exact_match=True
+    )  # users with provided user_group
+
+    for user in users:
+        if matching_tasks.filter(user=user).exists():
+            Task.objects.filter(
+                id=matching_tasks.id
+            ).delete()  # delete those user's tasks
+    return True
diff --git a/hypha/public/mailchimp/tests/__init__.py b/hypha/apply/todo/tests.py
similarity index 100%
rename from hypha/public/mailchimp/tests/__init__.py
rename to hypha/apply/todo/tests.py
diff --git a/hypha/apply/todo/views.py b/hypha/apply/todo/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..276b770cb8d77953109abcf8d66c2fa0241eb15a
--- /dev/null
+++ b/hypha/apply/todo/views.py
@@ -0,0 +1,138 @@
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Count
+
+from .models import Task
+from .options import get_task_template
+from .services import validate_user_groups_uniqueness, validate_user_uniquness
+
+
+def add_task_to_user(code, user, related_obj):
+    """
+    Add task for a user
+    input:
+        code: TASKS_CODE_CHOICES.keys()
+        user: User object
+        related_obj: Object - Submission, Project, Invoice, Report
+    output: task - Task object / None in case of no creation
+    """
+    user_uniqueness = validate_user_uniquness(
+        code=code, user=user, related_obj=related_obj
+    )
+    if user_uniqueness:
+        task = Task.objects.create(code=code, user=user, related_object=related_obj)
+        return task
+    return None
+
+
+def add_task_to_user_group(code, user_group, related_obj):
+    """
+    Add task for user_groups
+    input:
+        code: TASKS_CODE_CHOICES.keys()
+        user_group: Queryset - Group objects
+        related_obj: Object - Submission, Project, Invoice, Report
+    output: task - Task object / None in case of no creation
+    """
+    user_groups_uniqueness = validate_user_groups_uniqueness(
+        code=code, user_groups=user_group, related_obj=related_obj
+    )
+    if user_groups_uniqueness:
+        task = Task.objects.create(code=code, related_object=related_obj)
+        groups = [Group.objects.filter(id=group.id).first() for group in user_group]
+        task.user_group.add(*groups)
+        return task
+    return None
+
+
+def remove_tasks_for_user(code, user, related_obj):
+    """
+    Remove task for a user
+    input:
+        code: TASKS_CODE_CHOICES.keys()
+        user: User object
+        related_obj: Object - Submission, Project, Invoice, Report
+    output: None
+    """
+    task = Task.objects.filter(
+        code=code,
+        user=user,
+        related_content_type=ContentType.objects.get_for_model(related_obj).id,
+        related_object_id=related_obj.id,
+    ).first()
+    if task:
+        task.delete()
+    return None
+
+
+def remove_tasks_for_user_group(code, user_group, related_obj):
+    """
+    Remove task for user_groups
+    input:
+        code: TASKS_CODE_CHOICES.keys()
+        user_group: Queryset - Group objects
+        related_obj: Object - Submission, Project, Invoice, Report
+    output: None
+    """
+    matching_tasks = Task.objects.filter(
+        code=code,
+        related_content_type=ContentType.objects.get_for_model(related_obj).id,
+        related_object_id=related_obj.id,
+    )
+    user_group_matching_tasks = matching_tasks.annotate(
+        group_count=Count("user_group")
+    ).filter(group_count=len(user_group.all()))
+    for group in user_group.all():
+        user_group_matching_tasks = user_group_matching_tasks.filter(
+            user_group__id=group.id
+        )
+    if user_group_matching_tasks.exists():
+        user_group_matching_tasks.delete()
+    return None
+
+
+def remove_tasks_of_related_obj(related_obj):
+    """
+    Remove all tasks of a related object irrespective of their code and users
+    input:
+        related_obj: Object - Submission, Project, Invoice, Report
+    """
+    Task.objects.filter(
+        related_content_type=ContentType.objects.get_for_model(related_obj).id,
+        related_object_id=related_obj.id,
+    ).delete()
+    return None
+
+
+def get_tasks_for_user(user):
+    user_tasks = Task.objects.filter(user=user).annotate(
+        group_count=Count("user_group")
+    )
+    user_group_tasks = Task.objects.annotate(group_count=Count("user_group")).filter(
+        group_count=len(user.groups.all())
+    )
+    for group in user.groups.all():
+        user_group_tasks = user_group_tasks.filter(user_group__id=group.id)
+
+    return user_tasks.union(user_group_tasks)
+
+
+def render_task_templates_for_user(request, user):
+    """
+    input: request (HttpRequest)
+    input: user   (User object)
+
+    output: [{
+                 "text":"",
+                 "icon":"",
+                 "url":"",
+                 "type":"",
+             },
+            ]
+    """
+    tasks = get_tasks_for_user(user)
+    templates = [
+        get_task_template(request, code=task.code, related_obj=task.related_object)
+        for task in tasks
+    ]
+    return templates
diff --git a/hypha/apply/users/admin_views.py b/hypha/apply/users/admin_views.py
index 2f49edb44ad0eb0259c7dcb21aab49098c8dce4f..f9a433c7bf5c1438e3cd8e6a1b46cf268e8b2593 100644
--- a/hypha/apply/users/admin_views.py
+++ b/hypha/apply/users/admin_views.py
@@ -9,6 +9,7 @@ from django.core.paginator import Paginator
 from django.db.models import Q
 from django.http import HttpResponse
 from django.shortcuts import get_object_or_404
+from django.template.defaultfilters import mark_safe
 from django.template.response import TemplateResponse
 from django.utils.translation import gettext as _
 from django.views.decorators.vary import vary_on_headers
@@ -16,7 +17,9 @@ from wagtail.admin.auth import any_permission_required
 from wagtail.admin.filters import WagtailFilterSet
 from wagtail.admin.forms.search import SearchForm
 from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
-from wagtail.users.views.groups import GroupViewSet
+from wagtail.users.views.groups import GroupViewSet, IndexView
+
+from .models import GroupDesc
 
 User = get_user_model()
 
@@ -200,12 +203,48 @@ def index(request, *args):
         )
 
 
+class CustomGroupIndexView(IndexView):
+    """
+    Overriding of wagtail.users.views.groups.IndexView to allow for the addition of help text to the displayed group names. This is done utilizing the get_queryset method
+    """
+
+    def get_queryset(self):
+        """
+        Overriding the normal queryset that would return all Group objects, this returnd an iterable of groups with custom names containing HTML help text.
+        """
+        group_qs = super().get_queryset()
+
+        custom_groups = []
+
+        for group in group_qs:
+            help_text = GroupDesc.get_from_group(group)
+            if help_text:
+                group.name = mark_safe(
+                    f"{group.name}<p class=group-help-text>{help_text}</p>"
+                )
+
+            custom_groups.append(group)
+
+        return custom_groups
+
+
 class CustomGroupViewSet(GroupViewSet):
     """
     Overriding the wagtail.users.views.groups.GroupViewSet just to use custom users view(index)
     when getting all users for a group.
     """
 
+    index_view_class = CustomGroupIndexView
+
     @property
     def users_view(self):
         return index
+
+    def __init__(self, name, **kwargs):
+        super().__init__(name, **kwargs)
+
+    @property
+    def index_view(self):
+        return self.index_view_class.as_view(
+            **self.get_index_view_kwargs(),
+        )
diff --git a/hypha/apply/users/forms.py b/hypha/apply/users/forms.py
index 2264dedc34f84052472049ef2c475e761814d66a..ef8005b26de1888e9aa38075e4a071a98462f5e4 100644
--- a/hypha/apply/users/forms.py
+++ b/hypha/apply/users/forms.py
@@ -1,11 +1,12 @@
 from django import forms
 from django.contrib.auth import get_user_model
 from django.contrib.auth.forms import AuthenticationForm
+from django.template.defaultfilters import mark_safe
 from django.utils.translation import gettext_lazy as _
 from django_select2.forms import Select2Widget
 from wagtail.users.forms import UserCreationForm, UserEditForm
 
-from .models import AuthSettings
+from .models import AuthSettings, GroupDesc
 
 User = get_user_model()
 
@@ -68,27 +69,73 @@ class CustomUserAdminFormBase:
         )
 
 
+class GroupsModelMultipleChoiceField(forms.ModelMultipleChoiceField):
+    """
+    A custom ModelMultipleChoiceField utilized to provide a custom label for the group prompts
+    """
+
+    @classmethod
+    def get_group_mmcf(
+        cls, model_mulitple_choice_field: forms.ModelMultipleChoiceField
+    ):  # Handle the insertion of group help text
+        group_field_dict = model_mulitple_choice_field.__dict__
+        queryset = group_field_dict[
+            "_queryset"
+        ]  # Pull the queryset form the group field
+        unneeded_keys = ("empty_label", "_queryset")
+        for key in unneeded_keys:
+            group_field_dict.pop(
+                key, None
+            )  # Pop unneeded keys/values, ignore if they don't exist.
+
+        # Overwrite the existing group's ModelMultipleChoiceField with the custom GroupsModelMultipleChoiceField that will provide the help text
+        return GroupsModelMultipleChoiceField(queryset=queryset, **group_field_dict)
+
+    def label_from_instance(self, group_obj):
+        """
+        Overwriting ModelMultipleChoiceField's label from instance to provide help_text (if it exists)
+        """
+        help_text = GroupDesc.get_from_group(group_obj)
+        if help_text:
+            return mark_safe(
+                f'{group_obj.name}<p class="group-help-text">{help_text}</p>'
+            )
+        return group_obj.name
+
+
 class CustomUserEditForm(CustomUserAdminFormBase, UserEditForm):
-    pass
+    #    pass
+    """
+    A custom UserEditForm used to provide custom fields (ie. custom group fields)
+    """
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
 
-class CustomWagtailUserCreationForm(CustomUserAdminFormBase, UserCreationForm):
-    pass
+        # Overwrite the existing group's ModelMultipleChoiceField with the custom GroupsModelMultipleChoiceField that will provide the help text
+        self.fields["groups"] = GroupsModelMultipleChoiceField.get_group_mmcf(
+            self.fields["groups"]
+        )
 
 
 class CustomUserCreationForm(CustomUserAdminFormBase, UserCreationForm):
-    def __init__(self, request=None, *args, **kwargs):
+    def __init__(self, register_view=False, request=None, *args, **kwargs):
         self.request = request
         super().__init__(*args, **kwargs)
 
         self.user_settings = AuthSettings.load(request_or_site=self.request)
-        if self.user_settings.consent_show:
+        if register_view and self.user_settings.consent_show:
             self.fields["consent"] = forms.BooleanField(
                 label=self.user_settings.consent_text,
                 help_text=self.user_settings.consent_help,
                 required=True,
             )
 
+        # Overwrite the existing group's ModelMultipleChoiceField with the custom GroupsModelMultipleChoiceField that will provide the help text
+        self.fields["groups"] = GroupsModelMultipleChoiceField.get_group_mmcf(
+            self.fields["groups"]
+        )
+
 
 class ProfileForm(forms.ModelForm):
     class Meta:
@@ -173,7 +220,7 @@ class EmailChangePasswordForm(forms.Form):
         return self.user
 
 
-class TWOFAPasswordForm(forms.Form):
+class Disable2FAConfirmationForm(forms.Form):
     confirmation_text = forms.CharField(
         label=_('To proceed, type "disable" below and then click on "confirm":'),
         strip=True,
@@ -181,10 +228,6 @@ class TWOFAPasswordForm(forms.Form):
         widget=forms.TextInput(attrs={"autofocus": True, "autocomplete": "off"}),
     )
 
-    def __init__(self, user, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.user = user
-
     def clean_confirmation_text(self):
         text = self.cleaned_data["confirmation_text"]
         if text != "disable":
diff --git a/hypha/apply/users/groups.py b/hypha/apply/users/groups.py
index 25244fb2e0b9fb4b850236df128ecebe6ffd0db2..882f02f819fc600fc8db802ffb543f64e3d9e200 100644
--- a/hypha/apply/users/groups.py
+++ b/hypha/apply/users/groups.py
@@ -1,51 +1,93 @@
 from django.utils.translation import gettext_lazy as _
 
 SUPERADMIN = _("Administrator")
-APPLICANT_GROUP_NAME = "Applicant"
-STAFF_GROUP_NAME = "Staff"
-REVIEWER_GROUP_NAME = "Reviewer"
-TEAMADMIN_GROUP_NAME = "Staff Admin"
-PARTNER_GROUP_NAME = "Partner"
-COMMUNITY_REVIEWER_GROUP_NAME = "Community reviewer"
-APPROVER_GROUP_NAME = "Approver"
-FINANCE_GROUP_NAME = "Finance"
-CONTRACTING_GROUP_NAME = "Contracting"
+APPLICANT_GROUP_NAME = _("Applicant")
+STAFF_GROUP_NAME = _("Staff")
+REVIEWER_GROUP_NAME = _("Reviewer")
+TEAMADMIN_GROUP_NAME = _("Staff Admin")
+PARTNER_GROUP_NAME = _("Partner")
+COMMUNITY_REVIEWER_GROUP_NAME = _("Community reviewer")
+APPROVER_GROUP_NAME = _("Approver")
+FINANCE_GROUP_NAME = _("Finance")
+CONTRACTING_GROUP_NAME = _("Contracting")
+
+APPLICANT_HELP_TEXT = _(
+    "Can access their own application and communicate via the communication tab."
+)
+STAFF_HELP_TEXT = _(
+    "View and edit all submissions, submit reviews, send determinations, and set up applications."
+)
+REVIEWER_HELP_TEXT = _(
+    "Has a dashboard and can submit reviews. Advisory Council Members are typically assigned this role."
+)
+
+TEAMADMIN_HELP_TEXT = _(
+    "Can view application message log. Must also be in group Staff."
+)
+
+PARTNER_HELP_TEXT = _(
+    "Can view, edit, and comment on a specific application they are assigned to."
+)
+
+COMMUNITY_REVIEWER_HELP_TEXT = _(
+    "An applicant with access to other applications utilizing the community/peer review workflow."
+)
+
+APPROVER_HELP_TEXT = _(
+    "Can review/approve PAF, and access compliance documents. Must also be in group: Staff, Contracting, or Finance."
+)
+FINANCE_HELP_TEXT = _(
+    "Can review/approve the PAF, access documents associated with contracting, and access invoices approved by Staff."
+)
+CONTRACTING_HELP_TEXT = _(
+    "Can review/approve the PAF and access documents associated with contracting."
+)
+
 
 GROUPS = [
     {
         "name": APPLICANT_GROUP_NAME,
         "permissions": [],
+        "help_text": APPLICANT_HELP_TEXT,
     },
     {
         "name": STAFF_GROUP_NAME,
         "permissions": [],
+        "help_text": STAFF_HELP_TEXT,
     },
     {
         "name": REVIEWER_GROUP_NAME,
         "permissions": [],
+        "help_text": REVIEWER_HELP_TEXT,
     },
     {
         "name": TEAMADMIN_GROUP_NAME,
         "permissions": [],
+        "help_text": TEAMADMIN_HELP_TEXT,
     },
     {
         "name": PARTNER_GROUP_NAME,
         "permissions": [],
+        "help_text": PARTNER_HELP_TEXT,
     },
     {
         "name": COMMUNITY_REVIEWER_GROUP_NAME,
         "permissions": [],
+        "help_text": COMMUNITY_REVIEWER_HELP_TEXT,
     },
     {
         "name": APPROVER_GROUP_NAME,
         "permissions": [],
+        "help_text": APPROVER_HELP_TEXT,
     },
     {
         "name": FINANCE_GROUP_NAME,
         "permissions": [],
+        "help_text": FINANCE_HELP_TEXT,
     },
     {
         "name": CONTRACTING_GROUP_NAME,
         "permissions": [],
+        "help_text": CONTRACTING_HELP_TEXT,
     },
 ]
diff --git a/hypha/apply/users/migrations/0021_groupdesc.py b/hypha/apply/users/migrations/0021_groupdesc.py
new file mode 100644
index 0000000000000000000000000000000000000000..44116250b45a6e53290f1bd60a361bf1b881ce61
--- /dev/null
+++ b/hypha/apply/users/migrations/0021_groupdesc.py
@@ -0,0 +1,44 @@
+# Generated by Django 3.2.22 on 2023-10-31 17:26
+
+from django.db import migrations, models
+from django.contrib.auth.models import Group
+import django.db.models.deletion
+
+from hypha.apply.users.groups import GROUPS
+from hypha.apply.users.models import GroupDesc
+
+
+def add_desc_groups(apps, schema_editor):
+    for group_data in GROUPS:
+        group, created = Group.objects.get_or_create(name=group_data["name"])
+        if group_data.get("help_text") is not None:
+            GroupDesc.objects.create(group=group, help_text=group_data["help_text"])
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+        ("users", "0020_auto_20230625_1825"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="GroupDesc",
+            fields=[
+                (
+                    "group",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        primary_key=True,
+                        serialize=False,
+                        to="auth.group",
+                    ),
+                ),
+                (
+                    "help_text",
+                    models.CharField(max_length=255, verbose_name="Help Text"),
+                ),
+            ],
+        ),
+        migrations.RunPython(add_desc_groups),
+    ]
diff --git a/hypha/apply/users/migrations/0023_merge_0021_groupdesc_0022_confirmaccesstoken.py b/hypha/apply/users/migrations/0023_merge_0021_groupdesc_0022_confirmaccesstoken.py
new file mode 100644
index 0000000000000000000000000000000000000000..7461821d1c9b23a862a59e3aecd1a5b99c08b35e
--- /dev/null
+++ b/hypha/apply/users/migrations/0023_merge_0021_groupdesc_0022_confirmaccesstoken.py
@@ -0,0 +1,12 @@
+# Generated by Django 4.1.13 on 2023-11-27 14:46
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("users", "0021_groupdesc"),
+        ("users", "0022_confirmaccesstoken"),
+    ]
+
+    operations = []
diff --git a/hypha/apply/users/models.py b/hypha/apply/users/models.py
index 8721410e3da688c0d884a510cc754e1e6f9417cd..43783f54429fffdc6fce4bca88a23b7d602a7a5e 100644
--- a/hypha/apply/users/models.py
+++ b/hypha/apply/users/models.py
@@ -1,6 +1,6 @@
 from django.conf import settings
 from django.contrib.auth.hashers import make_password
-from django.contrib.auth.models import AbstractUser, BaseUserManager
+from django.contrib.auth.models import AbstractUser, BaseUserManager, Group
 from django.core import exceptions
 from django.db import IntegrityError, models
 from django.db.models.constants import LOOKUP_SEP
@@ -377,6 +377,27 @@ class AuthSettings(BaseGenericSetting):
     ]
 
 
+class GroupDesc(models.Model):
+    group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True)
+    help_text = models.CharField(verbose_name="Help Text", max_length=255)
+
+    @staticmethod
+    def get_from_group(group_obj: Group) -> str | None:
+        """
+        Get the group description/help text string from a Group object. Returns None if group doesn't have a help text entry.
+
+        Args:
+            group_obj (Group): The group to retrieve the description of.
+        """
+        try:
+            return GroupDesc.objects.get(group_id=group_obj.id).help_text
+        except (exceptions.ObjectDoesNotExist, exceptions.FieldError):
+            return None
+
+    def __str__(self):
+        return self.help_text
+
+
 class PendingSignup(models.Model):
     """This model tracks pending passwordless self-signups, and is used to
     generate a  one-time use URLfor each signup.
diff --git a/hypha/apply/users/templates/two_factor/admin/disable.html b/hypha/apply/users/templates/two_factor/admin/disable.html
index 5a86419b7f780fd8bbbb166f776df901a9e2b8b8..1e1a97e25430f7b557d2a38fa638b2645e8c349a 100644
--- a/hypha/apply/users/templates/two_factor/admin/disable.html
+++ b/hypha/apply/users/templates/two_factor/admin/disable.html
@@ -10,22 +10,21 @@
     <form class="form" action="" method="POST" novalidate>
         <div class="tab-content">
             {% csrf_token %}
-
             <section id="account" class="active nice-padding">
-                <p>{% trans "Are you sure you want to disable the Two Factor Authentication for this user? Please type your password to confirm." %}</p>
+                <p>{% trans "Are you sure you want to disable the Two Factor Authentication for this user?" %}</p>
 
                 <ul class="fields">
                     {% block fields %}
-                        {% include "wagtailadmin/shared/field_as_li.html" with field=form.password %}
+                        {% include "wagtailadmin/shared/field_as_li.html" with field=form.confirmation_text %}
                     {% endblock %}
 
                     <li>
-                        <button class="button button--primary" type="submit">{% trans 'Disable 2FA' %}</button>
+                        <button class="button button--primary" type="submit">{% trans 'Confirm' %}</button>
                     </li>
                 </ul>
+
             </section>
         </div>
     </form>
 
-
 {% endblock %}
diff --git a/hypha/apply/users/templates/wagtailusers/groups/index.html b/hypha/apply/users/templates/wagtailusers/groups/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..12256926fcbc12eda9a6f413bec2772a024ba79c
--- /dev/null
+++ b/hypha/apply/users/templates/wagtailusers/groups/index.html
@@ -0,0 +1,8 @@
+{% extends "wagtailadmin/generic/index.html" %}
+{% load i18n static %}
+
+{% block extra_css %}
+    <link rel="stylesheet" href="{% static 'css/apply/wagtail_groups_list.css' %}">
+    {{ block.super }}
+    {{ media.css }}
+{% endblock %}
\ No newline at end of file
diff --git a/hypha/apply/users/templates/wagtailusers/users/create.html b/hypha/apply/users/templates/wagtailusers/users/create.html
index cdc722089f7d52892f772698ab734e8b0f9d403e..2c91a52a01634e97c43622828857da7ad68bbad4 100644
--- a/hypha/apply/users/templates/wagtailusers/users/create.html
+++ b/hypha/apply/users/templates/wagtailusers/users/create.html
@@ -1,4 +1,5 @@
 {% extends "wagtailusers/users/create.html" %}
+{% load  static %}
 
 {% block fields %}
     {% if form.separate_username_field %}
@@ -24,3 +25,7 @@
         {% include "wagtailadmin/shared/field_as_li.html" with field=form.is_active %}
     {% endif %}
 {% endblock fields %}
+
+{% block extra_css %}
+    <link rel="stylesheet" href="{% static 'css/apply/wagtail_groups_list.css' %}">
+{% endblock %}
diff --git a/hypha/apply/users/templates/wagtailusers/users/edit.html b/hypha/apply/users/templates/wagtailusers/users/edit.html
index ab4f80297d8bc430908ca169d738db12674ab601..b4d72a1f076e23c94fb11f1f5bb1a3ddc9001f30 100644
--- a/hypha/apply/users/templates/wagtailusers/users/edit.html
+++ b/hypha/apply/users/templates/wagtailusers/users/edit.html
@@ -2,6 +2,7 @@
 {% extends "wagtailusers/users/edit.html" %}
 {% load wagtailimages_tags %}
 {% load users_tags %}
+{% load  static %}
 {% load i18n %}
 {% block content %}
 
@@ -106,6 +107,7 @@
 {% endblock %}
 
 {% block extra_css %}
+    <link rel="stylesheet" href="{% static 'css/apply/wagtail_groups_list.css' %}">
     {{ block.super }}
     {{ form.media.css }}
 {% endblock %}
diff --git a/hypha/apply/users/views.py b/hypha/apply/users/views.py
index 9a9877c58b7d9117a0f2871a5a1155efd6acf8c9..6fa58f44caf0e1dda8a10ecebcc28ead39d7ca36 100644
--- a/hypha/apply/users/views.py
+++ b/hypha/apply/users/views.py
@@ -57,9 +57,9 @@ from .forms import (
     BecomeUserForm,
     CustomAuthenticationForm,
     CustomUserCreationForm,
+    Disable2FAConfirmationForm,
     PasswordlessAuthForm,
     ProfileForm,
-    TWOFAPasswordForm,
 )
 from .models import ConfirmAccessToken, PendingSignup
 from .services import PasswordlessAuthService
@@ -93,7 +93,7 @@ class RegisterView(View):
             return redirect(settings.LOGIN_REDIRECT_URL)
 
         ctx = {
-            "form": self.form(),
+            "form": self.form(register_view=True),
             "redirect_url": get_redirect_url(request, self.redirect_field_name),
         }
         return render(request, "users/register.html", ctx)
@@ -103,7 +103,7 @@ class RegisterView(View):
         if not settings.ENABLE_PUBLIC_SIGNUP:
             raise Http404
 
-        form = self.form(data=request.POST)
+        form = self.form(register_view=True, data=request.POST)
         context = {}
         if form.is_valid():
             # If using wagtail password management
@@ -500,39 +500,27 @@ class TWOFADisableView(ElevateMixin, TwoFactorDisableView):
 
     template_name = "two_factor/profile/disable.html"
     success_url = reverse_lazy("users:account")
-    form_class = TWOFAPasswordForm
-
-    def get_form_kwargs(self):
-        kwargs = super().get_form_kwargs()
-        kwargs["user"] = self.request.user
-        return kwargs
+    form_class = Disable2FAConfirmationForm
 
 
 @method_decorator(
     permission_required(change_user_perm, raise_exception=True), name="dispatch"
 )
-class TWOFAAdminDisableView(FormView):
+class TWOFAAdminDisableView(ElevateMixin, FormView):
     """
     View for PasswordForm to confirm the Disable 2FA process on wagtail admin.
     """
 
-    form_class = TWOFAPasswordForm
+    form_class = Disable2FAConfirmationForm
     template_name = "two_factor/admin/disable.html"
     user = None
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
-        # pass request's user to form to validate the password
-        kwargs["user"] = self.request.user
         # store the user from url for redirecting to the same user's account edit page
         self.user = get_object_or_404(User, pk=self.kwargs.get("user_id"))
         return kwargs
 
-    def get_form(self, form_class=None):
-        form = super(TWOFAAdminDisableView, self).get_form(form_class=form_class)
-        form.fields["password"].label = "Password"
-        return form
-
     def form_valid(self, form):
         for device in devices_for_user(self.user):
             device.delete()
@@ -542,7 +530,7 @@ class TWOFAAdminDisableView(FormView):
         return reverse("wagtailusers_users:edit", args=[self.user.id])
 
     def get_context_data(self, **kwargs):
-        ctx = super(TWOFAAdminDisableView, self).get_context_data(**kwargs)
+        ctx = super().get_context_data(**kwargs)
         ctx["user"] = self.user
         return ctx
 
diff --git a/hypha/apply/utils/templatetags/apply_tags.py b/hypha/apply/utils/templatetags/apply_tags.py
index fd1028c89e9d44f3740915a2aa0049606be95785..06b19b8415cfc2cbbaca6b5dae8374082cf50a69 100644
--- a/hypha/apply/utils/templatetags/apply_tags.py
+++ b/hypha/apply/utils/templatetags/apply_tags.py
@@ -1,6 +1,7 @@
 import babel.numbers
 from django import template
 from django.conf import settings
+from django.template.defaultfilters import stringfilter
 
 register = template.Library()
 
@@ -24,3 +25,16 @@ def format_number_as_currency(amount):
         return babel.numbers.get_currency_symbol(
             settings.CURRENCY_CODE, locale=settings.CURRENCY_LOCALE
         )
+
+
+@register.filter(is_safe=True)
+@stringfilter
+def truncatechars_middle(value, arg):
+    try:
+        ln = int(arg)
+    except ValueError:
+        return value
+    if len(value) <= ln:
+        return value
+    else:
+        return "{}...{}".format(value[: ln // 2], value[-((ln + 1) // 2) :])
diff --git a/hypha/locale/django.pot b/hypha/locale/django.pot
index 59cefaa710fb3ec585ca572c4206a80931df32b2..e08120d6923815c8e6e40005a31d5758fc62c20d 100644
--- a/hypha/locale/django.pot
+++ b/hypha/locale/django.pot
@@ -6473,38 +6473,6 @@ msgstr ""
 msgid "Promoted RFPs"
 msgstr ""
 
-#: hypha/public/mailchimp/forms.py:6
-msgid "Email Address"
-msgstr ""
-
-#: hypha/public/mailchimp/forms.py:7
-msgid "First Name"
-msgstr ""
-
-#: hypha/public/mailchimp/forms.py:8
-msgid "Last Name"
-msgstr ""
-
-#: hypha/public/mailchimp/models.py:18
-msgid "The title of the newsletter signup form."
-msgstr ""
-
-#: hypha/public/mailchimp/templates/mailchimp/newsletter_signup.html:16
-msgid "Sign up"
-msgstr ""
-
-#: hypha/public/mailchimp/views.py:86
-msgid "Sorry, there were errors with your form."
-msgstr ""
-
-#: hypha/public/mailchimp/views.py:91
-msgid "Sorry, there has been an problem. Please try again later."
-msgstr ""
-
-#: hypha/public/mailchimp/views.py:98
-msgid "Thank you for subscribing"
-msgstr ""
-
 #: hypha/public/navigation/models.py:14
 msgid "Leave blank to use the page's own title"
 msgstr ""
@@ -6659,22 +6627,6 @@ msgid ""
 "is not defined."
 msgstr ""
 
-#: hypha/public/utils/models.py:268
-msgid "Your Twitter username without the @, e.g. katyperry"
-msgstr ""
-
-#: hypha/public/utils/models.py:273
-msgid "Your Facebook app ID."
-msgstr ""
-
-#: hypha/public/utils/models.py:279
-msgid "Default sharing text to use if social text has not been set on a page."
-msgstr ""
-
-#: hypha/public/utils/models.py:286
-msgid "Site name, used by Open Graph."
-msgstr ""
-
 #: hypha/public/utils/models.py:303
 msgid "Default site logo"
 msgstr ""
@@ -6727,22 +6679,6 @@ msgstr ""
 msgid "You might also like…"
 msgstr ""
 
-#: hypha/templates/includes/share.html:4
-msgid "Share"
-msgstr ""
-
-#: hypha/templates/includes/share.html:8
-msgid "Share on Twitter"
-msgstr ""
-
-#: hypha/templates/includes/share.html:14
-msgid "Share on LinkedIn"
-msgstr ""
-
-#: hypha/templates/includes/share.html:21
-msgid "Share on Facebook"
-msgstr ""
-
 #: hypha/templates/password_required.html:4
 #: hypha/templates/password_required.html:9
 msgid "Password required"
diff --git a/hypha/public/forms/migrations/0005_remove_formpage_social_image_and_more.py b/hypha/public/forms/migrations/0005_remove_formpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..da06fd5fd75ded0b205232d6ccdc642a4bdd27ef
--- /dev/null
+++ b/hypha/public/forms/migrations/0005_remove_formpage_social_image_and_more.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("public_forms", "0004_auto_20220722_0844"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="formpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="formpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/funds/migrations/0015_remove_baseapplicationpage_social_image_and_more.py b/hypha/public/funds/migrations/0015_remove_baseapplicationpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..4888ad5080aef9fbdbc7a1989e9d627a1948fc55
--- /dev/null
+++ b/hypha/public/funds/migrations/0015_remove_baseapplicationpage_social_image_and_more.py
@@ -0,0 +1,52 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("public_funds", "0014_auto_20220722_0844"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="baseapplicationpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="baseapplicationpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="fundindex",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="fundindex",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="labindex",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="labindex",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="labpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="labpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="opencallindexpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="opencallindexpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/home/migrations/0014_remove_homepage_social_image_and_more.py b/hypha/public/home/migrations/0014_remove_homepage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..d931488efaab45b0e71072a0977f2b92daf9e617
--- /dev/null
+++ b/hypha/public/home/migrations/0014_remove_homepage_social_image_and_more.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("home", "0013_alter_homepage_our_work"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="homepage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="homepage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/home/templates/home/home_page.html b/hypha/public/home/templates/home/home_page.html
index 2f93125a3e409794166cdf0882343aaa0219fd0c..3afe23c5d580d10e17e32289fbd04fffb39a2615 100644
--- a/hypha/public/home/templates/home/home_page.html
+++ b/hypha/public/home/templates/home/home_page.html
@@ -27,10 +27,6 @@
             </a>
 
             <div class="header__inner header__inner--mobile-buttons">
-                <button class="button js-search-toggle" aria-haspopup="true">
-                    <svg class="header__icon header__icon--open-search header__icon--open-search-menu-closed icon icon--mobile-menu"><use xlink:href="#magnifying-glass"></use></svg>
-                    <svg class="header__icon header__icon--close-search header__icon--close-search-menu-closed icon icon--mobile-menu"><use xlink:href="#cross"></use></svg>
-                </button>
                 <button class="button button--left-space js-mobile-menu-toggle" aria-haspopup="true">
                     <svg class="icon icon--mobile-menu"><use xlink:href="#mobile-menu-toggle"></use></svg>
                 </button>
@@ -40,11 +36,6 @@
                 {% cache 3600 navigation__primary wagtail_site %}
                     {% primarynav %}
                 {% endcache %}
-
-                <button class="button button--contains-icons button--left-space js-search-toggle" aria-haspopup="true" aria-label="Toggle desktop search">
-                    <svg class="header__icon header__icon--open-search icon"><use xlink:href="#magnifying-glass"></use></svg>
-                    <svg class="header__icon header__icon--close-search icon"><use xlink:href="#cross"></use></svg>
-                </button>
             </section>
 
             <section class="header__menus header__menus--mobile">
@@ -57,14 +48,6 @@
                             <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg>
                         {% endif %}
                     </a>
-                    <div class="header__inner header__inner--mobile-buttons">
-                        <button class="button js-mobile-search-toggle" aria-haspopup="true" aria-label="Toggle mobile search">
-                            <svg class="header__icon header__icon--open-search icon icon--mobile-menu"><use xlink:href="#magnifying-glass"></use></svg>
-                        </button>
-                        <button class="button button--left-space js-mobile-menu-close">
-                            <svg class="header__icon header__icon--cross icon icon--mobile-menu"><use xlink:href="#cross"></use></svg>
-                        </button>
-                    </div>
                 </div>
                 {% cache 3600 navigation__primary wagtail_site %}
                     {% primarynav %}
@@ -79,15 +62,6 @@
             </div>
         </div>
 
-        <div class="header__search">
-            <form action="{% url 'search' %}" method="get" role="search" class="form form--header-search-desktop">
-                <button class="button" type="submit" aria-label="Search">
-                    <svg class="icon icon--magnifying-glass icon--search"><use xlink:href="#magnifying-glass"></use></svg>
-                </button>
-                <input class="input input--transparent input--secondary" type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}{% endif %}" aria-label="Search input">
-            </form>
-        </div>
-
         <div class="wrapper wrapper--medium wrapper--page-title">
             <h1 class="header__title header__title--homepage">{% block page_title %}{{ page.title }}{% endblock %}</h1>
             <p class="header__strapline">{{ page.strapline }}</p>
diff --git a/hypha/public/mailchimp/apps.py b/hypha/public/mailchimp/apps.py
deleted file mode 100644
index d952a8383af7aef582c6fbf9390c50399d4bdc6c..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class MailchimpConfig(AppConfig):
-    name = "hypha.public.mailchimp"
diff --git a/hypha/public/mailchimp/forms.py b/hypha/public/mailchimp/forms.py
deleted file mode 100644
index 6e64b51b410a347a270d9c98939c40fdb70abef1..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/forms.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django import forms
-from django.utils.translation import gettext_lazy as _
-
-
-class NewsletterForm(forms.Form):
-    email = forms.EmailField(label=_("Email Address"))
-    fname = forms.CharField(label=_("First Name"), required=False)
-    lname = forms.CharField(label=_("Last Name"), required=False)
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        for field in self.fields.values():
-            class_name = "input--secondary"
-            if field.required:
-                class_name += " input__secondary--required"
-            field.widget.attrs = {"class": class_name}
diff --git a/hypha/public/mailchimp/migrations/0001_add_newsletter_setting.py b/hypha/public/mailchimp/migrations/0001_add_newsletter_setting.py
deleted file mode 100644
index 25a7613a9713b64d66f79116053b7de281975eac..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/migrations/0001_add_newsletter_setting.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Generated by Django 2.1.11 on 2019-10-03 12:56
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-    initial = True
-
-    dependencies = [
-        ("wagtailcore", "0041_group_collection_permissions_verbose_name_plural"),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name="NewsletterSettings",
-            fields=[
-                (
-                    "id",
-                    models.AutoField(
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        verbose_name="ID",
-                    ),
-                ),
-                (
-                    "newsletter_title",
-                    models.CharField(
-                        default="Get the latest internet freedom news",
-                        help_text="The title of the newsletter signup form.",
-                        max_length=255,
-                        verbose_name="Newsletter title",
-                    ),
-                ),
-                (
-                    "site",
-                    models.OneToOneField(
-                        editable=False,
-                        on_delete=django.db.models.deletion.CASCADE,
-                        to="wagtailcore.Site",
-                    ),
-                ),
-            ],
-            options={
-                "verbose_name": "newsletter settings",
-            },
-        ),
-    ]
diff --git a/hypha/public/mailchimp/models.py b/hypha/public/mailchimp/models.py
deleted file mode 100644
index 878fbe6895d8aef64c4041dd41558ec8e1cedece..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/models.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django.db import models
-from django.utils.translation import gettext_lazy as _
-from wagtail.admin.panels import FieldPanel
-from wagtail.contrib.settings.models import BaseSiteSetting
-
-from hypha.core.wagtail.admin import register_public_site_setting
-
-
-@register_public_site_setting
-class NewsletterSettings(BaseSiteSetting):
-    class Meta:
-        verbose_name = "newsletter settings"
-
-    newsletter_title = models.CharField(
-        "Newsletter title",
-        max_length=255,
-        default="Get the latest internet freedom news",
-        help_text=_("The title of the newsletter signup form."),
-    )
-
-    panels = [
-        FieldPanel("newsletter_title"),
-    ]
diff --git a/hypha/public/mailchimp/templates/mailchimp/newsletter_signup.html b/hypha/public/mailchimp/templates/mailchimp/newsletter_signup.html
deleted file mode 100644
index f56ceafa6783ccca321ec724654a495f22bc246e..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/templates/mailchimp/newsletter_signup.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% load static i18n %}
-
-<h4>{{ settings.mailchimp.NewsletterSettings.newsletter_title }}</h4>
-<form class="form newsletter-form" action="#" data-actionpath="{{ PUBLIC_SITE.root_url }}{% url "newsletter:subscribe" %}" method="post">
-    <div>
-        {% for field in newsletter_form %}
-            <label for="{{ field.id_for_label }}"{% if field.field.required %} required{% endif %}>
-                <span>{{ field.label }}</span>
-                {% if field.field.required %}
-                    <span class="form__required">*</span>
-                {% endif %}
-            </label>
-            {{ field }}
-        {% endfor %}
-        <div class="form-actions form-wrapper">
-            <button class="form-submit button button--transparent--wide link--footer-signup" type="submit">{% trans 'Sign up' %}</button>
-        </div>
-    </div>
-</form>
-
-{% block extra_js %}
-    <script src="{% static 'js/public/protect-form.js' %}"></script>
-{% endblock %}
diff --git a/hypha/public/mailchimp/tests/test_views.py b/hypha/public/mailchimp/tests/test_views.py
deleted file mode 100644
index b5f7d3c69256739b23a5a65abdad70f369f5222b..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/tests/test_views.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import re
-from unittest import mock
-from urllib import parse
-
-from django.test import TestCase, override_settings
-from django.urls import reverse
-
-any_url = re.compile(".")
-
-
-class TestNewsletterView(TestCase):
-    url = reverse("newsletter:subscribe")
-
-    def setUp(self):
-        self.origin = "https://testserver/"
-
-    def assertNewsletterRedirects(self, response, target_url, *args, **kwargs):
-        url = response.redirect_chain[0][0]
-        parts = parse.urlsplit(url)
-        self.assertTrue(parts.query.startswith("newsletter-"))
-        target_url = target_url + "?" + parts.query
-        return self.assertRedirects(response, target_url, *args, **kwargs)
-
-    def test_redirected_home_if_get(self):
-        response = self.client.get(self.url, secure=True, follow=True)
-        request = response.request
-        self.assertRedirects(
-            response,
-            "{}://{}/".format(request["wsgi.url_scheme"], request["SERVER_NAME"]),
-        )
-
-    @override_settings(MAILCHIMP_API_KEY="a" * 32, MAILCHIMP_LIST_ID="12345")
-    def test_can_subscribe(self):
-        with mock.patch(
-            "hypha.public.mailchimp.views.subscribe_to_mailchimp"
-        ) as mc_mock:
-            mc_mock.return_value = None
-            response = self.client.post(
-                self.url, data={"email": "email@email.com"}, secure=True, follow=True
-            )
-
-            self.assertNewsletterRedirects(response, self.origin)
-
-            mc_mock.assert_called_once_with(
-                email="email@email.com", data={"fname": "", "lname": ""}
-            )
-
-    def test_error_in_form(self):
-        with mock.patch(
-            "hypha.public.mailchimp.views.subscribe_to_mailchimp"
-        ) as mc_mock:
-            mc_mock.return_value = None
-            response = self.client.post(
-                self.url, data={"email": "email_is_bad.com"}, secure=True, follow=True
-            )
-            self.assertNewsletterRedirects(response, self.origin)
-
-            assert not mc_mock.called, "method should not have been called"
diff --git a/hypha/public/mailchimp/urls.py b/hypha/public/mailchimp/urls.py
deleted file mode 100644
index 82819cc22f6d2b79f5cb6228237d57a562ffa4fa..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import path
-
-from .views import MailchimpSubscribeView
-
-app_name = "newsletter"
-
-
-urlpatterns = [path("subscribe/", MailchimpSubscribeView.as_view(), name="subscribe")]
diff --git a/hypha/public/mailchimp/views.py b/hypha/public/mailchimp/views.py
deleted file mode 100644
index 4fe50350e5de248e8890bf78dbcdd11be37067cc..0000000000000000000000000000000000000000
--- a/hypha/public/mailchimp/views.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import logging
-import uuid
-
-from django.conf import settings
-from django.contrib import messages
-from django.http import HttpResponseRedirect
-from django.utils.decorators import method_decorator
-from django.utils.translation import gettext as _
-from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import RedirectView
-from django.views.generic.edit import FormMixin
-from django_ratelimit.decorators import ratelimit
-from mailchimp3 import MailChimp
-
-from .forms import NewsletterForm
-
-logger = logging.getLogger(__name__)
-
-
-def subscribe_to_mailchimp(email: str, data) -> None:
-    mailchimp_enabled = settings.MAILCHIMP_API_KEY and settings.MAILCHIMP_LIST_ID
-
-    dummy_key = "a" * 32
-
-    if not mailchimp_enabled:
-        raise Exception(
-            f"Incorrect Mailchimp configuration: "
-            f"API_KEY: {settings.MAILCHIMP_API_KEY}, LIST_ID: {settings.MAILCHIMP_LIST_ID}"
-        )
-
-    client = MailChimp(
-        mc_api=settings.MAILCHIMP_API_KEY or dummy_key,
-        timeout=5.0,
-        enabled=mailchimp_enabled,
-    )
-    data = {k.upper(): v for k, v in data.items()}
-
-    client.lists.members.create(
-        settings.MAILCHIMP_LIST_ID,
-        {
-            "email_address": email,
-            "status": "pending",
-            "merge_fields": data,
-        },
-    )
-
-
-@method_decorator(
-    ratelimit(key="ip", rate=settings.DEFAULT_RATE_LIMIT, method="POST"),
-    name="dispatch",
-)
-@method_decorator(
-    ratelimit(key="post:email", rate=settings.DEFAULT_RATE_LIMIT, method="POST"),
-    name="dispatch",
-)
-@method_decorator(csrf_exempt, name="dispatch")
-class MailchimpSubscribeView(FormMixin, RedirectView):
-    form_class = NewsletterForm
-
-    def post(self, request, *args, **kwargs):
-        form = self.get_form()
-        if form.is_valid():
-            return self.form_valid(form)
-        else:
-            return self.form_invalid(form)
-
-    def form_invalid(self, form):
-        self.error(form)
-        return HttpResponseRedirect(self.get_success_url())
-
-    def form_valid(self, form):
-        data = form.cleaned_data.copy()
-        email = data.pop("email")
-
-        try:
-            subscribe_to_mailchimp(email=email, data=data)
-            self.success()
-        except Exception as e:
-            self.warning(e)
-
-        return super().form_valid(form)
-
-    def error(self, form):
-        messages.error(
-            self.request,
-            _("Sorry, there were errors with your form.") + str(form.errors),
-        )
-
-    def warning(self, e):
-        messages.warning(
-            self.request, _("Sorry, there has been an problem. Please try again later.")
-        )
-        # If there is a problem with subscribing uncomment this to get notifications.
-        # When things work warnings is only about spam scipts.
-        # logger.error(e.args[0])
-
-    def success(self):
-        messages.success(self.request, _("Thank you for subscribing"))
-
-    def get_success_url(self):
-        # Go back to where you came from, default to front page.
-        origin = (
-            self.request.META.get("HTTP_ORIGIN")
-            or self.request.META.get("HTTP_REFERER")
-            or "/"
-        )
-
-        # Add cache busting query string.
-        return origin + "?newsletter-" + uuid.uuid4().hex
-
-    def get_redirect_url(self):
-        # We don't know where you came from, go home
-        return "/"
diff --git a/hypha/public/news/migrations/0015_remove_newsindex_social_image_and_more.py b/hypha/public/news/migrations/0015_remove_newsindex_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1d1687d0b13909ffc524111f9ec83107e5d992f
--- /dev/null
+++ b/hypha/public/news/migrations/0015_remove_newsindex_social_image_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("news", "0014_alter_newspagenewstype_news_type"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="newsindex",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="newsindex",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="newspage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="newspage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/news/templates/news/news_page.html b/hypha/public/news/templates/news/news_page.html
index 9d40bb989b4fdaf40414f30bbe1754185f919906..2164a308ef81312119cb3c8ca2fed3d352fb5e4a 100644
--- a/hypha/public/news/templates/news/news_page.html
+++ b/hypha/public/news/templates/news/news_page.html
@@ -39,7 +39,6 @@
             </ul>
         {% endif %}
 
-        {% include "includes/share.html" %}
     </article>
 
     {% include "includes/relatedcontent.html" with related_documents=page.related_documents.all related_pages=page.related_pages.all %}
diff --git a/hypha/public/partner/migrations/0003_remove_partnerindexpage_social_image_and_more.py b/hypha/public/partner/migrations/0003_remove_partnerindexpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..a61ebbc866fa95904898105f55f7fc70b9c7be78
--- /dev/null
+++ b/hypha/public/partner/migrations/0003_remove_partnerindexpage_social_image_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("partner", "0002_currency_symbol_setting"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="partnerindexpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="partnerindexpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="partnerpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="partnerpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/people/migrations/0016_remove_personindexpage_social_image_and_more.py b/hypha/public/people/migrations/0016_remove_personindexpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2729adb6bdce2d69db97929f83fc83e7e456a2b
--- /dev/null
+++ b/hypha/public/people/migrations/0016_remove_personindexpage_social_image_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("people", "0015_alter_personpage_biography"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="personindexpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="personindexpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="personpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="personpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/projects/migrations/0011_remove_projectindexpage_social_image_and_more.py b/hypha/public/projects/migrations/0011_remove_projectindexpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee51e678370c4c59263a22541413a074a4e31549
--- /dev/null
+++ b/hypha/public/projects/migrations/0011_remove_projectindexpage_social_image_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("projects", "0010_alter_projectpage_body"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="projectindexpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="projectindexpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="projectpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="projectpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/search/__init__.py b/hypha/public/search/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/hypha/public/search/templates/search/includes/search_result.html b/hypha/public/search/templates/search/includes/search_result.html
deleted file mode 100644
index 9e7d4b8365e153c5a30ce20dc5eb507939d5f52d..0000000000000000000000000000000000000000
--- a/hypha/public/search/templates/search/includes/search_result.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load static wagtailcore_tags wagtailsearchpromotions_tags wagtailimages_tags %}
-
-<a class="listing" href="{% pageurl result %}">
-    {# breadcrumbs #}
-    <h6 class="listing__path">
-        {% for ancestor in result.get_ancestors %}
-            {% if not ancestor.is_root %}
-                {% if ancestor.depth > 2 %}
-                    <span>{{ ancestor.title }}</span>
-                    {% if ancestor.depth|add:1 < result.depth %}
-                        <span class="nav__item--breadcrumb"></span>
-                    {% endif %}
-                {% else %}<span class="nav__item--breadcrumb"></span>{% endif %} {# the first one #}
-            {% endif %}
-        {% endfor %}
-    </h6>
-
-    {% if result.listing_image or result.icon %}
-        {% image result.listing_image|default:result.icon fill-180x180 class="listing__image" %}
-    {% else %}
-        <div class="listing__image listing__image--default">
-            <svg><use xlink:href="#logo-mobile-no-text"></use></svg>
-        </div>
-    {% endif %}
-
-    <h4 class="listing__title">{{ result.listing_title|default:result.title }}</h4>
-
-    {% if pick.description or result.listing_summary or result.search_description or result.listing_summary or result.introduction  %}
-        <h6 class="listing__teaser">{{ pick.description|default:result.listing_summary|default:result.search_description|default:result.listing_summary|default:result.introduction|truncatechars_html:155 }}</h6>
-    {% endif %}
-</a>
diff --git a/hypha/public/search/templates/search/search.html b/hypha/public/search/templates/search/search.html
deleted file mode 100644
index 98361a1ec1b4491d4941f46a3e0fc357be86b220..0000000000000000000000000000000000000000
--- a/hypha/public/search/templates/search/search.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "base.html" %}
-{% load static wagtailcore_tags wagtailsearchpromotions_tags %}
-{% block body_class %}template-searchresults light-grey-bg{% endblock %}
-{% block page_title %}Search results{% endblock %}
-{% block title %}{% if search_query %}Search results for &ldquo;{{ search_query }}&rdquo;{% else %}Search{% endif %}{% endblock %}
-{% block content %}
-    <div class="wrapper wrapper--small wrapper--inner-space-medium">
-        <h2 class="heading heading--no-margin">{% if search_query %}Search results for &ldquo;{{ search_query }}&rdquo;{% else %}Search{% endif %}</h2>
-
-        {% if search_results %}
-            {% with count=search_results.paginator.count %}
-                <p>{{ count }} result{{ count|pluralize }} found.</p>
-            {% endwith %}
-        {% elif search_query and not search_picks %}
-            <p>No results found.</p>
-        {% endif %}
-
-        <form class="form" action="{% url 'search' %}" method="get" role="search" aria-label="Search form">
-            <input class="input input--bottom-space" type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}"{% endif %} aria-label="Search input">
-            <input class="link link--button" type="submit" value="Search" aria-label="search">
-        </form>
-
-        {% get_search_promotions search_query as search_picks %}
-        {% if search_picks %}
-            <div class="wrapper wrapper--listings">
-                {% for pick in search_picks %}
-                    {% include "search/includes/search_result.html" with result=pick.page.specific %}
-                {% endfor %}
-            </div>
-        {% endif %}
-
-        {% if search_results %}
-            <div class="wrapper wrapper--listings">
-                {% for result in search_results %}
-                    {% include "search/includes/search_result.html" with result=result.specific %}
-                {% endfor %}
-            </div>
-            {% include "includes/pagination.html" with paginator_page=search_results %}
-        {% endif %}
-    </div>
-{% endblock %}
diff --git a/hypha/public/search/views.py b/hypha/public/search/views.py
deleted file mode 100644
index ade7b21730126c74c6d656441ecf405905b2fb25..0000000000000000000000000000000000000000
--- a/hypha/public/search/views.py
+++ /dev/null
@@ -1,64 +0,0 @@
-import re
-
-from django.conf import settings
-from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
-from django.http import Http404
-from django.shortcuts import render
-from wagtail.models import Page, Site
-from wagtail.search.models import Query
-
-
-def search(request):
-    site = Site.find_for_request(request)
-    if (
-        not site.is_default_site
-        and "apply" in site.site_name.lower()
-        and "apply" in site.hostname
-        and "apply" in site.root_page.title.lower()
-    ):
-        raise Http404
-
-    search_query = request.GET.get("query", None)
-    page = request.GET.get("page", 1)
-
-    # Search
-    if search_query:
-        # Allow only word characters and spaces in search query.
-        words = re.findall(r"\w+", search_query.strip())
-        search_query = " ".join(words)
-
-        public_site = site.root_page
-
-        search_results = (
-            Page.objects.live()
-            .descendant_of(
-                public_site,
-                inclusive=True,
-            )
-            .specific()
-            .search(search_query, operator="and")
-        )
-        query = Query.get(search_query)
-
-        # Record hit
-        query.add_hit()
-    else:
-        search_results = Page.objects.none()
-
-    # Pagination
-    paginator = Paginator(search_results, settings.DEFAULT_PER_PAGE)
-    try:
-        search_results = paginator.page(page)
-    except PageNotAnInteger:
-        search_results = paginator.page(1)
-    except EmptyPage:
-        search_results = paginator.page(paginator.num_pages)
-
-    return render(
-        request,
-        "search/search.html",
-        {
-            "search_query": search_query,
-            "search_results": search_results,
-        },
-    )
diff --git a/hypha/public/standardpages/migrations/0007_remove_indexpage_social_image_and_more.py b/hypha/public/standardpages/migrations/0007_remove_indexpage_social_image_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7dfe858d4f8d3607b21edc79e4d730c6b0d822e
--- /dev/null
+++ b/hypha/public/standardpages/migrations/0007_remove_indexpage_social_image_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("standardpages", "0006_alter_informationpage_body"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="indexpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="indexpage",
+            name="social_text",
+        ),
+        migrations.RemoveField(
+            model_name="informationpage",
+            name="social_image",
+        ),
+        migrations.RemoveField(
+            model_name="informationpage",
+            name="social_text",
+        ),
+    ]
diff --git a/hypha/public/standardpages/templates/standardpages/information_page.html b/hypha/public/standardpages/templates/standardpages/information_page.html
index e676eafdaccafb2138480d349939b3ff9a788209..53f0ceda8a5589a108161e7581edb284559403c3 100644
--- a/hypha/public/standardpages/templates/standardpages/information_page.html
+++ b/hypha/public/standardpages/templates/standardpages/information_page.html
@@ -10,7 +10,6 @@
             {% endif %}
 
             {% include_block page.body %}
-            {% include "includes/share.html" %}
 
         </article>
     </div>
diff --git a/hypha/public/urls.py b/hypha/public/urls.py
index cbc340f23d81203392434914b8bfbb2b96a54038..4c9a378fa158dbeb2c2848457d2490f0f34bb370 100644
--- a/hypha/public/urls.py
+++ b/hypha/public/urls.py
@@ -1,17 +1,13 @@
-from django.urls import include, path
+from django.urls import path
 
-from .mailchimp import urls as newsletter_urls
 from .news import feeds as news_feeds
 from .partner import views as partner_views
-from .search import views as search_views
 
 urlpatterns = [
-    path("search/", search_views.search, name="search"),
     path("news/feed/", news_feeds.NewsFeed(), name="news_feed"),
     path(
         "news/<int:news_type>/feed/", news_feeds.NewsTypeFeed(), name="news_type_feed"
     ),
-    path("newsletter/", include(newsletter_urls)),
     path(
         "about/portfolio/",
         partner_views.InvestmentTableView.as_view(),
diff --git a/hypha/public/utils/context_processors.py b/hypha/public/utils/context_processors.py
index 7bdfc326d969d60ca2ff25410e8f060d614ecb5e..b19178e54ada1cbac5f232a2063193a55cc04a7e 100644
--- a/hypha/public/utils/context_processors.py
+++ b/hypha/public/utils/context_processors.py
@@ -1,12 +1,7 @@
-from django.conf import settings
-
 from hypha.public.home.models import HomePage
-from hypha.public.mailchimp.forms import NewsletterForm
 
 
 def global_vars(request):
     return {
         "PUBLIC_SITE": HomePage.objects.first().get_site(),
-        "newsletter_form": NewsletterForm(),
-        "newsletter_enabled": settings.MAILCHIMP_API_KEY and settings.MAILCHIMP_LIST_ID,
     }
diff --git a/hypha/public/utils/migrations/0010_delete_socialmediasettings.py b/hypha/public/utils/migrations/0010_delete_socialmediasettings.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf29e280068a8b88dd92b59820fdf364b48d336f
--- /dev/null
+++ b/hypha/public/utils/migrations/0010_delete_socialmediasettings.py
@@ -0,0 +1,15 @@
+# Generated by Django 4.2.9 on 2024-01-07 18:52
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("utils", "0009_systemmessagessettings_body_403_and_more"),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name="SocialMediaSettings",
+        ),
+    ]
diff --git a/hypha/public/utils/models.py b/hypha/public/utils/models.py
index b715c1375b6601c508d473789049e43c73747038..64c96eec04591ab51d78ca4bc1d661d05503ca4a 100644
--- a/hypha/public/utils/models.py
+++ b/hypha/public/utils/models.py
@@ -13,7 +13,6 @@ from wagtail.admin.panels import (
 )
 from wagtail.contrib.settings.models import (
     BaseGenericSetting,
-    BaseSiteSetting,
     register_setting,
 )
 from wagtail.fields import RichTextField, StreamField
@@ -21,8 +20,6 @@ from wagtail.models import Orderable, Page
 from wagtail.snippets.models import register_snippet
 from wagtailcache.cache import WagtailCacheMixin, cache_page
 
-from hypha.core.wagtail.admin import register_public_site_setting
-
 
 class LinkFields(models.Model):
     """
@@ -118,31 +115,6 @@ class RelatedPage(Orderable, models.Model):
     ]
 
 
-# Generic social fields abstract class to add social image/text to any new content type easily.
-class SocialFields(models.Model):
-    social_image = models.ForeignKey(
-        "images.CustomImage",
-        null=True,
-        blank=True,
-        on_delete=models.SET_NULL,
-        related_name="+",
-    )
-    social_text = models.CharField(max_length=255, blank=True)
-
-    class Meta:
-        abstract = True
-
-    promote_panels = [
-        MultiFieldPanel(
-            [
-                FieldPanel("social_image"),
-                FieldPanel("social_text"),
-            ],
-            "Social networks",
-        ),
-    ]
-
-
 # Generic listing fields abstract class to add listing image/text to any new content type easily.
 class ListingFields(models.Model):
     listing_image = models.ForeignKey(
@@ -260,33 +232,6 @@ class CallToActionSnippet(models.Model):
         return self.title
 
 
-@register_public_site_setting
-class SocialMediaSettings(BaseSiteSetting):
-    twitter_handle = models.CharField(
-        max_length=255,
-        blank=True,
-        help_text=_("Your Twitter username without the @, e.g. katyperry"),
-    )
-    facebook_app_id = models.CharField(
-        max_length=255,
-        blank=True,
-        help_text=_("Your Facebook app ID."),
-    )
-    default_sharing_text = models.CharField(
-        max_length=255,
-        blank=True,
-        help_text=_(
-            "Default sharing text to use if social text has not been set on a page."
-        ),
-    )
-    site_name = models.CharField(
-        max_length=255,
-        blank=True,
-        default="hypha",
-        help_text=_("Site name, used by Open Graph."),
-    )
-
-
 @register_setting
 class SystemMessagesSettings(BaseGenericSetting):
     wagtail_reference_index_ignore = True
@@ -383,7 +328,7 @@ class SystemMessagesSettings(BaseGenericSetting):
 
 
 @method_decorator(cache_page, name="serve")
-class BasePage(WagtailCacheMixin, SocialFields, ListingFields, Page):
+class BasePage(WagtailCacheMixin, ListingFields, Page):
     wagtail_reference_index_ignore = True
     show_in_menus_default = True
 
@@ -400,9 +345,7 @@ class BasePage(WagtailCacheMixin, SocialFields, ListingFields, Page):
 
     content_panels = Page.content_panels + [FieldPanel("header_image")]
 
-    promote_panels = (
-        Page.promote_panels + SocialFields.promote_panels + ListingFields.promote_panels
-    )
+    promote_panels = Page.promote_panels + ListingFields.promote_panels
 
     def cache_control(self):
         return f"public, s-maxage={settings.CACHE_CONTROL_S_MAXAGE}"
diff --git a/hypha/public/utils/templatetags/util_tags.py b/hypha/public/utils/templatetags/util_tags.py
index 5e43c9b19aafaa26d26f702e618ed09bbbabfa70..63e01d8cac3fad538e6673350a13caada8f8d294 100644
--- a/hypha/public/utils/templatetags/util_tags.py
+++ b/hypha/public/utils/templatetags/util_tags.py
@@ -1,20 +1,9 @@
 from django import template
 from wagtail.coreutils import camelcase_to_underscore
 
-from hypha.public.utils.models import SocialMediaSettings
-
 register = template.Library()
 
 
-# Social text
-@register.filter(name="social_text")
-def social_text(page, site):
-    try:
-        return page.social_text
-    except AttributeError:
-        return SocialMediaSettings.for_site(site).default_sharing_text
-
-
 # Get widget type of a field
 @register.filter(name="widget_type")
 def widget_type(bound_field):
diff --git a/hypha/settings/base.py b/hypha/settings/base.py
index 4ccabafc51e6afd67c9c87b1a1c5c6bce7a07505..ef5c0276a13528e2c1e74e3edfc6dc429fe144b1 100644
--- a/hypha/settings/base.py
+++ b/hypha/settings/base.py
@@ -27,7 +27,7 @@ CURRENCY_LOCALE = env.str("CURRENCY_LOCALE", "en_US")
 DEFAULT_PER_PAGE = 20
 
 # Form Rate-Limit Configuration
-# DEFAULT_RATE_LIMIT is used by login, password, 2FA and Mailchimp forms.
+# DEFAULT_RATE_LIMIT is used by login, password, 2FA, etc
 DEFAULT_RATE_LIMIT = env.str("DEFAULT_RATE_LIMIT", "5/m")
 
 # IF Hypha should enforce 2FA for all users.
@@ -144,6 +144,9 @@ TRANSITION_AFTER_ASSIGNED = env.bool("TRANSITION_AFTER_ASSIGNED", False)
 # Possible values are: False, 1,2,3,…
 TRANSITION_AFTER_REVIEWS = env.bool("TRANSITION_AFTER_REVIEWS", False)
 
+# Default visibility for reviews.
+REVIEW_VISIBILITY_DEFAULT = env.str("REVIEW_VISIBILITY_DEFAULT", "private")
+
 
 # Project settings.
 
@@ -298,7 +301,7 @@ WAGTAIL_SITE_NAME = "hypha"
 WAGTAILIMAGES_IMAGE_MODEL = "images.CustomImage"
 WAGTAILIMAGES_FEATURE_DETECTION_ENABLED = False
 WAGTAIL_USER_EDIT_FORM = "hypha.apply.users.forms.CustomUserEditForm"
-WAGTAIL_USER_CREATION_FORM = "hypha.apply.users.forms.CustomWagtailUserCreationForm"
+WAGTAIL_USER_CREATION_FORM = "hypha.apply.users.forms.CustomUserCreationForm"
 WAGTAIL_USER_CUSTOM_FIELDS = ["full_name"]
 WAGTAIL_PASSWORD_MANAGEMENT_ENABLED = False
 WAGTAILUSERS_PASSWORD_ENABLED = False
@@ -469,14 +472,7 @@ AWS_MIGRATION_BUCKET_NAME = env.str("AWS_MIGRATION_BUCKET_NAME", "")
 AWS_MIGRATION_ACCESS_KEY_ID = env.str("AWS_MIGRATION_ACCESS_KEY_ID", "")
 AWS_MIGRATION_SECRET_ACCESS_KEY = env.str("AWS_MIGRATION_SECRET_ACCESS_KEY", "")
 
-# Mailchimp settings.
-
-MAILCHIMP_API_KEY = env.str("MAILCHIMP_API_KEY", None)
-MAILCHIMP_LIST_ID = env.str("MAILCHIMP_LIST_ID", None)
-
-
 # Basic auth settings
-
 if env.bool("BASIC_AUTH_ENABLED", False):
     MIDDLEWARE.insert(0, "baipw.middleware.BasicAuthIPWhitelistMiddleware")
     BASIC_AUTH_LOGIN = env.str("BASIC_AUTH_LOGIN", None)
diff --git a/hypha/settings/django.py b/hypha/settings/django.py
index 9418a797e90da8f6dfb461ba11c5c99962968800..5e4b72e2e8980e7ccf2dbd5db4aa53db798f0092 100644
--- a/hypha/settings/django.py
+++ b/hypha/settings/django.py
@@ -18,16 +18,15 @@ INSTALLED_APPS = [
     "hypha.apply.review",
     "hypha.apply.determinations",
     "hypha.apply.stream_forms",
+    "hypha.apply.todo",
     "hypha.apply.utils.apps.UtilsConfig",
     "hypha.apply.projects.apps.ProjectsConfig",
     "hypha.public.funds",
     "hypha.public.home",
-    "hypha.public.mailchimp",
     "hypha.public.navigation",
     "hypha.public.news",
     "hypha.public.people",
     "hypha.public.projects",
-    "hypha.public.search",
     "hypha.public.standardpages",
     "hypha.public.forms",
     "hypha.public.utils",
@@ -38,7 +37,6 @@ INSTALLED_APPS = [
     "django_web_components",
     "wagtail.contrib.modeladmin",
     "wagtail.contrib.settings",
-    "wagtail.contrib.search_promotions",
     "wagtail.contrib.forms",
     "wagtail.contrib.redirects",
     "wagtail.embeds",
diff --git a/hypha/static_src/src/images/favicons/android-chrome-144.png b/hypha/static_src/src/images/favicons/android-chrome-144.png
deleted file mode 100644
index 90ff8e96e63db2067c0aaba084e04ceb09925185..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/android-chrome-144.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/android-chrome-192x192.png b/hypha/static_src/src/images/favicons/android-chrome-192x192.png
index c75978f3cd025424d71e1e44e9f228bb0699be19..8eda0dcb4fe10ce1fa85c8db4fc0a84d895ef41c 100644
Binary files a/hypha/static_src/src/images/favicons/android-chrome-192x192.png and b/hypha/static_src/src/images/favicons/android-chrome-192x192.png differ
diff --git a/hypha/static_src/src/images/favicons/android-chrome-512x512.png b/hypha/static_src/src/images/favicons/android-chrome-512x512.png
index 9e9d36cce0d5ed1219483132ac70bc117370d886..f62831d0abbe56e9abb64dc31c78bf4eee8e8eb7 100644
Binary files a/hypha/static_src/src/images/favicons/android-chrome-512x512.png and b/hypha/static_src/src/images/favicons/android-chrome-512x512.png differ
diff --git a/hypha/static_src/src/images/favicons/apple-icon-120.png b/hypha/static_src/src/images/favicons/apple-icon-120.png
deleted file mode 100644
index 8e69eeaa71cee3732b25fe8716da6501f0a50562..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/apple-icon-120.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/apple-icon-152.png b/hypha/static_src/src/images/favicons/apple-icon-152.png
deleted file mode 100644
index f8ec829a936b041d5b267e0e1f53b9eaad197f8e..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/apple-icon-152.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/apple-icon-180.png b/hypha/static_src/src/images/favicons/apple-icon-180.png
deleted file mode 100644
index d3be1f4876df2c6040e15cf1ba23187f17973be8..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/apple-icon-180.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/apple-icon-76.png b/hypha/static_src/src/images/favicons/apple-icon-76.png
deleted file mode 100644
index 9c339ad519c2d0c5ec4bcf68efcdccddcca9917d..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/apple-icon-76.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/apple-touch-icon-512x512.png b/hypha/static_src/src/images/favicons/apple-touch-icon-512x512.png
deleted file mode 100644
index 9e9d36cce0d5ed1219483132ac70bc117370d886..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/apple-touch-icon-512x512.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/apple-touch-icon.png b/hypha/static_src/src/images/favicons/apple-touch-icon.png
index 95cef8d7ed55030f02384eb5f210f9abed3af173..3c76a69a386e8e5830afed4d7d5ff7d7ea7cf1de 100644
Binary files a/hypha/static_src/src/images/favicons/apple-touch-icon.png and b/hypha/static_src/src/images/favicons/apple-touch-icon.png differ
diff --git a/hypha/static_src/src/images/favicons/favicon-16.png b/hypha/static_src/src/images/favicons/favicon-16.png
deleted file mode 100644
index d6b9ad2e38bd3fbba259c135839178c9fa995afc..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/favicon-16.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/favicon-32.png b/hypha/static_src/src/images/favicons/favicon-32.png
deleted file mode 100644
index a9705041eb12b433320b4b54f4549f2fc661988b..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/favicon-32.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/favicon.ico b/hypha/static_src/src/images/favicons/favicon.ico
index 4365d407ed2796bd97ff7b44efb7652d858e5574..8bc1c067bea918ae39f7dcf67fe745df53a2ed50 100644
Binary files a/hypha/static_src/src/images/favicons/favicon.ico and b/hypha/static_src/src/images/favicons/favicon.ico differ
diff --git a/hypha/static_src/src/images/favicons/mstile-150.png b/hypha/static_src/src/images/favicons/mstile-150.png
deleted file mode 100644
index ac8348baf572804f0ab4203d1435d4beaa90b291..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/favicons/mstile-150.png and /dev/null differ
diff --git a/hypha/static_src/src/images/favicons/mstile-150x150.png b/hypha/static_src/src/images/favicons/mstile-150x150.png
index 9af57361535c5eb89dfeb9144b007e06d9666940..9d7c5526277ae07ed137cb21a8b50f3ecdb2d3f2 100644
Binary files a/hypha/static_src/src/images/favicons/mstile-150x150.png and b/hypha/static_src/src/images/favicons/mstile-150x150.png differ
diff --git a/hypha/static_src/src/images/favicons/safari-pinned-tab.svg b/hypha/static_src/src/images/favicons/safari-pinned-tab.svg
index 5dde630ad27edf5def2680457cf59908716e010c..3effb09a168e09833925aff7c6f9e7bd526f95a1 100644
--- a/hypha/static_src/src/images/favicons/safari-pinned-tab.svg
+++ b/hypha/static_src/src/images/favicons/safari-pinned-tab.svg
@@ -1,29 +1 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
- "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
-<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
- width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
- preserveAspectRatio="xMidYMid meet">
-<metadata>
-Created by potrace 1.11, written by Peter Selinger 2001-2013
-</metadata>
-<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
-fill="#000000" stroke="none">
-<path d="M0 2560 l0 -2560 2560 0 2560 0 0 2560 0 2560 -2560 0 -2560 0 0
--2560z m1968 1358 c2 -171 0 -198 -14 -213 -14 -13 -40 -16 -168 -14 l-151 1
--5 -163 -5 -164 -198 -5 -197 -5 -3 -778 c-2 -758 -3 -779 -21 -793 -15 -11
--60 -14 -200 -14 -100 0 -188 5 -196 10 -13 8 -15 108 -18 782 -1 425 0 783 3
-796 5 22 6 22 205 22 l200 0 2 203 3 202 165 1 165 2 2 148 c1 82 2 157 2 167
-1 16 16 17 214 15 l212 -3 3 -197z m1612 37 l0 -165 186 0 c112 0 193 -4 205
--11 19 -10 20 -20 18 -202 l-1 -192 166 -3 166 -2 0 -795 0 -794 -22 -12 c-15
--8 -77 -10 -201 -7 -161 4 -181 6 -193 23 -12 16 -14 150 -14 792 l0 773 -165
-0 -165 0 -2 166 -3 167 -188 -2 c-133 -2 -193 1 -203 10 -11 9 -14 50 -14 209
-0 108 3 200 7 203 3 4 100 7 215 7 l208 0 0 -165z m-1972 -2191 c22 -6 22 -9
-22 -170 l0 -164 158 0 c110 0 162 -4 170 -12 15 -15 17 -370 2 -399 -10 -18
--23 -19 -215 -19 l-205 0 -2 166 -3 167 -150 -2 c-83 -1 -158 1 -168 4 -15 7
--17 27 -17 215 0 157 3 210 13 213 17 8 368 8 395 1z m2370 -7 c15 -10 17
--383 2 -407 -8 -12 -43 -15 -202 -17 l-193 -2 -5 -163 -5 -163 -207 -3 -208
--2 -5 22 c-3 13 -4 109 -3 213 l3 190 200 2 200 2 3 162 c2 115 6 166 15 172
-15 10 387 5 405 -6z"/>
-</g>
-</svg>
+<svg height="525pt" preserveAspectRatio="xMidYMid meet" viewBox="0 0 525 525" width="525pt" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.1 0 0 -.1 0 525)"><path d="m194 3681c2-751 6-1386 10-1411 3-25 11-70 16-100s12-73 16-95c3-22 17-81 29-131 152-583 532-1094 1045-1404 129-78 273-148 403-194 97-36 97-36 99-13 5 70 9 104 38 277 114 671 441 1312 924 1810 362 373 776 646 1261 832 50 19 287 97 315 103 8 2 62 15 120 29s119 27 135 30c17 3 71 12 120 20 50 9 112 18 139 21 27 2 50 6 51 7 9 10-120 301-183 413-129 232-355 494-572 666-228 181-483 317-757 403-46 14-93 27-105 30-13 2-41 9-63 14-22 6-56 14-75 17s-46 7-60 10c-14 2-43 7-65 10-22 4-53 9-70 11-16 3-647 7-1402 8l-1372 3zm2736 425c198-42 357-97 453-156 27-17 51-30 53-30 10 0 164-114 164-121 0-4-10-11-22-14-57-17-363-166-468-227-107-62-325-202-340-218-3-3-30-23-60-45-115-83-250-202-395-349-82-83-159-163-170-176-11-14-49-61-85-105-132-161-308-423-385-575-61-119-145-291-145-296 0-3-11-30-25-60s-25-56-25-59c0-2-5-16-10-30-13-35-23-27-94 74-122 175-204 375-244 596-3 17-7 430-9 918l-4 888 888-4c488-2 904-7 923-11z"/><path d="m4925 2762c-213-23-549-111-760-199-198-83-495-251-588-333-9-8 475-500 491-500 4 0 30 15 57 33 89 60 209 124 320 172 103 44 301 109 368 120 17 4 50 10 72 15 22 4 62 11 89 15 27 3 52 8 55 10 4 2 6 153 6 337v333h-40c-22 0-53-1-70-3z"/><path d="m2989 1618c-218-301-411-791-464-1180-12-87-14-106-20-169l-7-65h340 340l6 60c27 245 145 586 281 811 26 44 50 83 51 87 4 6-483 498-492 498-2 0-18-19-35-42z"/><path d="m4945 1393c-527-122-893-466-1040-978-25-87-31-114-39-181l-4-30h586l587 1v599c0 525-2 599-15 601-8 1-42-4-75-12z"/></g></svg>
\ No newline at end of file
diff --git a/hypha/static_src/src/images/favicons/site.webmanifest b/hypha/static_src/src/images/favicons/site.webmanifest
index 4cd236b851fd753a4870524194e0fdada8bd2189..3e6f2857ab6a3426a5e9c77a13cca6cbe422a2c5 100644
--- a/hypha/static_src/src/images/favicons/site.webmanifest
+++ b/hypha/static_src/src/images/favicons/site.webmanifest
@@ -1,6 +1,6 @@
 {
-    "name": "",
-    "short_name": "",
+    "name": "Hypha",
+    "short_name": "Hypha",
     "icons": [
         {
             "src": "/static/images/favicons/android-chrome-192x192.png",
diff --git a/hypha/static_src/src/images/favicons/site.webmanifest.json b/hypha/static_src/src/images/favicons/site.webmanifest.json
deleted file mode 100644
index ec3552ca7993f5410acb25cc7bc07f6f17c5b1e2..0000000000000000000000000000000000000000
--- a/hypha/static_src/src/images/favicons/site.webmanifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "name": "",
-  "short_name": "",
-  "icons": [
-    {
-      "src": "/android-chrome-144.png",
-      "sizes": "144x144",
-      "type": "image/png"
-    }
-  ],
-  "theme_color": "#ffffff",
-  "background_color": "#ffffff",
-  "display": "standalone"
-}
diff --git a/hypha/static_src/src/images/logo-small.png b/hypha/static_src/src/images/logo-small.png
new file mode 100644
index 0000000000000000000000000000000000000000..f62831d0abbe56e9abb64dc31c78bf4eee8e8eb7
Binary files /dev/null and b/hypha/static_src/src/images/logo-small.png differ
diff --git a/hypha/static_src/src/images/logo.png b/hypha/static_src/src/images/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee04d9f82622715a774ef2d3d2eedca489c909fe
Binary files /dev/null and b/hypha/static_src/src/images/logo.png differ
diff --git a/hypha/static_src/src/images/otf_social.jpg b/hypha/static_src/src/images/otf_social.jpg
deleted file mode 100644
index 926ea5b4bacb3cac9ee4497acf6957eed08e4df0..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/otf_social.jpg and /dev/null differ
diff --git a/hypha/static_src/src/images/radio-free-asia-logo.svg b/hypha/static_src/src/images/radio-free-asia-logo.svg
deleted file mode 100644
index 47118cce988d2aeec55d114ead816b586b92d38d..0000000000000000000000000000000000000000
--- a/hypha/static_src/src/images/radio-free-asia-logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="144" height="61" viewBox="0 0 144 61" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image width="165" height="62" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAakAAAChCAYAAAB9CECCAAAABGdBTUEAA1teXP8meAAAHhVJREFUeAHtnf1127i2xWfuev9fvQouU8EoFYSuYJQKQlcQpwLLFdipwHIFUSoQU0E0FZhTwehVMG9vXUIDwSRFgh8CyY21EOLjnIODH2geAaKdX35REgEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREIF+Cfzar/n5WP/7779jzHaBvET+d37F5Vhne5OU5sIZrn8i88q8//XXXw+4KomACIjALAgoSHkscx6QGIw+IEfILA+VMgzE/AM5RVbgAgQlERCBaRJQkKqxrghKDEIrZAalGDm0tIdDKfJ37LR4VRIBERCBSRBQkCpZRgQmBqXfkXldlIiF2HyAUynyd+StjgdBQUkERGC0BBSkrKXLd0yf0TS2wGTN4k1xi5YXBCtelURABERgVARmH6QQmBZYsQSZwSlCnmo6YGJfkTcIWNlUJ6l5iYAITIvAbIMUglOEpbxHXiEzUM0pbTBZ7q7SOU1acxUBERgfgdkFKSs4JeNbrs49TmHxQcGqc64yKAIi0BGB2QQpBafKOyZFr4JVJSJ1ioAIXIPA5INU/p3TI+Am1wA8sjFT+HuLnVU2Mr/lrgiIwEQJ/Gui8zpOCwHqDoVX5OTYoH8uEYgh8Apuj3lwvySvfhEQARHolcAkd1J4wMagxt3Tsld60zZ+wPS4q9pOe5qanQiIQMgEJrWT4qd/7gIAfIesANXuzltA/Rt47pCjdqakLQIiIAJ+BCazk8KDlEHpG3Lkh0JaFQQO6NOuqgKQukRABPohMImdFALUGnh+IkfISt0TMLuqZ7BmWUkEREAEBiEw6p1U/sDk7ikehJYGIYEM+SO+q9qzoiQCIiACfRIY7U4KAWoJMNw9xX0Cku03BCK07MA/edOjBhEQARHomMAog1T+gOTLEVHHPGSuHgEe+T1jHR7riUtKBERABPwIjO64Dw/GNabKv7mnFAaBLdzgSxWHMNyRFyIgAlMiMKoghQD1DPjJlBZgInPZYx43ClQTWU1NQwQCIjCaIKUAFdBdU+yKAlUxF7WKgAi0IBB8kEJwWmB++uXcFos8oCoDFY/+eFUSAREQgdYEgg5SClCt1/caBg4YlEd/ClTXoK8xRWBiBEIPUnzFfDkx5nOYDgMUAxUDlpIIiIAIeBMI9hX0/DsoBSjvpb2qItdtl++Er+qIBhcBERg3gSB3UnmASsaNVt6DgHZUug1EQARaEQhuJ4UAdYcZJa1mJeVQCHBHxV8bUBIBERABLwJBBSkEqASz0F8x8FrKYJVWWFetabDLI8dEIGwCwRz34UF2/B4DuBZhI5N3ngRu8SLFxlNXaiIgAjMlEESQQoBiYOKbfNFM12EO0z5gknzjbz+HyWqOIiAC3RAI5biP31tE3UxJVgIlwA8i/J9+eVUSAREQgVoErh6k8NC6g6erWt5KaOwEIkxAL1KMfRXlvwgMSOCqx336HmrAlQ5rKP6niduwXJI3IiACIRK4dpDSX5QI8a7o36cDhniPQJX1P5RGEAERGDOBqx33YRe1BrjlmOHJd28CC2jq2M8bnxRFYD4ErrKTQoCKgPh1Ppg10xICOvYrAaNmERCB/xK4VpDaYfhYizB7Ajz2e4djP16VREAEROANgcGP+7CLSuBF/MYTNcyRAI/97uc4cc1ZBESgHoFBd1IIUHwo6Zd2663NnKS4m8rmNGHNVQREoB6BoXdSd3ArqueapGZEQC9RzGixNVURaEJgsJ1UvoviyxLcTSmJgEvgBrup1G1UXQREYN4EhtxJcRelADXv+61q9vpuqoqO+kRgpgQG2Unlu6i/ZspY065PQLup+qwkKQKzIDDUToq7KCURuERAu6lLhNQvAjMj0PtOSt9FzeyOaj9d7abaM5QFEZgMgSF2UvouajK3yyAT+TzIKBpEBERgFASG2Enxjb5oFDTkZCgE3uFNvywUZ+SHCIjA9Qj0upPCUd8KU4uuNz2NPFIC2k2NdOHktgh0TaDXIAVnP3XtsOzNgkAyi1lqkiIgAhcJ9BaksIuKMDp3Ukoi0JTAIt+FN9WTvAiIwMQI9BakwEkBamI3y8DT0S58YOAaTgRCJNDbixP4JMw/JLsMcdLyaTQE/hcvUBxG460cFQER6JzA/3RuEQYRoBa48OGSIlclBjHKKr0lcIum7G3zxZYIEs8XpcYhsIKbmz5cxT1a997LrvmmIfyMMH9mkw7wZ28q6I9NOb/uhwrs+c85Ofqkwfz0cc5Xp4wJ1iT1tVmlV7D+V71fq3z17ettJ9XEIYCOIM+bnfkDcozsk258lAbU2dUciz/A72vKvhEDz1c0Rm86xtewBYePfbgNRlyLuIHtFLIv8GfTQKe1KPxcw8i9ZSiFD6f7HP1/W30s3qA/ddp6qWLoGIbJ0ScN5qePc746YJJA97lA/z3W5fThoqDfq6lg/R8wztrLWKBKveykms4VUDPoMG+RzU5shSJ/OCPkWgl20lqCVxJ6+zwpdeRraU+9Duo/1hMNWor3QCgphiMx1vAD7rPbUJySH8ER+FTiEX+tQvdNCZyq5j5fnKgat7IPD4ED8gb5HQT5STqrVJhW5wHTOQbrFtPatNANShVBIaRARTYJfFoHBUnOBEEA90UER+ISZ0K7j0vcDK85iJ1UFRYEqi0WP4UMd1V3VbIT6eMR16HNXKgPZlvYmMIPxgfMg3MZIj0UDMJPxpHT/hl8n9quk2PTt3rjKO6d+tDVDAMyX0qHSwIj7E8qfD7+WgWfZxUyPl3u+mc+RkLWCT5IEV7+MPiCB8MfqD6HDLQD3146sEETtLPqyNY1zcRDDY77bO2OxWCENr6pGll9C5SXyKnVxmNqtq2Qf0OmDFOGzPuWHz5YLkzQjdDBIyHaYNoj89i2kwT79CdBZtA3vqH4yw9k+sbxukgvRRyLDOc+La2+A/3IWcRsR33Dq0noo/wKmfMwKUPhhytrOs21hMGBusg8uWG5TeIHmqrE/m2VAHzk3H5Hjiy5DGWzTl4+lnDbw665N73sWj6qaAgANo9bCpORCfVa6PR542uXvsP067n50dYWXXKhLZDYuTTKxoDc2pVFPTbyKPNT8rcCGbcpMTr2FUJF9o3uqynk152j63T/45eRg8Ad8l+uoFPfoR4ZnTpXyMeODVbXdXQpU6D/E23PyCad5oqGOoypvywaH+0JchUD9iVFunXaOC6ym57dBtQL72W2I++QqxJ95L1yslEgvLb9pSzyrkDObmo1d3s8lXMCoPtsEzbl0AEZPyuud13OAePwhp5CWnXJhbYAZeeCKRsDcs+uLOqxkS/pL1A5Np3NBS13ZYIl7acHN8cvkDn5lfc/FsiUNfFhFVGvToJsXGBoXUeXMiX6tsnTXNG4szsqym/mANmkQt7tOlufBnN5dgy9oh45bawW/oyjfcfOmmlt/CqQP/VRBv27Apmyppg6oaUgX5yoAekLZA415MYmsunY4a7tdexebXPL2pIdC+KnmWOvCswe7z/0L9CXOP171G+QPyJnyHb6ZCq57r2pW9ctyk/IB6utcRH2YyjdFSju0ZYiu/YXaHtGbpM+YdzdhZw0GQC2OIfY0dmifov8hHyw+hYon5hCN0LdnVOGNj5DmPfIdnqGDm00TStHwRzvZk77J6fOQLJEW+y0b1B/QH5CzpBN4rHk2lSqriV2t9Ax9+bB0ecxo1JXBLAAa+Sz1JXtvuycOfu24v4gdeIGhtm9HWp0LbtOYFhGSriQlZvR9Ca9GlPo4THPzsl86BwT2t379DQX9CXIbrqzdGnbTSd9yrmdqMeW/jenn7sMu59HQc+ODKsn/42toivk4gLdOk1r2qvQp99r9udyryjb6ZntJqGjiFPEfvQ92ooovyIvLF0yYJudTmtg5KquUExs5by8og7K7vjsjmx7qHOuduI6nXzM7VDm0dbL2209ltdGBmVy2Tn5ZBftrm9n95axc+3rKF6cKIH0hHZ+0XyCXiI3lubvPTn6ArtxT7aHMrscaKC6nPgJ/pjwqXaPAj+ZnhJ++Pngi9HAXPXp9AP67ZTBHu/rY6Jt2Nmissqbml5cva+wmRojKB9gn7uJxLTlV+rtnbahqme/9Ar/lhg4cgb/nvO1m+kvZU1aofCEzKudtqjw4W23pagkVgPXjLp105s1BluOw8Sfv7tj6Z9/+Nwi97K0QMcrfExx/QOZ1yeuF661E+T3EK66N+Paxq4o2GmQAlQuBgFXJYImvH1T6LZR6uaLuLLbR1rmw2nbh++wuwGnR9he9GF/IJsLzGHR5n7pyM8Udr7AD96/Zwn+RWjgwydGXiLXSZEjlDp1VvmQWhW0VzbBn7hAYOu2kSlkU7THVp8bPK2ufosFbKOCEb8VtLlNv+UNkdNxhzpzVYqrOu2+fN3d9Tlx5nwgc4DOwtKjvB2kKH9v9bNIecoxH/tgZ4PyA2xmuNZOuY+8N2krqq0YiGCnQQpzemwyrxw6f+gPTfQs2e8oE3yrBD8SGPjUykg7ZX7a6jNtYTzpc4ABbC8xRjrAOGVD8IPE2adSI5jfP7z3F6bN8/qnp14tNfi/ryXYTugB46zbmTjT5rr7pMhHyUNnVaDzw2nbop5YbRHumRU4sf0XrgvqTyjeWTJFxQSNK8jeUKdIwG3L781nt31M9c6CFGAsPSaeQId67z10qVJroWrYjiAT15DrS2TTl+Hc7ldck57H6Nt81PcAsM8f/hT38gLln8gRskl8sKzRvzYNvOb3fdFDgPfmC/JvyAlynfTvOkK+MvA1gv+Zr35Aeg81fCmb5wa6f9bQryvyuUCQ3/U8FrTbTb+jsjUNWBf+Hig/dNPeyrQXXHlvcmf1saDvrAn2YjR0dW+e2R6y0lmQgtOE55OWgHn6VNHEABaWn0CaqIQou+37wZFz2mPyyxAB1PQpqinXWgy8Driv+CB0f8A/o/2J/dYg7kMqQ99HMqcM5F0bbC5LRevznzLhC+3H8R2ZGPWN08aqO26RboHaIE0pRrl3RnLX4NgN1gwM38E+deTt6g/0b+wGlqF7h8uhqM+VNXXokFtk6g2v3BG5p0jcrR+Dj2X7A+wmyAvL/soqVxXde3MPYd6bGZUwxjdeQ0//6tDBuIUtLvZc08tAE+duaszptyGdzx9W/KG20wIVPgjtFNkVlF+ge9TDQ4B9K6ffrvKTs53i/OF0bKuhb+ueleHDAQ3ZWSMe9rDJOZwS6gyiZ22o/zgJXL+QFbjgBi0TZO4gu+OckKNcb+/o36PvbL6oLyFDm88ovyKvHJ2y6qeyjhrt9OE4DsZbIHMdfpqxeQ8hb5G/oH2D7JM4hp1eYC9jA8ZZ4nIcn/WQU5c7qTbzbPMAOmBgdzHa+DKkLj+5bZsOiBsshl7aUI/j8AdhrOkaa8wHxM4BloD/V/DfO+2myt0W++jvfX41fe51iwY36H2DPndxTJ+Racc30Y695hHqr7C/wfX/kD8gx8h24qd5+hVEgi8Z/E3hTGw5dIe2CPWvyOTzO3KCbFKCwnfkDJkyLoMd9NnOfqNrOEdo+4Rch0ECOTelbkNep/2l0/cJfnCcndXH9d+jTv+Z/oOcsGAl6vgkjpdBcYHs3nc+9salg8k/I/smb2AY8C8zqC8x6K+NjYGvjecN/yLknz5zhd4z8liT15zLOAHCrgBE7MpDhg8NN/Ghckzo4AOzSTrp0gAUH5soQ9bVd9Xjo2P5P5R3BS7Uz/RtW24ZduICW2tXrqxepF8kC7kl8unnvGBMt+nZtoPOn65ARZ2yfIhXJsisCmyU3qO0WSDPJtppMjfqxMY5Vpy0tvqaPtfO7i1j59rXLo/7ohaT+bOF7sUbqoXtvlX5aa5pWkGBP7Q+835pOlhA8ssr+fKlYFw+nLkOv+CT/hMu+wIZ0+T2LUwHr9Cn/Q3LJelQ0l63+SMEtzWEOQ6/r0hryA4qAp/2GPAGuQ6LB8jfOg5SlzYupRQCN9CvM86nAmPUL0y5zW1BZ4Q2+pciX0r0q8kaPUF+X2E0q+gLpiuU476ixQsGUk+OpLhxMw/b5odjBd1NE32Ml+LhmkEnaqI3UdkXzOuHM7fMqTOIZGB2i/bI6VtY9RuU75B/R17m7SmuL9DfQH+dtxVeIHMLGfrCtY1zoT2u35E3yAmySZkp5NcHp37WD9sH9H+E/RhXYz9C2aQUheM4uaxpr3PNIOSOn9ZRzGUyXF39QnX4tscc3qHzHnmFHCGbxDlukR8gl5lGc83n9R76Cdo+Iy9NX36l7nfIbZz2quof6GS208auFJS/os3V4ZH/Hu03+RqZeyjO9TNcmb8jb/K5oHhKLr/U9FAWNm9Qv0Pm2kfITCnyS35NcJ1HAowdsk9a+xLCYPxEe0q+dvrWOzl4Xkiajgv1hWXiW1N9ykP/zrIxqqLPfKUjAiIwbgLXPO7LgI5b13ULhO4nohamBlXlp6eNx4grS2eFCLOw6nWL27qCkhMBERCBaxPo8rjvtsFkzBa3gUqhKLewY0y+gYJHAXZi0NrYDZfKCI48vuL41B1VYlDmEcaonJazIiAC8ySAB1aEfJZCJXHm5H8rSx9fC+z4HvlxFzbGFPtwk44IiMB4CXR53Dc0hcehB+xovD12A/umthBRinY+Xkd+GH+L8bOmPkheBERABIYmMMoghQd2AlBFD+2h+fmMxzd8fJJ71Gds+HJgoFISAREQARHokgAC1BK58JffuhynS1vw1yT6vfCxDb3COaPd98jvzXGpcTLgqxc7H97SEQERCIPAqHZSeHgugW2HPNaH1dbni/983mVz9j3yy8AxRR5N8mE3msnJUREQgUICowlSeFDfYQY/kcse1oUTDKyRv0Dnky69xbjyMQodX388h5OaCIiACEyMAIJTjPwT+WIKdeq546++/kH/9cLkvY786A/slh0jXhhy+G5fftITAREYL4Fgd1J4BB6DE9DyeG85XsQnz71emACHCBaYq1Jc1Xmhb3uhX90iIAIicDUCXf4yb9eTOMDgIMEJgSDBWJeO1NrOb+NpYFVDb8Ggju9s0hqyrgiDZ+I2BlhPA/RJLomACMyZAB68j8i1ky8rDLCuPYif4LOvb0PoYUq1jlP9pt6ZFnfUSiIgAjMj0NlxHx5FfMtsV5Dr7ATKsD+ggzuqsafQX1DwOooceFGygcfTcCIgAgEQ6PK4j8EkLphThMDF/5aicbChDnS/wGbQO5GCOdtNmecxnG2j7/IWA4TO+M++Ici+CIhAeAQ620lhalnJ9CK035X0XWzGA34DofSiYLgCoe+ifsk/QGzCRXj0LAvcP7knAiLQA4HOghQedFmFf/fYEUUV/Ze6bi8JBNy/Cdg327XQg2lmO6uyCIjAPAh0FqRyXPsKbI8VfZVdeQB8qBQKs5N/YSIL07Vzr+BnipbsvDWcWu5fOA7JExEQgUEIDBmk+GJF3GJWT9DNWuhfQzX03YnLJNQXKMa27i5X1UVABDwJdB2k/rzgh/eX8/gkfYBtvkQxlsQXJrZjcTb3M1R/9yPjKHdFQAQ6ItB1kEov+MU3/dYXZEq784d+qA9S1++x+HnyG3wzVEL0+4+TkyqIgAjMikCnQQoPubQGvc8tX6LgbupQY5xri4R6dHaJS4hHlOklp9UvAiIwTQJd/p6UIbRHYWkqBdcF2u6Rbwv6Ljbx0z6CHAMAbYSa+HthmY9zeQBPfHRtHYy/tut1y9DbwocM8lFdnb7l4FPa9xiyLwIiMBMCeMDV/VNGcRskGOcV+Sz52oORrv8sUtLCl7uzSflXVi18qLuG/t7V19z5zkN6IiAC4yfQ6XFfjuNHTSzer6Tn9r12YjV9ayN2wCf/TQsDn1ro2qq/25WG5ZCOKuveTw2nKHEREIExEOg8SPG4qObEl/gwfVdT9o1YfgRUd6w3+j02bHxtg0cE3aWvvqO3cuq1q2CbQTitrdCvYIhr3O+MZV0EROBEoPMglVuu+2C5x4N5cfKmeYG7qUNztV41XlpYX7XQdVUXYNvGXpt5uL741rkr3fsqS08ERGD8BPoKUt9rollAzvvYDw+wA/Qfao41hNi+5UO1q6M+M1fvIz/MYwMj5HvNtL3m4BpbBETg+gT6ClJNHi4JPvEvfVHgYfoE3b2vfsd63t/lgEEEX7w5lMxjVdJet3lbV7AnubofdnoaXmZFQASuTaCXIJXvcJo84Lx3UznAL9cGifEPyE3m7Lq8chs6qPPIb9nCjnfQbTGmUeVRXxuexo6uIiACIybQx+9JGRz8FFz3wRvjYfq3UfS9dmHDd+xc71uLabQJJlVu06esSiDQPgWoQBdGbonAkAR+7WswPBgXsP1XX/Zld/IEbrCTSic/S01QBESgkkAvx30cMT/y21SOrk4RKCbAP86bFnepVQREYE4EegtSOcQQXmOe03pOZa7X/C5sKgw1DxGYBIHejvsMHRz7/US5r+9bzDC6TovA/+Y78WnNSrMRARFoTKDvnRQd0qfixssya4WNAtSs11+TF4EzAr3vpDgadlOvuEQsK4nABQLvEKSyCzLqFgERmAmBIXZSRKnvpmZyQ7WcJndRWUsbUhcBEZgQgaF2UnwdnbspXpVEoIzADYJUWtapdhEQgfkRGGQnhQfPAWj13dT87q8mM04VoJrgkqwIzIPAIDsposx/uZdv+kWsK4mAQ+AdglTmtKkqAiIwcwKD7KTION9NPcyct6ZfTEDfRRVzUasIzJ7AYDspQ1q/N2VI6JoT4FHwe+2idD+IgAgUERhsJ2UN/sUqqygCXxWgdBOIgAiUERh8J0VHsJt6xiVhWWnWBDIEqHezJqDJi4AIVBK4VpDSK+mVyzKbzhsEqXQ2s9VERUAEGhO4xnGfeYnitrG3UpgSgScFqCktp+YiAv0QuMpOykwFx37fUF6Zuq6zIZBhpnxZgi9NKImACIhAKYFrBykd+5UuzaQ7dMw36eXV5ESgOwJXOe4z7uefpHXsZ4DM4/qAdU/nMVXNUgREoC2Bq+6kjPM49ntE+c7UdZ0sgRQB6mays9PEREAEOicQRJDirPRLvp2vbWgG+f3TOwQpfQ8V2srIHxEImMBVj/scLvyErQeYA2VC1RsFqAmtpqYiAgMRCCZI5Q8wBaqBFn7gYW6xvvuBx9RwIiACEyAQTJAiy/xBpj+bNIEby5oCfx9qY9VVFAEREIHaBIL5Tsr2GN9PJajzTycpjZvABgHqdtxTkPciIALXJBBkkCIQvfF3zduik7H1Jl8nGGVEBOZNINggxWVBoNIfoh3n/cnvn26wi9KLMONcP3ktAsEQCDpIkZICVTD3Sl1HFKDqkpKcCIjARQLBBynOQIHq4jqGIqAAFcpKyA8RmAiBoN7uK2Oaf/n+VNav9iAIbOGFjviCWAo5IQLTITCKnZTBjR1VgrLe+jNAwrnqLb5w1kKeiMCkCIxiJ2WIY0e1QfkW+WDadL06gYd8p3t1R+SACIjA9AiMaidl8GNHtUR5h7wwbboOToAfFL7kHxwGH1wDioAIzIPAKIMUlwaBigGKgYoBS2lYAhmG+4gAtR92WI0mAiIwNwKjOu6zFwcPyAPye7Q92e0q904gxQj8X3UVoHpHrQFEQARGG6TM0uFhyb/19xH5YNp07Y0Aj/dukMW6N8QyLAIiYBMY7XGfPQmW8+M/vvm3cvtUb02Au6ZbBCdelURABERgMAKj30kZUvx0j8wdFXdW+qRvwLS/PoCrjvfac5QFERABDwKT2UnZc9euyqbhXU6hyeM97Z68EUpRBESgLYFJBikDBcEqRplHgBGyUj0C3IUyOG3qiUtKBERABPojMOkgZbAhWCUoPyIvTJuubwgwOH1F5n9SyLKSCIiACFydwCyCFCnnR4B3KH5GVrAilH/SBsUHBKfsnyaVREAEROD6BGYTpAxqBStD4vhyyRY1BacTEhVEQARCIzC7IGUvQH4MeI+2yG6feDnD/F6Qdaw38YXW9ERgCgRmHaTMAiJYxSh/Qk6Qp5pSTOwFR3qbqU5Q8xIBEZgeAQUpa03zo8AVmvi91dLqGmsxg+N8GWKr75vGuoTyWwTmTUBBqmT9EbAidDFgfcivuIwi7eElj/NSBCaWlURABERgtAQUpGosXb7DiiHKgMVrSLusDP6kyD+QuWM64KokAiIgApMgoCDlsYx50GKgipF/Q46QhwhcGcZhZkDaI3O3pKAEEEoiIALTJKAg1eG6IngxUC2QzfU/KEfIJpl2U7evqVU5oPxHXj+2Ixgdr3mbLiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAh0Q+H+1KtMi9Jz1xQAAAABJRU5ErkJggg==" transform="translate(-7 -1)" fill="none" fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/hypha/static_src/src/images/usagm.png b/hypha/static_src/src/images/usagm.png
deleted file mode 100644
index 3edb74116391bdbae3b06865ace60dc9c307320b..0000000000000000000000000000000000000000
Binary files a/hypha/static_src/src/images/usagm.png and /dev/null differ
diff --git a/hypha/static_src/src/javascript/apply/edit-comment.js b/hypha/static_src/src/javascript/apply/edit-comment.js
index ae1270d75254bd5d218730da36eb31e722cd9d87..d092bf5fe0583d5af074890f378058f5aa47a903 100644
--- a/hypha/static_src/src/javascript/apply/edit-comment.js
+++ b/hypha/static_src/src/javascript/apply/edit-comment.js
@@ -3,10 +3,7 @@
 
     const comment = ".js-comment";
     const pageDown = ".js-pagedown";
-    const feedMeta = ".js-feed-meta";
     const editBlock = ".js-edit-block";
-    const lastEdited = ".js-last-edited";
-    const commentVisibility = ".js-comment-visibility";
     const editButton = ".js-edit-comment";
     const feedContent = ".js-feed-content";
     const commentError = ".js-comment-error";
@@ -22,10 +19,6 @@
         const editBlockWrapper = $(this).closest(feedContent).find(editBlock);
         const commentWrapper = $(this).closest(feedContent).find(comment);
         const commentContents = $(commentWrapper).attr("data-comment");
-        const visibilityOptions = $.parseJSON(
-            $(commentWrapper).attr("data-visibility-options")
-        );
-        const currentVisibility = $(commentWrapper).attr("data-visibility");
 
         // hide the edit link and original comment
         $(this).parent().hide();
@@ -36,24 +29,9 @@
                 <div id="wmd-button-bar-edit-comment" class="wmd-button-bar"></div>
                 <textarea id="wmd-input-edit-comment" class="wmd-input" rows="10">${commentContents}</textarea>
                 <div id="wmd-preview-edit-comment" class="wmd-preview"></div>
-                <br>
-                <div>Visible to:</div>
             </div>
         `;
 
-        const radioButtonsDiv = '<div id="edit-comment-visibility"></div>';
-        let key = "";
-        let label = "";
-        let radioButtons = "";
-
-        $.each(visibilityOptions, function (idx, value) {
-            key = value[0];
-            label = value[1];
-            radioButtons += `
-            <input type="radio" name='radio-visibility' value=${key} id='visible-to-${key}' />
-            <label for="visible-to-${key}">${label}</label><br>`;
-        });
-
         const buttons = `
                 <div class="wrapper--outer-space-medium">
                     <button class="button button--primary js-submit-edit" type="submit">Update</button>
@@ -62,10 +40,8 @@
         `;
 
         // add the comment to the editor
-        const markupEditor = $(markup).append(radioButtonsDiv).append(buttons);
+        const markupEditor = $(markup).append(buttons);
         $(editBlockWrapper).append(markupEditor);
-        $("#edit-comment-visibility").html(radioButtons);
-        $(`#visible-to-${currentVisibility}`).prop("checked", true); // ensure current visibility is checked
 
         // run the editor
         initEditor();
@@ -88,9 +64,7 @@
             .closest(pageDown)
             .find(".wmd-preview")
             .html();
-        const editedVisibility = $(
-            'input[name="radio-visibility"]:checked'
-        ).val();
+        // const editedVisibility = $('input[name="radio-visibility"]:checked').val();
         const commentMD = $(this).closest(editBlock).find("textarea").val();
         const editUrl = $(commentContainer).attr("data-edit-url");
 
@@ -102,7 +76,7 @@
             },
             body: JSON.stringify({
                 message: editedComment,
-                visibility: editedVisibility,
+                // visibility: editedVisibility
             }),
         })
             .then((response) => {
@@ -124,8 +98,6 @@
                     data.edit_url,
                     commentMD
                 );
-                updateVisibility(this, data.visibility);
-                updateLastEdited(this, data.edited);
                 showComment(this);
                 showEditButton(this);
                 hidePageDownEditor(this);
@@ -164,12 +136,7 @@
     };
 
     const showEditButton = (el) => {
-        $(el)
-            .closest(editBlock)
-            .siblings(feedMeta)
-            .find(editButton)
-            .parent()
-            .show();
+        $(editButton).parent().show();
     };
 
     const hidePageDownEditor = (el) => {
@@ -180,44 +147,6 @@
         $(el).closest(editBlock).siblings(comment).show();
     };
 
-    const updateVisibility = (el, visibility) => {
-        if (visibility !== "all") {
-            $(el)
-                .closest(feedContent)
-                .find(commentVisibility)
-                .parent()
-                .attr("hidden", false);
-            $(el).closest(feedContent).find(commentVisibility).text(visibility);
-        } else {
-            $(el)
-                .closest(feedContent)
-                .find(commentVisibility)
-                .parent()
-                .attr("hidden", true);
-            $(el)
-                .closest(feedContent)
-                .find(commentVisibility)
-                .html(`${visibility}`);
-        }
-    };
-
-    const updateLastEdited = (el, date) => {
-        const parsedDate = new Date(date).toISOString().split("T")[0];
-        const time = new Date(date).toLocaleTimeString([], {
-            hour: "2-digit",
-            minute: "2-digit",
-        });
-        $(el)
-            .closest(feedContent)
-            .find(lastEdited)
-            .parent()
-            .attr("hidden", false);
-        $(el)
-            .closest(feedContent)
-            .find(lastEdited)
-            .html(`${parsedDate} ${time}`);
-    };
-
     const updateComment = (
         el,
         id,
@@ -240,12 +169,4 @@
     };
 
     const hideError = () => $(commentError).remove();
-
-    window.addEventListener("beforeunload", (e) => {
-        if ($(submitEditButton).length) {
-            e.preventDefault();
-            e.returnValue =
-                "It looks like you're still editing a comment. Are you sure you want to leave?";
-        }
-    });
 })(jQuery);
diff --git a/hypha/static_src/src/javascript/main.js b/hypha/static_src/src/javascript/main.js
index c37dbfc02414ce634bc9c71c260653c32ca23606..d76cfac8f66ce73d1415f5d5a9710414949c68ec 100644
--- a/hypha/static_src/src/javascript/main.js
+++ b/hypha/static_src/src/javascript/main.js
@@ -1,38 +1,6 @@
 (function ($) {
     "use strict";
 
-    let Search = class {
-        static selector() {
-            return ".js-search-toggle";
-        }
-
-        constructor(node, searchForm) {
-            this.node = node;
-            this.searchForm = searchForm;
-            this.bindEventListeners();
-        }
-
-        bindEventListeners() {
-            this.node.click(this.toggle.bind(this));
-        }
-
-        toggle() {
-            // show the search
-            this.searchForm[0].classList.toggle("is-visible");
-
-            // swap the icons
-            this.node[0]
-                .querySelector(".header__icon--open-search")
-                .classList.toggle("is-hidden");
-            this.node[0]
-                .querySelector(".header__icon--close-search")
-                .classList.toggle("is-unhidden");
-
-            // add modifier to header to be able to change header icon colours
-            document.querySelector(".header").classList.toggle("search-open");
-        }
-    };
-
     let MobileMenu = class {
         static selector() {
             return ".js-mobile-menu-toggle";
@@ -42,7 +10,6 @@
             this.node = node;
             this.closeButton = closeButton;
             this.mobileMenu = mobileMenu;
-            this.search = search;
 
             this.bindEventListeners();
         }
@@ -55,67 +22,6 @@
         toggle() {
             // toggle mobile menu
             this.mobileMenu[0].classList.toggle("is-visible");
-
-            // check if search exists
-            if (document.body.contains(this.search[0])) {
-                // reset the search whenever the mobile menu is toggled
-                if (this.search[0].classList.contains("is-visible")) {
-                    this.search[0].classList.toggle("is-visible");
-                    document
-                        .querySelector(".header__inner--menu-open")
-                        .classList.toggle("header__inner--search-open");
-                }
-            }
-
-            // reset the search show/hide icons
-            if (
-                this.mobileMenu[0].classList.contains("is-visible") &&
-                document.body.contains(this.search[0])
-            ) {
-                document
-                    .querySelector(".header__icon--open-search-menu-closed")
-                    .classList.remove("is-hidden");
-                document
-                    .querySelector(".header__icon--close-search-menu-closed")
-                    .classList.remove("is-unhidden");
-            }
-        }
-    };
-
-    let MobileSearch = class {
-        static selector() {
-            return ".js-mobile-search-toggle";
-        }
-
-        constructor(node, mobileMenu, searchForm, searchToggleButton) {
-            this.node = node;
-            this.mobileMenu = mobileMenu[0];
-            this.searchForm = searchForm[0];
-            this.searchToggleButton = searchToggleButton[0];
-            this.bindEventListeners();
-        }
-
-        bindEventListeners() {
-            this.node.click(this.toggle.bind(this));
-        }
-
-        toggle() {
-            // hide the mobile menu
-            this.mobileMenu.classList.remove("is-visible");
-
-            // wait for the mobile menu to close
-            setTimeout(() => {
-                // open the search
-                this.searchForm.classList.add("is-visible");
-
-                // swap the icons
-                this.searchToggleButton
-                    .querySelector(".header__icon--open-search")
-                    .classList.add("is-hidden");
-                this.searchToggleButton
-                    .querySelector(".header__icon--close-search")
-                    .classList.add("is-unhidden");
-            }, 250);
         }
     };
 
@@ -123,21 +29,7 @@
         new MobileMenu(
             $(el),
             $(".js-mobile-menu-close"),
-            $(".header__menus--mobile"),
-            $(".header__search")
-        );
-    });
-
-    $(Search.selector()).each((index, el) => {
-        new Search($(el), $(".header__search"));
-    });
-
-    $(MobileSearch.selector()).each((index, el) => {
-        new MobileSearch(
-            $(el),
-            $(".header__menus--mobile"),
-            $(".header__search"),
-            $(".js-search-toggle")
+            $(".header__menus--mobile")
         );
     });
 
diff --git a/hypha/static_src/src/sass/apply/components/_dashboard-table.scss b/hypha/static_src/src/sass/apply/components/_dashboard-table.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1a63c5334f8438bc3b698d4ac176979c8f7febff
--- /dev/null
+++ b/hypha/static_src/src/sass/apply/components/_dashboard-table.scss
@@ -0,0 +1,52 @@
+// stylelint-disable selector-max-compound-selectors
+
+.paf-review-table {
+    @include table-ordering-styles;
+
+    thead {
+        display: none;
+
+        @include media-query($table-breakpoint) {
+            display: table-header-group;
+        }
+
+        th {
+            // ordering - adjust alignment
+            &.desc {
+                position: relative;
+                color: $color--dark-grey;
+
+                &::after {
+                    position: absolute;
+                    top: 40%;
+                    margin-left: 5px;
+                }
+            }
+
+            &.asc {
+                position: relative;
+                color: $color--dark-grey;
+
+                &::after {
+                    position: absolute;
+                    top: 50%;
+                    margin-left: 5px;
+                }
+            }
+        }
+    }
+
+    tbody {
+        td {
+            // stylelint-disable-next-line force-element-nesting
+            > span.mobile-label {
+                display: inline-block;
+                width: 90px;
+
+                @include media-query($table-breakpoint) {
+                    display: none;
+                }
+            }
+        }
+    }
+}
diff --git a/hypha/static_src/src/sass/apply/components/_feed.scss b/hypha/static_src/src/sass/apply/components/_feed.scss
index 5228385373bd59a63c1d04286df35066251136c6..8bda96812d4241272dd7c649b707b4b8403db103 100644
--- a/hypha/static_src/src/sass/apply/components/_feed.scss
+++ b/hypha/static_src/src/sass/apply/components/_feed.scss
@@ -2,9 +2,7 @@
     &__item {
         position: relative;
         display: flex;
-        padding-bottom: 20px;
-        margin-bottom: 25px;
-        border-bottom: 1px solid $color--mid-grey;
+        margin-bottom: 1.5em;
 
         &:last-child {
             border-bottom: 0;
@@ -27,10 +25,7 @@
     }
 
     &__label {
-        padding: 5px 10px;
         margin: 0;
-        font-size: 12px;
-        font-weight: $weight--bold;
         color: $color--white;
         text-align: center;
 
@@ -71,20 +66,11 @@
     }
 
     &__pre-content {
-        display: none;
         width: 110px;
-
-        @include media-query(small-tablet) {
-            display: block;
-        }
     }
 
     &__content {
         width: 100%;
-
-        @include media-query(small-tablet) {
-            padding-left: 20px;
-        }
     }
 
     &__meta {
diff --git a/hypha/static_src/src/sass/apply/components/_form.scss b/hypha/static_src/src/sass/apply/components/_form.scss
index 0305fa36b3fbcf8cf310663fe2315c995119d93c..639b40a8782b95aabc2fac9ee912feb09c13bd9a 100644
--- a/hypha/static_src/src/sass/apply/components/_form.scss
+++ b/hypha/static_src/src/sass/apply/components/_form.scss
@@ -114,23 +114,24 @@
         &--multi_file_field,
         &--single_file_field,
         &--file_field {
-            @include button($color--light-blue, $color--darkest-blue);
-            max-width: 15rem;
-            text-align: center;
-            border: 0;
+            @include button($color--white, $color--light-blue);
+            display: inline-block;
+            color: $color--light-blue;
+            border: 1px solid $color--mid-grey;
 
-            .no-js & {
-                display: none;
+            &:focus {
+                color: $color--light-blue;
             }
 
-            .form__required {
+            &:hover {
                 color: $color--white;
             }
 
-            &:hover {
-                .no-js & {
-                    background: none;
-                }
+            max-width: 15rem;
+            text-align: center;
+
+            .no-js & {
+                display: none;
             }
         }
 
@@ -456,42 +457,43 @@
     }
 
     &__comments {
-        display: grid;
-        grid-template-areas: "message" "visibility" "actions";
-        grid-template-rows: auto auto auto;
-        grid-template-columns: 1fr;
+        .fields--visible {
+            display: grid;
+            grid-template-areas: "message" "attachments" "visibility" "actions";
+            grid-template-rows: auto auto auto auto;
+            grid-template-columns: 1fr;
 
-        @include media-query(tablet-landscape) {
-            grid-template-areas: "message visibility" "actions actions";
-            grid-template-rows: auto auto;
-            grid-template-columns: 2fr 1fr;
-            column-gap: 2rem;
+            @include media-query(tablet-landscape) {
+                grid-template-areas: "message attachments" "message visibility";
+                grid-template-rows: auto auto;
+                grid-template-columns: 2fr 1fr;
+                column-gap: 2rem;
+
+                .wmd-input,
+                .wmd-preview {
+                    max-width: 100%;
+                    margin-bottom: 0;
+                }
+            }
 
-            .wmd-input,
-            .wmd-preview {
-                max-width: 100%;
-                margin-bottom: 0;
+            // stylelint-disable-next-line selector-class-pattern
+            .id_attachments {
+                grid-area: attachments;
             }
-        }
 
-        // stylelint-disable-next-line selector-class-pattern
-        .id_message {
-            grid-area: message;
+            // stylelint-disable-next-line selector-class-pattern
+            .id_message {
+                grid-area: message;
 
-            > label {
-                @include hidden;
+                > label {
+                    @include hidden;
+                }
             }
-        }
-
-        // stylelint-disable-next-line selector-class-pattern
-        .id_visibility_0 {
-            grid-area: visibility;
-        }
 
-        .button {
-            grid-area: actions;
-            max-width: 210px;
-            text-align: center;
+            // stylelint-disable-next-line selector-class-pattern
+            .id_visibility_0 {
+                grid-area: visibility;
+            }
         }
     }
 
diff --git a/hypha/static_src/src/sass/apply/components/_icon.scss b/hypha/static_src/src/sass/apply/components/_icon.scss
index dcc3730deec2e9d416edeabce3a0da74557c6359..c3478566621144de9d1389015609a6590ad9e1b0 100644
--- a/hypha/static_src/src/sass/apply/components/_icon.scss
+++ b/hypha/static_src/src/sass/apply/components/_icon.scss
@@ -110,6 +110,13 @@
         height: 16px;
     }
 
+    &--dashboard-tasks {
+        margin-left: 4px;
+        margin-right: 8px;
+        width: 24px;
+        height: 24px;
+    }
+
     &--action-cross {
         stroke: $color--black;
         fill: transparent;
diff --git a/hypha/static_src/src/sass/apply/components/_projects-table.scss b/hypha/static_src/src/sass/apply/components/_projects-table.scss
index 425d24c246cda1c2d129f3ae940958f51d4b8d60..4af3a5963704c29803717bb52a2cf49266e21ee2 100644
--- a/hypha/static_src/src/sass/apply/components/_projects-table.scss
+++ b/hypha/static_src/src/sass/apply/components/_projects-table.scss
@@ -1,6 +1,30 @@
+// stylelint-disable selector-max-compound-selectors
+
 .projects-table {
     @include table-ordering-styles;
 
+    thead {
+        display: none;
+
+        @include media-query($table-breakpoint) {
+            display: table-header-group;
+        }
+    }
+
+    tbody {
+        td {
+            // stylelint-disable-next-line force-element-nesting
+            > span.mobile-label {
+                display: inline-block;
+                width: 90px;
+
+                @include media-query($table-breakpoint) {
+                    display: none;
+                }
+            }
+        }
+    }
+
     .reporting {
         .icon {
             margin-right: 0.3rem;
@@ -13,4 +37,26 @@
 
 .invoices-table {
     @include table-ordering-styles;
+
+    thead {
+        display: none;
+
+        @include media-query($table-breakpoint) {
+            display: table-header-group;
+        }
+    }
+
+    tbody {
+        td {
+            // stylelint-disable-next-line force-element-nesting
+            > span.mobile-label {
+                display: inline-block;
+                width: 90px;
+
+                @include media-query($table-breakpoint) {
+                    display: none;
+                }
+            }
+        }
+    }
 }
diff --git a/hypha/static_src/src/sass/apply/components/_status-bar.scss b/hypha/static_src/src/sass/apply/components/_status-bar.scss
index b47e7a73f9dd4ace61e95c64f64770a19e4ea1c2..e6d9ba86a9bec3bb791e2aa538a5cd35c83c3ef1 100644
--- a/hypha/static_src/src/sass/apply/components/_status-bar.scss
+++ b/hypha/static_src/src/sass/apply/components/_status-bar.scss
@@ -1,10 +1,15 @@
 // stylelint-disable  selector-class-pattern
 
 .status-bar {
+    --bar: 3px;
+    --dot: 20px;
+    --triangle: 5px;
+    --tooltip-padding: 10px;
+    --tooltip-max-width: 12ch;
+    --tooltip-margin-top: 17px;
+
     $root: &;
     display: none;
-    padding: 30px 10px 80px;
-    margin-right: 16px;
 
     @include media-query(tablet-portrait) {
         display: flex;
@@ -17,6 +22,7 @@
     }
 
     &--small {
+        --tooltip-max-width: 10ch;
         width: 100%;
         max-width: 750px;
         margin-right: 30px;
@@ -26,189 +32,137 @@
     &__subheading {
         display: inline-block;
         padding: 5px 10px;
-        margin: 10px 0 0;
         color: $color--white;
         background-color: $color--tomato;
     }
 
-    &__icon {
-        position: absolute;
-        top: -10px;
-        left: 0;
-        z-index: 30;
-        display: none;
-        width: 20px;
-        height: 20px;
-
-        .status-bar__item--is-current &,
-        .status-bar__item--is-complete & {
-            display: block;
-
-            .status-bar--small & {
-                display: block;
-                border-radius: 50%;
-                box-shadow: 0 1px 9px 0 $color--black-50;
-            }
-        }
-
-        .status-bar__item:first-of-type & {
-            left: -10px;
-        }
+    &__text {
+        font-size: map-get($font-sizes, milli);
+        font-weight: $weight--bold;
     }
 
     &__item {
-        position: relative;
         flex: 1;
-        height: 3px;
-        background: $color--mid-grey;
+        position: relative;
+        padding-block-start: var(--tooltip-margin-top);
+
+        // The bar for each item.
+        border-block-start: var(--bar) solid $color--mid-grey;
 
-        // every items dot
+        // The dot for each item.
         &::before {
+            display: flex;
+            place-items: center;
             position: absolute;
-            top: -10px;
-            left: 0;
-            width: 20px;
-            height: 20px;
+            top: calc(-1 * var(--dot) / 2);
+            left: calc(-1 * var(--dot) / 2);
+            width: var(--dot);
+            height: var(--dot);
             background: $color--dark-grey;
-            border: 5px solid $color--mid-grey;
+            border: calc(var(--dot) / 4) solid $color--mid-grey;
             border-radius: 50%;
             content: "";
 
-            .status-bar--small & {
+            #{$root}--small & {
                 background: $color--white;
             }
         }
 
-        // last items dont have a dot
-        &:last-of-type {
-            flex: 0;
-            height: 0;
+        &--is-current {
+            &::before {
+                background: $color--white;
+                border-color: $color--tomato;
+            }
 
-            &.status-bar__item--is-complete {
-                &::after {
-                    display: none;
-                }
+            // Fill the bar all the way on accepted/declined.
+            &:last-of-type {
+                border-color: $color--primary;
             }
         }
 
-        &:first-of-type {
+        &--is-complete {
+            border-color: $color--primary;
+
             &::before {
-                left: -10px;
+                font-size: map-get($font-sizes, milli);
+                font-weight: $weight--bold;
+                color: $color--white;
+                background: $color--primary;
+                border-color: $color--primary;
+                content: "✓";
+
+                #{$root}--small & {
+                    background: $color--primary;
+                }
             }
         }
 
-        &--is-current {
+        &:first-of-type {
             &::before {
-                position: absolute;
-                top: -10px;
-                right: -20px;
-                z-index: 10;
-                width: 20px;
-                height: 20px;
-                background: $color--white;
-                border: 5px solid $color--error;
-                border-radius: 50%;
-                content: "";
+                left: 0;
             }
         }
 
-        &--is-complete {
-            background: $color--primary;
+        &:nth-last-of-type(2) {
+            flex: 0;
+        }
 
-            &:last-of-type {
-                &::after {
-                    background: $color--primary;
-                }
-            }
+        &:last-of-type {
+            flex: 0;
 
             &::before {
-                background: $color--primary;
-                border-color: $color--primary;
-
-                .status-bar--small & {
-                    background: $color--primary;
-                }
+                left: auto;
+                right: 0;
             }
         }
     }
 
     &__tooltip {
-        // tooltip hover area - not visibile
-        position: absolute;
-        top: -10px;
-        z-index: 100;
-        width: 20px;
-        height: 20px;
-        border-radius: 50%;
-        opacity: 1;
-        transition: opacity $transition;
-
-        .status-bar__item:first-of-type & {
-            left: -10px;
+        --tooltip-width: min(var(--tooltip-max-width), var(--tooltip-chars));
+        width: var(--tooltip-width);
+        margin-inline-start: calc(-1 * var(--tooltip-width) / 2 - 5px);
+        text-align: center;
+        padding-block: var(--tooltip-padding);
+        color: $color--mid-grey;
+
+        #{$root}__item--is-complete & {
+            color: $color--primary;
         }
 
-        .status-bar__item--is-current & {
-            opacity: 1;
+        #{$root}__item--is-current & {
+            width: calc(var(--tooltip-width) + var(--tooltip-padding));
+            position: relative;
+            color: $color--white;
+            background-color: $color--tomato;
+            padding-inline: var(--tooltip-padding);
         }
 
         &::before {
-            .status-bar__item--is-current & {
-                @include triangle(top, $color--error, 5px);
+            #{$root}__item--is-current & {
+                @include triangle(top, $color--tomato, 5px);
                 position: absolute;
-                bottom: -10px;
-                left: 5px;
-            }
-        }
-
-        // tooltip contents
-        &::after {
-            position: absolute;
-            top: 30px;
-            left: -25px;
-            text-align: center;
-            display: block;
-            padding: 5px 10px;
-            font-size: 12px;
-            font-weight: $weight--bold;
-            background-color: $color--error;
-            content: attr(data-title);
-
-            // prevent first tooltip hitting viewport edge
-            .status-bar__item:first-of-type & {
-                left: 0;
-
-                @include media-query(desktop) {
-                    left: -25px;
-                }
+                top: calc(-1 * var(--triangle) - 2px);
+                left: calc(50% - var(--triangle));
             }
 
-            // prevent last tooltip hitting viewport edge
-            .status-bar__item:last-of-type & {
-                left: -45px;
-
-                @include media-query(tablet-portrait) {
-                    left: -60px;
-                }
-
-                @include media-query(desktop-medium) {
-                    left: -35px;
-                }
+            #{$root}__item--is-current:first-of-type & {
+                left: var(--triangle);
             }
 
-            .status-bar__item & {
-                background-color: inherit;
-                color: $color--mid-grey;
+            #{$root}__item--is-current:last-of-type & {
+                left: initial;
+                right: var(--triangle);
             }
+        }
 
-            .status-bar__item--is-complete & {
-                background-color: inherit;
-                color: $color--primary;
-            }
+        #{$root}__item:first-of-type & {
+            margin-inline-start: initial;
+            text-align: start;
+        }
 
-            .status-bar__item--is-current & {
-                background-color: $color--tomato;
-                color: $color--white;
-            }
+        #{$root}__item:last-of-type & {
+            margin-inline-start: initial;
+            text-align: end;
         }
     }
 }
diff --git a/hypha/static_src/src/sass/apply/layout/_header.scss b/hypha/static_src/src/sass/apply/layout/_header.scss
index cbdb7d25d496128dcce225dd268083b827d745fd..e4e746baecc535a240f8bf6580010f33cbfcb6ac 100644
--- a/hypha/static_src/src/sass/apply/layout/_header.scss
+++ b/hypha/static_src/src/sass/apply/layout/_header.scss
@@ -26,15 +26,10 @@
     }
 
     &__logo {
-        fill: $color--default;
+        max-width: none;
 
         &--mobile {
             width: 60px;
-            height: 60px;
-
-            .is-visible & {
-                fill: $color--white;
-            }
 
             @include media-query(tablet-landscape) {
                 display: none;
@@ -46,8 +41,7 @@
 
             @include media-query(tablet-landscape) {
                 display: block;
-                width: 160px;
-                height: 40px;
+                width: 215px;
             }
         }
     }
diff --git a/hypha/static_src/src/sass/apply/main.scss b/hypha/static_src/src/sass/apply/main.scss
index d8f84ab777e7f5fa4a2e447f8df8f8917dfb6939..d24180d0262d3c9b44c77c3b5a451253017f849e 100644
--- a/hypha/static_src/src/sass/apply/main.scss
+++ b/hypha/static_src/src/sass/apply/main.scss
@@ -71,6 +71,7 @@
 @import "components/activity-notifications";
 @import "components/dropdown";
 @import "components/banner";
+@import "components/dashboard-table";
 
 // Layout
 @import "layout/header";
diff --git a/hypha/static_src/src/sass/apply/styleguide.scss b/hypha/static_src/src/sass/apply/styleguide.scss
index 6667c9202f6c24cfefb8cdd9700e9fbd982b6751..5499fb5d17026b8a1db7730c1f8c50eb33266e7b 100644
--- a/hypha/static_src/src/sass/apply/styleguide.scss
+++ b/hypha/static_src/src/sass/apply/styleguide.scss
@@ -65,6 +65,7 @@
 @import "components/two-factor";
 @import "components/determination";
 @import "components/dropdown";
+@import "components/dashboard-table";
 
 // Layout
 @import "layout/header";
diff --git a/hypha/static_src/src/sass/apply/wagtail_groups_list.scss b/hypha/static_src/src/sass/apply/wagtail_groups_list.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8e136ec19b7d1c28de87a6095b11b8538da824f9
--- /dev/null
+++ b/hypha/static_src/src/sass/apply/wagtail_groups_list.scss
@@ -0,0 +1,27 @@
+.group-help-text {
+    font-size: smaller;
+    padding-left: 10px;
+}
+
+// Stylings used for the Wagtail admin "roles" tab when creating/editing a user
+.form {
+    &__label {
+        p {
+            margin: 0;
+        }
+
+        .group-help-text {
+            color: var(--w-color-grey-400);
+        }
+    }
+}
+
+// Stylings used for the Wagtail admin groups view
+.title-wrapper {
+    a {
+        .group-help-text {
+            font-weight: normal;
+            display: inline;
+        }
+    }
+}
diff --git a/hypha/static_src/src/sass/public/components/_icon.scss b/hypha/static_src/src/sass/public/components/_icon.scss
index 14a561720997471591651abd1f03e86fc1ac466c..ebe641709061b31ec9b684678528ec3c22931f23 100644
--- a/hypha/static_src/src/sass/public/components/_icon.scss
+++ b/hypha/static_src/src/sass/public/components/_icon.scss
@@ -13,12 +13,6 @@
         fill: $color--white;
     }
 
-    &--footer-social {
-        @include media-query(tablet-portrait) {
-            margin-right: 10px;
-        }
-    }
-
     &--mobile-menu {
         width: 32px;
         height: 28px;
diff --git a/hypha/static_src/src/sass/public/layout/_header.scss b/hypha/static_src/src/sass/public/layout/_header.scss
index c360d3eb9139c4f220f2b98f3d0003cfb2715479..d3b179044d570ccfb5cc8d513a3fbfb500bdedf5 100644
--- a/hypha/static_src/src/sass/public/layout/_header.scss
+++ b/hypha/static_src/src/sass/public/layout/_header.scss
@@ -170,24 +170,14 @@
     }
 
     &__logo {
-        fill: $color--white;
+        max-width: none;
 
         &--mobile {
             width: 60px;
-            height: 60px;
 
             @include media-query(tablet-landscape) {
                 display: none;
             }
-
-            .header--light-bg & {
-                fill: $color--dark-grey;
-            }
-
-            // stylelint-disable-next-line selector-class-pattern
-            .header__menus--mobile.is-visible & {
-                fill: $color--white;
-            }
         }
 
         &--desktop {
@@ -196,27 +186,6 @@
             @include media-query(tablet-landscape) {
                 display: block;
                 width: 215px;
-                height: 50px;
-            }
-        }
-
-        &--desktop-light {
-            @include media-query(tablet-landscape) {
-                display: block;
-            }
-
-            .header--light-bg & {
-                display: none;
-            }
-        }
-
-        &--desktop-dark {
-            display: none;
-
-            .header--light-bg & {
-                @include media-query(tablet-landscape) {
-                    display: block;
-                }
             }
         }
     }
diff --git a/hypha/templates/base-apply.html b/hypha/templates/base-apply.html
index a8bc8e1c8de26191efddf214149f4cacd5ad58c9..5090295df7fc2e2aaaf0190d59db5add98a8137e 100644
--- a/hypha/templates/base-apply.html
+++ b/hypha/templates/base-apply.html
@@ -1,13 +1,14 @@
 {% load i18n static wagtailcore_tags wagtailimages_tags navigation_tags util_tags hijack cookieconsent_tags %}<!doctype html>
 {% wagtail_site as current_site %}
 {% get_current_language as LANGUAGE_CODE %}
-<html class="no-js" lang="{{ LANGUAGE_CODE }}">
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+<html class="no-js" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
     <head>
         {# TODO fallbacks if page is not defined e.g. for 404 page #}
-        <meta charset="utf-8" />
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width">
         <title>{% block title_prefix %}{% if current_site.site_name %}{{ current_site.site_name }} | {% endif %}{% endblock %}{% block title %}{% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %}{% endblock %}{% block title_suffix %}{{ TITLE_SUFFIX }}{% endblock %}</title>
-        <meta name="description" content="{% if page.search_description %}{{ page.search_description }}{% else %}{{ page.listing_summary }}{% endif %}" />
+        <meta name="description" content="{% if page.search_description %}{{ page.search_description }}{% else %}{{ page.listing_summary }}{% endif %}">
 
         <!-- favicons -->
         {% comment %}
@@ -90,24 +91,36 @@
                     {% if settings.utils.SystemMessagesSettings.site_logo_default %}
                         {% image settings.utils.SystemMessagesSettings.site_logo_default width-215 as logo_default %}
                         <img class="header__logo header__logo--desktop"
+                             width="215"
                              src="{{ logo_default.url }}"
                              alt="{{ settings.utils.SystemMessagesSettings.site_logo_default.alt }}"
                         >
                         {% if settings.utils.SystemMessagesSettings.site_logo_mobile %}
                             {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %}
                             <img class="header__logo header__logo--mobile"
+                                 width="60"
                                  src="{{ logo_mobile.url }}"
                                  alt="{{ settings.utils.SystemMessagesSettings.site_logo_mobile.alt }}"
                             >
                         {% else %}
                             <img class="header__logo header__logo--mobile"
+                                 width="60"
                                  src="{{ logo_default.url }}"
                                  alt="{{ settings.utils.SystemMessagesSettings.site_logo_default.alt }}"
                             >
                         {% endif %}
                     {% else %}
-                        <svg class="header__logo header__logo--desktop" aria-label="Site Logo"><use xlink:href="#logo-desktop--dark"></use></svg>
-                        <svg class="header__logo header__logo--mobile" aria-label="Site Logo"><use xlink:href="#logo-mobile"></use></svg>
+                        <img class="header__logo header__logo--desktop"
+                             width="215"
+                             height="40"
+                             src="{% static 'images/logo.png' %}"
+                             alt="Hypha logo"
+                        >
+                        <img class="header__logo header__logo--mobile"
+                             width="60" height="60"
+                             src="{% static 'images/logo-small.png' %}"
+                             alt="Hypha logo"
+                        >
                     {% endif %}
                 </a>
 
@@ -131,9 +144,18 @@
                         <a href="{{ settings.utils.SystemMessagesSettings.site_logo_link|default:"/" }}" aria-label="Home link">
                             {% if settings.utils.SystemMessagesSettings.site_logo_mobile %}
                                 {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %}
-                                <img class="header__logo header__logo--mobile" src="{{ logo_mobile.url }}">
+                                <img class="header__logo header__logo--mobile"
+                                     width="60"
+                                     src="{{ logo_mobile.url }}"
+                                     alt="{{ settings.utils.SystemMessagesSettings.site_logo_mobile.alt }}"
+                                >
                             {% else %}
-                                <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg>
+                                <img class="header__logo header__logo--mobile"
+                                     width="60"
+                                     height="60"
+                                     src="{% static 'images/logo-small.png' %}"
+                                     alt="Hypha logo"
+                                >
                             {% endif %}
                         </a>
                         <div class="header__inner header__inner--mobile-buttons">
diff --git a/hypha/templates/base.html b/hypha/templates/base.html
index afbd06535640956ed3e7ed57925d8be57297593f..7f782c7192993c4d54fc88380ecdde9584d695b7 100644
--- a/hypha/templates/base.html
+++ b/hypha/templates/base.html
@@ -1,13 +1,14 @@
 {% load static cache wagtailcore_tags wagtailimages_tags navigation_tags util_tags cookieconsent_tags i18n %}<!doctype html>
 {% wagtail_site as current_site %}
 {% get_current_language as LANGUAGE_CODE %}
-<html class="no-js" lang="{{ LANGUAGE_CODE }}">
+{% get_current_language_bidi as LANGUAGE_BIDI %}
+<html class="no-js" lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
     <head>
         {# TODO fallbacks if page is not defined e.g. for 404 page #}
-        <meta charset="utf-8" />
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width">
         <title>{% block title_prefix %}{% if current_site.site_name %}{{ current_site.site_name }} | {% endif %}{% endblock %}{% block title %}{% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %}{% endblock %}{% block title_suffix %}{{ TITLE_SUFFIX }}{% endblock %}</title>
-        <meta name="description" content="{% if page.search_description %}{{ page.search_description }}{% else %}{{ page.listing_summary }}{% endif %}" />
+        <meta name="description" content="{% if page.search_description %}{{ page.search_description }}{% else %}{{ page.listing_summary }}{% endif %}">
         {% block feedlinks %}{% endblock %}
 
         <!-- favicons -->
@@ -27,29 +28,13 @@
 
         <!-- Twitter summary card - see https://dev.twitter.com/cards/types/summary -->
         <!--  and https://dev.twitter.com/cards/getting-started -->
-        <meta name="twitter:card" content="summary" />
-        <meta name="twitter:site" content="@{{ settings.utils.SocialMediaSettings.twitter_handle }}" />
-        <meta name="twitter:title" content="{{ page.title }}" />
-        <meta name="twitter:description" content="{{ page|social_text:current_site }}">
-        {% if page.social_image  %}
-            {% image page.social_image width-320 as social_img %}
-            <meta name="twitter:image" content="{% if page.social_image.is_stored_locally %}{{ current_site.root_url }}{% endif %}{{ social_img.url }}">
-        {% else %}
-            <meta name="twitter:image" content="{{ current_site.root_url }}{% static 'images/otf_social.jpg' %}">
-        {% endif %}
+        <meta name="twitter:card" content="summary">
+        <meta name="twitter:title" content="{{ page.title }}">
 
         <!--facebook opengraph tags-->
-        <meta property="fb:app_id" content="{{ settings.utils.SocialMediaSettings.facebook_app_id }}" />
-        <meta property="og:type" content="website" />
-        <meta property="og:url" content="{{ page.url }}" />
-        <meta property="og:title" content="{{ page.title }}" />
-        {% if page.social_image %}
-            <meta property="og:image" content="{% if page.social_image.is_stored_locally %}{{ current_site.root_url }}{% endif %}{{ social_img.url }}" />
-        {% else %}
-            <meta property="og:image" content="{{ current_site.root_url }}{% static 'images/otf_social.jpg' %}" />
-        {% endif %}
-        <meta property="og:description" content="{{ page|social_text:current_site }}" />
-        <meta property="og:site_name" content="{{ settings.utils.SocialMediaSettings.site_name }}" />
+        <meta property="og:type" content="website">
+        <meta property="og:url" content="{{ page.url }}">
+        <meta property="og:title" content="{{ page.title }}">
 
         <link rel="stylesheet" href="{% static 'css/normalize.css' %}">
         <link rel="stylesheet" href="{% static 'css/public/main.css' %}">
@@ -89,25 +74,41 @@
                     <a href="{{ settings.utils.SystemMessagesSettings.site_logo_link|default:"/" }}" aria-label="Home link">
                         {% if settings.utils.SystemMessagesSettings.site_logo_default %}
                             {% image settings.utils.SystemMessagesSettings.site_logo_default width-215 as logo_default %}
-                            <img class="header__logo header__logo--desktop" src="{{ logo_default.url }}">
+                            <img class="header__logo header__logo--desktop"
+                                 width="215"
+                                 src="{{ logo_default.url }}"
+                                 alt="{{ settings.utils.SystemMessagesSettings.site_logo_default.alt }}"
+                            >
                             {% if settings.utils.SystemMessagesSettings.site_logo_mobile %}
                                 {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %}
-                                <img class="header__logo header__logo--mobile" src="{{ logo_mobile.url }}">
+                                <img class="header__logo header__logo--mobile"
+                                     width="60"
+                                     src="{{ logo_mobile.url }}"
+                                     alt="{{ settings.utils.SystemMessagesSettings.site_logo_mobile.alt }}"
+                                >
                             {% else %}
-                                <img class="header__logo header__logo--mobile" src="{{ logo_default.url }}">
+                                <img class="header__logo header__logo--mobile"
+                                     width="60"
+                                     src="{{ logo_default.url }}"
+                                     alt="{{ settings.utils.SystemMessagesSettings.site_logo_default.alt }}"
+                                >
                             {% endif %}
                         {% else %}
-                            <svg class="header__logo header__logo--desktop header__logo--desktop-light"><use xlink:href="#logo-desktop"></use></svg>
-                            <svg class="header__logo header__logo--desktop header__logo--desktop-dark"><use xlink:href="#logo-desktop--dark"></use></svg>
-                            <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg>
+                            <img class="header__logo header__logo--desktop"
+                                 width="215"
+                                 height="40"
+                                 src="{% static 'images/logo.png' %}"
+                                 alt="Hypha logo"
+                            >
+                            <img class="header__logo header__logo--mobile"
+                                 width="60" height="60"
+                                 src="{% static 'images/logo-small.png' %}"
+                                 alt="Hypha logo"
+                            >
                         {% endif %}
                     </a>
 
                     <div class="header__inner header__inner--mobile-buttons">
-                        <button class="button js-search-toggle" aria-haspopup="true">
-                            <svg class="header__icon header__icon--open-search header__icon--open-search-menu-closed icon icon--mobile-menu"><use xlink:href="#magnifying-glass"></use></svg>
-                            <svg class="header__icon header__icon--close-search header__icon--close-search-menu-closed icon icon--mobile-menu"><use xlink:href="#cross"></use></svg>
-                        </button>
                         <button class="button button--left-space js-mobile-menu-toggle" aria-haspopup="true">
                             <svg class="icon icon--mobile-menu"><use xlink:href="#mobile-menu-toggle"></use></svg>
                         </button>
@@ -121,10 +122,6 @@
                                 {% primarynav %}
                             {% endcache %}
                         {% endif %}
-                        <button class="button button--contains-icons button--left-space js-search-toggle" aria-haspopup="true" aria-label="Toggle desktop search">
-                            <svg class="header__icon header__icon--open-search icon"><use xlink:href="#magnifying-glass"></use></svg>
-                            <svg class="header__icon header__icon--close-search icon"><use xlink:href="#cross"></use></svg>
-                        </button>
                     </section>
 
                     <section class="header__menus header__menus--mobile">
@@ -132,19 +129,20 @@
                             <a href="{{ settings.utils.SystemMessagesSettings.site_logo_link|default:"/" }}" aria-label="Home link">
                                 {% if settings.utils.SystemMessagesSettings.site_logo_mobile %}
                                     {% image settings.utils.SystemMessagesSettings.site_logo_mobile width-60 as logo_mobile %}
-                                    <img class="header__logo header__logo--mobile" src="{{ logo_mobile.url }}">
+                                    <img class="header__logo header__logo--mobile"
+                                         width="60"
+                                         src="{{ logo_mobile.url }}"
+                                         alt="{{ settings.utils.SystemMessagesSettings.site_logo_mobile.alt }}"
+                                    >
                                 {% else %}
-                                    <svg class="header__logo header__logo--mobile"><use xlink:href="#logo-mobile"></use></svg>
+                                    <img class="header__logo header__logo--mobile"
+                                         width="60"
+                                         height="60"
+                                         src="{% static 'images/logo-small.png' %}"
+                                         alt="Hypha logo"
+                                    >
                                 {% endif %}
                             </a>
-                            <div class="header__inner header__inner--mobile-buttons">
-                                <button class="button js-mobile-search-toggle" aria-haspopup="true" aria-label="Toggle mobile search">
-                                    <svg class="header__icon header__icon--open-search icon icon--mobile-menu"><use xlink:href="#magnifying-glass"></use></svg>
-                                </button>
-                                <button class="button button--left-space js-mobile-menu-close">
-                                    <svg class="header__icon header__icon--cross icon icon--mobile-menu"><use xlink:href="#cross"></use></svg>
-                                </button>
-                            </div>
                         </div>
                         {% if settings.utils.SystemMessagesSettings.nav_content %}
                             {{ settings.utils.SystemMessagesSettings.nav_content|safe }}
@@ -170,15 +168,6 @@
                     </div>
                 </div>
 
-                <div class="header__search">
-                    <form action="{{ PUBLIC_SITE.root_url }}{% url 'search' %}" method="get" role="search" class="form form--header-search-desktop">
-                        <button class="button" type="submit" aria-label="Search">
-                            <svg class="icon icon--magnifying-glass icon--search"><use xlink:href="#magnifying-glass"></use></svg>
-                        </button>
-                        <input class="input input--transparent input--secondary" type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}{% endif %}" aria-label="Search input">
-                    </form>
-                </div>
-
                 <div class="wrapper wrapper--small wrapper--page-title">
                     {% block breadcrumbs %}
                         {% include "navigation/breadcrumbs.html" %}
@@ -196,25 +185,10 @@
 
         <footer class="footer">
             <div class="grid grid--two wrapper wrapper--large">
-                {% if newsletter_enabled %}
-                    <div class="footer__inner">
-                        {% include "mailchimp/newsletter_signup.html" %}
-                    </div>
-                {% endif %}
-
                 <div class="footer__inner">
-                    <div class="footer__social-links">
-                        {% if settings.utils.SocialMediaSettings.twitter_handle %}
-                            <a href="https://twitter.com/{{ settings.utils.SocialMediaSettings.twitter_handle }}">
-                                <svg class="icon icon--footer-social"><use xlink:href="#twitter"></use></svg>
-                                <h4 class="heading heading--no-margin">@{{ settings.utils.SocialMediaSettings.twitter_handle }}</h4>
-                            </a>
-                        {% endif %}
-                    </div>
                     {{ settings.utils.SystemMessagesSettings.footer_content|safe }}
                 </div>
             </div>
-
         </footer>
 
         {% cookie_banner %}
diff --git a/hypha/templates/includes/share.html b/hypha/templates/includes/share.html
deleted file mode 100644
index fa189d7f2f03c42ee5c4b3d8c8263bdf7377328a..0000000000000000000000000000000000000000
--- a/hypha/templates/includes/share.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{% load i18n util_tags wagtailcore_tags wagtailimages_tags %}
-{% wagtail_site as current_site %}
-<section class="section section--share">
-    <h5>{% trans "Share" %}</h5>
-    {% image page.social_image fill-150x150 as social_img %}
-    {% with settings.utils.SocialMediaSettings as social_media_settings %}
-        <!-- see https://dev.twitter.com/web/tweet-button/web-intent -->
-        <a href="https://twitter.com/intent/tweet?text={{ page|social_text:current_site|urlencode }}&amp;url={{ page.full_url|urlencode }}&amp;via={{ social_media_settings.twitter_handle|urlencode }}" title="{% trans "Share on Twitter" %}">
-            <svg class="icon icon--social-share icon--twitter-share"><use xlink:href="#twitter"></use></svg>
-        </a>
-
-        <!-- see https://developer.linkedin.com/docs/share-on-linkedin -->
-        <a href="https://www.linkedin.com/shareArticle?mini=true&amp;url={{ page.full_url|urlencode }}&amp;title={{ page.title|urlencode }}&amp;summary={{ page|social_text:current_site|urlencode }}&amp;source={{ social_media_settings.site_name|urlencode }}"
-           title="{% trans "Share on LinkedIn" %}">
-            <svg class="icon icon--social-share icon--linkedin-share"><use xlink:href="#linkedin"></use></svg>
-        </a>
-
-        <!-- see https://developers.facebook.com/docs/sharing/reference/feed-dialog/v2.5 -->
-        <!-- Add a default image to use for social sharing here in case one is not provided on the page. -->
-        {% if social_media_settings.facebook_app_id %}
-            <a href="https://www.facebook.com/dialog/feed?app_id={{ social_media_settings.facebook_app_id }}&amp;link={{ page.full_url|urlencode }}&amp;picture={% if social_img %}{{ 'http://'|add:current_site.hostname|add:social_img.url|urlencode }}{% endif %}&amp;name={{ page.title|urlencode }}&amp;description={{ page|social_text:current_site|urlencode }}&amp;redirect_uri={{ page.full_url|urlencode }}" title="{% trans "Share on Facebook" %}">
-                <svg class="icon icon--social-share icon--facebook-share"><use xlink:href="#facebook"></use></svg>
-            </a>
-        {% endif %}
-    {% endwith %}
-</section>
diff --git a/hypha/templates/includes/sprites.html b/hypha/templates/includes/sprites.html
index 1087051313a55f766558e55cf6fc0566bf4f8555..07d4aab9aaef7ea2457583286c63f07914c3adb3 100644
--- a/hypha/templates/includes/sprites.html
+++ b/hypha/templates/includes/sprites.html
@@ -4,14 +4,6 @@
         <path d="M9.412 17.824C4.772 17.824 1 14.058 1 9.412 1 4.765 4.773 1 9.412 1c4.639 0 8.412 3.774 8.412 8.412 0 4.638-3.776 8.412-8.412 8.412zm0-15.142c-3.71 0-6.73 3.02-6.73 6.73 0 3.71 3.02 6.73 6.73 6.73 3.71 0 6.73-3.02 6.73-6.73 0-3.71-3.02-6.73-6.73-6.73zM21.902 23l-5.373-5.364 1.099-1.107L23 21.894z" />
     </symbol>
 
-    <symbol id="twitter" viewBox="0 0 20.56 18.88">
-        <path d="M20.56,2.24A7.69,7.69,0,0,1,18.14,3,4.71,4.71,0,0,0,20,.35,7.86,7.86,0,0,1,17.31,1.5,4,4,0,0,0,14.23,0,4.52,4.52,0,0,0,10,4.77a5.32,5.32,0,0,0,.11,1.09,11.58,11.58,0,0,1-8.69-5,5.24,5.24,0,0,0-.57,2.4,5,5,0,0,0,1.88,4,3.86,3.86,0,0,1-1.91-.6V6.7a4.66,4.66,0,0,0,3.38,4.67,3.67,3.67,0,0,1-1.11.17,3.72,3.72,0,0,1-.79-.08,4.32,4.32,0,0,0,3.94,3.31,7.87,7.87,0,0,1-5.24,2,7.51,7.51,0,0,1-1-.07,11,11,0,0,0,6.47,2.14c7.76,0,12-7.26,12-13.56,0-.21,0-.41,0-.62a9.25,9.25,0,0,0,2.1-2.47"/>
-    </symbol>
-
-    <symbol id="facebook" viewBox="0 0 8.95 20.95">
-        <path d="M0,6.93H1.92V4.82a6.24,6.24,0,0,1,.62-3.25A3.29,3.29,0,0,1,5.51,0,10.75,10.75,0,0,1,8.95.39L8.47,3.6a5.8,5.8,0,0,0-1.54-.26c-.75,0-1.41.3-1.41,1.14V6.93H8.57l-.21,3.13H5.51V21H1.92V10.06H0Z"/>
-    </symbol>
-
     <symbol id="home" viewBox="0 0 512 512">
         <g><path d="M506.555,208.064L263.859,30.367c-4.68-3.426-11.038-3.426-15.716,0L5.445,208.064 c-5.928,4.341-7.216,12.665-2.875,18.593s12.666,7.214,18.593,2.875L256,57.588l234.837,171.943c2.368,1.735,5.12,2.57,7.848,2.57 c4.096,0,8.138-1.885,10.744-5.445C513.771,220.729,512.483,212.405,506.555,208.064z"/></g>
         <g><path d="M442.246,232.543c-7.346,0-13.303,5.956-13.303,13.303v211.749H322.521V342.009c0-36.68-29.842-66.52-66.52-66.52 s-66.52,29.842-66.52,66.52v115.587H83.058V245.847c0-7.347-5.957-13.303-13.303-13.303s-13.303,5.956-13.303,13.303v225.053 c0,7.347,5.957,13.303,13.303,13.303h133.029c6.996,0,12.721-5.405,13.251-12.267c0.032-0.311,0.052-0.651,0.052-1.036v-128.89 c0-22.009,17.905-39.914,39.914-39.914s39.914,17.906,39.914,39.914v128.89c0,0.383,0.02,0.717,0.052,1.024 c0.524,6.867,6.251,12.279,13.251,12.279h133.029c7.347,0,13.303-5.956,13.303-13.303V245.847  C455.549,238.499,449.593,232.543,442.246,232.543z"/></g>
@@ -29,6 +21,25 @@
         <path d="M1 5.72222L6.5 11L15 1" stroke="black" stroke-width="2"/>
     </svg>
 
+    <symbol id="dashboard-contract" viewBox="0 0 24 24" fill="none">
+        <path d="M12.8105 12.7789C14.0035 11.9649 14.9404 11.0667 15.6211 10.0842C16.3018 9.10175 16.6421 8.11228 16.6421 7.11579C16.6421 6.52632 16.5439 6.07018 16.3474 5.74737C16.1509 5.42456 15.8842 5.26316 15.5474 5.26316C14.7474 5.26316 14.0667 5.85965 13.5053 7.05263C12.9439 8.24561 12.6632 9.64912 12.6632 11.2632C12.6632 11.5439 12.6737 11.8105 12.6947 12.0632C12.7158 12.3158 12.7544 12.5544 12.8105 12.7789ZM3.84211 20V18.7368H5.10526V20H3.84211ZM7.31579 20V18.7368H8.57895V20H7.31579ZM10.7895 20V18.7368H12.0526V20H10.7895ZM14.2632 20V18.7368H15.5263V20H14.2632ZM17.7368 20V18.7368H19V20H17.7368ZM3.88421 16.5474L3 15.6632L4.34737 14.3158L3 12.9684L3.88421 12.0842L5.23158 13.4316L6.57895 12.0842L7.46316 12.9684L6.11579 14.3158L7.46316 15.6632L6.57895 16.5474L5.23158 15.2L3.88421 16.5474ZM14.3263 15.7895C13.8772 15.7895 13.4737 15.6912 13.1158 15.4947C12.7579 15.2982 12.4526 15.0105 12.2 14.6316C11.8491 14.8281 11.4877 15.014 11.1158 15.1895C10.7439 15.3649 10.3544 15.5298 9.94737 15.6842L9.50526 14.5053C9.89825 14.3509 10.2737 14.193 10.6316 14.0316C10.9895 13.8702 11.3368 13.6912 11.6737 13.4947C11.5754 13.1719 11.5053 12.8246 11.4632 12.4526C11.4211 12.0807 11.4 11.6842 11.4 11.2632C11.4 9.21404 11.7895 7.49123 12.5684 6.09474C13.3474 4.69825 14.3404 4 15.5474 4C16.2632 4 16.8351 4.28772 17.2632 4.86316C17.6912 5.4386 17.9053 6.21053 17.9053 7.17895C17.9053 8.41404 17.5018 9.62105 16.6947 10.8C15.8877 11.9789 14.7614 13.0386 13.3158 13.9789C13.4561 14.1614 13.6105 14.2982 13.7789 14.3895C13.9474 14.4807 14.1298 14.5263 14.3263 14.5263C14.8175 14.5263 15.3193 14.3158 15.8316 13.8947C16.3439 13.4737 16.8246 12.8702 17.2737 12.0842L18.4316 12.6316C18.3614 12.9965 18.3228 13.3193 18.3158 13.6C18.3088 13.8807 18.3263 14.2035 18.3684 14.5684C18.5649 14.4702 18.7719 14.3404 18.9895 14.1789C19.207 14.0175 19.414 13.8175 19.6105 13.5789L20.6211 14.3579C20.2702 14.793 19.8842 15.1404 19.4632 15.4C19.0421 15.6596 18.6491 15.7895 18.2842 15.7895C17.9474 15.7895 17.6737 15.6702 17.4632 15.4316C17.2526 15.193 17.1263 14.8491 17.0842 14.4C16.6772 14.8491 16.2386 15.193 15.7684 15.4316C15.2982 15.6702 14.8175 15.7895 14.3263 15.7895Z" fill="black"/>
+    </symbol>
+
+    <symbol id="dashboard-paf" viewBox="0 0 24 24" fill="none" >
+        <path d="M4.5 22C4.0875 22 3.73438 21.8531 3.44063 21.5594C3.14688 21.2656 3 20.9125 3 20.5V5.5C3 5.0875 3.14688 4.73438 3.44063 4.44062C3.73438 4.14688 4.0875 4 4.5 4H9.625C9.70833 3.41667 9.975 2.9375 10.425 2.5625C10.875 2.1875 11.4 2 12 2C12.6 2 13.125 2.1875 13.575 2.5625C14.025 2.9375 14.2917 3.41667 14.375 4H19.5C19.9125 4 20.2656 4.14688 20.5594 4.44062C20.8531 4.73438 21 5.0875 21 5.5V20.5C21 20.9125 20.8531 21.2656 20.5594 21.5594C20.2656 21.8531 19.9125 22 19.5 22H4.5ZM4.5 20.5H19.5V5.5H4.5V20.5ZM7 18H13.825V16.5H7V18ZM7 13.75H17V12.25H7V13.75ZM7 9.5H17V8H7V9.5ZM12 5.075C12.2333 5.075 12.4375 4.9875 12.6125 4.8125C12.7875 4.6375 12.875 4.43333 12.875 4.2C12.875 3.96667 12.7875 3.7625 12.6125 3.5875C12.4375 3.4125 12.2333 3.325 12 3.325C11.7667 3.325 11.5625 3.4125 11.3875 3.5875C11.2125 3.7625 11.125 3.96667 11.125 4.2C11.125 4.43333 11.2125 4.6375 11.3875 4.8125C11.5625 4.9875 11.7667 5.075 12 5.075Z" fill="black"/>
+    </symbol>
+
+    <symbol id="dashboard-document" viewBox="0 0 24 24" fill="none">
+        <path d="M5.5 22C5.1 22 4.75 21.85 4.45 21.55C4.15 21.25 4 20.9 4 20.5V3.5C4 3.1 4.15 2.75 4.45 2.45C4.75 2.15 5.1 2 5.5 2H14.525L20 7.475V20.5C20 20.9 19.85 21.25 19.55 21.55C19.25 21.85 18.9 22 18.5 22H5.5ZM13.775 8.15V3.5H5.5V20.5H18.5V8.15H13.775Z" fill="black"/>
+    </symbol>
+
+    <symbol id="dashboard-invoice" viewBox="0 0 24 24" fill="none">
+        <path d="M11.25 18.975H12.75V17.975H14.25C14.4625 17.975 14.6406 17.9031 14.7844 17.7594C14.9281 17.6156 15 17.4375 15 17.225V13.975C15 13.7625 14.9281 13.5844 14.7844 13.4406C14.6406 13.2969 14.4625 13.225 14.25 13.225H10.5V11.475H15V9.975H12.75V8.975H11.25V9.975H9.75C9.5375 9.975 9.35938 10.0469 9.21563 10.1906C9.07188 10.3344 9 10.5125 9 10.725V13.975C9 14.1875 9.07188 14.3656 9.21563 14.5094C9.35938 14.6531 9.5375 14.725 9.75 14.725H13.5V16.475H9V17.975H11.25V18.975ZM5.5 22C5.1 22 4.75 21.85 4.45 21.55C4.15 21.25 4 20.9 4 20.5V3.5C4 3.1 4.15 2.75 4.45 2.45C4.75 2.15 5.1 2 5.5 2H14.525L20 7.475V20.5C20 20.9 19.85 21.25 19.55 21.55C19.25 21.85 18.9 22 18.5 22H5.5ZM13.275 7.475V3.5H5.5V20.5H18.5V7.475H13.275Z" fill="black"/>
+    </symbol>
+
+    <symbol id="dashboard-report" viewBox="0 0 24 24" fill="none">
+        <path d="M4 19V17.5H13.65V19H4ZM4 14.825V13.325H20V14.825H4ZM4 10.675V9.175H20V10.675H4ZM4 6.5V5H20V6.5H4Z" fill="black"/>
+    </symbol>
 
     <symbol id="hero-standard-right-pixels" viewBox="0 0 326 333">
         <g fill-rule="nonzero">
@@ -127,10 +138,6 @@
         <path d="M41.09 10.45l-2.77-3.36c-.56-.66-1.39-1.09-2.32-1.09h-24c-.93 0-1.76.43-2.31 1.09l-2.77 3.36c-.58.7-.92 1.58-.92 2.55v25c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-25c0-.97-.34-1.85-.91-2.55zm-17.09 24.55l-11-11h7v-4h8v4h7l-11 11zm-13.75-25l1.63-2h24l1.87 2h-27.5z"/><path d="M0 0h48v48h-48z" fill="none"/>
     </symbol>
 
-    <symbol id="linkedin" viewBox="0 0 17 16" >
-        <path d="M17 9.81V16h-3.644v-5.776c0-1.45-.527-2.441-1.846-2.441-1.006 0-1.605.667-1.87 1.313-.095.23-.12.552-.12.875V16H5.875s.05-9.782 0-10.796H9.52v1.53l-.024.035h.024v-.035c.484-.734 1.349-1.783 3.284-1.783C15.202 4.95 17 6.494 17 9.81zM2.062 0C.816 0 0 .806 0 1.865 0 2.9.792 3.73 2.014 3.73h.024c1.272 0 2.062-.83 2.062-1.866C4.076.805 3.31 0 2.062 0zM.216 16H3.86V5.204H.216V16z" fill-rule="nonzero" />
-    </symbol>
-
     <symbol id="bell-icon" viewBox="0 0 16 16">
         <path d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2zm.995-14.901a1 1 0 1 0-1.99 0A5.002 5.002 0 0 0 3 6c0 1.098-.5 6-2 7h14c-1.5-1-2-5.902-2-7 0-2.42-1.72-4.44-4.005-4.901z"/>
     </symbol>
diff --git a/mkdocs.yml b/mkdocs.yml
index 5607f87c902120705ffbbb6b0661a874e83cbac4..6ce436846505dd8ecd50e81835e7b8feef092ea7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,7 +1,7 @@
 site_name: Hypha Documentation
 site_url: https://docs.hypha.app/
 site_description: "Documentation for Hypha, an open source submission management platform."
-copyright: Copyright &copy; 2018 - 2023 - Open Technology Fund
+copyright: Copyright &copy; 2018 - 2024 - Open Technology Fund
 repo_name: HyphaApp/hypha
 repo_url: https://github.com/HyphaApp/hypha
 edit_uri: edit/main/docs/
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 27e4a42a0212e338bb1c3bff8ea9bfe8305735a7..c4f1ca7c91fd65cf60604655236129b9226ba79d 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,6 +1,5 @@
 -r requirements.txt
 
-black==23.11.0
 coverage==7.3.2
 django-browser-reload==1.12.1
 django-coverage-plugin==3.1.0
diff --git a/requirements.txt b/requirements.txt
index 6ad03d0af605b36afb32626d2a50ab3666e1b752..7abfb72c2eaf0c2f2a8dba98e7463815e13d2aec 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,6 @@ scout-apm==2.26.1
 sentry-sdk==1.16.0
 
 # Production dependencies
-bleach==5.0.0
 Babel==2.13.1
 boto3==1.28.82
 celery==5.2.7
@@ -11,7 +10,7 @@ click==8.1.7
 dj-database-url==2.1.0
 django-anymail==10.2
 django-basic-auth-ip-whitelist==0.5
-django-bleach==3.0.1
+django-bleach==3.1.0
 django-countries==7.5.1
 django-elevate==2.0.3
 django-extensions==3.2.3
@@ -34,7 +33,7 @@ django-tables2==2.5.1
 django-tinymce==3.5.0
 django-two-factor-auth==1.15.5
 django-web-components==0.1.1
-django==4.2.8
+django==4.2.9
 djangorestframework-api-key==2.3.0
 djangorestframework==3.14.0
 drf-nested-routers==0.93.4
@@ -45,7 +44,6 @@ heroicons==2.5.0
 python-docx<1.0.0
 htmldocx==0.0.6
 lark==1.1.8
-mailchimp3==3.0.17
 mistune==3.0.1
 more-itertools==10.1.0
 phonenumberslite==8.13.26