Compare commits

..

22 Commits

Author SHA1 Message Date
ayesha waris
2bee6f38d7 refactor: moved hardcoded support urls to constants (#1260)
Co-authored-by: Ayesha Waris <ayesha.waris@192.168.1.31>
2025-06-03 18:01:45 +05:00
Awais Ansari
98e515497d fix: scrolling issue for active menu item 2025-04-15 20:09:42 +05:00
Awais Ansari
168fdb8f8d chore: updated package-lock file 2025-04-15 17:35:45 +05:00
Awais Ansari
41de736e2d test: updated test case snapshots 2025-04-15 17:20:15 +05:00
Hassan Raza
cfc9183dc5 fix: Update course preference title for ora submission (#1222) 2025-04-15 14:29:01 +05:00
sundasnoreen12
58e6521137 test: added test cases 2025-04-15 14:29:01 +05:00
sundasnoreen12
f14fef6b12 refactor: refactor code 2025-04-15 14:29:01 +05:00
sundasnoreen12
72a7d31765 refactor: refactor code 2025-04-15 14:29:01 +05:00
sundasnoreen12
545fabbc0a fix: fixed course level preference issue 2025-04-15 14:29:01 +05:00
sundasnoreen12
b3dbc7499f fix: fixed varaible name 2025-04-15 14:29:01 +05:00
sundasnoreen12
8e2d321bd6 fix: added changes for restricted country 2025-04-15 14:29:01 +05:00
sundasnoreen12
6e995f83eb fix: removed unused selector 2025-04-15 14:29:01 +05:00
sundasnoreen12
517e9f5b7e refactor: refactored code 2025-04-15 14:29:01 +05:00
sundasnoreen12
c611f55b92 fix: changed frequency from never to daily on email preference change 2025-04-15 14:29:01 +05:00
Awais Ansari
5c472198cc fix: updated notifications section url (#1185)
* fix: updated notifiations section url

* fix: updated test cases
2025-04-15 14:28:56 +05:00
Awais Ansari
8b96e6719e feat: added notification preferences settings at account level (#1159)
* feat: added notification preferences settings at account level

* fix: fixed test cases

* feat: added api for account notification type

* fix: fixed test cases and label

* test: added update account preference test case

* fix: fixed issue to update email cadence for account notification type

* refactor: updated time

* fix: fixed mixed cadence issue

* fix: fixed border issue when no preferences

* refactor: refactor code

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@gmail.com>
2025-04-15 14:27:59 +05:00
Muhammad Adeel Tajamul
69927f1be1 refactor: moved unable to delete into component (#1177) 2025-04-15 14:23:12 +05:00
Muhammad Adeel Tajamul
5026ac3b3a feat: added feature to hide delete button for countries (#1176) 2025-04-15 14:23:12 +05:00
sundasnoreen12
805b76c7e2 fix: rebase with 2u 2025-04-15 14:22:00 +05:00
Awais Ansari
bd507e2037 fix: translation and console errors (#1166) 2025-04-15 14:16:44 +05:00
ayesha waris
2e7c172fb0 chore: rebase with master (#1158)
* fix: fixed support urls (#1155)

* fix(deps): update dependency @edx/frontend-component-header to v5.7.1 (#1156)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.6 (#1157)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix: fixed certificates url

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 14:14:16 +05:00
Awais Ansari
aa15df3ab9 feat: added country disabling feature (#1116)
* feat: added country disabling feature

* refactor: removed isDisabledCountry additional call
2025-04-15 14:10:42 +05:00
20 changed files with 508 additions and 2398 deletions

8
.env
View File

@@ -15,7 +15,7 @@ LOGO_WHITE_URL=''
SHOW_EMAIL_CHANNEL=''
LOGOUT_URL=''
MARKETING_SITE_BASE_URL=''
NODE_ENV='production'
NODE_ENV=''
ORDER_HISTORY_URL=''
PUBLISHER_BASE_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT=''
@@ -31,6 +31,10 @@ MARKETING_EMAILS_OPT_IN=''
APP_ID=
MFE_CONFIG_API_URL=
PASSWORD_RESET_SUPPORT_LINK=''
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT=''
ACCOUNT_BASICS_SUPPORT_URL=''
EMAIL_CONFIRMATION_SUPPORT_URL=''
CERTIFICATES_SUPPORT_URL=''
LEARNER_SUPPORT_URL=''
LEARNER_FEEDBACK_URL=''
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'

View File

@@ -33,5 +33,9 @@ APP_ID=
MFE_CONFIG_API_URL=
PASSWORD_RESET_SUPPORT_LINK='mailto:support@example.com'
LEARNER_FEEDBACK_URL=''
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT=''
ACCOUNT_BASICS_SUPPORT_URL=''
EMAIL_CONFIRMATION_SUPPORT_URL=''
CERTIFICATES_SUPPORT_URL=''
LEARNER_SUPPORT_URL=''
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'

2583
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@
],
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-footer": "^14.3.0",
"@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-platform": "^8.3.3",
"@edx/openedx-atlas": "^0.6.0",
@@ -38,7 +38,8 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/frontend-plugin-framework": "^1.6.0",
"@openedx/frontend-slot-footer": "^1.1.0",
"@openedx/paragon": "^22.16.0",
"@tensorflow-models/blazeface": "0.1.0",
"@tensorflow/tfjs-converter": "4.22.0",
@@ -60,7 +61,7 @@
"lodash.pick": "4.4.0",
"lodash.pickby": "4.6.0",
"lodash.snakecase": "4.1.1",
"long": "5.3.2",
"long": "5.3.1",
"memoize-one": "^6.0.0",
"prop-types": "15.8.1",
"qs": "6.14.0",

View File

@@ -53,7 +53,6 @@ import { fetchSiteLanguages } from './site-language';
import { fetchCourseList } from '../notification-preferences/data/thunks';
import NotificationSettings from '../notification-preferences/NotificationSettings';
import { withLocation, withNavigate } from './hoc';
import AdditionalProfileFieldsSlot from '../plugin-slots/AdditionalProfileFieldsSlot';
class AccountSettingsPage extends React.Component {
constructor(props, context) {
@@ -733,8 +732,6 @@ class AccountSettingsPage extends React.Component {
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
{...editableFieldProps}
/>
<AdditionalProfileFieldsSlot />
</div>
<div className="account-section pt-3 mb-6" id="social-media">
<h2 className="section-heading h4 mb-3">

View File

@@ -59,7 +59,7 @@ export class DeleteAccount extends React.Component {
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
} = this.props;
const canDelete = isVerifiedAccount && !hasLinkedTPA;
const supportArticleUrl = process.env.SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT;
const supportArticleUrl = getConfig().SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT;
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
// to allow edx.org to fulfill its business requirements.
@@ -102,7 +102,7 @@ export class DeleteAccount extends React.Component {
)}
</p>
<p>
<Hyperlink destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics">
<Hyperlink destination={getConfig().ACCOUNT_BASICS_SUPPORT_URL}>
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
</Hyperlink>
</p>
@@ -118,7 +118,7 @@ export class DeleteAccount extends React.Component {
{isVerifiedAccount ? null : (
<BeforeProceedingBanner
instructionMessageId={optInInstructionMessageId}
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
supportArticleUrl={getConfig().EMAIL_CONFIRMATION_SUPPORT_URL}
/>
)}
{hasLinkedTPA ? (

View File

@@ -11,7 +11,7 @@ const PrintingInstructions = (props) => {
// TODO: What would a generic version of this link look like? Should
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
// We've removed the link from the default message.
destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UVVOA2/certificates"
destination={getConfig().CERTIFICATES_SUPPORT_URL}
>
{props.intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
</Hyperlink>

View File

@@ -27,7 +27,6 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
<p>
<a
className="pgn__hyperlink default-link standalone-link"
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
target="_self"
>
Want to change your email, name, or password instead?
@@ -73,7 +72,6 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
<p>
<a
className="pgn__hyperlink default-link standalone-link"
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
target="_self"
>
Want to change your email, name, or password instead?
@@ -112,15 +110,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
</svg>
</div>
<div>
Before proceeding, please
<a
className="pgn__hyperlink default-link standalone-link"
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
target="_self"
>
activate your account
</a>
.
Before proceeding, please activate your account.
</div>
</div>
</div>
@@ -153,7 +143,6 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
<p>
<a
className="pgn__hyperlink default-link standalone-link"
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
target="_self"
>
Want to change your email, name, or password instead?
@@ -192,15 +181,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
</svg>
</div>
<div>
Before proceeding, please
<a
className="pgn__hyperlink default-link standalone-link"
href="https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account"
target="_self"
>
unlink all social media accounts
</a>
.
Before proceeding, please unlink all social media accounts.
</div>
</div>
</div>

View File

@@ -130,7 +130,7 @@ const SummaryPanel = (props) => {
`}
values={{
support_link: (
<Alert.Link href="https://support.edx.org/hc/en-us">
<Alert.Link href={getConfig().LEARNER_SUPPORT_URL}>
{props.intl.formatMessage(
messages['id.verification.review.error'],
{ siteName: getConfig().SITE_NAME },

View File

@@ -7,7 +7,7 @@ import {
render, act, screen, fireEvent,
} from '@testing-library/react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import IdVerificationPageSlot from '../../plugin-slots/IdVerificationPageSlot';
import IdVerificationPage from '../IdVerificationPage';
import * as selectors from '../data/selectors';
jest.mock('../data/selectors', () => jest.fn().mockImplementation(() => ({ idVerificationSelector: () => ({}) })));
@@ -47,7 +47,7 @@ jest.mock('../panels/SubmittedPanel', () => function SubmittedPanelMock() {
return <></>;
});
const IntlIdVerificationPage = injectIntl(IdVerificationPageSlot);
const IntlIdVerificationPage = injectIntl(IdVerificationPage);
const mockStore = configureStore();
describe('IdVerificationPage', () => {

View File

@@ -12,7 +12,7 @@ import { createRoot } from 'react-dom/client';
import { Route, Routes, Outlet } from 'react-router-dom';
import Header from '@edx/frontend-component-header';
import { FooterSlot } from '@edx/frontend-component-footer';
import FooterSlot from '@openedx/frontend-slot-footer';
import configureStore from './data/configureStore';
import AccountSettingsPage, { NotFoundPage } from './account-settings';
@@ -72,6 +72,11 @@ initialize({
ENABLE_DOB_UPDATE: (process.env.ENABLE_DOB_UPDATE || false),
MARKETING_EMAILS_OPT_IN: (process.env.MARKETING_EMAILS_OPT_IN || false),
PASSWORD_RESET_SUPPORT_LINK: process.env.PASSWORD_RESET_SUPPORT_LINK,
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT: process.env.SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT,
ACCOUNT_BASICS_SUPPORT_URL: process.env.ACCOUNT_BASICS_SUPPORT_URL,
EMAIL_CONFIRMATION_SUPPORT_URL: process.env.EMAIL_CONFIRMATION_SUPPORT_URL,
CERTIFICATES_SUPPORT_URL: process.env.CERTIFICATES_SUPPORT_URL,
LEARNER_SUPPORT_URL: process.env.LEARNER_SUPPORT_URL,
LEARNER_FEEDBACK_URL: process.env.LEARNER_FEEDBACK_URL,
}, 'App loadConfig override handler');
},

View File

@@ -27,7 +27,7 @@ const messages = defineMessages({
newQuestionPost {New question posts}
contentReported {Reported content}
courseUpdates {Course updates}
oraStaffNotifications {New ORA submission for staff grading}
oraStaffNotifications {ORA new submissions}
oraGradeAssigned {Essay assignment grade received}
other {{text}}
}`,

View File

@@ -1,87 +0,0 @@
# Additional Profile Fields
### Slot ID: `org.openedx.frontend.account.additional_profile_fields.v1`
## Description
This slot is used to replace/modify/hide the additional profile fields in the account page.
## Example
The following `env.config.jsx` will extend the default fields with a additional custom fields through a simple example component.
![Screenshot of Custom Fields](./images/custom_fields.png)
### Using the Example Component
Create a file named `env.config.jsx` at the MFE root with this:
```jsx
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
import Example from './src/plugin-slots/AdditionalProfileFieldsSlot/example';
const config = {
pluginSlots: {
'org.openedx.frontend.account.additional_profile_fields.v1': {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'additional_account_fields',
type: DIRECT_PLUGIN,
RenderWidget: Example,
},
},
],
},
},
};
export default config;
```
## Plugin Props
When implementing a plugin for this slot, the following props are available:
### `updateUserProfile`
- **Type**: Function
- **Description**: A function for updating the user's profile with new field values. This handles the API call to persist changes to the backend.
- **Usage**: Pass an object containing the field updates to be saved to the user's profile preserving the required structure. The function automatically handles the persistence and UI updates.
#### Example
``` javascript
updateUserProfile({ extendedProfile: [{ fieldName: 'favorite_color', fieldValue: value }] });
```
### `profileFieldValues`
- **Type**: Array of Objects
- **Description**: Contains the current values of all additional profile fields as an array of objects. Each object has a `fieldName` property (string) and a `fieldValue` property (which can be string, boolean, number, or other data types depending on the field type).
- **Usage**: Access specific field values by finding the object with the matching `fieldName` and reading its `fieldValue` property. Use array methods like `find()` to locate specific fields.
#### Example
```json
[
{
"fieldName": "favorite_color",
"fieldValue": "red"
},
{
"fieldName": "data_authorization",
"fieldValue": true
},
]
```
### `profileFieldErrors`
- **Type**: Object
- **Description**: Contains validation errors for profile fields. Each key corresponds to a field name, and the value is the error message.
- **Usage**: Check for field-specific errors to display validation feedback to users.
### `formComponents`
- **Type**: Object
- **Description**: Provides access to reusable form components that are consistent with the rest of the account page styling and behavior. These components follow the platform's design system and include proper validation and accessibility features.
- **Usage**: Use these components in your custom fields implementation to maintain UI consistency. Available components include `SwitchContent` for managing different UI states.
### `refreshUserProfile`
- **Type**: Function
- **Description**: A function that triggers a refresh of the user's profile data. This can be used after updating profile fields to ensure the UI reflects the latest data from the server.
- **Usage**: Call this function when you need to manually reload the user profile information. Note that `updateUserProfile` typically handles data refresh automatically.

View File

@@ -1,104 +0,0 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import { Form, Button } from '@openedx/paragon';
/**
* Straightforward example of how you could use the pluginProps provided by
* the AdditionalProfileFieldsSlot to create a custom profile field.
*
* Here you can set a 'favorite_color' field with radio buttons and
* save it to the user's profile, especifically to their `meta` in
* the user's model. For more information, see the documentation:
*
* https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/user_api/README.rst#persisting-optional-user-metadata
*/
const Example = ({
updateUserProfile, profileFieldValues, profileFieldErrors, formComponents: { SwitchContent } = {},
}) => {
const [formMode, setFormMode] = useState('default');
// Get current favorite color from profileFieldValues
const currentColorField = profileFieldValues?.find(field => field.fieldName === 'favorite_color');
const currentColor = currentColorField ? currentColorField.fieldValue : '';
const [value, setValue] = useState(currentColor);
const handleChange = e => setValue(e.target.value);
// Get any validation errors for the favorite_color field
const colorFieldError = profileFieldErrors?.favorite_color;
const handleSubmit = () => {
try {
updateUserProfile({ extendedProfile: [{ fieldName: 'favorite_color', fieldValue: value }] });
setFormMode('default');
} catch (error) {
setFormMode('edit');
}
};
return (
<div className="border .border-accent-500 p-3">
<h3 className="h3">Example Additional Profile Fields Slot</h3>
<SwitchContent
expression={formMode}
cases={{
default: (
<>
<h3 className="text-muted">
{value ? `Selected value: ${value}` : 'No color selected'}
</h3>
<Button onClick={() => setFormMode('edit')}>Edit</Button>
</>
),
edit: (
<>
<Form.Group>
<Form.Label>Which Color?</Form.Label>
<Form.RadioSet
name="colors"
onChange={handleChange}
value={value}
isInvalid={!!colorFieldError}
>
<Form.Radio value="red">Red</Form.Radio>
<Form.Radio value="green">Green</Form.Radio>
<Form.Radio value="blue">Blue</Form.Radio>
</Form.RadioSet>
{colorFieldError && (
<Form.Control.Feedback type="invalid">
{colorFieldError}
</Form.Control.Feedback>
)}
</Form.Group>
<Button onClick={handleSubmit} disabled={!value}>
Save
</Button>
</>
),
}}
/>
</div>
);
};
Example.propTypes = {
updateUserProfile: PropTypes.func.isRequired,
profileFieldValues: PropTypes.arrayOf(
PropTypes.shape({
fieldName: PropTypes.string.isRequired,
fieldValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.number,
]).isRequired,
}),
),
profileFieldErrors: PropTypes.objectOf(PropTypes.string),
formComponents: PropTypes.shape({
SwitchContent: PropTypes.elementType.isRequired,
}),
};
export default Example;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -1,32 +0,0 @@
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { useDispatch, useSelector } from 'react-redux';
import { camelCaseObject, snakeCaseObject } from '@edx/frontend-platform';
import { fetchSettings, saveSettings } from '../../account-settings/data/actions';
import SwitchContent from '../../account-settings/SwitchContent';
const AdditionalProfileFieldsSlot = () => {
const dispatch = useDispatch();
const extendedProfileValues = useSelector((state) => state.accountSettings.values.extended_profile);
const errors = useSelector((state) => state.accountSettings.errors);
const pluginProps = {
refreshUserProfile: (username) => dispatch(fetchSettings(username)),
updateUserProfile: (params) => dispatch(saveSettings(null, null, snakeCaseObject(params))),
profileFieldValues: camelCaseObject(extendedProfileValues),
profileFieldErrors: errors,
formComponents: {
SwitchContent,
},
};
return (
<PluginSlot
id="org.openedx.frontend.account.additional_profile_fields.v1"
pluginProps={pluginProps}
/>
);
};
export default AdditionalProfileFieldsSlot;

View File

@@ -1,15 +1,12 @@
# Footer Slot
### Slot ID: `org.openedx.frontend.layout.footer.v1`
### Slot ID Aliases
* `footer_slot`
### Slot ID: `footer_slot`
## Description
This slot is used to replace/modify/hide the footer.
The implementation of the `FooterSlot` component lives in [the `frontend-component-footer` repository](https://github.com/openedx/frontend-component-footer/).
The implementation of the `FooterSlot` component lives in [the `frontend-component-footer` repository](https://github.com/openedx/frontend-component-footer/tree/master/src/components/footer-slot).
## Example
@@ -26,7 +23,7 @@ import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-frame
const config = {
pluginSlots: {
'org.openedx.frontend.layout.footer.v1': {
footer_slot: {
plugins: [
{
// Hide the default footer

View File

@@ -1,9 +1,6 @@
# ID Verification Page Slot
# Footer Slot
### Slot ID: `org.openedx.frontend.account.id_verification_page.v1`
### Slot ID Aliases
* `id_verification_page_plugin`
### Slot ID: `id_verification_page_plugin`
## Description
@@ -22,13 +19,13 @@ import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-frame
const config = {
pluginSlots: {
'org.openedx.frontend.account.id_verification_page.v1': {
id_verification_page_plugin: {
plugins: [
{
// Insert a custom IDV Page
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_id_verification_page',
id: 'id_verification_page_plugin',
type: DIRECT_PLUGIN,
RenderWidget: () => (
<div>

View File

@@ -2,10 +2,7 @@ import { PluginSlot } from '@openedx/frontend-plugin-framework';
import IdVerificationPage from '../../id-verification';
const IdVerificationPageSlot = () => (
<PluginSlot
id="org.openedx.frontend.account.id_verification_page.v1"
idAliases={['id_verification_page_plugin']}
>
<PluginSlot id="id_verification_page_plugin">
<IdVerificationPage />
</PluginSlot>
);

View File

@@ -1,5 +1,4 @@
# `frontend-app-account` Plugin Slots
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
* [`org.openedx.frontend.account.id_verification_page.v1`](./IdVerificationPageSlot/)
* [`org.openedx.frontend.account.additional_profile_fields.v1`](./AdditionalProfileFieldsSlot/)
* [`footer_slot`](./FooterSlot/)
* [`id_verification_page_plugin`](./IdVerificationPageSlot/)