Repository: oppia/oppia

Issue:
[Feature Request]: Updated footer with added donation and newsletter entrypoints)
### Overview
The goal of this issue is to update Oppia's footer. I have been researching how to improve Oppia's donation user flow and my competitive audit suggests some improvement opportunities for Oppia.

Many nonprofit's footers include the following:

- The org's mission statement
- Newsletter sign up forms
- Entry points to both volunteer and donation pages

As seen below, our current footer lacks all of those elements
<img width="1470" alt="Screenshot 2023-07-24 at 11 15 30 AM" src="https://github.com/oppia/oppia/assets/11729306/c12b9bd6-518d-417b-80a7-14be072367b0">


###  The Solution

Refer to the following figma file for the new, updated web and mobile footers:
https://www.figma.com/file/O3mJt26w9CGOnSKlUS4RXy/Oppia-Footer-and-Newsletter?type=design&node-id=51%3A168&mode=design&t=qjPX9toSTsLKC6wW-1

You can also view it here:
<img width="748" alt="Screenshot 2023-07-24 at 11 18 04 AM" src="https://github.com/oppia/oppia/assets/11729306/0917f275-bacb-4797-bb61-40315cbef4cd">

The footer columns are mostly the same, with a few changes. 

The updated footer has Oppia’s mission statement in a column that includes links to donate and volunteer. 

The columns “About Oppia”, Teach/Learn” and Follow us” are still the same. The “Contribute” column was removed because the content was spread out into different areas. Donate and volunteer moved to the missions statement and “Terms of service” and Privacy Policy” was moved to a row below the columns to create a hierarchy of information. Site Feedback is moved to the About Oppia” column. 

### Describe alternatives you've considered

n/a

### Additional context

This project has been reviewed by our UX writing team. It also adds more entry points to our donation page and volunteer page.
[Feature Request]: Updated footer with added donation and newsletter entrypoints)
### Overview
The goal of this issue is to update Oppia's footer. I have been researching how to improve Oppia's donation user flow and my competitive audit suggests some improvement opportunities for Oppia.

Many nonprofit's footers include the following:

- The org's mission statement
- Newsletter sign up forms
- Entry points to both volunteer and donation pages

As seen below, our current footer lacks all of those elements
<img width="1470" alt="Screenshot 2023-07-24 at 11 15 30 AM" src="https://github.com/oppia/oppia/assets/11729306/c12b9bd6-518d-417b-80a7-14be072367b0">


###  The Solution

Refer to the following figma file for the new, updated web and mobile footers:
https://www.figma.com/file/O3mJt26w9CGOnSKlUS4RXy/Oppia-Footer-and-Newsletter?type=design&node-id=51%3A168&mode=design&t=qjPX9toSTsLKC6wW-1

You can also view it here:
<img width="748" alt="Screenshot 2023-07-24 at 11 18 04 AM" src="https://github.com/oppia/oppia/assets/11729306/0917f275-bacb-4797-bb61-40315cbef4cd">

The footer columns are mostly the same, with a few changes. 

The updated footer has Oppia’s mission statement in a column that includes links to donate and volunteer. 

The columns “About Oppia”, Teach/Learn” and Follow us” are still the same. The “Contribute” column was removed because the content was spread out into different areas. Donate and volunteer moved to the missions statement and “Terms of service” and Privacy Policy” was moved to a row below the columns to create a hierarchy of information. Site Feedback is moved to the About Oppia” column. 

### Describe alternatives you've considered

n/a

### Additional context

This project has been reviewed by our UX writing team. It also adds more entry points to our donation page and volunteer page.


Discussion:
@seanlip @ConorMack  @prafulbbandre i would like to work on this. Should i follow the exact the design based on the provided figma as the standard one.
Hello, @ConorMack! I just need a little clarification. Is it necessary to always display the Google Play button? Additionally, I'm having trouble finding the second footer pattern you mentioned. Could you please guide me to the appropriate file?
@seanlip @ConorMack  @prafulbbandre i would like to work on this. Should i follow the exact the design based on the provided figma as the standard one.
Hello, @ConorMack! I just need a little clarification. Is it necessary to always display the Google Play button? Additionally, I'm having trouble finding the second footer pattern you mentioned. Could you please guide me to the appropriate file?

Resolving patch:
diff --git a/core/controllers/profile.py b/core/controllers/profile.py
--- a/core/controllers/profile.py
+++ b/core/controllers/profile.py
@@ -251,9 +251,9 @@ def put(self) -> None:
         """Handles PUT request."""
         assert self.normalized_payload is not None
         email = self.normalized_payload['email']
-        name = self.normalized_payload['name']
+        name = self.normalized_payload.get('name')
         tag = self.normalized_payload['tag']
-        status = user_services.add_user_to_mailing_list(email, name, tag)
+        status = user_services.add_user_to_mailing_list(email, tag, name=name)
         self.render_json({'status': status})
 
 
diff --git a/core/domain/user_services.py b/core/domain/user_services.py
--- a/core/domain/user_services.py
+++ b/core/domain/user_services.py
@@ -41,7 +41,8 @@
 import requests
 
 from typing import (
-    Dict, Final, List, Literal, Optional, Sequence, TypedDict, overload)
+    Dict, Final, List, Literal, Optional, Sequence, TypedDict,
+    Union, overload)
 
 MYPY = False
 if MYPY: # pragma: no cover
@@ -1744,21 +1745,23 @@ def record_user_created_an_exploration(user_id: str) -> None:
         save_user_settings(user_settings)
 
 
