diff --git a/.env.example b/.env.example
deleted file mode 100644
index 57afba9aee..0000000000
--- a/.env.example
+++ /dev/null
@@ -1,42 +0,0 @@
-COMPOSE_PROFILES=backend,frontend
-
-CELERY_TASK_ALWAYS_EAGER=True
-DJANGO_LOG_LEVEL=INFO
-LOG_LEVEL=info
-MITOPEN_BASE_URL=http://od.odl.local:8063
-MITOPEN_COOKIE_NAME=discussions
-MITOPEN_COOKIE_DOMAIN=odl.local
-MITOPEN_JWT_SECRET=
-MITOPEN_USE_S3=False
-MITOPEN_AXIOS_WITH_CREDENTIALS=False
-MITOPEN_AXIOS_BASE_PATH=""
-INDEXING_API_USERNAME=mitodl
-MAILGUN_SENDER_DOMAIN=
-MAILGUN_KEY=
-MAILGUN_RECIPIENT_OVERRIDE=
-STATUS_TOKEN=
-OPENSEARCH_INDEX=discussions_local
-OPENSEARCH_INDEXING_CHUNK_SIZE=100
-AWS_ACCESS_KEY_ID=
-AWS_SECRET_ACCESS_KEY=
-AWS_STORAGE_BUCKET_NAME=
-SOCIAL_AUTH_MICROMASTERS_LOGIN_URL=
-YOUTUBE_DEVELOPER_KEY=
-TIKA_SERVER_ENDPOINT=http://tika:9998/
-TIKA_CLIENT_ONLY=True
-WEBPACK_ANALYZE=False
-
-SOCIAL_AUTH_OL_OIDC_OIDC_ENDPOINT=
-SOCIAL_AUTH_OL_OIDC_KEY=
-SOCIAL_AUTH_OL_OIDC_SECRET=
-AUTHORIZATION_URL=
-ACCESS_TOKEN_URL=
-USERINFO_URL=
-KEYCLOAK_BASE_URL=
-KEYCLOAK_REALM_NAME=
-
-POSTHOG_PROJECT_ID=
-POSTHOG_PROJECT_API_KEY=
-POSTHOG_PERSONAL_API_KEY=
-POSTHOG_HOST=https://app.posthog.com
-POSTHOG_TIMEOUT_MS=1500
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 478ee415e9..d17a5de1dd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,7 +41,7 @@ jobs:
- 9200:9200
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- name: update apt
run: sudo apt-get update -y
@@ -85,7 +85,7 @@ jobs:
CELERY_BROKER_URL: redis://localhost:6379/4
CELERY_RESULT_BACKEND: redis://localhost:6379/4
TIKA_CLIENT_ONLY: "True"
- MITOPEN_BASE_URL: http://localhost:8063/
+ MITOPEN_APP_BASE_URL: http://localhost:8062/
MAILGUN_KEY: fake_mailgun_key
MAILGUN_SENDER_DOMAIN: other.fake.site
OPENSEARCH_INDEX: testindex
@@ -101,7 +101,7 @@ jobs:
javascript-tests:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "^20"
@@ -123,7 +123,7 @@ jobs:
- name: Webpack build
run: yarn run build
env:
- MITOPEN_AXIOS_BASE_PATH: https://api.mitopen-test.odl.mit.edu
+ MITOPEN_API_BASE_URL: https://api.mitopen-test.odl.mit.edu
- name: Lints
run: yarn run lint-check
@@ -156,7 +156,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
@@ -167,25 +167,8 @@ jobs:
- name: Install dependencies
run: yarn install
- - name: Copy static assets
- run: |
- mkdir -p frontends/mit-open/build/static;
- cp -R frontends/mit-open/public/ frontends/mit-open/build/static;
-
- - run: PUBLIC_URL=/mit-open yarn build-github-pages
- working-directory: frontends
-
- - run: PUBLIC_URL=/mit-open yarn build-storybook --output-dir ../github-pages/build/storybook
- working-directory: frontends
-
- - name: Setup Pages
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
-
- - name: Upload artifact
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
- with:
- name: pages-site
- path: frontends/github-pages/build
+ - name: Build Storybook
+ run: yarn workspace mit-open build-storybook
openapi-generated-client-check-v0:
# This job checks that the output of openapi-generator-typescript-axios that
@@ -197,7 +180,7 @@ jobs:
GENERATOR_OUTPUT_DIR_VC: ./frontends/api/src/generated/v0
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "^20"
@@ -236,7 +219,7 @@ jobs:
GENERATOR_OUTPUT_DIR_VC: ./frontends/api/src/generated/v1
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
node-version: "^20"
@@ -271,7 +254,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with:
diff --git a/.github/workflows/openapi-diff.yml b/.github/workflows/openapi-diff.yml
index 1145023d81..3866b94472 100644
--- a/.github/workflows/openapi-diff.yml
+++ b/.github/workflows/openapi-diff.yml
@@ -5,12 +5,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout HEAD
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
ref: ${{ github.head_ref }}
path: head
- name: Checkout BASE
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
ref: ${{ github.base_ref }}
path: base
diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml
index 3ca4b0abc8..fbc1ca38c5 100644
--- a/.github/workflows/production.yml
+++ b/.github/workflows/production.yml
@@ -15,7 +15,7 @@ jobs:
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
ref: release
@@ -41,7 +41,8 @@ jobs:
POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID_PROD }}
POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_PROD }}
MITOPEN_AXIOS_WITH_CREDENTIALS: true
- MITOPEN_AXIOS_BASE_PATH: https://api.mitopen.odl.mit.edu
+ MITOPEN_API_BASE_URL: https://api.mitopen.odl.mit.edu
+ MITOPEN_SUPPORT_EMAIL: mitopen-support@mit.edu
- uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520
with:
diff --git a/.github/workflows/publish-pages.yml b/.github/workflows/publish-pages.yml
new file mode 100644
index 0000000000..4e9d044a61
--- /dev/null
+++ b/.github/workflows/publish-pages.yml
@@ -0,0 +1,48 @@
+on:
+ # Runs on pushes targeting the default branch
+ push:
+ branches: [$default-branch]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+
+ - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
+ with:
+ node-version: "^20"
+ cache: yarn
+ cache-dependency-path: yarn.lock
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build Storybook
+ run: yarn workspace mit-open build-storybook
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./frontends/mit-open/storybook-static
+
+ deploy:
+ needs: build
+
+ permissions:
+ pages: write # to deploy to Pages
+ id-token: write # to verify the deployment originates from an appropriate source
+
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml
index 097fdd442d..4d0454255f 100644
--- a/.github/workflows/release-candidate.yml
+++ b/.github/workflows/release-candidate.yml
@@ -15,7 +15,7 @@ jobs:
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
ref: release-candidate
@@ -41,7 +41,8 @@ jobs:
POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID_RC }}
POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_RC }}
MITOPEN_AXIOS_WITH_CREDENTIALS: true
- MITOPEN_AXIOS_BASE_PATH: https://api.mitopen-rc.odl.mit.edu
+ MITOPEN_API_BASE_URL: https://api.mitopen-rc.odl.mit.edu
+ MITOPEN_SUPPORT_EMAIL: odl-mitopen-rc-support@mit.edu
- uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520
with:
diff --git a/.gitignore b/.gitignore
index d82f14b80e..05e6d520b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@ __pycache__/
# Distribution / packaging
.Python
-env/
build/
build-exports/
develop-eggs/
diff --git a/README.md b/README.md
index 8740c88be8..dcca76c8b5 100644
--- a/README.md
+++ b/README.md
@@ -17,18 +17,25 @@ MIT Open follows the same [initial setup steps outlined in the common OL web app
Run through those steps **including the addition of `/etc/hosts` aliases and the optional step for running the
`createsuperuser` command**.
-### Configure required `.env` settings
+### Configuration
+
+Configuration can be put in the following filess which are gitignored:
+
+```
+mit-open/
+ ├── env/
+ │ ├── shared.local.env (provided to both frontend and backend containers)
+ │ ├── frontend.local.env (provided only to frontend containers)
+ │ └── backend.local.env (provided only to frontend containers)
+ └── .env (legacy file)
+```
The following settings must be configured before running the app:
- `COMPOSE_PROFILES`
Controls which docker containers run. To run them all, use `COMPOSE_PROFILES=backend,frontend`. See [Frontend Development](./frontends/README.md) for more.
-
-- `INDEXING_API_USERNAME`
-
- At least to start out, this should be set to the username of the superuser
- you created above.
+ This can be set either in a top-level `.env` that `docker compose` [automatically ingests](https://docs.docker.com/compose/environment-variables/envvars/#compose_env_files) or through any other method of setting an environment variable in your shell (e.g. `direnv`).
- `MAILGUN_KEY` and `MAILGUN_SENDER_DOMAIN`
@@ -216,7 +223,7 @@ Once these are set (and you've restarted the app), you should see events flowing
A Javascript bundle of exported frontend components can be generated for use in external websites that have CORS allowance into a given instance of `mit-open`. There are a few settings you might want to change in order to get the expected results.
- `MITOPEN_AXIOS_WITH_CREDENTIALS` - This sets `withCredentials: true` when initializing the Axios API, which tells the end user's browser to send along any browser level cookies for the current domain when making CORS requests
-- `MITOPEN_AXIOS_BASE_PATH` - This sets the base path used for API requests, which will need to be set to a fully qualified url pointing to an instance of `mit-open` (i.e. https://mitopen.odl.mit.edu) in order for requests from the external site to reach the proper destination
+- `MITOPEN_API_BASE_URL` - This sets the base url used for API requests, which will need to be set to a fully qualified url pointing to an instance of `mit-open` (i.e. https://mitopen.odl.mit.edu) in order for requests from the external site to reach the proper destination
- `CORS_ALLOWED_ORIGINS`, `CSRF_TRUSTED_ORIGINS` - On the instance of `mit-open` that the externally hosted components will access via the API, the domains of any sites that need CORS access need to be here as a list of strings
To build the bundle of exported components, run:
@@ -247,6 +254,6 @@ $("#add-to-user-list-button").on("click", async (event) => {
This is just an example, and you could input any `readable_id` to bring up a dialog to add any given `LearningResource` object to a `UserList`.
-## GitHub Pages
+## GitHub Pages Storybook
-A static site for this repo with developer resources publishes to https://mitodl.github.io/mit-open/ during CI runs.
+Demos and documentation of reusable UI components in this repo are published as a [storybook](https://storybook.js.org/) at https://mitodl.github.io/mit-open/.
diff --git a/RELEASE.rst b/RELEASE.rst
index 6c5e076eab..b3903d7f30 100644
--- a/RELEASE.rst
+++ b/RELEASE.rst
@@ -1,6 +1,37 @@
Release Notes
=============
+Version 0.13.18
+---------------
+
+- Fix logout view (#1236)
+- remove manage widgets (#1239)
+- Unit and detail page copy updates (#1235)
+- Align departments listing colors to designs (#1238)
+- resource drawer UI fixes (#1237)
+- Remove "Top picks" carousel if no results (#1195)
+- fix learning path count, increase item page size (#1230)
+- Use ovewrite=True when calling pluggy function from upsert_offered_by (#1227)
+- open resources in new tab (#1220)
+- extra weight for instructors (#1231)
+- Homepage and nav drawer copy edits (#1233)
+- Update dependency eslint-plugin-jest to v28 (#1038)
+- Only publish enrollable mitxonline courses (#1229)
+- Navigation UI fixes (#1228)
+- better spacing around pagination component (#1219)
+- Update resource drawer text and URL for podcast episodes (#1191)
+- resource type (#1222)
+- Data fixtures app for loading static fixtures (#1218)
+- Webpack build config loads .env files for running outside of Docker (#1221)
+- Updates icons to use Remixicons where they don't already (#1157)
+- make primary buttons shadowy, remove edge=none (#1213)
+- resource category tabs (#1211)
+- Fix storybook github pages publishing (#1200)
+- Fix and reenable onboarding page tests (#1216)
+- Removed nginx serving of frontend locally (#1179)
+- Update actions/checkout digest to 692973e (#961)
+- Privacy policy updates (#1208)
+
Version 0.13.17 (Released July 02, 2024)
---------------
diff --git a/app.json b/app.json
index a129046b63..d4e43ecf3d 100644
--- a/app.json
+++ b/app.json
@@ -4,9 +4,6 @@
{
"url": "https://github.com/heroku/heroku-buildpack-apt"
},
- {
- "url": "https://github.com/heroku/heroku-buildpack-nodejs"
- },
{
"url": "https://github.com/moneymeets/python-poetry-buildpack"
},
@@ -172,9 +169,6 @@
"description": "Prefix path for cached images generated by imagekit",
"required": false
},
- "INDEXING_API_USERNAME": {
- "description": "Username used for indexing"
- },
"INDEXING_ERROR_RETRIES": {
"description": "Number of times to retry an indexing operation on failure",
"required": false
@@ -298,20 +292,12 @@
"description": "List of pluggy plugins to use for authentication",
"required": false
},
- "MITOPEN_AXIOS_WITH_CREDENTIALS": {
- "description": "When building the Axios API, set defaults.withCredentials to this value",
- "required": false
- },
- "MITOPEN_AXIOS_BASE_PATH": {
- "description": "The base path to use when making API requests",
- "required": false
- },
"MITOPEN_LEARNING_RESOURCES_PLUGINS": {
"description": "List of pluggy plugins to use for learning resources",
"required": false
},
- "MITOPEN_BASE_URL": {
- "description": "Base url to link users to in emails"
+ "MITOPEN_APP_BASE_URL": {
+ "description": "Base url to create links to the app"
},
"MITOPEN_COOKIE_NAME": {
"description": "Name of the cookie for the JWT auth token"
diff --git a/authentication/views.py b/authentication/views.py
index 7e8114494f..55f7cdd3cc 100644
--- a/authentication/views.py
+++ b/authentication/views.py
@@ -4,7 +4,6 @@
from django.conf import settings
from django.contrib.auth import views
-from django.http import Http404
from django.shortcuts import redirect
from social_django.utils import load_strategy
@@ -37,25 +36,38 @@ def _keycloak_logout_url(self, user):
qs = urlencode(
{
"id_token_hint": id_token,
- "post_logout_redirect_uri": settings.LOGOUT_REDIRECT_URL,
+ "post_logout_redirect_uri": self.request.build_absolute_uri(
+ settings.LOGOUT_REDIRECT_URL
+ ),
}
)
- return f"{settings.KEYCLOAK_BASE_URL}/realms/{settings.KEYCLOAK_REALM_NAME}/protocol/openid-connect/logout?{qs}" # noqa: E501
+
+ return (
+ f"{settings.KEYCLOAK_BASE_URL}/realms/"
+ f"{settings.KEYCLOAK_REALM_NAME}/protocol/openid-connect/logout"
+ f"?{qs}"
+ )
def get(
self,
request,
*args, # noqa: ARG002
**kwargs, # noqa: ARG002
- ): # pylint:disable=unused-argument
+ ):
"""
GET endpoint for loggin a user out.
- Raises 404 if the user is not included in the request.
+
+ The logout redirect path the user follows is:
+
+ - api.example.com/logout (this view)
+ - keycloak.example.com/realms/REALM/protocol/openid-connect/logout
+ - api.example.com/app (see main/urls.py)
+ - app.example.com
+
"""
user = getattr(request, "user", None)
if user and user.is_authenticated:
super().get(request)
return redirect(self._keycloak_logout_url(user))
else:
- msg = "Not currently logged in."
- raise Http404(msg)
+ return redirect("/app")
diff --git a/channels/models.py b/channels/models.py
index c56967f5a6..22f7dd2308 100644
--- a/channels/models.py
+++ b/channels/models.py
@@ -1,8 +1,5 @@
"""Models for channels"""
-from urllib.parse import urljoin
-
-from django.conf import settings
from django.contrib.auth.models import Group
from django.core.validators import RegexValidator
from django.db import models
@@ -18,6 +15,7 @@
LearningResourceTopic,
)
from main.models import TimestampedModel
+from main.utils import frontend_absolute_url
from profiles.utils import avatar_uri, banner_uri
from widgets.models import WidgetList
@@ -99,7 +97,7 @@ class Meta:
@property
def channel_url(self) -> str:
"""Return the channel url"""
- return urljoin(settings.SITE_BASE_URL, f"/c/{self.channel_type}/{self.name}/")
+ return frontend_absolute_url(f"/c/{self.channel_type}/{self.name}/")
class ChannelTopicDetail(TimestampedModel):
diff --git a/channels/serializers_test.py b/channels/serializers_test.py
index 30211e8571..cdd78d1dad 100644
--- a/channels/serializers_test.py
+++ b/channels/serializers_test.py
@@ -1,10 +1,8 @@
"""Tests for channels.serializers"""
from types import SimpleNamespace
-from urllib.parse import urljoin
import pytest
-from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
from channels.constants import CHANNEL_ROLE_MODERATORS, ChannelType
@@ -37,6 +35,7 @@
)
from learning_resources.serializers import LearningResourceOfferorDetailSerializer
from main.factories import UserFactory
+from main.utils import frontend_absolute_url
# pylint:disable=redefined-outer-name
pytestmark = pytest.mark.django_db
@@ -124,8 +123,8 @@ def test_serialize_channel( # pylint: disable=too-many-arguments
"updated_on": mocker.ANY,
"created_on": mocker.ANY,
"id": channel.id,
- "channel_url": urljoin(
- settings.SITE_BASE_URL, f"/c/{channel.channel_type}/{channel.name}/"
+ "channel_url": frontend_absolute_url(
+ f"/c/{channel.channel_type}/{channel.name}/"
),
"lists": [
LearningPathPreviewSerializer(channel_list.channel_list).data
diff --git a/config/nginx.conf b/config/nginx.conf
deleted file mode 100644
index f6f9bbc25a..0000000000
--- a/config/nginx.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-# This is the version used in development environments
-server {
- server_name lemelsonx.mit.edu;
- listen 8063;
- return 301 https://open.mit.edu/c/lemelsoneducators;
-}
-
-server {
- server_name themove.mit.edu;
- listen 8063;
- return 301 https://open.mit.edu/c/themove;
-}
-
-server {
- listen 8063 default_server;
-
- root /src/frontends/mit-open/build;
-
- location / {
- try_files /static$uri $uri @index;
- }
-
- location ~ ^/program_letter/([0-9]+)/view$ {
- try_files /index.html =404;
- }
-
- location @index {
- try_files /index.html =404;
- }
-
- location = /.well-known/dnt-policy.txt {
- return 204;
- }
-
- location ~ ^/(api|login|complete/ol-oidc|logout|admin|static/admin|static/rest_framework|static/hijack|_/features/|scim/|o/|disconnect/|hijack/|podcasts/rss_feed|__debug__/|media/|profile/|program_letter/([0-9]+)/) {
- include uwsgi_params;
- uwsgi_pass web:8061;
- uwsgi_pass_request_headers on;
- uwsgi_pass_request_body on;
- client_max_body_size 25M;
- }
-}
diff --git a/config/nginx.conf.erb b/config/nginx.conf.erb
index 29a7423481..04eb208e10 100644
--- a/config/nginx.conf.erb
+++ b/config/nginx.conf.erb
@@ -38,49 +38,15 @@ http {
default_type application/octet-stream;
sendfile on;
- server {
- server_name lemelsonx.mit.edu;
- listen <%= ENV["PORT"] %>;
- return 301 https://open.mit.edu/c/lemelsoneducators;
- }
-
- server {
- server_name themove.mit.edu;
- listen <%= ENV["PORT"] %>;
- return 301 https://open.mit.edu/c/themove;
- }
-
- server {
- server_name discussions.odl.mit.edu;
- listen <%= ENV["PORT"] %>;
- return 301 https://open.mit.edu$request_uri;
- }
-
server {
listen <%= ENV["PORT"] %> default_server;
server_name _;
- root /app;
-
- location / {
- expires max;
- try_files /frontends/mit-open/build/static$uri /frontends/mit-open/build$uri @index;
- }
-
- location ~ ^/program_letter/([0-9]+)/view$ {
- expires 1m;
- try_files /frontends/mit-open/build/index.html =404;
- }
-
- location @index {
- expires 1m;
- try_files /frontends/mit-open/build/index.html =404;
- }
location = /.well-known/dnt-policy.txt {
return 204;
}
- location ~ ^/(api|login|complete/ol-oidc|logout|admin|static/admin|static/rest_framework|static/hijack|_/features/|hijack/|scim/|o/|disconnect/|podcasts/rss_feed|__debug__/|media/|profile/|program_letter/([0-9]+)/) {
+ location / {
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
@@ -98,7 +64,7 @@ http {
uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto;
uwsgi_param X-Forwarded-Port $http_x_forwarded_port;
uwsgi_param X-Forwarded-Host $http_x_forwarded_host;
- uwsgi_pass unix:/tmp/nginx.socket;
+ uwsgi_pass <%= ENV["NGINX_UWSGI_PASS"] || "unix:/tmp/nginx.socket" %>;
uwsgi_pass_request_headers on;
uwsgi_pass_request_body on;
client_max_body_size 25M;
diff --git a/config/static-app.conf b/config/static-app.conf
new file mode 100644
index 0000000000..5a7d8c47d7
--- /dev/null
+++ b/config/static-app.conf
@@ -0,0 +1,10 @@
+# This is the version used ONLY for e2e tests because we statically compile to production mode
+server {
+ listen 8063 $APP_BASE_URL;
+ root /src/frontends/mit-open/build;
+
+ location / {
+ try_files /index.html =404;
+ }
+
+}
diff --git a/data_fixtures/__init__.py b/data_fixtures/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/data_fixtures/apps.py b/data_fixtures/apps.py
new file mode 100644
index 0000000000..075133ff20
--- /dev/null
+++ b/data_fixtures/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DataFixturesConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "data_fixtures"
diff --git a/data_fixtures/migrations/0001_add_testimonial_data.py b/data_fixtures/migrations/0001_add_testimonial_data.py
new file mode 100644
index 0000000000..ad9001688a
--- /dev/null
+++ b/data_fixtures/migrations/0001_add_testimonial_data.py
@@ -0,0 +1,256 @@
+# Generated by Django 4.2.13 on 2024-06-25 15:30
+
+import logging
+from pathlib import Path
+
+from django.conf import settings
+from django.core.files import File
+from django.db import migrations
+
+"""
+Fix an issue with PIL's logger when running in test
+https://github.com/camptocamp/pytest-odoo/issues/15
+"""
+pil_logger = logging.getLogger("PIL")
+pil_logger.setLevel(logging.INFO)
+
+fixtures = [
+ {
+ "attestant_name": "Maria Eduarda",
+ "title": "Independent Learner, Brazil",
+ "quote": (
+ "I am not exaggerating when I say MIT OpenCourseWare "
+ "has changed my life significantly for the better… "
+ "I would never have had the opportunity to "
+ "take Calculus during high school, let alone Solid State Chemistry. "
+ "These courses have greatly expanded my "
+ "interests and opportunities and I'm forever grateful for that."
+ ),
+ "position": 1,
+ "offerors": ["ocw"],
+ },
+ {
+ "attestant_name": "Chansa Kabwe",
+ "title": "Independent Learner, Zambia",
+ "quote": (
+ "OpenCourseWare continues to be a big part of my career."
+ " My foundation is linked to it — I don't know if "
+ "I would be the same engineer today if not for OpenCourseWare."
+ ),
+ "position": 1,
+ "offerors": ["ocw"],
+ },
+ {
+ "attestant_name": "Emmanuel Kasigazi",
+ "title": "Entrepreneur, Uganda",
+ "quote": (
+ "This is what OpenCourseWare has enabled me to do: "
+ "I get the chance to not only watch the "
+ "future happen, but I can actually be a part of it and create it."
+ ),
+ "position": 1,
+ "offerors": ["ocw"],
+ },
+ {
+ "attestant_name": "Arthur Julio Nelson",
+ "title": "Brand Strategy Lead, Google",
+ "quote": (
+ "The Bootcamp is, well, it's an accelerator "
+ "meets a creativity incubator. "
+ "It's all of these things mashed into one. "
+ "If you come into the Bootcamp with a "
+ "purpose and desire for impact and where you want "
+ "to channel it, the Bootcamp will help you unleash it."
+ ),
+ "position": 1,
+ "offerors": ["bootcamps"],
+ },
+ {
+ "attestant_name": "Jasmine Latham",
+ "title": "Lead Data Scientist, Office for National Statistics",
+ "quote": (
+ "I am very pleased with the course content, "
+ "it is exactly the level I am looking for. "
+ "Each professor/course presenter has packed "
+ "a lot of information and has explained complex "
+ "algorithms in good detail. "
+ "Some with good sense of humor. Thank you very much."
+ ),
+ "position": 1,
+ "offerors": ["xpro"],
+ },
+ {
+ "attestant_name": "Lauren Moscioni",
+ "title": "Associate, Morgan Stanley",
+ "quote": (
+ "Coming from the finance sector, "
+ "I wanted to learn more about real estate as an "
+ "alternative investment for ultra-high-net-worth clients. "
+ "What I got from MIT was a well-rounded, "
+ "holistic view of the state of the field."
+ ),
+ "position": 1,
+ "offerors": ["mitpe"],
+ },
+ {
+ "attestant_name": "Sebastian Bello",
+ "title": "MicroMasters learner",
+ "quote": (
+ "I did the Micromaster in supply chain, "
+ "what motivated me the most to finish it was "
+ "to have a certificate in supply chain "
+ "from MIT to improve my career. I was so engaged with "
+ "the courses that I wanted to continue learning, "
+ "now I'm doing the Supply Chain Management Master's at MIT."
+ ),
+ "position": 1,
+ "offerors": ["mitx"],
+ },
+ {
+ "attestant_name": "John Goodloe",
+ "title": (
+ "Senior Director for Society Business Solutions, "
+ "American Chemical Society (ACS)"
+ ),
+ "quote": (
+ "The courses at MIT Sloan Executive Education brought together "
+ "true leaders from across the world. You're in classes with diplomats, "
+ "CEOs, heads of family businesses and "
+ "Fortune 500s—every course is a different mix."
+ ),
+ "position": 1,
+ "offerors": ["see"],
+ },
+ {
+ "attestant_name": "Linda Obregon",
+ "title": "Founder and CEO",
+ "quote": (
+ "The program experience was 10-fold compared to what I expected. "
+ "They not only taught me how to create "
+ "a company in a week, but they gave me a "
+ "supportive network of the best "
+ "and brightest minds. After I left the Bootcamp, "
+ "I founded a startup that uses science and"
+ " plants to develop high-protein and tasty new foods."
+ ),
+ "position": 1,
+ "offerors": ["bootcamps"],
+ },
+ {
+ "attestant_name": "Murali Thyagarajan",
+ "title": "DBA & Application Support, NASDAQ",
+ "quote": (
+ "The course was easy to understand and had depth. "
+ "All the concepts were clearly laid out and explained. "
+ "This is the best course I have come across on this topic."
+ ),
+ "position": 2,
+ "offerors": ["xpro"],
+ },
+ {
+ "attestant_name": "Claudio Mirti",
+ "title": "Senior Advanced Analytics and AI Specialist, Microsoft",
+ "quote": (
+ "The Certificate Program in Machine Learning and AI is a great experience. "
+ "You get a lot of powerful insights during "
+ "exchanges with the professors and other "
+ "students—and you learn how to incorporate the "
+ "material into your daily work."
+ ),
+ "position": 2,
+ "offerors": ["mitpe"],
+ },
+ {
+ "attestant_name": "Stephen Okiya",
+ "title": "MITx Learner",
+ "quote": (
+ "I am really grateful for the efforts by MIT in coming up with innovative "
+ "ways of making quality education affordable "
+ "and accessible to everyone all over the world. "
+ "I am sure that the programs impact the lives of millions of "
+ "people either directly or indirectly "
+ "hence making the world a better place."
+ ),
+ "position": 2,
+ "offerors": ["mitx"],
+ },
+ {
+ "attestant_name": "Brenda Patel",
+ "title": "CEO, Bonova Advisory",
+ "quote": (
+ "You're a part of an ecosystem that is a life-long community of "
+ "people with the same goal to create something new and valuable. "
+ "It's an investment of five days, but it stays with you forever."
+ ),
+ "position": 2,
+ "offerors": ["see"],
+ },
+ {
+ "attestant_name": "Emily R. Wright",
+ "title": "Versatile Technologist",
+ "quote": (
+ "This is an awesome program for emerging leaders "
+ "and for those currently in leadership positions. "
+ "The knowledge, tools, and techniques "
+ "are very useful to companies worldwide. "
+ "I would highly recommend this program to all levels of employees."
+ ),
+ "position": 3,
+ "offerors": ["xpro"],
+ },
+ {
+ "attestant_name": "Anesha Santhanam",
+ "title": "MITx Learner",
+ "quote": (
+ "I recently completed an MITx course in Python, "
+ "and it was phenomenal. I looked into this course to "
+ "give me a better foundation and a deeper "
+ "understanding so I could use Python "
+ "in other settings and applications. "
+ "I'm using what I learned everyday to further my passion, "
+ "so thank you for providing me with this opportunity!"
+ ),
+ "position": 3,
+ "offerors": ["mitx"],
+ },
+]
+
+
+def load_fixtures(apps, schema_editor):
+ """
+ Load fixtures for testimonials
+ """
+ Attestation = apps.get_model("testimonials", "Attestation")
+ LearningResourceOfferor = apps.get_model(
+ "learning_resources", "LearningResourceOfferor"
+ )
+
+ for fixture in fixtures:
+ offerors = fixture.pop("offerors")
+ testimonial, _ = Attestation.objects.get_or_create(
+ attestant_name=fixture["attestant_name"],
+ title=fixture["title"],
+ defaults=fixture,
+ )
+ # make sure related offerors exists in system before setting
+ if LearningResourceOfferor.objects.filter(pk__in=offerors).count() == len(
+ offerors
+ ):
+ testimonial.offerors.set(offerors)
+ if not testimonial.avatar:
+ """
+ Save the image from fixture so alternate sizes are generated
+ """
+ with Path.open(
+ f"{settings.BASE_DIR}/testimonials/fixtures/avatars/{fixture["attestant_name"]}.png",
+ "rb",
+ ) as imagefile:
+ testimonial.avatar.save("avatar.png", File(imagefile), save=True)
+
+
+class Migration(migrations.Migration):
+ dependencies = []
+
+ operations = [
+ migrations.RunPython(load_fixtures, migrations.RunPython.noop),
+ ]
diff --git a/data_fixtures/migrations/0002_unit_page_copy_updates.py b/data_fixtures/migrations/0002_unit_page_copy_updates.py
new file mode 100644
index 0000000000..d8d3b29523
--- /dev/null
+++ b/data_fixtures/migrations/0002_unit_page_copy_updates.py
@@ -0,0 +1,131 @@
+# Generated by Django 4.2.13 on 2024-07-08 17:08
+
+from django.db import migrations
+
+fixtures = [
+ {
+ "name": "ocw",
+ "offeror_configuration": {
+ "audience": ["Students", "Professionals", "Educators", "Lifelong Learners"],
+ "value_prop": (
+ "For millions of learners and educators around the "
+ "world, OpenCourseWare shares free open educational resources from "
+ "across the entire MIT curriculum. With no "
+ "sign-up needed, thousands of downloadable materials, "
+ "and convenient online access, you're "
+ "empowered for self-paced learning and adapting these materials in "
+ "the ways that suit you best."
+ ),
+ },
+ "channel_configuration": {
+ "sub_heading": (
+ "For millions of learners and educators around "
+ "the world, OpenCourseWare shares free open educational resources"
+ " from across the entire MIT curriculum. With no sign-up needed, "
+ "thousands of downloadable materials, and convenient online access, "
+ "you're empowered for self-paced learning and adapting these "
+ "materials in the ways that suit you best."
+ ),
+ },
+ },
+ {
+ "name": "mitx",
+ "channel_configuration": {},
+ "offeror_configuration": {
+ "audience": ["Lifelong Learners", "Students", "Professionals"],
+ "certifications": ["Certificate of Completion", "MicroMasters Credential"],
+ },
+ },
+ {
+ "name": "mitpe",
+ "offeror_configuration": {
+ "audience": [
+ "Engineering Professionals",
+ "Technical Professionals",
+ "Scientists and Researchers",
+ "Senior Executives",
+ ],
+ "formats": ["Online", "In-Person", "Hybrid"],
+ "certifications": ["Certificate of Completion", "Professional Certificate"],
+ "content_types": ["Professional"],
+ "value_prop": (
+ "MIT Professional Education is a leader in "
+ "technology and engineering education"
+ " for working professionals pursuing career advancement, and "
+ "for organizations seeking to meet modern-day challenges "
+ "by expanding the knowledge and skills of their employees. "
+ "Courses are delivered in a range of formats—in-person "
+ "(on-campus and live virtual), online, and through hybrid "
+ "approaches—to meet the needs of today's learners."
+ ),
+ },
+ "channel_configuration": {
+ "heading": (
+ "Join a powerful network of innovators and master "
+ "skills the global market needs in years to come."
+ ),
+ "sub_heading": (
+ "MIT Professional Education is a leader in technology and engineering "
+ "education for working professionals pursuing career "
+ "advancement, and for organizations seeking to meet modern-day "
+ "challenges by expanding the knowledge and skills of their employees. "
+ "Courses are delivered in a range of formats—in-person (on-campus and"
+ " live virtual), online, and through hybrid approaches—to "
+ "meet the needs of today's learners."
+ ),
+ },
+ },
+ {
+ "name": "see",
+ "offeror_configuration": {
+ "audience": [
+ "Business Professionals",
+ "Senior Executives",
+ "Entrepreneurs",
+ "Intrapreneurs",
+ ],
+ "formats": ["Online", "In-Person", "Hybrid"],
+ "fee": ["Paid"],
+ "certifications": ["Certificate of Completion", "Professional Certificate"],
+ },
+ "channel_configuration": {},
+ },
+ {
+ "name": "xpro",
+ "offeror_configuration": {
+ "formats": ["Online", "In Person", "Hybrid"],
+ "certifications": ["Professional Certificate"],
+ },
+ "channel_configuration": {},
+ },
+]
+
+
+def update_copy(apps, schema_editor):
+ Channel = apps.get_model("channels", "Channel")
+ LearningResourceOfferor = apps.get_model(
+ "learning_resources", "LearningResourceOfferor"
+ )
+ for fixture in fixtures:
+ channel_configuration_updates = fixture["channel_configuration"]
+ offeror_configuration_updates = fixture["offeror_configuration"]
+ channel = Channel.objects.get(name=fixture["name"])
+ if Channel.objects.filter(name=fixture["name"]).exists():
+ for key, val in channel_configuration_updates.items():
+ channel.configuration[key] = val
+ channel.save()
+ if LearningResourceOfferor.objects.filter(code=fixture["name"]).exists():
+ offeror = LearningResourceOfferor.objects.get(code=fixture["name"])
+ for key, val in offeror_configuration_updates.items():
+ setattr(offeror, key, val)
+ offeror.save()
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("data_fixtures", "0001_add_testimonial_data"),
+ ]
+
+ operations = [
+ migrations.RunPython(update_copy, migrations.RunPython.noop),
+ ]
diff --git a/data_fixtures/migrations/__init__.py b/data_fixtures/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docker-compose-e2e-tests.yml b/docker-compose-e2e-tests.yml
index 826998eec9..56c922aa03 100644
--- a/docker-compose-e2e-tests.yml
+++ b/docker-compose-e2e-tests.yml
@@ -23,7 +23,15 @@ services:
- web
volumes:
- ./config/nginx.conf:/etc/nginx/conf.d/web.conf
+ - ./config/static-app.conf:/etc/nginx/templates/static-app.template
- ./frontends/mit-open/build:/src/frontends/mit-open/build
+ env_file:
+ - path: ./env/shared.env
+ networks:
+ default:
+ aliases:
+ - "open.odl.local"
+ - "api.open.odl.local"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8063"]
interval: 30s
@@ -48,7 +56,7 @@ services:
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/e2e_postgres # pragma: allowlist secret
PORT: 8061
- env_file: .env.ci
+ env_file: env/ci.env
depends_on:
db:
condition: service_healthy
@@ -72,7 +80,7 @@ services:
context: e2e_testing
environment:
- CI=true
- - BASE_URL=http://nginx:8063
+ - BASE_URL=http://open.odl.local:8063
depends_on:
nginx:
condition: service_healthy
diff --git a/docker-compose.yml b/docker-compose.yml
index ea06411db5..93c84fca44 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,17 +1,4 @@
x-environment: &py-environment
- DEBUG: ${DEBUG:-True}
- DEV_ENV: "True" # necessary to have nginx connect to web container
- NODE_ENV: ${NODE_ENV:-development}
- DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
- MITOPEN_SECURE_SSL_REDIRECT: "False"
- MITOPEN_DB_DISABLE_SSL: "True"
- MITOPEN_FEATURES_DEFAULT: ${MITOPEN_FEATURES_DEFAULT:-True}
- OPENSEARCH_URL: opensearch-node-mitopen:9200
- CELERY_TASK_ALWAYS_EAGER: "False"
- CELERY_BROKER_URL: redis://redis:6379/4
- CELERY_RESULT_BACKEND: redis://redis:6379/4
- TIKA_SERVER_ENDPOINT: ${TIKA_SERVER_ENDPOINT:-http://tika:9998/}
- TIKA_CLIENT_ONLY: "True"
services:
db:
@@ -60,14 +47,18 @@ services:
nginx:
profiles:
- backend
- image: nginx:1.27.0
+ build:
+ context: ./nginx
ports:
- "8063:8063"
links:
- web
+ environment:
+ PORT: 8063
+ NGINX_WORKERS: 1
+ NGINX_UWSGI_PASS: "web:8061"
volumes:
- - ./config/nginx.conf:/etc/nginx/conf.d/web.conf
- - ./frontends/mit-open/build:/src/frontends/mit-open/build
+ - ./config:/etc/nginx/templates
web:
profiles:
@@ -75,10 +66,16 @@ services:
build:
context: .
dockerfile: Dockerfile
- environment:
- <<: *py-environment
- PORT: 8061
- env_file: .env
+ env_file:
+ - path: env/shared.env
+ - path: env/shared.local.env
+ required: false
+ - path: env/backend.env
+ - path: env/backend.local.env
+ required: false
+ # DEPRECATED: legacy .env file at the repo root
+ - path: .env
+ required: false
command: ./scripts/run-django-dev.sh
stdin_open: true
tty: true
@@ -103,11 +100,16 @@ services:
yarn install --immutable
yarn workspace mit-open storybook &
yarn workspace mit-open watch:docker
- environment:
- NODE_ENV: ${NODE_ENV:-development}
- PORT: 8062
- MITOPEN_AXIOS_BASE_PATH: ${MITOPEN_BASE_URL}
- env_file: .env
+ env_file:
+ - path: env/shared.env
+ - path: env/shared.local.env
+ required: false
+ - path: env/frontend.env
+ - path: env/frontend.local.env
+ required: false
+ # DEPRECATED: legacy .env file at the repo root
+ - path: .env
+ required: false
ports:
- "8062:8062"
- "6006:6006"
@@ -120,8 +122,16 @@ services:
build:
context: .
dockerfile: Dockerfile
- environment: *py-environment
- env_file: .env
+ env_file:
+ - path: env/shared.env
+ - path: env/shared.local.env
+ required: false
+ - path: env/backend.env
+ - path: env/backend.local.env
+ required: false
+ # DEPRECATED: legacy .env file at the repo root
+ - path: .env
+ required: false
command: >
/bin/bash -c '
sleep 3;
diff --git a/docs/404.html b/docs/404.html
deleted file mode 100644
index c472b4ea0a..0000000000
--- a/docs/404.html
+++ /dev/null
@@ -1,24 +0,0 @@
----
-layout: default
----
-
-
-
-
-
404
-
-
Page not found :(
-
The requested page could not be found.
-
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index 0462a1a160..0000000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,33 +0,0 @@
-source "https://rubygems.org"
-
-# Hello! This is where you manage which Jekyll version is used to run.
-# When you want to use a different version, change it below, save the
-# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
-#
-# bundle exec jekyll serve
-#
-# This will help ensure the proper Jekyll version is running.
-# Happy Jekylling!
-# gem "jekyll", "~> 3.8.7"
-
-# This is the default theme for new Jekyll sites. You may change this to anything you like.
-gem "minima", "~> 2.0"
-
-# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
-# uncomment the line below. To upgrade, run `bundle update github-pages`.
-gem "github-pages", "~> 228", group: :jekyll_plugins
-
-# If you have any plugins, put them here!
-group :jekyll_plugins do
- gem "jekyll-feed", "~> 0.6"
-end
-
-# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-# and associated library.
-install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
- gem "tzinfo", "~> 1.2"
- gem "tzinfo-data"
-end
-
-# Performance-booster for watching directories on Windows
-gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
deleted file mode 100644
index f087a71f23..0000000000
--- a/docs/Gemfile.lock
+++ /dev/null
@@ -1,271 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- activesupport (6.0.6.1)
- concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (>= 0.7, < 2)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- zeitwerk (~> 2.2, >= 2.2.2)
- addressable (2.8.6)
- public_suffix (>= 2.0.2, < 6.0)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.11.1)
- colorator (1.1.0)
- commonmarker (0.23.10)
- concurrent-ruby (1.3.1)
- dnsruby (1.72.1)
- simpleidn (~> 0.2.1)
- em-websocket (0.5.3)
- eventmachine (>= 0.12.9)
- http_parser.rb (~> 0)
- ethon (0.16.0)
- ffi (>= 1.15.0)
- eventmachine (1.2.7)
- execjs (2.9.1)
- faraday (2.9.0)
- faraday-net_http (>= 2.0, < 3.2)
- faraday-net_http (3.1.0)
- net-http
- ffi (1.16.3)
- forwardable-extended (2.6.0)
- gemoji (3.0.1)
- github-pages (228)
- github-pages-health-check (= 1.17.9)
- jekyll (= 3.9.3)
- jekyll-avatar (= 0.7.0)
- jekyll-coffeescript (= 1.1.1)
- jekyll-commonmark-ghpages (= 0.4.0)
- jekyll-default-layout (= 0.1.4)
- jekyll-feed (= 0.15.1)
- jekyll-gist (= 1.5.0)
- jekyll-github-metadata (= 2.13.0)
- jekyll-include-cache (= 0.2.1)
- jekyll-mentions (= 1.6.0)
- jekyll-optional-front-matter (= 0.3.2)
- jekyll-paginate (= 1.1.0)
- jekyll-readme-index (= 0.3.0)
- jekyll-redirect-from (= 0.16.0)
- jekyll-relative-links (= 0.6.1)
- jekyll-remote-theme (= 0.4.3)
- jekyll-sass-converter (= 1.5.2)
- jekyll-seo-tag (= 2.8.0)
- jekyll-sitemap (= 1.4.0)
- jekyll-swiss (= 1.0.0)
- jekyll-theme-architect (= 0.2.0)
- jekyll-theme-cayman (= 0.2.0)
- jekyll-theme-dinky (= 0.2.0)
- jekyll-theme-hacker (= 0.2.0)
- jekyll-theme-leap-day (= 0.2.0)
- jekyll-theme-merlot (= 0.2.0)
- jekyll-theme-midnight (= 0.2.0)
- jekyll-theme-minimal (= 0.2.0)
- jekyll-theme-modernist (= 0.2.0)
- jekyll-theme-primer (= 0.6.0)
- jekyll-theme-slate (= 0.2.0)
- jekyll-theme-tactile (= 0.2.0)
- jekyll-theme-time-machine (= 0.2.0)
- jekyll-titles-from-headings (= 0.5.3)
- jemoji (= 0.12.0)
- kramdown (= 2.3.2)
- kramdown-parser-gfm (= 1.1.0)
- liquid (= 4.0.4)
- mercenary (~> 0.3)
- minima (= 2.5.1)
- nokogiri (>= 1.13.6, < 2.0)
- rouge (= 3.26.0)
- terminal-table (~> 1.4)
- github-pages-health-check (1.17.9)
- addressable (~> 2.3)
- dnsruby (~> 1.60)
- octokit (~> 4.0)
- public_suffix (>= 3.0, < 5.0)
- typhoeus (~> 1.3)
- html-pipeline (2.14.3)
- activesupport (>= 2)
- nokogiri (>= 1.4)
- http_parser.rb (0.8.0)
- i18n (1.14.5)
- concurrent-ruby (~> 1.0)
- jekyll (3.9.3)
- addressable (~> 2.4)
- colorator (~> 1.0)
- em-websocket (~> 0.5)
- i18n (>= 0.7, < 2)
- jekyll-sass-converter (~> 1.0)
- jekyll-watch (~> 2.0)
- kramdown (>= 1.17, < 3)
- liquid (~> 4.0)
- mercenary (~> 0.3.3)
- pathutil (~> 0.9)
- rouge (>= 1.7, < 4)
- safe_yaml (~> 1.0)
- jekyll-avatar (0.7.0)
- jekyll (>= 3.0, < 5.0)
- jekyll-coffeescript (1.1.1)
- coffee-script (~> 2.2)
- coffee-script-source (~> 1.11.1)
- jekyll-commonmark (1.4.0)
- commonmarker (~> 0.22)
- jekyll-commonmark-ghpages (0.4.0)
- commonmarker (~> 0.23.7)
- jekyll (~> 3.9.0)
- jekyll-commonmark (~> 1.4.0)
- rouge (>= 2.0, < 5.0)
- jekyll-default-layout (0.1.4)
- jekyll (~> 3.0)
- jekyll-feed (0.15.1)
- jekyll (>= 3.7, < 5.0)
- jekyll-gist (1.5.0)
- octokit (~> 4.2)
- jekyll-github-metadata (2.13.0)
- jekyll (>= 3.4, < 5.0)
- octokit (~> 4.0, != 4.4.0)
- jekyll-include-cache (0.2.1)
- jekyll (>= 3.7, < 5.0)
- jekyll-mentions (1.6.0)
- html-pipeline (~> 2.3)
- jekyll (>= 3.7, < 5.0)
- jekyll-optional-front-matter (0.3.2)
- jekyll (>= 3.0, < 5.0)
- jekyll-paginate (1.1.0)
- jekyll-readme-index (0.3.0)
- jekyll (>= 3.0, < 5.0)
- jekyll-redirect-from (0.16.0)
- jekyll (>= 3.3, < 5.0)
- jekyll-relative-links (0.6.1)
- jekyll (>= 3.3, < 5.0)
- jekyll-remote-theme (0.4.3)
- addressable (~> 2.0)
- jekyll (>= 3.5, < 5.0)
- jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
- rubyzip (>= 1.3.0, < 3.0)
- jekyll-sass-converter (1.5.2)
- sass (~> 3.4)
- jekyll-seo-tag (2.8.0)
- jekyll (>= 3.8, < 5.0)
- jekyll-sitemap (1.4.0)
- jekyll (>= 3.7, < 5.0)
- jekyll-swiss (1.0.0)
- jekyll-theme-architect (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-cayman (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-dinky (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-hacker (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-leap-day (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-merlot (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-midnight (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-minimal (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-modernist (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-primer (0.6.0)
- jekyll (> 3.5, < 5.0)
- jekyll-github-metadata (~> 2.9)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-slate (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-tactile (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-time-machine (0.2.0)
- jekyll (> 3.5, < 5.0)
- jekyll-seo-tag (~> 2.0)
- jekyll-titles-from-headings (0.5.3)
- jekyll (>= 3.3, < 5.0)
- jekyll-watch (2.2.1)
- listen (~> 3.0)
- jemoji (0.12.0)
- gemoji (~> 3.0)
- html-pipeline (~> 2.2)
- jekyll (>= 3.0, < 5.0)
- kramdown (2.3.2)
- rexml
- kramdown-parser-gfm (1.1.0)
- kramdown (~> 2.0)
- liquid (4.0.4)
- listen (3.9.0)
- rb-fsevent (~> 0.10, >= 0.10.3)
- rb-inotify (~> 0.9, >= 0.9.10)
- mercenary (0.3.6)
- mini_portile2 (2.8.6)
- minima (2.5.1)
- jekyll (>= 3.5, < 5.0)
- jekyll-feed (~> 0.9)
- jekyll-seo-tag (~> 2.1)
- minitest (5.23.1)
- net-http (0.4.1)
- uri
- nokogiri (1.16.5)
- mini_portile2 (~> 2.8.2)
- racc (~> 1.4)
- octokit (4.25.1)
- faraday (>= 1, < 3)
- sawyer (~> 0.9)
- pathutil (0.16.2)
- forwardable-extended (~> 2.6)
- public_suffix (4.0.7)
- racc (1.8.0)
- rb-fsevent (0.11.2)
- rb-inotify (0.11.1)
- ffi (~> 1.0)
- rexml (3.2.8)
- strscan (>= 3.0.9)
- rouge (3.26.0)
- rubyzip (2.3.2)
- safe_yaml (1.0.5)
- sass (3.7.4)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- sawyer (0.9.2)
- addressable (>= 2.3.5)
- faraday (>= 0.17.3, < 3)
- simpleidn (0.2.3)
- strscan (3.1.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
- thread_safe (0.3.6)
- typhoeus (1.4.1)
- ethon (>= 0.9.0)
- tzinfo (1.2.11)
- thread_safe (~> 0.1)
- tzinfo-data (1.2024.1)
- tzinfo (>= 1.0.0)
- unicode-display_width (1.8.0)
- uri (0.13.0)
- wdm (0.1.1)
- zeitwerk (2.6.15)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- github-pages (~> 228)
- jekyll-feed (~> 0.6)
- minima (~> 2.0)
- tzinfo (~> 1.2)
- tzinfo-data
- wdm (~> 0.1.0)
-
-BUNDLED WITH
- 2.1.4
diff --git a/docs/_config.yml b/docs/_config.yml
deleted file mode 100644
index 2eff3861b1..0000000000
--- a/docs/_config.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-title: Open Discussions
-description: This provides a discussion forum for use with other MIT applications.
-
-show_downloads: false
-google_analytics:
-
-remote_theme: pmarsceill/just-the-docs
-markdown: kramdown
-plugins:
- - jekyll-feed
diff --git a/env/.gitignore b/env/.gitignore
new file mode 100644
index 0000000000..dd8d87118d
--- /dev/null
+++ b/env/.gitignore
@@ -0,0 +1 @@
+*.local.env
diff --git a/env/backend.env b/env/backend.env
new file mode 100644
index 0000000000..0ed73a7b1d
--- /dev/null
+++ b/env/backend.env
@@ -0,0 +1,32 @@
+CELERY_BROKER_URL=redis://redis:6379/4
+CELERY_RESULT_BACKEND=redis://redis:6379/4
+CELERY_TASK_ALWAYS_EAGER=False
+
+# local hostname shenanigans
+CORS_ALLOWED_ORIGINS='["http://open.odl.local:8062"]'
+CSRF_TRUSTED_ORIGINS='["http://open.odl.local:8062", "http://api.open.odl.local:8063"]'
+CSRF_COOKIE_DOMAIN=open.odl.local
+CSRF_COOKIE_SECURE=False
+MITOPEN_COOKIE_DOMAIN=open.odl.local
+MITOPEN_COOKIE_NAME=discussions
+
+
+DEBUG=True
+DJANGO_LOG_LEVEL=INFO
+LOG_LEVEL=info
+DEV_ENV=true
+
+DATABASE_URL=postgres://postgres:postgres@db:5432/postgres
+
+MITOPEN_DB_DISABLE_SSL=True
+MITOPEN_FEATURES_DEFAULT=True
+MITOPEN_SECURE_SSL_REDIRECT=False
+
+OPENSEARCH_URL=opensearch-node-mitopen:9200
+OPENSEARCH_INDEX=discussions_local
+OPENSEARCH_INDEXING_CHUNK_SIZE=100
+
+PORT=8061
+
+TIKA_SERVER_ENDPOINT=http://tika:9998/
+TIKA_CLIENT_ONLY=True
diff --git a/env/backend.local.example.env b/env/backend.local.example.env
new file mode 100644
index 0000000000..3180b3bd70
--- /dev/null
+++ b/env/backend.local.example.env
@@ -0,0 +1,22 @@
+# MITOPEN_JWT_SECRET=
+# MITOPEN_USE_S3=True
+# MAILGUN_SENDER_DOMAIN=
+# MAILGUN_KEY=
+# MAILGUN_RECIPIENT_OVERRIDE=
+# STATUS_TOKEN=
+# AWS_ACCESS_KEY_ID=
+# AWS_SECRET_ACCESS_KEY=
+# AWS_STORAGE_BUCKET_NAME=
+# YOUTUBE_DEVELOPER_KEY=
+#
+# SOCIAL_AUTH_OL_OIDC_OIDC_ENDPOINT=
+# SOCIAL_AUTH_OL_OIDC_KEY=
+# SOCIAL_AUTH_OL_OIDC_SECRET=
+# AUTHORIZATION_URL=
+# ACCESS_TOKEN_URL=
+# USERINFO_URL=
+# KEYCLOAK_BASE_URL=
+# KEYCLOAK_REALM_NAME=
+#
+# POSTHOG_PROJECT_ID=
+# POSTHOG_PERSONAL_API_KEY=
diff --git a/.env.ci b/env/ci.env
similarity index 90%
rename from .env.ci
rename to env/ci.env
index 83c2ade6b7..d86ad0ff77 100644
--- a/.env.ci
+++ b/env/ci.env
@@ -10,10 +10,9 @@ CELERY_TASK_ALWAYS_EAGER=False
CELERY_BROKER_URL=redis://redis:6379/4
CELERY_RESULT_BACKEND=redis://redis:6379/4
TIKA_CLIENT_ONLY=True
-MITOPEN_BASE_URL=http://localhost:8063/
+MITOPEN_APP_BASE_URL=http://localhost:8063/
MAILGUN_KEY=fake_mailgun_key
MAILGUN_SENDER_DOMAIN=other.fake.site
OPENSEARCH_INDEX=testindex
-INDEXING_API_USERNAME=mitodl
MITOPEN_COOKIE_DOMAIN=localhost
MITOPEN_COOKIE_NAME=cookie_monster
diff --git a/env/frontend.env b/env/frontend.env
new file mode 100644
index 0000000000..a6adf9277f
--- /dev/null
+++ b/env/frontend.env
@@ -0,0 +1,3 @@
+NODE_ENV=development
+PORT=8062
+MITOPEN_AXIOS_WITH_CREDENTIALS=true
diff --git a/env/frontend.local.example.env b/env/frontend.local.example.env
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/env/shared.env b/env/shared.env
new file mode 100644
index 0000000000..67c604a1df
--- /dev/null
+++ b/env/shared.env
@@ -0,0 +1,5 @@
+MITOPEN_API_BASE_URL=http://api.open.odl.local:8063
+MITOPEN_APP_BASE_URL=http://open.odl.local:8062
+MITOPEN_SUPPORT_EMAIL=support@localhost
+
+POSTHOG_TIMEOUT_MS=1500
diff --git a/env/shared.local.example.env b/env/shared.local.example.env
new file mode 100644
index 0000000000..c18b9bdde5
--- /dev/null
+++ b/env/shared.local.example.env
@@ -0,0 +1,5 @@
+# MITOPEN_API_BASE_URL=http://api.open.odl.local:8063
+# MITOPEN_APP_BASE_URL=http://open.odl.local:8063
+# POSTHOG_PROJECT_API_KEY=
+# POSTHOG_TIMEOUT_MS=1500
+# EMBEDLY_KEY=
diff --git a/fixtures/common.py b/fixtures/common.py
index fbd6097be8..2166abab96 100644
--- a/fixtures/common.py
+++ b/fixtures/common.py
@@ -11,8 +11,6 @@
from pytest_mock import PytestMockWarning
from urllib3.exceptions import InsecureRequestWarning
-from main.factories import UserFactory
-
@pytest.fixture(autouse=True)
def silence_factory_logging(): # noqa: PT004
@@ -80,14 +78,6 @@ def mocked_celery(mocker):
)
-@pytest.fixture()
-def indexing_user(settings):
- """Sets and returns the indexing user""" # noqa: D401
- user = UserFactory.create()
- settings.INDEXING_API_USERNAME = user.username
- return user
-
-
@pytest.fixture()
def mocked_responses():
"""Mock responses fixture"""
diff --git a/frontends/.eslintrc.js b/frontends/.eslintrc.js
index a2843799ac..acf1615251 100644
--- a/frontends/.eslintrc.js
+++ b/frontends/.eslintrc.js
@@ -154,11 +154,6 @@ function restrictedImports({ paths = [], patterns = [] } = {}) {
message: "Please use @faker-js/faker/locale/en instead.",
allowTypeImports: true,
},
- {
- name: "@mui/icons-material",
- message: "Please use @mui/icons-material/ instead.",
- allowTypeImports: true,
- },
{
name: "@mui/material",
message: "Please use @mui/material/ instead.",
diff --git a/frontends/api/src/clients.ts b/frontends/api/src/clients.ts
index e5a3c8249b..b0768a3f5a 100644
--- a/frontends/api/src/clients.ts
+++ b/frontends/api/src/clients.ts
@@ -24,7 +24,7 @@ import {
import axiosInstance from "./axios"
-const BASE_PATH = process.env.MITOPEN_AXIOS_BASE_PATH?.replace(/\/+$/, "") ?? ""
+const BASE_PATH = process.env.MITOPEN_API_BASE_URL?.replace(/\/+$/, "") ?? ""
const learningResourcesApi = new LearningResourcesApi(
undefined,
diff --git a/frontends/api/src/generated/v0/api.ts b/frontends/api/src/generated/v0/api.ts
index 7ac7efb3a2..305da411e6 100644
--- a/frontends/api/src/generated/v0/api.ts
+++ b/frontends/api/src/generated/v0/api.ts
@@ -2071,18 +2071,6 @@ export interface ProgramCertificate {
* @memberof ProgramCertificate
*/
record_hash: string
- /**
- *
- * @type {string}
- * @memberof ProgramCertificate
- */
- program_letter_generate_url: string
- /**
- *
- * @type {string}
- * @memberof ProgramCertificate
- */
- program_letter_share_url: string
/**
*
* @type {string}
diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts
index 273caa2827..73f118a282 100644
--- a/frontends/api/src/generated/v1/api.ts
+++ b/frontends/api/src/generated/v1/api.ts
@@ -4285,18 +4285,6 @@ export interface ProgramCertificate {
* @memberof ProgramCertificate
*/
record_hash: string
- /**
- *
- * @type {string}
- * @memberof ProgramCertificate
- */
- program_letter_generate_url: string
- /**
- *
- * @type {string}
- * @memberof ProgramCertificate
- */
- program_letter_share_url: string
/**
*
* @type {string}
diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts
index a97bc4509c..13b930754a 100644
--- a/frontends/api/src/hooks/learningResources/index.ts
+++ b/frontends/api/src/hooks/learningResources/index.ts
@@ -427,6 +427,13 @@ const useSchoolsList = () => {
return useQuery(learningResources.schools())
}
+/*
+ * Not intended to be imported except for special cases.
+ * It's used in the ResourceCarousel to dynamically build a single useQueries hook
+ * from config because a React component cannot conditionally call hooks during renders.
+ */
+export { default as learningResourcesKeyFactory } from "./keyFactory"
+
export {
useLearningResourcesList,
useFeaturedLearningResourcesList,
diff --git a/frontends/api/src/test-utils/factories/learningResources.ts b/frontends/api/src/test-utils/factories/learningResources.ts
index 209be56bc5..d496c85ce5 100644
--- a/frontends/api/src/test-utils/factories/learningResources.ts
+++ b/frontends/api/src/test-utils/factories/learningResources.ts
@@ -412,6 +412,7 @@ const podcastEpisode: LearningResourceFactory = (
podcast_episode: {
id: uniqueEnforcerId.enforce(() => faker.number.int()),
duration: faker.helpers.arrayElement(["PT1H13M44S", "PT2H30M", "PT1M"]),
+ episode_link: faker.internet.url(),
},
},
overrides,
diff --git a/frontends/api/src/test-utils/urls.ts b/frontends/api/src/test-utils/urls.ts
index c1478f4cdf..ca3575339f 100644
--- a/frontends/api/src/test-utils/urls.ts
+++ b/frontends/api/src/test-utils/urls.ts
@@ -26,7 +26,7 @@ import type {
import type { BaseAPI } from "../generated/v1/base"
import type { BaseAPI as BaseAPIv0 } from "../generated/v0/base"
-const API_BASE_URL = process.env.MITOPEN_AXIOS_BASE_PATH
+const API_BASE_URL = process.env.MITOPEN_API_BASE_URL
// OpenAPI Generator declares parameters using interfaces, which makes passing
// them to functions a little annoying.
diff --git a/frontends/github-pages/.stylelintignore b/frontends/github-pages/.stylelintignore
deleted file mode 100644
index 325e0cd214..0000000000
--- a/frontends/github-pages/.stylelintignore
+++ /dev/null
@@ -1 +0,0 @@
-build/static/css
diff --git a/frontends/github-pages/craco.config.js b/frontends/github-pages/craco.config.js
deleted file mode 100644
index 673a158751..0000000000
--- a/frontends/github-pages/craco.config.js
+++ /dev/null
@@ -1,27 +0,0 @@
-module.exports = {
- webpack: {
- configure: (webpackConfig, { env, paths }) => {
- webpackConfig.module.rules.push(
- {
- test: /\.tsx?$/,
- loader: "babel-loader",
- },
- {
- test: /\.(svg|ttf|woff|woff2|eot|gif|png)$/,
- type: "asset/inline",
- },
- {
- test: /\.tsx?$/,
- use: {
- loader: "swc-loader",
- options: {
- parseMap: true,
- },
- },
- exclude: /node_modules/,
- },
- )
- return webpackConfig
- },
- },
-}
diff --git a/frontends/github-pages/package.json b/frontends/github-pages/package.json
deleted file mode 100644
index 55fc103019..0000000000
--- a/frontends/github-pages/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "github-pages",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "ol-components": "0.0.0",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "react-scripts": "5.0.1"
- },
- "scripts": {
- "start": "craco start",
- "build": "craco build"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "devDependencies": {
- "@craco/craco": "^7.1.0",
- "@types/eslint": "^8",
- "babel-loader": "^9.1.3",
- "eslint": "8",
- "eslint-config-react-app": "^7.0.1",
- "swc-loader": "^0.2.6"
- }
-}
diff --git a/frontends/github-pages/public/favicon.ico b/frontends/github-pages/public/favicon.ico
deleted file mode 100644
index 95d530203d..0000000000
Binary files a/frontends/github-pages/public/favicon.ico and /dev/null differ
diff --git a/frontends/github-pages/public/index.html b/frontends/github-pages/public/index.html
deleted file mode 100644
index 47e579a5c4..0000000000
--- a/frontends/github-pages/public/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
- MIT Open - Developer Materials
-
-
-
-
-
-
diff --git a/frontends/github-pages/public/robots.txt b/frontends/github-pages/public/robots.txt
deleted file mode 100644
index e9e57dc4d4..0000000000
--- a/frontends/github-pages/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/frontends/github-pages/public/static/images/course_search_banner.png b/frontends/github-pages/public/static/images/course_search_banner.png
deleted file mode 100644
index f4b6b18c86..0000000000
Binary files a/frontends/github-pages/public/static/images/course_search_banner.png and /dev/null differ
diff --git a/frontends/github-pages/public/static/images/mit-logo-transparent.svg b/frontends/github-pages/public/static/images/mit-logo-transparent.svg
deleted file mode 100644
index 67da18b013..0000000000
--- a/frontends/github-pages/public/static/images/mit-logo-transparent.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
diff --git a/frontends/github-pages/src/App.tsx b/frontends/github-pages/src/App.tsx
deleted file mode 100644
index 0218eab0c0..0000000000
--- a/frontends/github-pages/src/App.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from "react"
-import Header from "./Header"
-import { Grid, ThemeProvider, styled, Typography } from "ol-components"
-import { createBrowserRouter, RouterProvider } from "react-router-dom"
-import { RouteObject, Outlet } from "react-router"
-import "./style.css"
-
-const PUBLIC_URL = process.env.PUBLIC_URL || ""
-
-const Page = styled.div`
- margin: auto;
- max-width: 1200px;
-`
-
-const Links = styled.div`
- margin-top: 4rem;
-`
-
-const LinkItem = styled.div`
- margin-bottom: 3rem;
-
- a {
- text-decoration: underline;
- }
-`
-
-const routes: RouteObject[] = [
- {
- element: (
- <>
-
-
- >
- ),
- children: [
- {
- path: "/",
- element: (
-
-
- Developer Materials
-
- Libraries and tools for application development for use in your
- projects
-
-
-
-
- MIT Open Learning's React component library, presented with{" "}
- Storybook.
-
-
-
- The report from the most recent{" "}
- Playwright E2E testing
- run.
-
-
-
-
- ),
- },
- ],
- },
-]
-
-const router = createBrowserRouter(routes, {
- basename: PUBLIC_URL,
-})
-
-function App() {
- return (
-
-
-
- )
-}
-
-export default App
diff --git a/frontends/github-pages/src/Header.tsx b/frontends/github-pages/src/Header.tsx
deleted file mode 100644
index 5be09b0018..0000000000
--- a/frontends/github-pages/src/Header.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React, { FunctionComponent } from "react"
-import { styled, AppBar, Divider, Toolbar } from "ol-components"
-import { MITLogoLink } from "ol-utilities"
-import { Link } from "react-router-dom"
-
-const Bar = styled(AppBar)`
- background-color: ${({ theme }) => theme.custom.colors.white};
- color: ${({ theme }) => theme.typography.body1.color};
- min-height: 80px;
- display: flex;
- flex-direction: column;
- box-shadow: 0 2px 10px rgba(120 169 197 / 15%);
-`
-
-const StyledToolbar = styled(Toolbar)({
- flex: 1,
-})
-
-const LogoLink = styled(MITLogoLink)({
- width: 45,
- height: "auto",
- img: {
- height: 20,
- },
-})
-
-const StyledDivider = styled(Divider)({
- margin: "0.5em 1em",
-})
-
-const BrandLink = styled(Link)`
- font-weight: bold;
- color: ${({ theme }) => theme.palette.secondary.main};
-`
-
-const Spacer = styled.div`
- flex: 1;
-`
-
-const Header: FunctionComponent = () => {
- return (
-
-
-
-
- MIT Open
-
-
-
- )
-}
-
-export default Header
diff --git a/frontends/github-pages/src/index.tsx b/frontends/github-pages/src/index.tsx
deleted file mode 100644
index ad0f4f1ae1..0000000000
--- a/frontends/github-pages/src/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from "react"
-import ReactDOM from "react-dom/client"
-import App from "./App"
-
-const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
-root.render(
-
-
- ,
-)
diff --git a/frontends/github-pages/src/style.css b/frontends/github-pages/src/style.css
deleted file mode 100644
index c77e67f46c..0000000000
--- a/frontends/github-pages/src/style.css
+++ /dev/null
@@ -1,32 +0,0 @@
-html {
- font-family: "Source Sans Pro", helvetica, arial, sans-serif;
- color: #03152d;
-}
-
-body {
- background-color: #edeff5;
- margin: 0;
- padding: 0;
-}
-
-a {
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
-
- color: inherit;
-}
-
-h1 {
- font-size: 24px;
-}
-
-h2 {
- font-size: 18px;
-}
-
-h4 {
- font-size: 14px;
-}
diff --git a/frontends/github-pages/src/style.scss b/frontends/github-pages/src/style.scss
deleted file mode 100644
index 361bdebb94..0000000000
--- a/frontends/github-pages/src/style.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-html {
- font-family: "Source Sans Pro", helvetica, arial, sans-serif;
- color: #03152d;
-}
-
-body {
- background-color: #edeff5;
- margin: 0;
- padding: 0;
-}
-
-* {
- box-sizing: border-box;
-}
-
-a {
- text-decoration: none;
-
- &:hover {
- text-decoration: underline;
- }
-
- color: inherit;
-}
-
-h1 {
- font-size: 24px;
-}
-
-h2 {
- font-size: 18px;
-}
-
-h4 {
- font-size: 14px;
-}
diff --git a/frontends/github-pages/tsconfig.json b/frontends/github-pages/tsconfig.json
deleted file mode 100644
index 3cf966d7cf..0000000000
--- a/frontends/github-pages/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["src"]
-}
diff --git a/frontends/mit-open/.storybook/main.ts b/frontends/mit-open/.storybook/main.ts
index 13ef9317e5..eef19855b4 100644
--- a/frontends/mit-open/.storybook/main.ts
+++ b/frontends/mit-open/.storybook/main.ts
@@ -31,7 +31,6 @@ const config = {
docs: {
autodocs: "tag",
},
- staticDirs: ["../build"],
env: (config: any) => ({
...config,
PUBLIC_URL: process.env.PUBLIC_URL || "",
diff --git a/frontends/mit-open/.storybook/preview.tsx b/frontends/mit-open/.storybook/preview.tsx
index cc3f15b864..dff37eda94 100644
--- a/frontends/mit-open/.storybook/preview.tsx
+++ b/frontends/mit-open/.storybook/preview.tsx
@@ -17,10 +17,15 @@ const preview: Preview = {
options: {
// @ts-expect-error These have type {import("@storybook/types").IndexEntry}
// But this function is run in JS and seems not to be compiled.
- storySort: (a, b) =>
- a.id === b.id
- ? 0
- : a.id.localeCompare(b.id, undefined, { numeric: true }),
+ storySort: ({ id: a }, { id: b }) => {
+ if (a.slice(0, 3) === "old" && b.slice(0, 3) !== "old") {
+ return 1
+ }
+ if (b.slice(0, 3) === "old" && a.slice(0, 3) !== "old") {
+ return -1
+ }
+ return a.localeCompare(b, undefined, { numeric: true })
+ },
},
},
globals: {
diff --git a/frontends/mit-open/jest.config.ts b/frontends/mit-open/jest.config.ts
index 5500ff1746..0f60c4a731 100644
--- a/frontends/mit-open/jest.config.ts
+++ b/frontends/mit-open/jest.config.ts
@@ -18,7 +18,7 @@ const config: Config.InitialOptions = {
embedlyKey: "embedly_key",
axios_base_path: "https://api.mitopen-test.odl.mit.edu",
},
- MITOPEN_AXIOS_BASE_PATH: "https://api.mitopen-test.odl.mit.edu",
+ MITOPEN_API_BASE_URL: "https://api.mitopen-test.odl.mit.edu",
},
}
diff --git a/frontends/mit-open/package.json b/frontends/mit-open/package.json
index da54eb16d7..497c1a6771 100644
--- a/frontends/mit-open/package.json
+++ b/frontends/mit-open/package.json
@@ -9,12 +9,12 @@
},
"scripts": {
"watch": "NODE_ENV=development ENVIRONMENT=local webpack serve",
- "watch:docker": "API_DEV_PROXY_BASE_URL=http://nginx:8063 NODE_ENV=development ENVIRONMENT=local webpack serve",
+ "watch:docker": "API_DEV_PROXY_BASE_URL=http://nginx:8063 NODE_ENV=development ENVIRONMENT=docker webpack serve",
"watch:rc": "API_DEV_PROXY_BASE_URL=https://api.mitopen-rc.odl.mit.edu/ NODE_ENV=development ENVIRONMENT=local webpack serve",
"build": "webpack --config webpack.config.js --bail",
"build-exports": "webpack --config webpack.exports.js --bail",
"storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build --output-dir build/storybook"
+ "build-storybook": "storybook build"
},
"devDependencies": {
"@emotion/react": "^11.11.1",
@@ -33,6 +33,7 @@
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "14.5.2",
"@types/lodash": "^4.14.182",
+ "@types/react-refresh": "^0",
"@types/react-slick": "^0",
"@types/slick-carousel": "^1",
"copy-webpack-plugin": "^12.0.2",
@@ -42,6 +43,7 @@
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.6.1",
"ol-test-utilities": "0.0.0",
+ "react-refresh": "^0.14.2",
"storybook": "^8.1.10",
"swc-loader": "^0.2.6",
"tsconfig-paths-webpack-plugin": "^4.1.0",
@@ -57,8 +59,7 @@
},
"dependencies": {
"@ebay/nice-modal-react": "^1.2.13",
- "@mitodl/course-search-utils": "3.1.3",
- "@mui/icons-material": "^5.15.15",
+ "@mitodl/course-search-utils": "^3.1.4",
"@remixicon/react": "^4.2.0",
"@sentry/react": "^7.57.0",
"@tanstack/react-query": "^4.36.1",
diff --git a/frontends/mit-open/public/images/navdrawer/certificate.svg b/frontends/mit-open/public/images/navdrawer/certificate.svg
deleted file mode 100644
index 37410ceb24..0000000000
--- a/frontends/mit-open/public/images/navdrawer/certificate.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/courses.svg b/frontends/mit-open/public/images/navdrawer/courses.svg
deleted file mode 100644
index 7017824928..0000000000
--- a/frontends/mit-open/public/images/navdrawer/courses.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/departments.svg b/frontends/mit-open/public/images/navdrawer/departments.svg
deleted file mode 100644
index cd266fe5a5..0000000000
--- a/frontends/mit-open/public/images/navdrawer/departments.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/free.svg b/frontends/mit-open/public/images/navdrawer/free.svg
deleted file mode 100644
index 5c77692f56..0000000000
--- a/frontends/mit-open/public/images/navdrawer/free.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/learning_materials.svg b/frontends/mit-open/public/images/navdrawer/learning_materials.svg
deleted file mode 100644
index 061cc55e0f..0000000000
--- a/frontends/mit-open/public/images/navdrawer/learning_materials.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/new.svg b/frontends/mit-open/public/images/navdrawer/new.svg
deleted file mode 100644
index b338bb99da..0000000000
--- a/frontends/mit-open/public/images/navdrawer/new.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/pathways.svg b/frontends/mit-open/public/images/navdrawer/pathways.svg
deleted file mode 100644
index efe88552f3..0000000000
--- a/frontends/mit-open/public/images/navdrawer/pathways.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/popular.svg b/frontends/mit-open/public/images/navdrawer/popular.svg
deleted file mode 100644
index 8988481082..0000000000
--- a/frontends/mit-open/public/images/navdrawer/popular.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/programs.svg b/frontends/mit-open/public/images/navdrawer/programs.svg
deleted file mode 100644
index 6fb3c243d2..0000000000
--- a/frontends/mit-open/public/images/navdrawer/programs.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/provider.svg b/frontends/mit-open/public/images/navdrawer/provider.svg
deleted file mode 100644
index 1f5f1db289..0000000000
--- a/frontends/mit-open/public/images/navdrawer/provider.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/navdrawer/topics.svg b/frontends/mit-open/public/images/navdrawer/topics.svg
deleted file mode 100644
index 266ce1b713..0000000000
--- a/frontends/mit-open/public/images/navdrawer/topics.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/frontends/mit-open/public/images/ocw-logo.png b/frontends/mit-open/public/images/ocw-logo.png
index 9a5e2d0b2b..c4ffd9a2f0 100644
Binary files a/frontends/mit-open/public/images/ocw-logo.png and b/frontends/mit-open/public/images/ocw-logo.png differ
diff --git a/frontends/mit-open/src/common/urls.test.ts b/frontends/mit-open/src/common/urls.test.ts
index 7ffd04c531..d1f3ed85dc 100644
--- a/frontends/mit-open/src/common/urls.test.ts
+++ b/frontends/mit-open/src/common/urls.test.ts
@@ -1,22 +1,20 @@
import { login } from "./urls"
-const { MITOPEN_AXIOS_BASE_PATH } = process.env
+const { MITOPEN_API_BASE_URL } = process.env
test("login encodes the next parameter appropriately", () => {
expect(login()).toBe(
- `${MITOPEN_AXIOS_BASE_PATH}/login/ol-oidc/?next=http://localhost/`,
+ `${MITOPEN_API_BASE_URL}/login/ol-oidc/?next=http://localhost/`,
)
expect(login({})).toBe(
- `${MITOPEN_AXIOS_BASE_PATH}/login/ol-oidc/?next=http://localhost/`,
+ `${MITOPEN_API_BASE_URL}/login/ol-oidc/?next=http://localhost/`,
)
expect(
login({
pathname: "/foo/bar",
}),
- ).toBe(
- `${MITOPEN_AXIOS_BASE_PATH}/login/ol-oidc/?next=http://localhost/foo/bar`,
- )
+ ).toBe(`${MITOPEN_API_BASE_URL}/login/ol-oidc/?next=http://localhost/foo/bar`)
expect(
login({
@@ -24,6 +22,6 @@ test("login encodes the next parameter appropriately", () => {
search: "?cat=meow",
}),
).toBe(
- `${MITOPEN_AXIOS_BASE_PATH}/login/ol-oidc/?next=http://localhost/foo/bar%3Fcat%3Dmeow`,
+ `${MITOPEN_API_BASE_URL}/login/ol-oidc/?next=http://localhost/foo/bar%3Fcat%3Dmeow`,
)
})
diff --git a/frontends/mit-open/src/common/urls.ts b/frontends/mit-open/src/common/urls.ts
index 623e603d98..0fafaf37c5 100644
--- a/frontends/mit-open/src/common/urls.ts
+++ b/frontends/mit-open/src/common/urls.ts
@@ -40,8 +40,8 @@ export const makeChannelManageWidgetsPath = (
name: string,
) => generatePath(CHANNEL_EDIT_WIDGETS, { channelType, name })
-export const LOGIN = `${process.env.MITOPEN_AXIOS_BASE_PATH}/login/ol-oidc/`
-export const LOGOUT = `${process.env.MITOPEN_AXIOS_BASE_PATH}/logout/`
+export const LOGIN = `${process.env.MITOPEN_API_BASE_URL}/login/ol-oidc/`
+export const LOGOUT = `${process.env.MITOPEN_API_BASE_URL}/logout/`
/**
* Returns the URL to the login page, with a `next` parameter to redirect back
diff --git a/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.test.tsx b/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.test.tsx
index c7b8ea36ea..fcf720665a 100644
--- a/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.test.tsx
+++ b/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.test.tsx
@@ -9,7 +9,7 @@ import { channels as factory } from "api/test-utils/factories"
import { ThemeProvider } from "ol-components"
describe("ChannelMenu", () => {
- it("Includes links to channel management and widget management", async () => {
+ it("Includes links to channel management", async () => {
const channel = factory.channel()
setMockResponse.get(
urls.channels.details(channel.channel_type, channel.name),
@@ -29,10 +29,5 @@ describe("ChannelMenu", () => {
expect((item1 as HTMLAnchorElement).href).toContain(
`/c/${channel.channel_type}/${channel.name}/manage`,
)
-
- const item2 = screen.getByRole("menuitem", { name: "Manage Widgets" })
- expect((item2 as HTMLAnchorElement).href).toContain(
- `/c/${channel.channel_type}/${channel.name}/manage/widgets`,
- )
})
})
diff --git a/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.tsx b/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.tsx
index ba08fb2b58..c80f773ecd 100644
--- a/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.tsx
+++ b/frontends/mit-open/src/components/ChannelMenu/ChannelMenu.tsx
@@ -2,7 +2,7 @@ import React, { useMemo } from "react"
import * as routes from "../../common/urls"
import { SimpleMenu, ActionButton, styled } from "ol-components"
import type { SimpleMenuItem } from "ol-components"
-import SettingsIcon from "@mui/icons-material/Settings"
+import { RiSettings4Fill } from "@remixicon/react"
const InvertedButton = styled(ActionButton)({ color: "white" })
@@ -17,11 +17,6 @@ const ChannelMenu: React.FC<{ channelType: string; name: string }> = ({
label: "Channel Settings",
href: routes.makeChannelEditPath(channelType, name),
},
- {
- key: "widget",
- label: "Manage Widgets",
- href: routes.makeChannelManageWidgetsPath(channelType, name),
- },
]
}, [channelType, name])
return (
@@ -29,7 +24,7 @@ const ChannelMenu: React.FC<{ channelType: string; name: string }> = ({
items={items}
trigger={
-
+
}
/>
diff --git a/frontends/mit-open/src/components/RootTopicIcon/RootTopicIcon.tsx b/frontends/mit-open/src/components/RootTopicIcon/RootTopicIcon.tsx
index 2b0c298b48..6cf4f4a06e 100644
--- a/frontends/mit-open/src/components/RootTopicIcon/RootTopicIcon.tsx
+++ b/frontends/mit-open/src/components/RootTopicIcon/RootTopicIcon.tsx
@@ -1,31 +1,31 @@
import {
RiPaletteLine,
- RiSeedlingLine,
- RiBriefcaseLine,
- RiMacbookLine,
- RiBarChartBoxLine,
+ RiShakeHandsLine,
RiEarthLine,
- RiLightbulbLine,
- RiBuildingLine,
- RiServiceLine,
RiQuillPenLine,
- RiMicroscopeLine,
+ RiBriefcase3Line,
+ RiLightbulbFlashLine,
+ RiRobot2Line,
+ RiStethoscopeLine,
+ RiInfinityLine,
+ RiTestTubeLine,
+ RiUserSearchLine,
} from "@remixicon/react"
import React from "react"
/* TODO Using any icons until we have a solution for specifying them */
const ICON_MAP = {
- Business: RiBriefcaseLine,
- Energy: RiLightbulbLine,
- Engineering: RiBuildingLine,
+ Business: RiBriefcase3Line,
+ Energy: RiLightbulbFlashLine,
+ Engineering: RiRobot2Line,
"Fine Arts": RiPaletteLine,
- "Health and Medicine": RiServiceLine,
+ "Health and Medicine": RiStethoscopeLine,
Humanities: RiQuillPenLine,
- Mathematics: RiBarChartBoxLine,
- Science: RiMicroscopeLine,
- "Social Science": RiSeedlingLine,
+ Mathematics: RiInfinityLine,
+ Science: RiTestTubeLine,
+ "Social Science": RiUserSearchLine,
Society: RiEarthLine,
- "Teaching and Education": RiMacbookLine,
+ "Teaching and Education": RiShakeHandsLine,
}
type RootTopicIconProps = { name: string }
diff --git a/frontends/mit-open/src/page-components/CardTemplate/CardTemplate.tsx b/frontends/mit-open/src/page-components/CardTemplate/CardTemplate.tsx
index 77aa74385a..d88b45050c 100644
--- a/frontends/mit-open/src/page-components/CardTemplate/CardTemplate.tsx
+++ b/frontends/mit-open/src/page-components/CardTemplate/CardTemplate.tsx
@@ -7,7 +7,7 @@ import {
styled,
TruncateText,
} from "ol-components"
-import DragIndicatorIcon from "@mui/icons-material/DragIndicator"
+import { RiDraggable } from "@remixicon/react"
import {
DEFAULT_RESOURCE_IMG,
EmbedlyConfig,
@@ -238,7 +238,7 @@ const CardTemplate = ({
{sortable ? (
-
+
) : null}
diff --git a/frontends/mit-open/src/page-components/ChannelDetails/ChannelDetails.tsx b/frontends/mit-open/src/page-components/ChannelDetails/ChannelDetails.tsx
index 61337d8c2e..27dee8fa8e 100644
--- a/frontends/mit-open/src/page-components/ChannelDetails/ChannelDetails.tsx
+++ b/frontends/mit-open/src/page-components/ChannelDetails/ChannelDetails.tsx
@@ -2,7 +2,7 @@ import React, { useMemo } from "react"
import { styled, Typography, Box } from "ol-components"
import { capitalize } from "ol-utilities"
import { ChannelTypeEnum, Channel } from "api/v0"
-import OpenInNewIcon from "@mui/icons-material/OpenInNew"
+import { RiExternalLinkLine } from "@remixicon/react"
type ChannelDetailsProps = {
channel: Channel
@@ -88,9 +88,11 @@ const getFacetManifest = (channelType: ChannelTypeEnum) => {
{
name: "more_information",
title: "More Information",
+
labelFunction: (key: string, channelTitle: string) => (
-
- {channelTitle} website
+ // eslint-disable react/jsx-no-target-blank
+
+ {channelTitle} Website
),
order: 1,
@@ -107,7 +109,7 @@ const getFacetManifest = (channelType: ChannelTypeEnum) => {
},
{
name: "certifications",
- title: "Certificate",
+ title: "Certificates",
order: 0,
},
{
diff --git a/frontends/mit-open/src/page-components/Dialogs/AddToListDialog.tsx b/frontends/mit-open/src/page-components/Dialogs/AddToListDialog.tsx
index 5e9dcb9371..b087365b2f 100644
--- a/frontends/mit-open/src/page-components/Dialogs/AddToListDialog.tsx
+++ b/frontends/mit-open/src/page-components/Dialogs/AddToListDialog.tsx
@@ -11,10 +11,8 @@ import {
styled,
} from "ol-components"
-import LockOpenIcon from "@mui/icons-material/LockOpen"
-import LockIcon from "@mui/icons-material/Lock"
+import { RiLockLine, RiLockUnlockLine, RiAddLine } from "@remixicon/react"
-import AddIcon from "@mui/icons-material/Add"
import * as NiceModal from "@ebay/nice-modal-react"
import {
@@ -209,7 +207,7 @@ const PrivacyChip: React.FC = ({
selectedOption,
}) => {
const isPublic = selectedOption === publicOption
- const icon = isPublic ? :
+ const icon = isPublic ? :
return
}
@@ -253,7 +251,7 @@ const AddToListDialogInner: React.FC = ({
manageListDialogs.upsertLearningPath()}
>
-
+
@@ -269,7 +267,7 @@ const AddToListDialogInner: React.FC = ({
manageListDialogs.upsertUserList()}
>
-
+
diff --git a/frontends/mit-open/src/page-components/Header/Header.tsx b/frontends/mit-open/src/page-components/Header/Header.tsx
index 9f1a613a15..799cd61b45 100644
--- a/frontends/mit-open/src/page-components/Header/Header.tsx
+++ b/frontends/mit-open/src/page-components/Header/Header.tsx
@@ -9,7 +9,21 @@ import {
ClickAwayListener,
ActionButtonLink,
} from "ol-components"
-import { RiSearch2Line } from "@remixicon/react"
+import {
+ RiSearch2Line,
+ RiPencilRulerLine,
+ RiStackLine,
+ RiSignpostLine,
+ RiBookMarkedLine,
+ RiPresentationLine,
+ RiNodeTree,
+ RiVerifiedBadgeLine,
+ RiFileAddLine,
+ RiTimeLine,
+ RiHeartLine,
+ RiPriceTag3Line,
+ RiAwardLine,
+} from "@remixicon/react"
import { MITLogoLink, useToggle } from "ol-utilities"
import UserMenu from "./UserMenu"
import { MenuButton } from "./MenuButton"
@@ -79,10 +93,14 @@ const LogoLink = styled(MITLogoLink)(({ theme }) => ({
const LeftDivider = styled(Divider)({
margin: "0 24px",
+ height: "24px",
+ alignSelf: "auto",
})
const RightDivider = styled(Divider)(({ theme }) => ({
margin: "0 32px",
+ height: "24px",
+ alignSelf: "auto",
[theme.breakpoints.down("sm")]: {
margin: "0 16px",
},
@@ -151,29 +169,30 @@ const navData: NavData = {
items: [
{
title: "Courses",
- icon: "/static/images/navdrawer/courses.svg",
- description: "Learn with MIT instructors",
- href: querifiedSearchUrl({ tab: "courses" }),
+ icon: ,
+ description:
+ "Single courses on a specific subject, taught by MIT instructors",
+ href: querifiedSearchUrl({ resource_category: "course" }),
},
{
title: "Programs",
- icon: "/static/images/navdrawer/programs.svg",
+ icon: ,
description:
- "Learn in-depth from a series of courses and earn a certificate",
- href: querifiedSearchUrl({ tab: "programs" }),
+ "A series of courses for in-depth learning across a range of topics",
+ href: querifiedSearchUrl({ resource_category: "program" }),
},
{
title: "Pathways",
- icon: "/static/images/navdrawer/pathways.svg",
+ icon: ,
description:
"Achieve your learning goals with a curated collection of courses",
},
{
title: "Learning Materials",
- icon: "/static/images/navdrawer/learning_materials.svg",
+ icon: ,
description:
- "Free teaching and learning materials including videos, podcasts, lecture notes, etc.",
- href: querifiedSearchUrl({ tab: "learning-materials" }),
+ "Free learning and teaching materials, including videos, podcasts, lecture notes, and more",
+ href: querifiedSearchUrl({ resource_category: "learning_material" }),
},
],
},
@@ -182,17 +201,17 @@ const navData: NavData = {
items: [
{
title: "By Topic",
- icon: "/static/images/navdrawer/topics.svg",
+ icon: ,
href: TOPICS,
},
{
- title: "By Departments",
- icon: "/static/images/navdrawer/departments.svg",
+ title: "By Department",
+ icon: ,
href: DEPARTMENTS,
},
{
title: "By Provider",
- icon: "/static/images/navdrawer/provider.svg",
+ icon: ,
href: UNITS,
},
],
@@ -202,27 +221,27 @@ const navData: NavData = {
items: [
{
title: "New",
- icon: "/static/images/navdrawer/new.svg",
+ icon: ,
href: querifiedSearchUrl({ sortby: "new" }),
},
{
title: "Upcoming",
- icon: "/static/images/navdrawer/free.svg",
+ icon: ,
href: querifiedSearchUrl({ sortby: "upcoming" }),
},
{
title: "Popular",
- icon: "/static/images/navdrawer/popular.svg",
href: querifiedSearchUrl({ sortby: "-views" }),
+ icon: ,
},
{
title: "Free",
- icon: "/static/images/navdrawer/free.svg",
+ icon: ,
href: querifiedSearchUrl({ free: "true" }),
},
{
title: "With Certificate",
- icon: "/static/images/navdrawer/certificate.svg",
+ icon: ,
href: querifiedSearchUrl({ certification: "true" }),
},
],
diff --git a/frontends/mit-open/src/page-components/LearningResourceCard/LearningResourceCard.tsx b/frontends/mit-open/src/page-components/LearningResourceCard/LearningResourceCard.tsx
index 52689ea190..11b26ae985 100644
--- a/frontends/mit-open/src/page-components/LearningResourceCard/LearningResourceCard.tsx
+++ b/frontends/mit-open/src/page-components/LearningResourceCard/LearningResourceCard.tsx
@@ -10,8 +10,6 @@ import * as NiceModal from "@ebay/nice-modal-react"
import LearningResourceCardTemplate from "@/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate"
import type { LearningResourceCardTemplateProps } from "@/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate"
import { ActionButton, imgConfigs } from "ol-components"
-import PlaylistAddIcon from "@mui/icons-material/PlaylistAdd"
-import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder"
import {
AddToLearningPathDialog,
AddToUserListDialog,
@@ -20,6 +18,8 @@ import { LearningResource } from "api"
import { useUserMe } from "api/hooks/user"
import { useOpenLearningResourceDrawer } from "../LearningResourceDrawer/LearningResourceDrawer"
+import { RiMenuAddLine, RiBookmarkLine } from "@remixicon/react"
+
type LearningResourceCardProps = Pick<
LearningResourceCardTemplateProps,
"variant" | "resource" | "className" | "sortable" | "suppressImage"
@@ -77,7 +77,7 @@ const LearningResourceCard: React.FC = ({
aria-label="Add to Learning Path"
onClick={showAddToLearningPathDialog}
>
-
+
)}
{user?.is_authenticated && (
@@ -88,7 +88,7 @@ const LearningResourceCard: React.FC = ({
aria-label="Add to User List"
onClick={showAddToUserListDialog}
>
-
+
)}
diff --git a/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.test.tsx b/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.test.tsx
index 948901a64b..fc121f569d 100644
--- a/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.test.tsx
+++ b/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.test.tsx
@@ -142,7 +142,7 @@ describe("LearningResourceCard", () => {
/>,
)
- expect(!!screen.queryByTestId("DragIndicatorIcon")).toBe(sortable)
+ expect(!!screen.queryByTestId("CardDraggable")).toBe(sortable)
})
it.each([{ variant: "row" }, { variant: "column" }] as const)(
diff --git a/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.tsx b/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.tsx
index 1909c8dd66..7fda5f83e6 100644
--- a/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.tsx
+++ b/frontends/mit-open/src/page-components/LearningResourceCardTemplate/LearningResourceCardTemplate.tsx
@@ -2,7 +2,7 @@ import React, { useCallback } from "react"
import { ResourceTypeEnum } from "api"
import type { LearningResource } from "api"
import { Chip, styled } from "ol-components"
-import CalendarTodayIcon from "@mui/icons-material/CalendarToday"
+import { RiCalendarLine } from "@remixicon/react"
import {
formatDate,
pluralize,
@@ -72,7 +72,7 @@ const ResourceFooterDetails: React.FC<
return (
}
+ icon={}
label={formattedDate}
/>
)
diff --git a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx
index 358f21718d..ed54fb90f0 100644
--- a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx
+++ b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx
@@ -83,19 +83,13 @@ describe("ResourceCarousel", () => {
},
]
- const { resources, resolve } = setupApis({ autoResolve: false })
+ const { resources } = setupApis({ autoResolve: true })
renderWithProviders(
,
)
- expectLastProps(spyLearningResourceCard, {
- isLoading: true,
- ...cardProps,
- })
- resolve()
-
- const tabs = screen.getAllByRole("tab")
+ const tabs = await screen.findAllByRole("tab")
expect(tabs).toHaveLength(2)
expect(tabs[0]).toHaveTextContent("Resources")
@@ -178,7 +172,7 @@ describe("ResourceCarousel", () => {
expect(urlParams.get("professional")).toEqual("true")
})
- it("Shows the correct title", () => {
+ it("Shows the correct title", async () => {
const config: ResourceCarouselProps["config"] = [
{
label: "Resources",
@@ -193,8 +187,7 @@ describe("ResourceCarousel", () => {
renderWithProviders(
,
)
- expect(
- screen.getByRole("heading", { name: "My Favorite Carousel" }),
- ).toBeInTheDocument()
+
+ await screen.findByRole("heading", { name: "My Favorite Carousel" })
})
})
diff --git a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx
index b88abefe26..feac14bc43 100644
--- a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx
+++ b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx
@@ -1,9 +1,5 @@
import React from "react"
-import {
- useFeaturedLearningResourcesList,
- useLearningResourcesList,
- useLearningResourcesSearch,
-} from "api/hooks/learningResources"
+import { learningResourcesKeyFactory } from "api/hooks/learningResources"
import {
Carousel,
TabButton,
@@ -13,14 +9,14 @@ import {
styled,
Typography,
} from "ol-components"
-import type {
- TabConfig,
- ResourceDataSource,
- SearchDataSource,
- FeaturedDataSource,
-} from "./types"
-import { LearningResource } from "api"
+import type { TabConfig } from "./types"
+import { LearningResource, PaginatedLearningResourceList } from "api"
import { ResourceCard } from "../ResourceCard/ResourceCard"
+import {
+ useQueries,
+ UseQueryResult,
+ UseQueryOptions,
+} from "@tanstack/react-query"
const StyledCarousel = styled(Carousel)({
/**
@@ -40,107 +36,6 @@ const StyledCarousel = styled(Carousel)({
},
})
-type DataPanelProps = {
- dataConfig: T
- isLoading?: boolean
- children: ({
- resources,
- childrenLoading,
- }: {
- resources: LearningResource[]
- childrenLoading: boolean
- }) => React.ReactNode
-}
-
-type LoadTabButtonProps = {
- config: FeaturedDataSource
- label: React.ReactNode
- key: number
- value: string
-}
-
-const ResourcesData: React.FC> = ({
- dataConfig,
- children,
-}) => {
- const { data, isLoading } = useLearningResourcesList(dataConfig.params)
- return children({
- resources: data?.results ?? [],
- childrenLoading: isLoading,
- })
-}
-
-const SearchData: React.FC> = ({
- dataConfig,
- children,
-}) => {
- const { data, isLoading } = useLearningResourcesSearch(dataConfig.params)
- return children({
- resources: data?.results ?? [],
- childrenLoading: isLoading,
- })
-}
-
-const FeaturedData: React.FC> = ({
- dataConfig,
- children,
-}) => {
- const { data, isLoading } = useFeaturedLearningResourcesList(
- dataConfig.params,
- )
- return children({
- resources: data?.results ?? [],
- childrenLoading: isLoading,
- })
-}
-
-/**
- * A wrapper to load data based `TabConfig.data`.
- *
- * For each `TabConfig.data.type`, a different API endpoint, and hence
- * react-query hook, is used. Since hooks can't be called conditionally within
- * a single component, each type of data is handled in a separate component.
- */
-const DataPanel: React.FC = ({
- dataConfig,
- isLoading,
- children,
-}) => {
- if (!isLoading) {
- switch (dataConfig.type) {
- case "resources":
- return {children}
- case "lr_search":
- return {children}
- case "lr_featured":
- return {children}
- default:
- // @ts-expect-error This will always be an error if the switch statement
- // is exhaustive since dataConfig will have type `never`
- throw new Error(`Unknown data type: ${dataConfig.type}`)
- }
- } else
- return children({
- resources: [],
- childrenLoading: true,
- })
-}
-
-/**
- * Tab button that loads the resource, so we can determine if it needs to be
- * displayed or not. This shouldn't cause double-loading since React Query
- * should only run the thing once - when you switch into the tab, the data
- * should already be in the cache.
- */
-
-const LoadFeaturedTabButton: React.FC = (props) => {
- const { data, isLoading } = useFeaturedLearningResourcesList(
- props.config.params,
- )
-
- return !isLoading && data && data.count > 0 ? : null
-}
-
const HeaderRow = styled.div(({ theme }) => ({
display: "flex",
flexWrap: "wrap",
@@ -195,48 +90,46 @@ const TabsList = styled(TabButtonList)({
type ContentProps = {
resources: LearningResource[]
- isLoading?: boolean
+ childrenLoading?: boolean
tabConfig: TabConfig
}
type PanelChildrenProps = {
config: TabConfig[]
+ queries: UseQueryResult[]
children: (props: ContentProps) => React.ReactNode
- isLoading?: boolean
}
const PanelChildren: React.FC = ({
config,
+ queries,
children,
- isLoading,
}) => {
if (config.length === 1) {
- return (
-
- {({ resources, childrenLoading }) =>
- children({
- resources,
- isLoading: childrenLoading || isLoading,
- tabConfig: config[0],
- })
- }
-
- )
+ const { data, isLoading } = queries[0]
+ const resources = data?.results ?? []
+
+ return children({
+ resources,
+ childrenLoading: isLoading,
+ tabConfig: config[0],
+ })
}
return (
<>
- {config.map((tabConfig, index) => (
-
-
- {({ resources, childrenLoading }) =>
- children({
- resources,
- isLoading: childrenLoading || isLoading,
- tabConfig,
- })
- }
-
-
- ))}
+ {config.map((tabConfig, index) => {
+ const { data, isLoading } = queries[index]
+ const resources = data?.results ?? []
+
+ return (
+
+ {children({
+ resources,
+ childrenLoading: isLoading,
+ tabConfig,
+ })}
+
+ )
+ })}
>
)
}
@@ -258,6 +151,7 @@ type ResourceCarouselProps = {
title: string
className?: string
isLoading?: boolean
+ "data-testid"?: string
}
/**
* A tabbed carousel that fetches resources based on the configuration provided.
@@ -275,12 +169,45 @@ const ResourceCarousel: React.FC = ({
title,
className,
isLoading,
+ "data-testid": dataTestId,
}) => {
const [tab, setTab] = React.useState("0")
const [ref, setRef] = React.useState(null)
+ const queries = useQueries({
+ queries: config.map(
+ (
+ tab,
+ ): UseQueryOptions<
+ PaginatedLearningResourceList,
+ unknown,
+ unknown,
+ // The factory-generated types for queryKeys are very specific (tuples not arrays)
+ // and assignable to the loose QueryKey (readonly unknown[]) on the UseQueryOptions generic.
+ // But! as a queryFn arg the more specific QueryKey cannot be assigned to the looser QueryKey.
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ any
+ > => {
+ switch (tab.data.type) {
+ case "resources":
+ return learningResourcesKeyFactory.list(tab.data.params)
+ case "lr_search":
+ return learningResourcesKeyFactory.search(tab.data.params)
+ case "lr_featured":
+ return learningResourcesKeyFactory.featured(tab.data.params)
+ }
+ },
+ ),
+ })
+
+ const allChildrenLoaded = queries.every(({ isLoading }) => !isLoading)
+ const allChildrenEmpty = queries.every(({ data }) => !data?.count)
+ if (!isLoading && allChildrenLoaded && allChildrenEmpty) {
+ return null
+ }
+
return (
-
+
{title}
@@ -288,29 +215,32 @@ const ResourceCarousel: React.FC = ({
{config.length > 1 ? (
setTab(newValue)}>
- {config.map((tabConfig, index) =>
- tabConfig.data.type === "lr_featured" ? (
-
- ) : (
+ {config.map((tabConfig, index) => {
+ if (
+ !isLoading &&
+ !queries[index].isLoading &&
+ !queries[index].data?.count
+ ) {
+ return null
+ }
+ return (
- ),
- )}
+ )
+ })}
) : null}
-
- {({ resources, isLoading: childrenLoading, tabConfig }) => (
+ []}
+ >
+ {({ resources, childrenLoading, tabConfig }) => (
{isLoading || childrenLoading
? Array.from({ length: 6 }).map((_, index) => (
diff --git a/frontends/mit-open/src/page-components/SearchDisplay/ProfessionalToggle.tsx b/frontends/mit-open/src/page-components/SearchDisplay/ProfessionalToggle.tsx
index a72c0975a8..9edae6eec9 100644
--- a/frontends/mit-open/src/page-components/SearchDisplay/ProfessionalToggle.tsx
+++ b/frontends/mit-open/src/page-components/SearchDisplay/ProfessionalToggle.tsx
@@ -109,12 +109,12 @@ const ProfessionalToggle: React.FC<{
- Content developed from MIT’s academic curriculum
+ Content developed from MIT's academic curriculum
- Content developed from MIT’s professional curriculum
+ Content developed from MIT's professional curriculum
diff --git a/frontends/mit-open/src/page-components/SearchDisplay/ResourceTypeTabs.tsx b/frontends/mit-open/src/page-components/SearchDisplay/ResourceCategoryTabs.tsx
similarity index 53%
rename from frontends/mit-open/src/page-components/SearchDisplay/ResourceTypeTabs.tsx
rename to frontends/mit-open/src/page-components/SearchDisplay/ResourceCategoryTabs.tsx
index d28955dde4..95e74ff481 100644
--- a/frontends/mit-open/src/page-components/SearchDisplay/ResourceTypeTabs.tsx
+++ b/frontends/mit-open/src/page-components/SearchDisplay/ResourceCategoryTabs.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from "react"
+import React from "react"
import {
TabButton,
TabContext,
@@ -6,7 +6,7 @@ import {
TabPanel,
styled,
} from "ol-components"
-import { ResourceTypeEnum, LearningResourcesSearchResponse } from "api"
+import { ResourceCategoryEnum, LearningResourcesSearchResponse } from "api"
const TabsList = styled(TabButtonList)({
".MuiTabScrollButton-root.Mui-disabled": {
@@ -22,19 +22,19 @@ type TabConfig = {
label: string
name: string
defaultTab?: boolean
- resource_type: ResourceTypeEnum[]
+ resource_category: ResourceCategoryEnum | null
}
type Aggregations = LearningResourcesSearchResponse["metadata"]["aggregations"]
-const resourceTypeCounts = (aggregations?: Aggregations) => {
+const resourceCategoryCounts = (aggregations?: Aggregations) => {
if (!aggregations) return null
- const buckets = aggregations?.resource_type ?? []
+ const buckets = aggregations?.resource_category ?? []
const counts = buckets.reduce(
(acc, bucket) => {
- acc[bucket.key as ResourceTypeEnum] = bucket.doc_count
+ acc[bucket.key as ResourceCategoryEnum] = bucket.doc_count
return acc
},
- {} as Record,
+ {} as Record,
)
return counts
}
@@ -52,68 +52,75 @@ const appendCount = (label: string, count?: number | null) => {
/**
*
*/
-const ResourceTypesTabContext: React.FC<{
+const ResourceCategoryTabContext: React.FC<{
activeTabName: string
children: React.ReactNode
}> = ({ activeTabName, children }) => {
return {children}
}
-type ResourceTypeTabsProps = {
+type ResourceCategoryTabsProps = {
aggregations?: Aggregations
tabs: TabConfig[]
setSearchParams: (fn: (prev: URLSearchParams) => URLSearchParams) => void
onTabChange?: () => void
className?: string
}
-const ResourceTypeTabList: React.FC = ({
+const ResourceCategoryTabList: React.FC = ({
tabs,
aggregations,
setSearchParams,
onTabChange,
className,
}) => {
- const withCounts = useMemo(() => {
- const counts = resourceTypeCounts(aggregations)
- return tabs.map((t) => {
- const resourceTypes =
- t.resource_type.length === 0
- ? Object.values(ResourceTypeEnum)
- : t.resource_type
- const count = counts
- ? resourceTypes
- .map((rt) => counts[rt] ?? 0)
- .reduce((acc, c) => acc + c, 0)
- : null
- return { ...t, label: appendCount(t.label, count) }
- })
- }, [tabs, aggregations])
+ const counts = resourceCategoryCounts(aggregations)
+ const allCount = aggregations?.resource_category
+ ? (aggregations.resource_category || []).reduce((count, bucket) => {
+ count = count + bucket.doc_count
+ return count
+ }, 0)
+ : undefined
+
return (
{
const tab = tabs.find((t) => t.name === value)
- if (!tab) return
setSearchParams((prev) => {
const next = new URLSearchParams(prev)
- if (tab.defaultTab) {
- next.delete("tab")
+ if (tab?.resource_category) {
+ next.set("resource_category", tab.resource_category)
} else {
- next.set("tab", value)
+ next.delete("resource_category")
}
return next
})
onTabChange?.()
}}
>
- {withCounts.map((t) => {
- return
+ {tabs.map((t) => {
+ let count: number | undefined
+ if (t.name === "all") {
+ count = allCount
+ } else {
+ count =
+ counts && t.resource_category
+ ? counts[t.resource_category] ?? 0
+ : undefined
+ }
+ return (
+
+ )
})}
)
}
-const ResourceTypeTabPanels: React.FC<{
+const ResourceCategoryTabPanels: React.FC<{
tabs: TabConfig[]
children?: React.ReactNode
}> = ({ tabs, children }) => {
@@ -129,26 +136,26 @@ const ResourceTypeTabPanels: React.FC<{
}
/**
- * Components for a tabbed search UI with tabs controlling resource_type facet.
+ * Components for a tabbed search UI with tabs controlling resource_category facet.
*
* Intended usage is:
* ```jsx
- *
- *
- *
+ *
+ *
+ *
* Panel Content
- *
- *
+ *
+ *
* ```
*
* These are exported as three separate components (Context, TabList, TabPanels)
* to facilitate placement within a grid layout.
*/
-const ResourceTypeTabs = {
- Context: ResourceTypesTabContext,
- TabList: ResourceTypeTabList,
- TabPanels: ResourceTypeTabPanels,
+const ResourceCategoryTabs = {
+ Context: ResourceCategoryTabContext,
+ TabList: ResourceCategoryTabList,
+ TabPanels: ResourceCategoryTabPanels,
}
-export { ResourceTypeTabs }
+export { ResourceCategoryTabs }
export type { TabConfig }
diff --git a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx
index 94ea6772bc..391737997b 100644
--- a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx
+++ b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx
@@ -25,7 +25,7 @@ import {
import {
LearningResourcesSearchApiLearningResourcesSearchRetrieveRequest as LRSearchRequest,
- ResourceTypeEnum,
+ ResourceCategoryEnum,
} from "api"
import { useLearningResourcesSearch } from "api/hooks/learningResources"
import { GridColumn, GridContainer } from "@/components/GridLayout/GridLayout"
@@ -40,9 +40,9 @@ import type {
FacetManifest,
} from "@mitodl/course-search-utils"
import _ from "lodash"
-import { ResourceTypeTabs } from "./ResourceTypeTabs"
+import { ResourceCategoryTabs } from "./ResourceCategoryTabs"
import ProfessionalToggle from "./ProfessionalToggle"
-import type { TabConfig } from "./ResourceTypeTabs"
+import type { TabConfig } from "./ResourceCategoryTabs"
import { ResourceListCard } from "../ResourceCard/ResourceCard"
import { useSearchParams } from "@mitodl/course-search-utils/react-router"
@@ -51,25 +51,25 @@ export const StyledSelect = styled(SimpleSelect)`
min-width: 160px;
`
-export const StyledResourceTabs = styled(ResourceTypeTabs.TabList)`
+const StyledResourceTabs = styled(ResourceCategoryTabs.TabList)`
margin-top: 0 px;
`
-export const DesktopSortContainer = styled.div`
+const DesktopSortContainer = styled.div`
float: right;
${({ theme }) => theme.breakpoints.down("md")} {
display: none;
}
`
-export const MobileSortContainer = styled.div`
+const MobileSortContainer = styled.div`
float: right;
${({ theme }) => theme.breakpoints.up("md")} {
display: none;
}
`
-export const FacetStyles = styled.div`
+const FacetStyles = styled.div`
* {
color: ${({ theme }) => theme.palette.secondary.main};
}
@@ -270,7 +270,7 @@ export const FacetStyles = styled.div`
}
`
-export const FilterTitle = styled.div`
+const FilterTitle = styled.div`
svg {
margin-left: 8px;
}
@@ -282,7 +282,7 @@ export const FilterTitle = styled.div`
color: ${({ theme }) => theme.custom.colors.darkGray2};
`
-export const FacetsTitleContainer = styled.div`
+const FacetsTitleContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
@@ -293,8 +293,13 @@ export const FacetsTitleContainer = styled.div`
const PaginationContainer = styled.div`
display: flex;
justify-content: end;
- margin-top: 16px;
- margin-bottom: 16px;
+ margin-top: 24px;
+ margin-bottom: 80px;
+
+ ${({ theme }) => theme.breakpoints.down("md")} {
+ margin-top: 16px;
+ margin-bottom: 24px;
+ }
ul li button.Mui-selected {
${({ theme }) => css({ ...theme.typography.subtitle1 })}
@@ -406,39 +411,36 @@ const StyledGridContainer = styled(GridContainer)`
const PAGE_SIZE = 10
const MAX_PAGE = 50
-export const getLastPage = (count: number): number => {
+const getLastPage = (count: number): number => {
const pages = Math.ceil(count / PAGE_SIZE)
return pages > MAX_PAGE ? MAX_PAGE : pages
}
-export const TABS: TabConfig[] = [
+const TABS: TabConfig[] = [
{
name: "all",
label: "All",
defaultTab: true,
- resource_type: [],
+ resource_category: null,
},
{
name: "courses",
label: "Courses",
- resource_type: [ResourceTypeEnum.Course],
+ resource_category: ResourceCategoryEnum.Course,
},
{
name: "programs",
label: "Programs",
- resource_type: [ResourceTypeEnum.Program],
+ resource_category: ResourceCategoryEnum.Program,
},
{
name: "learning-materials",
label: "Learning Materials",
- resource_type: Object.values(ResourceTypeEnum).filter(
- (v) => v !== ResourceTypeEnum.Course && v !== ResourceTypeEnum.Program,
- ),
+ resource_category: ResourceCategoryEnum.LearningMaterial,
},
]
-export const ALL_RESOURCE_TABS = TABS.map((t) => t.resource_type)
-export const SORT_OPTIONS = [
+const SORT_OPTIONS = [
{
label: "Best Match",
value: "",
@@ -505,21 +507,27 @@ const SearchDisplay: React.FC = ({
const [searchParams] = useSearchParams()
const scrollHook = useRef(null)
const activeTab =
- TABS.find((t) => t.name === searchParams.get("tab")) ??
+ TABS.find(
+ (t) => t.resource_category === searchParams.get("resource_category"),
+ ) ??
TABS.find((t) => t.defaultTab) ??
TABS[0]
const allParams = useMemo(() => {
return {
...constantSearchParams,
- resource_type: activeTab.resource_type,
+ resource_category: activeTab.resource_category
+ ? [activeTab.resource_category]
+ : undefined,
...requestParams,
- aggregations: facetNames as LRSearchRequest["aggregations"],
+ aggregations: (facetNames || []).concat([
+ "resource_category",
+ ]) as LRSearchRequest["aggregations"],
offset: (page - 1) * PAGE_SIZE,
}
}, [
requestParams,
constantSearchParams,
- activeTab?.resource_type,
+ activeTab?.resource_category,
facetNames,
page,
])
@@ -571,7 +579,7 @@ const SearchDisplay: React.FC = ({
return (
-
+
= ({
aggregations={data?.metadata.aggregations}
onTabChange={() => setPage(1)}
/>
-
+
+
-
+
)
diff --git a/frontends/mit-open/src/page-components/SearchDisplay/SearchInput.tsx b/frontends/mit-open/src/page-components/SearchDisplay/SearchInput.tsx
index 33b91b0617..31992e8683 100644
--- a/frontends/mit-open/src/page-components/SearchDisplay/SearchInput.tsx
+++ b/frontends/mit-open/src/page-components/SearchDisplay/SearchInput.tsx
@@ -1,6 +1,5 @@
import React, { useCallback } from "react"
-import ClearIcon from "@mui/icons-material/Clear"
import {
Input,
AdornmentButton,
@@ -10,7 +9,7 @@ import {
css,
} from "ol-components"
import type { InputProps } from "ol-components"
-import { RiSearch2Line } from "@remixicon/react"
+import { RiSearch2Line, RiCloseLine } from "@remixicon/react"
export interface SearchSubmissionEvent {
target: {
@@ -139,7 +138,7 @@ const SearchInput: React.FC = (props) => {
aria-label="Clear search text"
onClick={props.onClear}
>
-
+
)
}
diff --git a/frontends/mit-open/src/page-components/SearchSubscriptionToggle/SearchSubscriptionToggle.tsx b/frontends/mit-open/src/page-components/SearchSubscriptionToggle/SearchSubscriptionToggle.tsx
index 4b49f5fca8..1415155a97 100644
--- a/frontends/mit-open/src/page-components/SearchSubscriptionToggle/SearchSubscriptionToggle.tsx
+++ b/frontends/mit-open/src/page-components/SearchSubscriptionToggle/SearchSubscriptionToggle.tsx
@@ -7,10 +7,10 @@ import {
} from "api/hooks/searchSubscription"
import { Button, SimpleMenu } from "ol-components"
import type { SimpleMenuItem } from "ol-components"
-import ExpandMoreSharpIcon from "@mui/icons-material/ExpandMoreSharp"
+import { RiArrowDownSLine, RiMailLine } from "@remixicon/react"
import { useUserMe } from "api/hooks/user"
import { SourceTypeEnum } from "api"
-import MailOutlineIcon from "@mui/icons-material/MailOutline"
+
import { SignupPopover } from "../SignupPopover/SignupPopover"
type SearchSubscriptionToggleProps = {
@@ -54,7 +54,7 @@ const SearchSubscriptionToggle: React.FC = ({
return (
}>
+ }>
Following
}
@@ -67,7 +67,7 @@ const SearchSubscriptionToggle: React.FC = ({
}
+ startIcon={}
onClick={(e) => {
if (user?.is_authenticated) {
subscriptionCreate.mutateAsync({
diff --git a/frontends/mit-open/src/page-components/SignupPopover/SignupPopover.tsx b/frontends/mit-open/src/page-components/SignupPopover/SignupPopover.tsx
index 131eb603db..3e034c9279 100644
--- a/frontends/mit-open/src/page-components/SignupPopover/SignupPopover.tsx
+++ b/frontends/mit-open/src/page-components/SignupPopover/SignupPopover.tsx
@@ -35,7 +35,7 @@ const SignupPopover: React.FC = (props) => {
As a member, get personalized recommendations, curate learning lists,
- and follow your areas of interests.
+ and follow your areas of interest.