-def add_user_to_mailing_list(email: str, name: str, tag: str) -> bool:
+def add_user_to_mailing_list(
+    email: str,
+    tag: str,
+    name: Union[str, None]=None
+) -> bool:
     """Adds user to the bulk email provider with the relevant tag and required
     merge fields.
 
     Args:
         email: str. Email of the user.
-        name: str. Name of the user.
         tag: str. Tag for the mailing list.
+        name: str or None. Name of the user, or None if no name was supplied.
 
     Returns:
         bool. Whether the operation was successful or not.
     """
-    merge_fields = {
-        'NAME': name
-    }
+    merge_fields = {'NAME': name} if name is not None else {}
     return bulk_email_services.add_or_update_user_status(
         email, merge_fields, tag, can_receive_email_updates=True)
 
diff --git a/core/platform/bulk_email/mailchimp_bulk_email_services.py b/core/platform/bulk_email/mailchimp_bulk_email_services.py
--- a/core/platform/bulk_email/mailchimp_bulk_email_services.py
+++ b/core/platform/bulk_email/mailchimp_bulk_email_services.py
@@ -252,18 +252,20 @@ def add_or_update_user_status(
             'email_address': user_email,
             'status': 'subscribed',
             'tags': [tag],
-            'merge_fields': {
-                'NAME': merge_fields['NAME']
-            }
+            'merge_fields': {}
         }
         subscribed_mailchimp_data = {
             'email_address': user_email,
             'status': 'subscribed',
-            'merge_fields': {
-                'NAME': merge_fields['NAME']
-            }
+            'merge_fields': {}
         }
 
+        if 'NAME' in merge_fields and merge_fields['NAME'] is not None:
+            new_user_mailchimp_data['merge_fields']['NAME'] = (
+                merge_fields['NAME'])
+            subscribed_mailchimp_data['merge_fields']['NAME'] = (
+                merge_fields['NAME'])
+
     try:
         client.lists.members.get(
             feconf.MAILCHIMP_AUDIENCE_ID, subscriber_hash)


Test patch:
diff --git a/core/controllers/profile_test.py b/core/controllers/profile_test.py
--- a/core/controllers/profile_test.py
+++ b/core/controllers/profile_test.py
@@ -986,7 +986,8 @@ class MailingListSubscriptionHandlerTests(test_utils.GenericTestBase):
 
     def test_put_function(self) -> None:
         swap_add_fn = self.swap(
-            user_services, 'add_user_to_mailing_list', lambda *args: True)
+            user_services, 'add_user_to_mailing_list', lambda *args,
+            **kwargs: True)
 
         self.signup(self.VIEWER_EMAIL, self.VIEWER_USERNAME)
         self.login(self.VIEWER_EMAIL)
@@ -996,8 +997,8 @@ def test_put_function(self) -> None:
             json_response = self.put_json(
                 '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': 'Name',
-                    'tag': 'Web'
+                    'tag': 'Web',
+                    'name': 'Name'
                 }, csrf_token=csrf_token)
             self.assertEqual(json_response, {'status': True})
 
@@ -1017,8 +1018,8 @@ def raise_exception() -> None:
             self.put_json(
                 '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': 'Name',
-                    'tag': 'Web'
+                    'tag': 'Web',
+                    'name': 'Name'
                 }, csrf_token=csrf_token, expected_status_int=500)
 
         self.logout()
@@ -1035,22 +1036,22 @@ def test_invalid_inputs(self) -> None:
             self.put_json(
                 '/mailinglistsubscriptionhandler', {
                     'email': 'invalidemail.com',
-                    'name': 'Name',
-                    'tag': 'Web'
+                    'tag': 'Web',
+                    'name': 'Name'
                 }, csrf_token=csrf_token, expected_status_int=400)
 
             self.put_json(
                 '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': '',
-                    'tag': 'Web'
+                    'tag': 'Web',
+                    'name': ''
                 }, csrf_token=csrf_token, expected_status_int=400)
 
             self.put_json(
                 '/mailinglistsubscriptionhandler', {
                     'email': 'email@example.com',
-                    'name': 'Name',
-                    'tag': ''
+                    'tag': '',
+                    'name': 'Name'
                 }, csrf_token=csrf_token, expected_status_int=400)
 
         self.logout()
diff --git a/core/domain/user_services_test.py b/core/domain/user_services_test.py
--- a/core/domain/user_services_test.py
+++ b/core/domain/user_services_test.py
@@ -693,7 +693,28 @@ def _mock_add_or_update_user_status(
         with fn_swap:
             self.assertTrue(
                 user_services.add_user_to_mailing_list(
-                    'email@example.com', 'Name', 'Android'))
+                    'email@example.com', 'Android', name='Name'))
+
+    def test_add_user_to_mailing_list_no_name(self) -> None:
+        def _mock_add_or_update_user_status(
+            unused_email: str,
+            merge_fields: Dict[str, str],
+            unused_tag: str,
+            *,
+            can_receive_email_updates: bool
+        ) -> bool:
+            """Mocks bulk_email_services.add_or_update_user_status()."""
+            # 'NAME' key is not present in merge_fields.
+            self.assertNotIn('NAME', merge_fields)
+            return can_receive_email_updates
+
+        fn_swap = self.swap(
+            bulk_email_services, 'add_or_update_user_status',
+            _mock_add_or_update_user_status)
+        with fn_swap:
+            self.assertTrue(
+                user_services.add_user_to_mailing_list(
+                    'email@example.com', 'Android'))
 
     def test_set_and_get_user_email_preferences(self) -> None:
         auth_id = 'someUser'