diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8708d1ac8e..131fde9ad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn @@ -123,14 +123,20 @@ jobs: - name: Style lint run: yarn run style-lint - - name: Webpack build - run: yarn run build - env: - MITOL_API_BASE_URL: https://api.mitlearn-test.odl.mit.edu - - name: Lints run: yarn run lint-check + - name: Build Next.js frontend + run: yarn workspace main build + env: + NODE_ENV: production + NEXT_PUBLIC_ORIGIN: https://ci.learn.mit.edu + NEXT_PUBLIC_MITOL_API_BASE_URL: https://api.ci.learn.mit.edu + NEXT_PUBLIC_CSRF_COOKIE_NAME: cookie-monster + NEXT_PUBLIC_SITE_NAME: MIT Learn + NEXT_PUBLIC_MITOL_SUPPORT_EMAIL: help@ci.learn.mit.edu + # do this before typecheck. See https://github.com/vercel/next.js/issues/53959#issuecomment-1735563224 + - name: Typecheck run: yarn run typecheck @@ -144,24 +150,64 @@ jobs: CODECOV: true NODE_ENV: test - - name: Upload frontend build - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 - with: - name: frontend-build - path: frontends/mit-learn/build - - name: Upload coverage to CodeCov uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: file: coverage/lcov.info + build-nextjs-container: + needs: javascript-tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + + - name: Build the Docker image + env: + ORIGIN: https://next.rc.learn.mit.edu + MITOL_API_BASE_URL: https://api.rc.learn.mit.edu + SITE_NAME: MIT Learn + SUPPORT_EMAIL: mitlearn-support@mit.edu + EMBEDLY_KEY: ${{ secrets.EMBEDLY_KEY_RC }} + MITOL_AXIOS_WITH_CREDENTIALS: true + CSRF_COOKIE_NAME: ${{ secrets.CSRF_COOKIE_NAME_RC }} + POSTHOG_API_HOST: https://app.posthog.com + POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID_RC }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_RC }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN_RC }} + SENTRY_ENV: ${{ secrets.MITOL_ENVIRONMENT_RC }} + SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE_RC }} + SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE_RC }} + APPZI_URL: ${{ secrets.APPZI_URL_RC }} + VERSION: ${{ github.sha }} + run: | + docker build \ + -f frontends/main/Dockerfile.web \ + --build-arg NEXT_PUBLIC_ORIGIN=$ORIGIN \ + --build-arg NEXT_PUBLIC_MITOL_API_BASE_URL=$MITOL_API_BASE_URL \ + --build-arg NEXT_PUBLIC_SITE_NAME="$SITE_NAME" \ + --build-arg NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=$SUPPORT_EMAIL \ + --build-arg NEXT_PUBLIC_EMBEDLY_KEY=$EMBEDLY_KEY \ + --build-arg NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=$MITOL_AXIOS_WITH_CREDENTIALS \ + --build-arg NEXT_PUBLIC_CSRF_COOKIE_NAME=$CSRF_COOKIE_NAME \ + --build-arg NEXT_PUBLIC_POSTHOG_API_HOST=$POSTHOG_API_HOST \ + --build-arg NEXT_PUBLIC_POSTHOG_PROJECT_ID=$POSTHOG_PROJECT_ID \ + --build-arg NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \ + --build-arg NEXT_PUBLIC_SENTRY_DSN=$SENTRY_DSN \ + --build-arg NEXT_PUBLIC_SENTRY_ENV=$SENTRY_ENV \ + --build-arg NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$SENTRY_PROFILES_SAMPLE_RATE \ + --build-arg NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$SENTRY_TRACES_SAMPLE_RATE \ + --build-arg NEXT_PUBLIC_APPZI_URL=$APPZI_URL \ + --build-arg NEXT_PUBLIC_VERSION=$VERSION \ + -t mitodl/mit-learn-frontend:$VERSION . + build-storybook: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn @@ -171,7 +217,7 @@ jobs: run: yarn install - name: Build Storybook - run: yarn workspace mit-learn build-storybook + run: yarn workspace ol-components build-storybook openapi-generated-client-check-v0: # This job checks that the output of openapi-generator-typescript-axios that @@ -184,7 +230,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn @@ -223,7 +269,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn @@ -259,7 +305,7 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 389deea711..248d3932e2 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -19,64 +19,78 @@ jobs: with: ref: release - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 - with: - node-version: "^20" - cache: yarn - cache-dependency-path: yarn.lock + - name: Set VERSION + run: echo "VERSION=$(./scripts/get_version.sh)" >> $GITHUB_ENV - - name: Setup environment - run: sudo apt-get install libelf1 + - name: Write commit SHA to file + run: echo $GITHUB_SHA > frontends/main/public/hash.txt - - name: Install frontend dependencies - run: yarn install --immutable + - name: Heroku login + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:login - - name: Set VERSION - run: echo "VERSION=$(./scripts/get_version.sh)" >> $GITHUB_ENV + - name: Release Backend on Heroku + uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 + with: + heroku_api_key: ${{ secrets.HEROKU_API_KEY }} + heroku_app_name: mitopen-production + heroku_email: ${{ secrets.HEROKU_EMAIL }} + branch: release - - name: Build frontend - run: NODE_ENV=production yarn build + - name: Build and push the Docker image env: + HEROKU_APP_NAME: mitopen-production-nextjs + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + ORIGIN: https://learn.mit.edu + MITOL_API_BASE_URL: https://api.learn.mit.edu + SITE_NAME: MIT Learn + SUPPORT_EMAIL: mitlearn-support@mit.edu EMBEDLY_KEY: ${{ secrets.EMBEDLY_KEY_PROD }} - POSTHOG_ENABLED: true + MITOL_AXIOS_WITH_CREDENTIALS: true + CSRF_COOKIE_NAME: ${{ secrets.CSRF_COOKIE_NAME_PROD }} POSTHOG_API_HOST: https://app.posthog.com - POSTHOG_TIMEOUT_MS: 1000 POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID_PROD }} - POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_PROD }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_PROD }} SENTRY_DSN: ${{ secrets.SENTRY_DSN_PROD }} SENTRY_ENV: ${{ secrets.MITOL_ENVIRONMENT_PROD }} + SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE_PROD }} + SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE_PROD }} APPZI_URL: ${{ secrets.APPZI_URL_PROD }} - CSRF_COOKIE_NAME: ${{ secrets.CSRF_COOKIE_NAME_PROD }} - MITOL_AXIOS_WITH_CREDENTIALS: true - MITOL_API_BASE_URL: https://api.learn.mit.edu - MITOL_SUPPORT_EMAIL: mitlearn-support@mit.edu - - - uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 - with: - heroku_api_key: ${{ secrets.HEROKU_API_KEY }} - heroku_app_name: "mitopen-production" - heroku_email: ${{ secrets.HEROKU_EMAIL }} - branch: release + VERSION: ${{ github.sha }} + run: | + heroku container:push web \ + --app $HEROKU_APP_NAME \ + --recursive \ + --arg NEXT_PUBLIC_ORIGIN=$ORIGIN,\ + NEXT_PUBLIC_MITOL_API_BASE_URL=$MITOL_API_BASE_URL,\ + NEXT_PUBLIC_SITE_NAME="$SITE_NAME",\ + NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=$SUPPORT_EMAIL,\ + NEXT_PUBLIC_EMBEDLY_KEY=$EMBEDLY_KEY,\ + NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=$MITOL_AXIOS_WITH_CREDENTIALS,\ + NEXT_PUBLIC_CSRF_COOKIE_NAME=$CSRF_COOKIE_NAME,\ + NEXT_PUBLIC_POSTHOG_API_HOST=$POSTHOG_API_HOST,\ + NEXT_PUBLIC_POSTHOG_PROJECT_ID=$POSTHOG_PROJECT_ID,\ + NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY,\ + NEXT_PUBLIC_SENTRY_DSN=$SENTRY_DSN,\ + NEXT_PUBLIC_SENTRY_ENV=$SENTRY_ENV,\ + NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$SENTRY_PROFILES_SAMPLE_RATE,\ + NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$SENTRY_TRACES_SAMPLE_RATE,\ + NEXT_PUBLIC_APPZI_URL=$APPZI_URL,\ + NEXT_PUBLIC_VERSION=$VERSION \ + --context-path . - - name: Write commit SHA to file - run: echo $GITHUB_SHA > frontends/mit-learn/build/static/hash.txt - - - name: Upload frontend build to s3 - uses: jakejarvis/s3-sync-action@master - with: - args: --acl public-read --follow-symlinks + - name: Release Frontend on Heroku env: - AWS_S3_BUCKET: ol-mitlearn-app-storage-production - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }} - SOURCE_DIR: "frontends/mit-learn/build" # optional: defaults to entire repository - DEST_DIR: "frontend" # This dir will get cluttered but it is okay for now + HEROKU_APP_NAME: mitopen-production-nextjs + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:release --app $HEROKU_APP_NAME web - name: Purge Fastly cache uses: jcoene/fastly-purge-action@master with: - api_key: "${{ secrets.FASTLY_API_KEY_PROD }}" - service_id: "${{ secrets.FASTLY_SERVICE_ID_PROD }}" + api_key: "${{ secrets.FASTLY_API_KEY_PROD_NEXTJS }}" + service_id: "${{ secrets.FASTLY_SERVICE_ID_PROD_NEXTJS }}" # runs ONLY on a failure of the CI workflow on-failure: diff --git a/.github/workflows/publish-pages.yml b/.github/workflows/publish-pages.yml index 3e7e83faf4..dd4f6bd089 100644 --- a/.github/workflows/publish-pages.yml +++ b/.github/workflows/publish-pages.yml @@ -1,3 +1,5 @@ +name: Publish Storybook + on: # Runs on pushes targeting the default branch push: @@ -13,7 +15,7 @@ jobs: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4 with: node-version: "^20" cache: yarn @@ -23,12 +25,12 @@ jobs: run: yarn install - name: Build Storybook - run: yarn workspace mit-learn build-storybook + run: yarn workspace ol-components build-storybook - name: Upload artifact uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: - path: ./frontends/mit-learn/storybook-static + path: ./frontends/ol-components/storybook-static deploy: needs: build diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 952b188479..c32c06ac33 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -19,64 +19,78 @@ jobs: with: ref: release-candidate - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 - with: - node-version: "^20" - cache: yarn - cache-dependency-path: yarn.lock + - name: Set VERSION + run: echo "VERSION=$(./scripts/get_version.sh)" >> $GITHUB_ENV - - name: Setup environment - run: sudo apt-get install libelf1 + - name: Write commit SHA to file + run: echo $GITHUB_SHA > frontends/main/public/hash.txt - - name: Install frontend dependencies - run: yarn install --immutable + - name: Heroku login + env: + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:login - - name: Set VERSION - run: echo "VERSION=$(./scripts/get_version.sh)" >> $GITHUB_ENV + - name: Release Backend on Heroku + uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 + with: + heroku_api_key: ${{ secrets.HEROKU_API_KEY }} + heroku_app_name: mitopen-rc + heroku_email: ${{ secrets.HEROKU_EMAIL }} + branch: release-candidate - - name: Build frontend - run: NODE_ENV=production yarn build + - name: Build and push the Docker image env: + HEROKU_APP_NAME: mitopen-rc-nextjs + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + ORIGIN: https://next.rc.learn.mit.edu + MITOL_API_BASE_URL: https://api.rc.learn.mit.edu + SITE_NAME: MIT Learn + SUPPORT_EMAIL: mitlearn-support@mit.edu EMBEDLY_KEY: ${{ secrets.EMBEDLY_KEY_RC }} - POSTHOG_ENABLED: true + MITOL_AXIOS_WITH_CREDENTIALS: true + CSRF_COOKIE_NAME: ${{ secrets.CSRF_COOKIE_NAME_RC }} POSTHOG_API_HOST: https://app.posthog.com - POSTHOG_TIMEOUT_MS: 1000 POSTHOG_PROJECT_ID: ${{ secrets.POSTHOG_PROJECT_ID_RC }} - POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_RC }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY_RC }} SENTRY_DSN: ${{ secrets.SENTRY_DSN_RC }} SENTRY_ENV: ${{ secrets.MITOL_ENVIRONMENT_RC }} - CSRF_COOKIE_NAME: ${{ secrets.CSRF_COOKIE_NAME_RC }} + SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE_RC }} + SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE_RC }} APPZI_URL: ${{ secrets.APPZI_URL_RC }} - MITOL_AXIOS_WITH_CREDENTIALS: true - MITOL_API_BASE_URL: https://api.rc.learn.mit.edu - MITOL_SUPPORT_EMAIL: mitlearn-support@mit.edu - - - uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 - with: - heroku_api_key: ${{ secrets.HEROKU_API_KEY }} - heroku_app_name: "mitopen-rc" - heroku_email: ${{ secrets.HEROKU_EMAIL }} - branch: release-candidate + VERSION: ${{ github.sha }} + run: | + heroku container:push web \ + --app $HEROKU_APP_NAME \ + --recursive \ + --arg NEXT_PUBLIC_ORIGIN=$ORIGIN,\ + NEXT_PUBLIC_MITOL_API_BASE_URL=$MITOL_API_BASE_URL,\ + NEXT_PUBLIC_SITE_NAME="$SITE_NAME",\ + NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=$SUPPORT_EMAIL,\ + NEXT_PUBLIC_EMBEDLY_KEY=$EMBEDLY_KEY,\ + NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=$MITOL_AXIOS_WITH_CREDENTIALS,\ + NEXT_PUBLIC_CSRF_COOKIE_NAME=$CSRF_COOKIE_NAME,\ + NEXT_PUBLIC_POSTHOG_API_HOST=$POSTHOG_API_HOST,\ + NEXT_PUBLIC_POSTHOG_PROJECT_ID=$POSTHOG_PROJECT_ID,\ + NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY,\ + NEXT_PUBLIC_SENTRY_DSN=$SENTRY_DSN,\ + NEXT_PUBLIC_SENTRY_ENV=$SENTRY_ENV,\ + NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$SENTRY_PROFILES_SAMPLE_RATE,\ + NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$SENTRY_TRACES_SAMPLE_RATE,\ + NEXT_PUBLIC_APPZI_URL=$APPZI_URL,\ + NEXT_PUBLIC_VERSION=$VERSION \ + --context-path . - - name: Write commit SHA to file - run: echo $GITHUB_SHA > frontends/mit-learn/build/static/hash.txt - - - name: Upload frontend build to s3 - uses: jakejarvis/s3-sync-action@master - with: - args: --acl public-read --follow-symlinks + - name: Release Frontend on Heroku env: - AWS_S3_BUCKET: ol-mitlearn-app-storage-rc - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_RC }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_RC }} - SOURCE_DIR: "frontends/mit-learn/build" # optional: defaults to entire repository - DEST_DIR: "frontend" # This dir will get cluttered but it is okay for now + HEROKU_APP_NAME: mitopen-rc-nextjs + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + run: heroku container:release --app $HEROKU_APP_NAME web - name: Purge Fastly cache uses: jcoene/fastly-purge-action@master with: - api_key: "${{ secrets.FASTLY_API_KEY_RC }}" - service_id: "${{ secrets.FASTLY_SERVICE_ID_RC }}" + api_key: "${{ secrets.FASTLY_API_KEY_RC_NEXTJS }}" + service_id: "${{ secrets.FASTLY_SERVICE_ID_RC_NEXTJS }}" # runs ONLY on a failure of the CI workflow on-failure: diff --git a/.gitignore b/.gitignore index 3ea33029f8..c103e2eaf6 100644 --- a/.gitignore +++ b/.gitignore @@ -136,5 +136,6 @@ storybook-static/ /e2e_testing/playwright/.cache/ !/e2e_testing/.env +/**/.yarn/cache e2e_testing/.yarn/cache .swc diff --git a/.prettierignore b/.prettierignore index 0948223964..39e1074b6a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ openapi/specs/* .yarn/ +.next/ diff --git a/RELEASE.rst b/RELEASE.rst index b1510e228b..d90ee927aa 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,84 @@ Release Notes ============= +Version 0.23.0 +-------------- + +- Revert "Fixed issue with full name not being pulled in (#1682)" (#1723) +- Fix path to Storybook build (#1722) +- Update for Storybook's new home in ol-components (#1721) +- bundle analyzer, fix search page + channel page JS sizes (#1707) +- Update ckeditor monorepo to v43 (major) (#1204) +- Update actions/setup-node digest to 0a44ba7 (#1623) +- Read Sentry rates from the Actions secrets +- Fixed issue with full name not being pulled in (#1682) +- Remove unnecessary Heroku vars +- fix textarea placeholder color (#1712) +- Reinstate backend release steps +- Capture the search_update event after making the adjustments to search terms (#1709) +- Move deploy jobs to respective workflow. Docker build for dry run +- Appzi env vars and Sentry config +- NextJS Sentry Integration (#1701) +- Increase cache duration (#1705) +- remove unnecessary webpack customizations (#1704) +- NextJS - re-enable program letter tests (#1696) +- Copy yarn releases to the Docker container (fixes build) (#1703) +- Config to set cache control headers (#1700) +- restoring up and down chevrons (#1690) +- synonyms in analyzer (#1697) +- NextJS Sentry Integration (#1701) +- Prod deployment. Add Posthog vars +- Increase cache duration (#1705) +- Update dependency @ckeditor/ckeditor5-react to v9 (#1532) +- remove unnecessary webpack customizations (#1704) +- NextJS - re-enable program letter tests (#1696) +- Update dependency @ckeditor/ckeditor5-dev-utils to v43 (#1552) +- Copy yarn releases to the Docker container (fixes build) (#1703) +- Update dependency @ckeditor/ckeditor5-dev-translations to v43 (#1551) +- Config to set cache control headers (#1700) +- Shanbady/improve bookmark button label (#1699) +- Shanbady/xpro logo for all xpro offerings (#1695) +- search facet accessibility fixes (#1698) +- exclude topics with no associated channel from Topic querySets (#1693) +- restoring up and down chevrons (#1690) +- Merge `main` into `nextjs` (#1687) +- Reinstate background steps image +- Handle 404 errors from API during server render (#1678) +- Fix search page history stack (#1680) +- fix spacing issues on nextjs branch (#1685) +- Make metadataBase apply to all pages, not just homepage (#1677) +- Positioning for search utils input close button (#1617) +- fix tests +- bump course search utils +- fix metadata baseurl +- switch search page to dynamic render +- Next.js Migration Bug Fixes (#1626) +- adding new og-image +- Various post-merge bug fixes: +- Adds PostHog back into the NextJS build (#1644) +- Restore nextjs main workspace tests (#1639) +- Migrate remaining images to Next.js (#1614) +- Add error handling (#1613) +- adding env vars (#1609) +- Merge latest from main (#1602) +- Fix homepage image hydration errors (#1605) +- Migrate useMediaQuery hooks (#1563) +- [NextJS] configure query client like on `main` (#1591) +- [NextJS] Make noindex tag opt-out (like main) rather than opt-in (#1592) +- CI to push to Heroku (#1569) +- [NextJS] Load correct font and a few other global things (#1583) +- Reinstate head metadata (#1572) +- set single react types resolution (#1586) +- Dockerfile for the Next.js frontend and Action to build and publish (#1542) +- [NextJS] Fix tests, re-enable on CI (#1560) +- fix carousel initial positioning (#1546) +- [NextJS] Local docker setup (#1538) +- [NextJS] fix frontend tests outside of `main` workspace (#1527) +- Migrate Storybook for Next.js (#1525) +- Fixes for successful build (#1516) +- Fix linting on nextjs branch (#1509) +- Next.js Initial Migration (#1505) + Version 0.22.1 (Released October 15, 2024) -------------- diff --git a/docker-compose.apps.yml b/docker-compose.apps.yml index 0f030d18a7..87b55552d2 100644 --- a/docker-compose.apps.yml +++ b/docker-compose.apps.yml @@ -33,13 +33,17 @@ services: command: - | yarn install --immutable - yarn workspace mit-learn storybook --no-open & - yarn workspace mit-learn watch:docker + yarn workspace ol-components storybook --no-open & + yarn watch ports: - "8062:8062" - "6006:6006" + environment: + - NEXT_SERVER_MITOL_API_BASE_URL=http://nginx:8063/ volumes: - .:/src + links: + - web celery: profiles: diff --git a/env/codespaces.env b/env/codespaces.env index 5ddce03263..4d8d81347e 100644 --- a/env/codespaces.env +++ b/env/codespaces.env @@ -48,3 +48,10 @@ MITOL_SECURE_SSL_REDIRECT=False CELERY_BROKER_URL=redis://redis:6379/4 CELERY_RESULT_BACKEND=redis://redis:6379/4 CELERY_TASK_ALWAYS_EAGER=False +NEXT_PUBLIC_ORIGIN=${MITOL_APP_BASE_URL} +NEXT_PUBLIC_MITOL_API_BASE_URL=${MITOL_API_BASE_URL} +NEXT_PUBLIC_CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME} +NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=${MITOL_SUPPORT_EMAIL} +NEXT_PUBLIC_SITE_NAME="MIT Learn" +NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=true +NEXT_SERVER_MITOL_API_BASE_URL= diff --git a/env/frontend.env b/env/frontend.env index 4a57fe35a0..b6d13aa462 100644 --- a/env/frontend.env +++ b/env/frontend.env @@ -1,4 +1,18 @@ NODE_ENV=development PORT=8062 -MITOL_AXIOS_WITH_CREDENTIALS=true -SENTRY_ENV=dev +SENTRY_ENV=dev # Re-enable sentry + +# Environment variables with `NEXT_PUBLIC_` prefix are exposed to the client side +NEXT_PUBLIC_ORIGIN=${MITOL_APP_BASE_URL} +NEXT_PUBLIC_MITOL_API_BASE_URL=${MITOL_API_BASE_URL} +NEXT_PUBLIC_CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME} +NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=${MITOL_SUPPORT_EMAIL} + +NEXT_PUBLIC_SITE_NAME="MIT Learn" +NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=true + +NEXT_PUBLIC_DEFAULT_SEARCH_MODE=phrase +NEXT_PUBLIC_DEFAULT_SEARCH_SLOP=6 +NEXT_PUBLIC_DEFAULT_SEARCH_STALENESS_PENALTY=2.5 +NEXT_PUBLIC_DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF=0 +NEXT_PUBLIC_DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY=90 diff --git a/env/frontend.local.example.env b/env/frontend.local.example.env index e69de29bb2..447d7ea82c 100644 --- a/env/frontend.local.example.env +++ b/env/frontend.local.example.env @@ -0,0 +1 @@ +NEXT_PUBLIC_EMBEDLY_KEY="" diff --git a/env/shared.env b/env/shared.env index e22219b661..82a3653e03 100644 --- a/env/shared.env +++ b/env/shared.env @@ -1,5 +1,5 @@ -MITOL_API_BASE_URL=http://api.open.odl.local:8063 MITOL_APP_BASE_URL=http://open.odl.local:8062 +MITOL_API_BASE_URL=http://api.open.odl.local:8063 MITOL_SUPPORT_EMAIL=support@localhost CSRF_COOKIE_NAME=csrftoken-local diff --git a/frontends/.eslintrc.js b/frontends/.eslintrc.js index 61f73d5562..636672dcc9 100644 --- a/frontends/.eslintrc.js +++ b/frontends/.eslintrc.js @@ -5,12 +5,13 @@ module.exports = { "plugin:styled-components-a11y/recommended", "plugin:import/typescript", "plugin:mdx/recommended", + "plugin:@next/next/recommended", "prettier", ], plugins: ["testing-library", "import", "styled-components-a11y"], ignorePatterns: [ "**/build/**", - "ol-ckeditor-2/dist", + "mit-learn", "github-pages", "storybook-static", ], diff --git a/frontends/api/jest.config.ts b/frontends/api/jest.config.ts index 9861f128df..8373c3a5df 100644 --- a/frontends/api/jest.config.ts +++ b/frontends/api/jest.config.ts @@ -7,13 +7,6 @@ const config: Config.InitialOptions = { ...baseConfig.setupFilesAfterEnv, "./test-utils/setupJest.ts", ], - globals: { - APP_SETTINGS: { - MITOL_AXIOS_WITH_CREDENTIALS: false, - MITOL_API_BASE_URL: "https://api.test.learn.mit.edu", - CSRF_COOKIE_NAME: "csrftoken-test", - }, - }, } export default config diff --git a/frontends/api/package.json b/frontends/api/package.json index 02ab0da58a..ecaa7c61c2 100644 --- a/frontends/api/package.json +++ b/frontends/api/package.json @@ -5,7 +5,9 @@ "sideEffects": false, "exports": { ".": "./src/generated/v1/api.ts", + "./clients": "./src/clients.ts", "./v0": "./src/generated/v0/api.ts", + "./v1": "./src/generated/v1/api.ts", "./hooks/*": "./src/hooks/*/index.ts", "./constants": "./src/common/constants.ts", "./test-utils/factories": "./src/test-utils/factories/index.ts", @@ -18,6 +20,7 @@ "@faker-js/faker": "^9.0.0", "@testing-library/react": "16.0.1", "enforce-unique": "^1.3.0", + "jest": "^29.7.0", "jest-when": "^3.6.0", "lodash": "^4.17.21", "ol-test-utilities": "0.0.0" diff --git a/frontends/api/src/axios.ts b/frontends/api/src/axios.ts index 4919d8cb35..cbfa7df9f0 100644 --- a/frontends/api/src/axios.ts +++ b/frontends/api/src/axios.ts @@ -4,10 +4,11 @@ import axios from "axios" * Our axios instance with default baseURL, headers, etc. */ const instance = axios.create({ - xsrfCookieName: APP_SETTINGS.CSRF_COOKIE_NAME, + xsrfCookieName: process.env.NEXT_PUBLIC_CSRF_COOKIE_NAME, xsrfHeaderName: "X-CSRFToken", withXSRFToken: true, - withCredentials: APP_SETTINGS.MITOL_AXIOS_WITH_CREDENTIALS, + withCredentials: + process.env.NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS === "true", }) export default instance diff --git a/frontends/api/src/clients.ts b/frontends/api/src/clients.ts index d56cd7a56f..9d776b395e 100644 --- a/frontends/api/src/clients.ts +++ b/frontends/api/src/clients.ts @@ -25,7 +25,13 @@ import { import axiosInstance from "./axios" -const { MITOL_API_BASE_URL } = APP_SETTINGS +const IS_SERVER = typeof window === "undefined" +const MITOL_API_BASE_URL = IS_SERVER + ? // NEXT_SERVER_MITOL_API_BASE_URL is generally only needed for local-dev + // in docker, where the client and server make API calls to different hosts + (process.env.NEXT_SERVER_MITOL_API_BASE_URL ?? + process.env.NEXT_PUBLIC_MITOL_API_BASE_URL) + : process.env.NEXT_PUBLIC_MITOL_API_BASE_URL const BASE_PATH = MITOL_API_BASE_URL?.replace(/\/+$/, "") ?? "" diff --git a/frontends/api/src/test-utils/factories/channels.ts b/frontends/api/src/test-utils/factories/channels.ts index d43f9e45a1..7a8a095388 100644 --- a/frontends/api/src/test-utils/factories/channels.ts +++ b/frontends/api/src/test-utils/factories/channels.ts @@ -50,7 +50,7 @@ const departmentChannel: PartialFactory = ( { channel_type: ChannelTypeEnum.Department }, { configuration: { - banner_background: "/static/images/unit_banners/mitpe.jpg", + banner_background: "/images/unit_banners/mitpe.jpg", heading: "test", logo: "/static/test.svg", name: "test", @@ -73,7 +73,7 @@ const topicChannel: PartialFactory = (overrides = {}) => { { channel_type: ChannelTypeEnum.Topic }, { configuration: { - banner_background: "/static/images/unit_banners/mitpe.jpg", + banner_background: "/images/unit_banners/mitpe.jpg", heading: "test", logo: "/static/test.svg", name: "test", @@ -96,7 +96,7 @@ const unitChannel: PartialFactory = (overrides = {}) => { { channel_type: ChannelTypeEnum.Unit }, { configuration: { - banner_background: "/static/images/unit_banners/mitpe.jpg", + banner_background: "/images/unit_banners/mitpe.jpg", heading: "test", logo: "/static/test.svg", name: "test", diff --git a/frontends/api/src/test-utils/factories/learningResources.ts b/frontends/api/src/test-utils/factories/learningResources.ts index 3ed4fb3be2..3e4193a455 100644 --- a/frontends/api/src/test-utils/factories/learningResources.ts +++ b/frontends/api/src/test-utils/factories/learningResources.ts @@ -101,7 +101,7 @@ const learningResourceBaseDepartment: Factory< return { department_id: uniqueEnforcerWords.enforce(() => faker.lorem.words()), name: uniqueEnforcerWords.enforce(() => faker.lorem.words()), - channel_url: faker.internet.url(), + channel_url: `${faker.internet.url({ appendSlash: false })}${faker.system.directoryPath()}`, ...overrides, } } @@ -223,7 +223,7 @@ const learningResourceTopic: Factory = ( const topic: LearningResourceTopic = { id: uniqueEnforcerId.enforce(() => faker.number.int()), name: uniqueEnforcerWords.enforce(() => faker.lorem.words()), - channel_url: `${faker.internet.url()}${faker.system.directoryPath()}`, + channel_url: `${faker.internet.url({ appendSlash: false })}${faker.system.directoryPath()}`, parent: null, ...overrides, } diff --git a/frontends/mit-learn/src/test-utils/example-request-mocks.test.ts b/frontends/api/src/test-utils/mock-requests-example.test.ts similarity index 98% rename from frontends/mit-learn/src/test-utils/example-request-mocks.test.ts rename to frontends/api/src/test-utils/mock-requests-example.test.ts index 6b588b8632..3c33108f18 100644 --- a/frontends/mit-learn/src/test-utils/example-request-mocks.test.ts +++ b/frontends/api/src/test-utils/mock-requests-example.test.ts @@ -1,7 +1,9 @@ -import axios from "@/services/axios" +import axiosBase from "axios" import { setMockResponse } from "./mockAxios" import { ControlledPromise, allowConsoleErrors } from "ol-test-utilities" +const axios = axiosBase.create() + describe("request mocking", () => { test("mocking specific responses and spying", async () => { setMockResponse.post( diff --git a/frontends/api/src/test-utils/urls.ts b/frontends/api/src/test-utils/urls.ts index bedc2f275b..89324b1a00 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 { MITOL_API_BASE_URL: API_BASE_URL } = APP_SETTINGS +const API_BASE_URL = process.env.NEXT_PUBLIC_MITOL_API_BASE_URL // OpenAPI Generator declares parameters using interfaces, which makes passing // them to functions a little annoying. diff --git a/frontends/jest-shared-setup.ts b/frontends/jest-shared-setup.ts index aa53284fc4..1195a262ae 100644 --- a/frontends/jest-shared-setup.ts +++ b/frontends/jest-shared-setup.ts @@ -4,9 +4,16 @@ import "cross-fetch/polyfill" import { configure } from "@testing-library/react" import { resetAllWhenMocks } from "jest-when" import * as matchers from "jest-extended" +import { mockRouter } from "ol-test-utilities/mocks/nextNavigation" expect.extend(matchers) +// env vars +process.env.NEXT_PUBLIC_MITOL_API_BASE_URL = + "http://api.test.learn.odl.local:8063" +process.env.NEXT_PUBLIC_ORIGIN = "http://test.learn.odl.local:8062" +process.env.NEXT_PUBLIC_EMBEDLY_KEY = "fake-embedly-key" + // Pulled from the docs - see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom Object.defineProperty(window, "matchMedia", { @@ -71,6 +78,13 @@ configure({ }, }) +jest.mock("next/navigation", () => { + return { + ...jest.requireActual("ol-test-utilities/mocks/nextNavigation") + .nextNavigationMocks, + } +}) + afterEach(() => { /** * Clear all mock call counts between tests. @@ -79,4 +93,6 @@ afterEach(() => { */ jest.clearAllMocks() resetAllWhenMocks() + mockRouter.setCurrentUrl("/") + window.history.replaceState({}, "", "/") }) diff --git a/frontends/jest.config.ts b/frontends/jest.config.ts index 8d1cba518d..eaeecce315 100644 --- a/frontends/jest.config.ts +++ b/frontends/jest.config.ts @@ -10,6 +10,7 @@ const projectsConfig: Config.InitialOptions = { collectCoverage: true, coverageDirectory: "coverage", projects: ["/*/jest.config.ts"], + modulePathIgnorePatterns: ["/mit-learn/"], watchPlugins: [ "jest-watch-typeahead/filename", "jest-watch-typeahead/testname", diff --git a/frontends/jest.jsdom.config.ts b/frontends/jest.jsdom.config.ts index 6c7ce2b08f..4c697e796c 100644 --- a/frontends/jest.jsdom.config.ts +++ b/frontends/jest.jsdom.config.ts @@ -12,7 +12,7 @@ const config: Config.InitialOptions & "^.+\\.(t|j)sx?$": "@swc/jest", }, moduleNameMapper: { - "\\.svg$": "ol-test-utilities/filemocks/svgmock.js", + "\\.(svg|jpg|jpeg|png)$": "ol-test-utilities/filemocks/imagemock.js", "\\.(css|scss)$": "ol-test-utilities/filemocks/filemock.js", }, rootDir: "./src", diff --git a/frontends/main/.gitignore b/frontends/main/.gitignore new file mode 100644 index 0000000000..1dd45b2022 --- /dev/null +++ b/frontends/main/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# Sentry Config File +.env.sentry-build-plugin diff --git a/frontends/main/Dockerfile.web b/frontends/main/Dockerfile.web new file mode 100644 index 0000000000..f1cd82337d --- /dev/null +++ b/frontends/main/Dockerfile.web @@ -0,0 +1,152 @@ +# Build: \ +# docker build \ +# -f frontends/main/Dockerfile.web \ +# --build-arg NEXT_PUBLIC_ORIGIN=http://api.open.odl.local:8062 \ +# --build-arg NEXT_PUBLIC_MITOL_API_BASE_URL=http://open.odl.local:8063 \ +# --build-arg NEXT_PUBLIC_SITE_NAME="MIT Learn" \ +# --build-arg NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=mitlearn-support@mit.edu \ +# --build-arg NEXT_PUBLIC_EMBEDLY_KEY= \ +# --build-arg NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=true \ +# --build-arg NEXT_PUBLIC_CSRF_COOKIE_NAME=csrftoken-local \ +# --build-arg NEXT_PUBLIC_POSTHOG_API_HOST= \ +# --build-arg NEXT_PUBLIC_POSTHOG_PROJECT_ID= \ +# --build-arg NEXT_PUBLIC_POSTHOG_API_KEY= \ +# --build-arg NEXT_PUBLIC_SENTRY_DSN= \ +# --build-arg NEXT_PUBLIC_SENTRY_ENV= \ +# --build-arg NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE= \ +# --build-arg NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE= \ +# --build-arg NEXT_PUBLIC_APPZI_URL= \ +# --build-arg NEXT_PUBLIC_VERSION= \ +# -t mitodl/mit-learn-frontend:latest . + + +# Run: +# docker run -p 8062:8062 -e PORT=8062 mit-learn-frontend:latest + +# Heroku build/push: +# +# heroku container:push web \ +# --app mitopen-rc-nextjs \ +# --recursive \ +# --arg NEXT_PUBLIC_ORIGIN=https://next.rc.learn.mit.edu +# NEXT_PUBLIC_MITOL_API_BASE_URL=https://api.rc.learn.mit.edu,\ +# NEXT_PUBLIC_SITE_NAME="MIT Learn",\ +# NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=mitlearn-support@mit.edu,\ +# NEXT_PUBLIC_EMBEDLY_KEY=*******,\ +# NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=$MITOL_AXIOS_WITH_CREDENTIALS,\ +# NEXT_PUBLIC_CSRF_COOKIE_NAME=learn-rc-csrftoken,\ +# NEXT_PUBLIC_POSTHOG_API_HOST=$POSTHOG_API_HOST,\ +# NEXT_PUBLIC_POSTHOG_PROJECT_ID=$POSTHOG_PROJECT_ID,\ +# NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY,\ +# NEXT_PUBLIC_SENTRY_DSN=$SENTRY_DSN,\ +# NEXT_PUBLIC_SENTRY_ENV=$SENTRY_ENV,\ +# NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$SENTRY_PROFILES_SAMPLE_RATE,\ +# NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$SENTRY_TRACES_SAMPLE_RATE,\ +# NEXT_PUBLIC_APPZI_URL=$APPZI_URL,\ +# NEXT_PUBLIC_VERSION=$VERSION \ +# --context-path . + +# Heroku release: +# heroku ps:scale frontend=1 --app mitopen-rc-nextjs +# heroku container:release --app mitopen-rc-nextjs frontend + + +FROM node:22-alpine + +RUN apk update +RUN apk add --no-cache libc6-compat + +WORKDIR /app + +COPY .yarnrc.yml /app +COPY .yarn/releases /app/.yarn/releases +COPY yarn.lock /app +COPY package.json /app +COPY frontends /app/frontends + +RUN yarn install --immutable + +WORKDIR /app/frontends/main + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED=1 + +ENV NODE_ENV=production + +ARG NEXT_PUBLIC_ORIGIN +ENV NEXT_PUBLIC_ORIGIN=$NEXT_PUBLIC_ORIGIN + +ARG NEXT_PUBLIC_MITOL_API_BASE_URL +ENV NEXT_PUBLIC_MITOL_API_BASE_URL=$NEXT_PUBLIC_MITOL_API_BASE_URL + +ARG NEXT_PUBLIC_SITE_NAME +ENV NEXT_PUBLIC_SITE_NAME=$NEXT_PUBLIC_SITE_NAME + +ARG NEXT_PUBLIC_MITOL_SUPPORT_EMAIL +ENV NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=$NEXT_PUBLIC_MITOL_SUPPORT_EMAIL + +ARG NEXT_PUBLIC_EMBEDLY_KEY=None +ENV NEXT_PUBLIC_EMBEDLY_KEY=$NEXT_PUBLIC_EMBEDLY_KEY + +ARG NEXT_PUBLIC_SENTRY_DSN +ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN + +ARG NEXT_PUBLIC_SENTRY_ENV +ENV NEXT_PUBLIC_SENTRY_ENV=$NEXT_PUBLIC_SENTRY_ENV + +ARG NEXT_PUBLIC_VERSION +ENV NEXT_PUBLIC_VERSION=$NEXT_PUBLIC_VERSION + +ARG NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE +ENV NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE + +ARG NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE +ENV NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE + +ARG NEXT_PUBLIC_POSTHOG_API_KEY +ENV NEXT_PUBLIC_POSTHOG_API_KEY=$NEXT_PUBLIC_POSTHOG_API_KEY + +ARG NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=true +ENV NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS=$NEXT_PUBLIC_MITOL_AXIOS_WITH_CREDENTIALS + +ARG NEXT_PUBLIC_CSRF_COOKIE_NAME +ENV NEXT_PUBLIC_CSRF_COOKIE_NAME=$NEXT_PUBLIC_CSRF_COOKIE_NAME + +ARG NEXT_PUBLIC_POSTHOG_API_HOST +ENV NEXT_PUBLIC_POSTHOG_API_HOST=$NEXT_PUBLIC_POSTHOG_API_HOST +ARG NEXT_PUBLIC_POSTHOG_PROJECT_ID +ENV NEXT_PUBLIC_POSTHOG_PROJECT_ID=$NEXT_PUBLIC_POSTHOG_PROJECT_ID +ARG NEXT_PUBLIC_POSTHOG_API_KEY +ENV NEXT_PUBLIC_POSTHOG_API_KEY=$NEXT_PUBLIC_POSTHOG_API_KEY + +ARG NEXT_PUBLIC_SENTRY_DSN +ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN +ARG NEXT_PUBLIC_SENTRY_ENV +ENV NEXT_PUBLIC_SENTRY_ENV=$NEXT_PUBLIC_SENTRY_ENV +ARG NEXT_PUBLIC_VERSION +ENV NEXT_PUBLIC_VERSION=$NEXT_PUBLIC_VERSION +ARG NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE +ENV NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE=$NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE +ARG NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE +ENV NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE + +ARG NEXT_PUBLIC_APPZI_URL +ENV NEXT_PUBLIC_APPZI_URL=$NEXT_PUBLIC_APPZI_URL + +ENV NEXT_PUBLIC_DEFAULT_SEARCH_MODE="phrase" +ENV NEXT_PUBLIC_DEFAULT_SEARCH_SLOP="6" +ENV NEXT_PUBLIC_DEFAULT_SEARCH_STALENESS_PENALTY="2.5" +ENV NEXT_PUBLIC_DEFAULT_SEARCH_MINIMUM_SCORE_CUTOFF="0" +ENV NEXT_PUBLIC_DEFAULT_SEARCH_MAX_INCOMPLETENESS_PENALTY="90" + +RUN yarn build + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# CMD ["node", "/app/frontends/main/.next/standalone/frontends/main/server.js"] +CMD ["yarn", "start"] diff --git a/frontends/main/README.md b/frontends/main/README.md new file mode 100644 index 0000000000..c4033664f8 --- /dev/null +++ b/frontends/main/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/frontends/mit-learn/jest.config.ts b/frontends/main/jest.config.ts similarity index 61% rename from frontends/mit-learn/jest.config.ts rename to frontends/main/jest.config.ts index c767e8583f..aeac67f356 100644 --- a/frontends/mit-learn/jest.config.ts +++ b/frontends/main/jest.config.ts @@ -1,7 +1,6 @@ import path from "path" import type { Config } from "@jest/types" import baseConfig from "../jest.jsdom.config" - const config: Config.InitialOptions = { ...baseConfig, setupFilesAfterEnv: [ @@ -9,18 +8,8 @@ const config: Config.InitialOptions = { "./test-utils/setupJest.ts", ], moduleNameMapper: { - "^@/(.*)$": path.resolve(__dirname, "src/$1"), ...baseConfig.moduleNameMapper, - }, - transformIgnorePatterns: ["/node_modules/(?!(" + "yaml", ")/)"], - globals: { - APP_SETTINGS: { - EMBEDLY_KEY: "embedly_key", - MITOL_API_BASE_URL: "https://api.test.learn.mit.edu", - PUBLIC_URL: "", - SITE_NAME: "MIT Learn", - }, + "^@/(.*)$": path.resolve(__dirname, "src/$1"), }, } - export default config diff --git a/frontends/main/next.config.js b/frontends/main/next.config.js new file mode 100644 index 0000000000..b10fc8fd9d --- /dev/null +++ b/frontends/main/next.config.js @@ -0,0 +1,140 @@ +// @ts-check +const { validateEnv } = require("./validateEnv") + +validateEnv() + +const processFeatureFlags = () => { + const featureFlagPrefix = + process.env.NEXT_PUBLIC_POSTHOG_FEATURE_PREFIX || "FEATURE_" + const bootstrapFeatureFlags = {} + + for (const [key, value] of Object.entries(process.env)) { + if (key.startsWith(`NEXT_PUBLIC_${featureFlagPrefix}`)) { + bootstrapFeatureFlags[ + key.replace(`NEXT_PUBLIC_${featureFlagPrefix}`, "") + ] = value === "True" ? true : JSON.stringify(value) + } + } + + return bootstrapFeatureFlags +} + +/** @type {import('next').NextConfig} */ +const nextConfig = { + productionBrowserSourceMaps: true, + async rewrites() { + return [ + /* Images moved from /static, though image paths are sometimes + * returned on the API, e.g. /api/v0/channels/type/unit/ocw/ + * TODO update API paths and remove the rewrite. + */ + { + source: "/static/images/:path*", + destination: "/images/:path*", + }, + ] + }, + + async headers() { + return [ + /* This is intended to target the base HTML responses and streamed RSC + * content. Some routes are dynamically rendered, so NextJS by default + * sets no-cache. However we are currently serving public content that is + * cacheable. + * + * Excludes everything with a file extension so we're matching only on routes. + */ + { + source: "/((?!.*\\.[a-zA-Z0-9]{2,4}$).*)", + headers: [ + { + key: "Cache-Control", + value: "s-maxage=1800", + }, + ], + }, + + /* Images rendered with the Next.js Image component have the cache header + * set on them, but CSS background images do not. + */ + { + source: "/images/(.*)", + headers: [ + { + key: "Cache-Control", + value: "s-maxage=31536000", + }, + ], + }, + { + source: "/favicon.ico", + headers: [ + { + key: "Cache-Control", + value: "s-maxage=31536000", + }, + ], + }, + ] + }, + + images: { + remotePatterns: [ + { + protocol: "http", + hostname: "**", + port: "", + pathname: "**", + }, + { + protocol: "https", + hostname: "**", + port: "", + pathname: "**", + }, + ], + }, + + env: { + FEATURE_FLAGS: JSON.stringify(processFeatureFlags()), + }, +} + +// Injected content via Sentry wizard below + +const { withSentryConfig } = require("@sentry/nextjs") +const withSentry = (config) => + withSentryConfig(config, { + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options + + org: "mit-office-of-digital-learning", + project: "open-next", + + // Only print logs for uploading source maps in CI + silent: !process.env.CI, + + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + + // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. + // This can increase your server load as well as your hosting bill. + // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- + // side errors will fail. + // tunnelRoute: "/monitoring", + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + }) + +const withBundleAnalyzer = require("@next/bundle-analyzer")({ + enabled: process.env.ANALYZE === "true", +}) + +module.exports = [withBundleAnalyzer, withSentry].reduce( + (acc, withPlugin) => withPlugin(acc), + nextConfig, +) diff --git a/frontends/main/package.json b/frontends/main/package.json new file mode 100644 index 0000000000..3e8ee9d250 --- /dev/null +++ b/frontends/main/package.json @@ -0,0 +1,57 @@ +{ + "name": "main", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "PORT=${PORT:-8062} next dev", + "build": "next build", + "build:no-lint": "next build --no-lint", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@ebay/nice-modal-react": "^1.2.13", + "@emotion/cache": "^11.13.1", + "@mitodl/course-search-utils": "^3.3.0", + "@next/bundle-analyzer": "^14.2.15", + "@remixicon/react": "^4.2.0", + "@sentry/nextjs": "^8", + "@tanstack/react-query": "^4.36.1", + "api": "workspace:*", + "formik": "^2.4.6", + "lodash": "^4.17.21", + "next": "^14.2.15", + "ol-ckeditor": "0.0.0", + "ol-components": "0.0.0", + "ol-utilities": "0.0.0", + "ol-widgets": "0.0.0", + "posthog-js": "^1.157.2", + "react": "^18", + "react-dom": "^18", + "react-slick": "^0.30.2", + "slick-carousel": "^1.8.1", + "tiny-invariant": "^1.3.3", + "yup": "^1.4.0" + }, + "devDependencies": { + "@faker-js/faker": "^8.4.1", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.2", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.17.7", + "@types/node": "^20", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@types/react-slick": "^0.23.13", + "@types/slick-carousel": "^1", + "eslint": "8.57.0", + "eslint-config-next": "^14.2.7", + "http-proxy-middleware": "^3.0.0", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", + "ol-test-utilities": "0.0.0", + "ts-jest": "^29.2.4", + "typescript": "^5" + } +} diff --git a/frontends/mit-learn/public/images/background_steps.jpg b/frontends/main/public/images/backgrounds/background_steps.jpg similarity index 100% rename from frontends/mit-learn/public/images/background_steps.jpg rename to frontends/main/public/images/backgrounds/background_steps.jpg diff --git a/frontends/mit-learn/public/images/course_search_banner.png b/frontends/main/public/images/backgrounds/course_search_banner.png similarity index 100% rename from frontends/mit-learn/public/images/course_search_banner.png rename to frontends/main/public/images/backgrounds/course_search_banner.png diff --git a/frontends/main/public/images/backgrounds/open-bg-texture-with-gradient.svg b/frontends/main/public/images/backgrounds/open-bg-texture-with-gradient.svg new file mode 100644 index 0000000000..f17f0d9076 --- /dev/null +++ b/frontends/main/public/images/backgrounds/open-bg-texture-with-gradient.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontends/mit-learn/public/images/user_menu_background.svg b/frontends/main/public/images/backgrounds/user_menu_background.svg similarity index 100% rename from frontends/mit-learn/public/images/user_menu_background.svg rename to frontends/main/public/images/backgrounds/user_menu_background.svg diff --git a/frontends/mit-learn/public/images/default_resource.jpg b/frontends/main/public/images/default_resource.jpg similarity index 100% rename from frontends/mit-learn/public/images/default_resource.jpg rename to frontends/main/public/images/default_resource.jpg diff --git a/frontends/mit-learn/public/images/hero/hero-1.png b/frontends/main/public/images/hero/hero-1.png similarity index 100% rename from frontends/mit-learn/public/images/hero/hero-1.png rename to frontends/main/public/images/hero/hero-1.png diff --git a/frontends/mit-learn/public/images/hero/hero-2.png b/frontends/main/public/images/hero/hero-2.png similarity index 100% rename from frontends/mit-learn/public/images/hero/hero-2.png rename to frontends/main/public/images/hero/hero-2.png diff --git a/frontends/mit-learn/public/images/hero/hero-3.png b/frontends/main/public/images/hero/hero-3.png similarity index 100% rename from frontends/mit-learn/public/images/hero/hero-3.png rename to frontends/main/public/images/hero/hero-3.png diff --git a/frontends/mit-learn/public/images/hero/hero-4.png b/frontends/main/public/images/hero/hero-4.png similarity index 100% rename from frontends/mit-learn/public/images/hero/hero-4.png rename to frontends/main/public/images/hero/hero-4.png diff --git a/frontends/mit-learn/public/images/hero/hero-5.png b/frontends/main/public/images/hero/hero-5.png similarity index 100% rename from frontends/mit-learn/public/images/hero/hero-5.png rename to frontends/main/public/images/hero/hero-5.png diff --git a/frontends/mit-learn/public/images/homepage/personalize-bg.png b/frontends/main/public/images/homepage/personalize-bg.png similarity index 100% rename from frontends/mit-learn/public/images/homepage/personalize-bg.png rename to frontends/main/public/images/homepage/personalize-bg.png diff --git a/frontends/mit-learn/public/images/homepage/personalize-image.png b/frontends/main/public/images/homepage/personalize-image.png similarity index 100% rename from frontends/mit-learn/public/images/homepage/personalize-image.png rename to frontends/main/public/images/homepage/personalize-image.png diff --git a/frontends/main/public/images/learn-og-image.jpg b/frontends/main/public/images/learn-og-image.jpg new file mode 100644 index 0000000000..cf57a0decd Binary files /dev/null and b/frontends/main/public/images/learn-og-image.jpg differ diff --git a/frontends/main/public/images/mit-learn-logo.jpg b/frontends/main/public/images/mit-learn-logo.jpg new file mode 100644 index 0000000000..488eae1e1b Binary files /dev/null and b/frontends/main/public/images/mit-learn-logo.jpg differ diff --git a/frontends/main/public/images/mit-learn-logo.svg b/frontends/main/public/images/mit-learn-logo.svg new file mode 100644 index 0000000000..0069f62884 --- /dev/null +++ b/frontends/main/public/images/mit-learn-logo.svg @@ -0,0 +1,5 @@ + + + diff --git a/frontends/main/public/images/mit-logo-black.svg b/frontends/main/public/images/mit-logo-black.svg new file mode 100644 index 0000000000..7f1fef3d1e --- /dev/null +++ b/frontends/main/public/images/mit-logo-black.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontends/main/public/images/mit-logo-white.svg b/frontends/main/public/images/mit-logo-white.svg new file mode 100644 index 0000000000..5b0774426b --- /dev/null +++ b/frontends/main/public/images/mit-logo-white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/frontends/main/public/images/opengraph-image.jpg b/frontends/main/public/images/opengraph-image.jpg new file mode 100644 index 0000000000..584fa27390 Binary files /dev/null and b/frontends/main/public/images/opengraph-image.jpg differ diff --git a/frontends/mit-learn/public/images/bootcamps_logo.svg b/frontends/main/public/images/platform_logos/bootcamps.svg similarity index 100% rename from frontends/mit-learn/public/images/bootcamps_logo.svg rename to frontends/main/public/images/platform_logos/bootcamps.svg diff --git a/frontends/mit-learn/public/images/csail_logo.svg b/frontends/main/public/images/platform_logos/csail.svg similarity index 100% rename from frontends/mit-learn/public/images/csail_logo.svg rename to frontends/main/public/images/platform_logos/csail.svg diff --git a/frontends/mit-learn/public/images/edx_logo.svg b/frontends/main/public/images/platform_logos/edx.svg similarity index 100% rename from frontends/mit-learn/public/images/edx_logo.svg rename to frontends/main/public/images/platform_logos/edx.svg diff --git a/frontends/mit-learn/public/images/oll_logo.svg b/frontends/main/public/images/platform_logos/oll.svg similarity index 100% rename from frontends/mit-learn/public/images/oll_logo.svg rename to frontends/main/public/images/platform_logos/oll.svg diff --git a/frontends/mit-learn/public/images/search_page_vector.png b/frontends/main/public/images/search_page_vector.png similarity index 100% rename from frontends/mit-learn/public/images/search_page_vector.png rename to frontends/main/public/images/search_page_vector.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-1.png b/frontends/main/public/images/testimonial_images/testimonial-image-1.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-1.png rename to frontends/main/public/images/testimonial_images/testimonial-image-1.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-2.png b/frontends/main/public/images/testimonial_images/testimonial-image-2.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-2.png rename to frontends/main/public/images/testimonial_images/testimonial-image-2.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-3.png b/frontends/main/public/images/testimonial_images/testimonial-image-3.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-3.png rename to frontends/main/public/images/testimonial_images/testimonial-image-3.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-4.png b/frontends/main/public/images/testimonial_images/testimonial-image-4.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-4.png rename to frontends/main/public/images/testimonial_images/testimonial-image-4.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-5.png b/frontends/main/public/images/testimonial_images/testimonial-image-5.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-5.png rename to frontends/main/public/images/testimonial_images/testimonial-image-5.png diff --git a/frontends/mit-learn/public/images/testimonial_images/testimonial-image-6.png b/frontends/main/public/images/testimonial_images/testimonial-image-6.png similarity index 100% rename from frontends/mit-learn/public/images/testimonial_images/testimonial-image-6.png rename to frontends/main/public/images/testimonial_images/testimonial-image-6.png diff --git a/frontends/mit-learn/public/images/unit_banners/bootcamps.jpg b/frontends/main/public/images/unit_banners/bootcamps.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/bootcamps.jpg rename to frontends/main/public/images/unit_banners/bootcamps.jpg diff --git a/frontends/mit-learn/public/images/unit_banners/mitpe.jpg b/frontends/main/public/images/unit_banners/mitpe.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/mitpe.jpg rename to frontends/main/public/images/unit_banners/mitpe.jpg diff --git a/frontends/mit-learn/public/images/unit_banners/mitx.jpg b/frontends/main/public/images/unit_banners/mitx.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/mitx.jpg rename to frontends/main/public/images/unit_banners/mitx.jpg diff --git a/frontends/mit-learn/public/images/unit_banners/ocw.jpg b/frontends/main/public/images/unit_banners/ocw.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/ocw.jpg rename to frontends/main/public/images/unit_banners/ocw.jpg diff --git a/frontends/mit-learn/public/images/unit_banners/see.jpg b/frontends/main/public/images/unit_banners/see.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/see.jpg rename to frontends/main/public/images/unit_banners/see.jpg diff --git a/frontends/mit-learn/public/images/unit_banners/xpro.jpg b/frontends/main/public/images/unit_banners/xpro.jpg similarity index 100% rename from frontends/mit-learn/public/images/unit_banners/xpro.jpg rename to frontends/main/public/images/unit_banners/xpro.jpg diff --git a/frontends/mit-learn/public/images/unit_logos/bootcamps.svg b/frontends/main/public/images/unit_logos/bootcamps.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/bootcamps.svg rename to frontends/main/public/images/unit_logos/bootcamps.svg diff --git a/frontends/mit-learn/public/images/unit_logos/mitpe.svg b/frontends/main/public/images/unit_logos/mitpe.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/mitpe.svg rename to frontends/main/public/images/unit_logos/mitpe.svg diff --git a/frontends/mit-learn/public/images/unit_logos/mitx.svg b/frontends/main/public/images/unit_logos/mitx.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/mitx.svg rename to frontends/main/public/images/unit_logos/mitx.svg diff --git a/frontends/mit-learn/public/images/unit_logos/ocw.svg b/frontends/main/public/images/unit_logos/ocw.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/ocw.svg rename to frontends/main/public/images/unit_logos/ocw.svg diff --git a/frontends/mit-learn/public/images/unit_logos/see.svg b/frontends/main/public/images/unit_logos/see.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/see.svg rename to frontends/main/public/images/unit_logos/see.svg diff --git a/frontends/mit-learn/public/images/unit_logos/xpro.svg b/frontends/main/public/images/unit_logos/xpro.svg similarity index 100% rename from frontends/mit-learn/public/images/unit_logos/xpro.svg rename to frontends/main/public/images/unit_logos/xpro.svg diff --git a/frontends/main/public/mit-dome-2.jpg b/frontends/main/public/mit-dome-2.jpg new file mode 100644 index 0000000000..ee78d2f4e5 Binary files /dev/null and b/frontends/main/public/mit-dome-2.jpg differ diff --git a/frontends/main/public/mit-logo-learn.svg b/frontends/main/public/mit-logo-learn.svg new file mode 100644 index 0000000000..14878e5100 --- /dev/null +++ b/frontends/main/public/mit-logo-learn.svg @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/frontends/main/public/person_with_headphones.png b/frontends/main/public/person_with_headphones.png new file mode 100644 index 0000000000..f9c28852e1 Binary files /dev/null and b/frontends/main/public/person_with_headphones.png differ diff --git a/frontends/main/sentry.client.config.ts b/frontends/main/sentry.client.config.ts new file mode 100644 index 0000000000..b0feb7266b --- /dev/null +++ b/frontends/main/sentry.client.config.ts @@ -0,0 +1,24 @@ +// Added by @sentry/wizard +// This file configures the initialization of Sentry on the client. +// The config you add here will be used whenever a users loads a page in their browser. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs" + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + release: process.env.NEXT_PUBLIC_VERSION, + environment: process.env.NEXT_PUBLIC_SENTRY_ENV, + profilesSampleRate: Number( + process.env.NEXT_PUBLIC_SENTRY_PROFILES_SAMPLE_RATE, + ), + tracesSampleRate: Number(process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE), + tracePropagationTargets: process.env.NEXT_PUBLIC_MITOL_API_BASE_URL + ? [process.env.NEXT_PUBLIC_MITOL_API_BASE_URL] + : [], + // Add optional integrations for additional features + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.browserProfilingIntegration(), + ], +}) diff --git a/frontends/main/sentry.edge.config.ts b/frontends/main/sentry.edge.config.ts new file mode 100644 index 0000000000..72510830d2 --- /dev/null +++ b/frontends/main/sentry.edge.config.ts @@ -0,0 +1,18 @@ +// Added by @sentry/wizard +// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). +// The config you add here will be used whenever one of the edge features is loaded. +// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs" + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + release: process.env.NEXT_PUBLIC_VERSION, + environment: process.env.NEXT_PUBLIC_SENTRY_ENV, + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, +}) diff --git a/frontends/main/sentry.server.config.ts b/frontends/main/sentry.server.config.ts new file mode 100644 index 0000000000..a7c03a3866 --- /dev/null +++ b/frontends/main/sentry.server.config.ts @@ -0,0 +1,17 @@ +// Added by @sentry/wizard +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs" + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + release: process.env.NEXT_PUBLIC_VERSION, + environment: process.env.NEXT_PUBLIC_SENTRY_ENV, + // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control. + tracesSampleRate: 1, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, +}) diff --git a/frontends/main/src/app-pages/AboutPage/AboutPage.test.tsx b/frontends/main/src/app-pages/AboutPage/AboutPage.test.tsx new file mode 100644 index 0000000000..d51b1f2b26 --- /dev/null +++ b/frontends/main/src/app-pages/AboutPage/AboutPage.test.tsx @@ -0,0 +1,12 @@ +import React from "react" +import { screen, renderWithProviders } from "@/test-utils" +import { AboutPage } from "./AboutPage" + +describe("AboutPage", () => { + test("Renders title", async () => { + renderWithProviders() + screen.getByRole("heading", { + name: "About Us", + }) + }) +}) diff --git a/frontends/mit-learn/src/pages/AboutPage/AboutPage.tsx b/frontends/main/src/app-pages/AboutPage/AboutPage.tsx similarity index 88% rename from frontends/mit-learn/src/pages/AboutPage/AboutPage.tsx rename to frontends/main/src/app-pages/AboutPage/AboutPage.tsx index 4f45d7be60..0c1aecdefb 100644 --- a/frontends/mit-learn/src/pages/AboutPage/AboutPage.tsx +++ b/frontends/main/src/app-pages/AboutPage/AboutPage.tsx @@ -1,13 +1,23 @@ -import { Breadcrumbs, Container, Typography, styled } from "ol-components" +"use client" + +import { + Breadcrumbs, + Container, + Typography, + theme, + styled, +} from "ol-components" import * as urls from "@/common/urls" import React from "react" -import MetaTags from "@/page-components/MetaTags/MetaTags" +import domeImage from "@/public/mit-dome-2.jpg" const WHAT_IS_MIT_OPEN_FRAGMENT_IDENTIFIER = "what-is-mit-learn" const NON_DEGREE_LEARNING_FRAGMENT_IDENTIFIER = "non-degree-learning" const ACADEMIC_AND_PROFESSIONAL_CONTENT = "kinds-of-content" -const PageContainer = styled(Container)(({ theme }) => ({ +const SITE_NAME = process.env.NEXT_PUBLIC_SITE_NAME + +const PageContainer = styled(Container)({ color: theme.custom.colors.darkGray2, paddingTop: "40px", paddingBottom: "80px", @@ -15,7 +25,7 @@ const PageContainer = styled(Container)(({ theme }) => ({ paddingTop: "28px", paddingBottom: "32px", }, -})) +}) const BannerContainer = styled.div({ display: "flex", @@ -40,7 +50,7 @@ const BodyContainer = styled.div({ gap: "40px", }) -const HighlightContainer = styled.div(({ theme }) => ({ +const HighlightContainer = styled.div({ display: "flex", flexDirection: "column", gap: "24px", @@ -51,9 +61,9 @@ const HighlightContainer = styled.div(({ theme }) => ({ [theme.breakpoints.down("md")]: { padding: "16px 16px", }, -})) +}) -const SubHeaderContainer = styled.div(({ theme }) => ({ +const SubHeaderContainer = styled.div({ display: "flex", alignItems: "center", alignSelf: "stretch", @@ -62,7 +72,7 @@ const SubHeaderContainer = styled.div(({ theme }) => ({ flexDirection: "column-reverse", gap: "16px", }, -})) +}) const SubHeaderTextContainer = styled.div({ display: "flex", @@ -71,17 +81,17 @@ const SubHeaderTextContainer = styled.div({ alignSelf: "flex-start", }) -const SubHeaderImage = styled.img(({ theme }) => ({ +const SubHeaderImage = styled.img({ flexGrow: 1, alignSelf: "stretch", borderRadius: "8px", backgroundSize: "cover", backgroundPosition: "center", - backgroundImage: "url('/static/images/mit-dome-2.jpg')", + backgroundImage: `url(${domeImage.src})`, [theme.breakpoints.down("md")]: { height: "300px", }, -})) +}) const BodySection = styled.div({ display: "flex", @@ -90,16 +100,15 @@ const BodySection = styled.div({ gap: "16px", }) -const List = styled.ul(({ theme }) => ({ +const List = styled.ul({ "li + li": { marginTop: theme.typography.pxToRem(4), }, -})) +}) const AboutPage: React.FC = () => { return ( - { component="h2" id={WHAT_IS_MIT_OPEN_FRAGMENT_IDENTIFIER} > - What is {APP_SETTINGS.SITE_NAME}? + What is {SITE_NAME}? - {APP_SETTINGS.SITE_NAME} offers a single platform for accessing - all of MIT's non-degree learning resources. This includes courses, - programs, and various educational materials from different MIT - units such as MITx, MIT Bootcamps, MIT OpenCourseWare, MIT - Professional Education, MIT Sloan Executive Education, MIT xPRO, - and other departments across the Institute. + {SITE_NAME} offers a single platform for accessing all of MIT's + non-degree learning resources. This includes courses, programs, + and various educational materials from different MIT units such as + MITx, MIT Bootcamps, MIT OpenCourseWare, MIT Professional + Education, MIT Sloan Executive Education, MIT xPRO, and other + departments across the Institute. Learners can search and browse by topic or department to explore diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelPage.test.tsx similarity index 80% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelPage.test.tsx index 615f64f3a7..1835398066 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx +++ b/frontends/main/src/app-pages/ChannelPage/ChannelPage.test.tsx @@ -1,17 +1,17 @@ +import React from "react" import { urls, factories, makeRequest } from "api/test-utils" import { ChannelTypeEnum, type Channel } from "api/v0" import type { LearningResourcesSearchResponse } from "api" import { - renderTestApp, screen, setMockResponse, - within, waitFor, - assertPartialMetas, -} from "../../test-utils" + renderWithProviders, +} from "@/test-utils" import ChannelSearch from "./ChannelSearch" -import { assertHeadings } from "ol-test-utilities" +import { assertHeadings, getByImageSrc } from "ol-test-utilities" import invariant from "tiny-invariant" +import ChannelPage from "./ChannelPage" jest.mock("./ChannelSearch", () => { const actual = jest.requireActual("./ChannelSearch") @@ -169,10 +169,12 @@ describe.each(ALL_CHANNEL_TYPES)( "platform=ocw&platform=mitxonline&department=8&department=9", channel_type: channelType, }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) if (channelType === ChannelTypeEnum.Topic) { setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) await screen.findAllByText(channel.title) const expectedProps = expect.objectContaining({ constantSearchParams: { @@ -192,10 +194,12 @@ describe.each(ALL_CHANNEL_TYPES)( channel_type: channelType, }) channel.search_filter = undefined + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) if (channelType === ChannelTypeEnum.Topic) { setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) await screen.findAllByText(channel.title) expect(mockedChannelSearch).toHaveBeenCalledTimes(0) @@ -206,10 +210,12 @@ describe.each(ALL_CHANNEL_TYPES)( channel_type: channelType, }) channel.search_filter = undefined + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) if (channelType === ChannelTypeEnum.Topic) { setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) await screen.findAllByText(channel.title) await waitFor(() => { @@ -232,10 +238,12 @@ describe.each(ALL_CHANNEL_TYPES)( {}, { isSubscribed }, ) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) if (channelType === ChannelTypeEnum.Topic) { setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) const subscribedButton = await screen.findAllByText("Follow") expect(subscribedButton[0]).toBeVisible() }, @@ -256,7 +264,9 @@ describe.each(NON_UNIT_CHANNEL_TYPES)( setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) await screen.findAllByText(channel.title) const carousels = screen.queryByText("Featured Courses") expect(carousels).toBe(null) @@ -271,13 +281,10 @@ describe.each(NON_UNIT_CHANNEL_TYPES)( setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) - const title = await screen.findByRole("heading", { name: channel.title }) - await waitFor(() => { - assertPartialMetas({ - title: `${channel.title} | ${APP_SETTINGS.SITE_NAME}`, - }) + const { view } = renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, }) + const title = await screen.findByRole("heading", { name: channel.title }) // Banner background image expect( someAncestor(title, (el) => @@ -287,11 +294,10 @@ describe.each(NON_UNIT_CHANNEL_TYPES)( ), ).toBe(true) // logo - const images = screen.getAllByRole("img") - const logos = images.filter((img) => - img.src.includes(channel.configuration.logo), + getByImageSrc( + view.container, + `${window.origin}${channel.configuration.logo}`, ) - expect(logos.length).toBe(1) }, 10000) test("headings", async () => { @@ -299,10 +305,12 @@ describe.each(NON_UNIT_CHANNEL_TYPES)( search_filter: "topic=Physics", channel_type: channelType, }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) if (channelType === ChannelTypeEnum.Topic) { setupTopicApis(channel) } - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) await waitFor(() => { assertHeadings([ @@ -322,11 +330,11 @@ describe("Channel Pages, Topic only", () => { search_filter: "topic=Physics", channel_type: ChannelTypeEnum.Topic, }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) const { topic, subTopics } = setupTopicApis(channel) invariant(topic) - renderTestApp({ - url: `/c/${channel.channel_type}/${channel.name.replace(/\s/g, "-")}`, - }) const subTopicsTitle = await screen.findByText("Subtopics") expect(subTopicsTitle).toBeInTheDocument() @@ -334,8 +342,11 @@ describe("Channel Pages, Topic only", () => { // name arg can be string, regex, or function name: (name) => subTopics?.results.map((t) => t.name).includes(name), }) - links.forEach(async (link, i) => { - expect(link).toHaveAttribute("href", subTopics.results[i].channel_url) + links.forEach((link, i) => { + expect(link).toHaveAttribute( + "href", + new URL(subTopics.results[i].channel_url!, "http://localhost").pathname, + ) }) }, 10000) @@ -351,7 +362,7 @@ describe("Channel Pages, Topic only", () => { (t) => t.name.replace(/\s/g, "-") !== subTopicChannel.name.replace(/\s/g, "-"), ) - renderTestApp({ + renderWithProviders(, { url: `/c/${subTopicChannel.channel_type}/${subTopicChannel.name.replace(/\s/g, "-")}`, }) @@ -368,63 +379,17 @@ describe("Channel Pages, Topic only", () => { }) describe("Channel Pages, Unit only", () => { - it("Sets the expected meta tags", async () => { - const { channel } = setupApis({ - search_filter: "offered_by=ocw", - channel_type: "unit", - }) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) - const title = `${channel.title} | ${APP_SETTINGS.SITE_NAME}` - const { heading: description } = channel.configuration - await waitFor(() => { - assertPartialMetas({ - title, - description, - og: { title, description }, - }) - }) - }) - - it("Sets the expected metadata tags when resource drawer is open", async () => { - /** - * Check that the meta tags are correct on channel page, even when the - * resource drawer is open. - */ - const { channel } = setupApis({ - search_filter: "offered_by=ocw", - channel_type: "unit", - }) - const resource = factories.learningResources.resource() - setMockResponse.get( - urls.learningResources.details({ id: resource.id }), - resource, - ) - - renderTestApp({ - url: `/c/${channel.channel_type}/${channel.name}?resource=${resource.id}`, - }) - await screen.findByRole("heading", { name: channel.title, hidden: true }) - const title = `${resource.title} | ${APP_SETTINGS.SITE_NAME}` - const description = resource.description - await waitFor(() => { - assertPartialMetas({ - title, - description, - og: { title, description }, - }) - }) - }) - it("Displays the channel title, banner, and avatar", async () => { const { channel } = setupApis({ search_filter: "offered_by=ocw", channel_type: "unit", }) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) const title = await screen.findByRole("heading", { name: channel.title }) - const image = within(title).getByRole("img") - expect(image.src).toContain(channel.configuration.logo) + getByImageSrc(title, `${window.origin}${channel.configuration.logo}`) }) it("Displays a featured carousel if the channel type is 'unit'", async () => { const { channel } = setupApis({ @@ -432,7 +397,9 @@ describe("Channel Pages, Unit only", () => { channel_type: "unit", }) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) await screen.findAllByText(channel.title) const carousel = await screen.findByText("Featured Courses") expect(carousel).toBeInTheDocument() @@ -459,7 +426,9 @@ describe("Channel Pages, Unit only", () => { search_filter: "offered_by=ocw", channel_type: "unit", }) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) await waitFor(() => { assertHeadings([ diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelPage.tsx similarity index 89% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelPage.tsx index a252894862..cffcee85e1 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx +++ b/frontends/main/src/app-pages/ChannelPage/ChannelPage.tsx @@ -1,5 +1,8 @@ +"use client" + import React from "react" -import { useParams } from "react-router" +import LearningResourceDrawer from "@/page-components/LearningResourceDrawer/LearningResourceDrawer" +import { useParams } from "next/navigation" import { ChannelPageTemplate } from "./ChannelPageTemplate" import { useChannelDetail } from "api/hooks/channels" import ChannelSearch from "./ChannelSearch" @@ -38,6 +41,7 @@ const ChannelPage: React.FC = () => { name && channelType && ( <> + {publicDescription && ( {publicDescription} diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPageTemplate.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelPageTemplate.tsx similarity index 100% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelPageTemplate.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelPageTemplate.tsx diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelSearch.test.tsx similarity index 93% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelSearch.test.tsx index 760e7523e2..c15513dcc0 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.test.tsx +++ b/frontends/main/src/app-pages/ChannelPage/ChannelSearch.test.tsx @@ -1,10 +1,18 @@ -import { screen, within, waitFor, renderTestApp, user } from "@/test-utils" +import React from "react" +import { + screen, + within, + waitFor, + renderWithProviders, + user, +} from "@/test-utils" import { setMockResponse, urls, factories, makeRequest } from "api/test-utils" import type { LearningResourcesSearchResponse } from "api" import invariant from "tiny-invariant" import { makeWidgetListResponse } from "ol-widgets/src/factories" import type { Channel } from "api/v0" import { ChannelTypeEnum } from "api/v0" +import ChannelPage from "./ChannelPage" const setMockApiResponses = ({ search, @@ -126,7 +134,9 @@ describe("ChannelSearch", () => { }, }) setMockResponse.get(urls.userMe.get(), {}) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}`, + }) await screen.findAllByText(channel.title) const tabpanel = await screen.findByRole("tabpanel") for (const resource of resources) { @@ -175,7 +185,7 @@ describe("ChannelSearch", () => { }) setMockResponse.get(urls.userMe.get(), {}) - renderTestApp({ + renderWithProviders(, { url: `/c/${channel.channel_type}/${channel.name}/${url}`, }) @@ -241,7 +251,9 @@ describe("ChannelSearch", () => { setMockResponse.get(urls.userMe.get(), {}) - renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}/` }) + renderWithProviders(, { + url: `/c/${channel.channel_type}/${channel.name}/`, + }) await waitFor(() => { expect(makeRequest.mock.calls.length > 0).toBe(true) @@ -290,9 +302,8 @@ describe("ChannelSearch", () => { setMockResponse.get(urls.userMe.get(), {}) const initialSearch = "?q=meow&page=2" - const finalSearch = "?q=woof" - const { location } = renderTestApp({ + const { location } = renderWithProviders(, { url: `/c/${channel.channel_type}/${channel.name}${initialSearch}`, }) @@ -302,8 +313,8 @@ describe("ChannelSearch", () => { expect(queryInput.value).toBe("meow") await user.clear(queryInput) await user.paste("woof") - expect(location.current.search).toBe(initialSearch) + expect(location.current.searchParams.get("q")).toBe("meow") await user.click(screen.getByRole("button", { name: "Search" })) - expect(location.current.search).toBe(finalSearch) + expect(location.current.searchParams.get("q")).toBe("woof") }) }) diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelSearch.tsx similarity index 97% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelSearch.tsx index 2e88ed9bac..e756bb4e8e 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx +++ b/frontends/main/src/app-pages/ChannelPage/ChannelSearch.tsx @@ -12,12 +12,12 @@ import type { BooleanFacets, FacetManifest, } from "@mitodl/course-search-utils" -import { useSearchParams } from "@mitodl/course-search-utils/react-router" +import { useSearchParams } from "@mitodl/course-search-utils/next" import SearchDisplay from "@/page-components/SearchDisplay/SearchDisplay" import { Container, styled, VisuallyHidden } from "ol-components" import { SearchField } from "@/page-components/SearchField/SearchField" -import { getFacetManifest } from "@/pages/SearchPage/SearchPage" +import { getFacetManifest } from "@/app-pages/SearchPage/SearchPage" import _ from "lodash" diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearchFacetDisplay.tsx b/frontends/main/src/app-pages/ChannelPage/ChannelSearchFacetDisplay.tsx similarity index 100% rename from frontends/mit-learn/src/pages/ChannelPage/ChannelSearchFacetDisplay.tsx rename to frontends/main/src/app-pages/ChannelPage/ChannelSearchFacetDisplay.tsx diff --git a/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx b/frontends/main/src/app-pages/ChannelPage/DefaultChannelTemplate.tsx similarity index 96% rename from frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx rename to frontends/main/src/app-pages/ChannelPage/DefaultChannelTemplate.tsx index 30726a41a9..cc9cdbb121 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/DefaultChannelTemplate.tsx +++ b/frontends/main/src/app-pages/ChannelPage/DefaultChannelTemplate.tsx @@ -10,7 +10,6 @@ import { CHANNEL_TYPE_BREADCRUMB_TARGETS, ChannelControls, } from "./ChannelPageTemplate" -import MetaTags from "@/page-components/MetaTags/MetaTags" const ChildrenContainer = styled.div(({ theme }) => ({ paddingTop: "40px", @@ -60,7 +59,6 @@ const DefaultChannelTemplate: React.FC = ({ const displayConfiguration = channel.data?.configuration return ( <> - = (props) => { return null } const isTopLevelTopic = topic?.parent === null + if (isTopLevelTopic) { return ( - } backgroundUrl={ displayConfiguration?.banner_background ?? - "/static/images/background_steps.jpg" + "/images/backgrounds/background_steps.jpg" } extraActions={ diff --git a/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx b/frontends/main/src/app-pages/ChannelPage/UnitChannelTemplate.tsx similarity index 95% rename from frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx rename to frontends/main/src/app-pages/ChannelPage/UnitChannelTemplate.tsx index fb356686fa..e939d42b29 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx +++ b/frontends/main/src/app-pages/ChannelPage/UnitChannelTemplate.tsx @@ -22,7 +22,6 @@ import { HOME as HOME_URL, UNITS as UNITS_URL } from "../../common/urls" import { ChannelTypeEnum } from "api/v0" import { ChannelControls, UNITS_LABEL } from "./ChannelPageTemplate" import TestimonialDisplay from "@/page-components/TestimonialDisplay/TestimonialDisplay" -import MetaTags from "@/page-components/MetaTags/MetaTags" const StyledBannerBackground = styled(BannerBackground)(({ theme }) => ({ padding: "48px 0 64px 0", @@ -106,11 +105,6 @@ const UnitChannelTemplate: React.FC = ({ return ( <> - { test("Renders title", async () => { setupAPIs() renderWithProviders() - await waitFor(() => { - expect(document.title).toBe("Your MIT Learning Journey | MIT Learn") - }) screen.getByRole("heading", { name: "Your MIT Learning Journey", }) diff --git a/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx b/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx similarity index 81% rename from frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx rename to frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx index 96310366cc..838228df00 100644 --- a/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx +++ b/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx @@ -1,4 +1,6 @@ -import React from "react" +"use client" + +import React, { Suspense } from "react" import { RiAccountCircleFill, RiDashboardLine, @@ -21,11 +23,11 @@ import { TypographyProps, styled, } from "ol-components" -import { Link } from "react-router-dom" +import Link from "next/link" import { useUserMe } from "api/hooks/user" -import { useLocation, useParams } from "react-router" -import UserListListingComponent from "../UserListListingComponent/UserListListingComponent" - +import { useParams } from "next/navigation" +import UserListListingComponent from "@/page-components/UserListListing/UserListListing" +import backgroundImage from "@/public/images/backgrounds/user_menu_background.svg" import { ProfileEditForm } from "./ProfileEditForm" import { useProfileMeQuery } from "api/hooks/profile" import { @@ -40,7 +42,7 @@ import ResourceCarousel from "@/page-components/ResourceCarousel/ResourceCarouse import UserListDetailsTab from "./UserListDetailsTab" import { SettingsPage } from "./SettingsPage" import { DASHBOARD_HOME, MY_LISTS, PROFILE, SETTINGS } from "@/common/urls" -import MetaTags from "@/page-components/MetaTags/MetaTags" +import LearningResourceDrawer from "@/page-components/LearningResourceDrawer/LearningResourceDrawer" /** * @@ -64,7 +66,7 @@ const DesktopOnly = styled.div(({ theme }) => ({ const Background = styled.div(({ theme }) => ({ backgroundColor: theme.custom.colors.lightGray1, - backgroundImage: "url('/static/images/user_menu_background.svg')", + backgroundImage: `url(${backgroundImage.src})`, backgroundAttachment: "fixed", backgroundRepeat: "no-repeat", height: "100%", @@ -285,7 +287,7 @@ const UserMenuTab: React.FC = (props) => { return ( { const { isLoading: isLoadingUser, data: user } = useUserMe() const { isLoading: isLoadingProfile, data: profile } = useProfileMeQuery() - const { pathname } = useLocation() + const params = useParams<{ tab: string }>() + + const appRouterPath = `${DASHBOARD_HOME}/${params.tab}` + const id = Number(useParams().id) || -1 - const showUserListDetail = pathname.includes(MY_LISTS) && id !== -1 + const showUserListDetail = appRouterPath === MY_LISTS && id !== -1 + const tabValue = showUserListDetail ? MY_LISTS - : [DASHBOARD_HOME, MY_LISTS, PROFILE, SETTINGS].includes(pathname) - ? pathname + : [DASHBOARD_HOME, MY_LISTS, PROFILE, SETTINGS].includes(appRouterPath) + ? appRouterPath : DASHBOARD_HOME const topics = profile?.preference_search_filters.topic @@ -404,8 +410,8 @@ const DashboardPage: React.FC = () => { return ( + - @@ -434,52 +440,54 @@ const DashboardPage: React.FC = () => { - - {topics?.map((topic, index) => ( + - ))} - {certification === true ? ( + {topics?.map((topic, index) => ( + + ))} + {certification === true ? ( + + ) : ( + + )} - ) : ( - )} - - + @@ -514,8 +522,6 @@ const DashboardPage: React.FC = () => { ) } -export { - DashboardPage, - TabKeys as DashboardTabKeys, - TabLabels as DashboardTabLabels, -} +export default DashboardPage + +export { TabKeys as DashboardTabKeys, TabLabels as DashboardTabLabels } diff --git a/frontends/mit-learn/src/pages/DashboardPage/ProfileEditForm.tsx b/frontends/main/src/app-pages/DashboardPage/ProfileEditForm.tsx similarity index 100% rename from frontends/mit-learn/src/pages/DashboardPage/ProfileEditForm.tsx rename to frontends/main/src/app-pages/DashboardPage/ProfileEditForm.tsx diff --git a/frontends/mit-learn/src/pages/DashboardPage/SettingsPage.test.tsx b/frontends/main/src/app-pages/DashboardPage/SettingsPage.test.tsx similarity index 100% rename from frontends/mit-learn/src/pages/DashboardPage/SettingsPage.test.tsx rename to frontends/main/src/app-pages/DashboardPage/SettingsPage.test.tsx diff --git a/frontends/mit-learn/src/pages/DashboardPage/SettingsPage.tsx b/frontends/main/src/app-pages/DashboardPage/SettingsPage.tsx similarity index 100% rename from frontends/mit-learn/src/pages/DashboardPage/SettingsPage.tsx rename to frontends/main/src/app-pages/DashboardPage/SettingsPage.tsx diff --git a/frontends/mit-learn/src/pages/DashboardPage/UserListDetailsTab.tsx b/frontends/main/src/app-pages/DashboardPage/UserListDetailsTab.tsx similarity index 92% rename from frontends/mit-learn/src/pages/DashboardPage/UserListDetailsTab.tsx rename to frontends/main/src/app-pages/DashboardPage/UserListDetailsTab.tsx index e27e25d308..0ec2ab4ff9 100644 --- a/frontends/mit-learn/src/pages/DashboardPage/UserListDetailsTab.tsx +++ b/frontends/main/src/app-pages/DashboardPage/UserListDetailsTab.tsx @@ -3,7 +3,7 @@ import { useInfiniteUserListItems, useUserListsDetail, } from "api/hooks/learningResources" -import { useNavigate } from "react-router" +import { useRouter } from "next/navigation" import { ListType } from "api/constants" import { useUserMe } from "api/hooks/user" import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs" @@ -19,7 +19,7 @@ const UserListDetailsTab: React.FC = (props) => { const { data: user } = useUserMe() const listQuery = useUserListsDetail(userListId) const itemsQuery = useInfiniteUserListItems({ userlist_id: userListId }) - const navigate = useNavigate() + const router = useRouter() const items = useMemo(() => { const pages = itemsQuery.data?.pages @@ -27,7 +27,7 @@ const UserListDetailsTab: React.FC = (props) => { }, [itemsQuery.data]) const onDestroyUserList = () => { - navigate("/dashboard/my-lists") + router.push("/dashboard/my-lists") } return ( diff --git a/frontends/mit-learn/src/pages/DashboardPage/carousels.ts b/frontends/main/src/app-pages/DashboardPage/carousels.ts similarity index 100% rename from frontends/mit-learn/src/pages/DashboardPage/carousels.ts rename to frontends/main/src/app-pages/DashboardPage/carousels.ts diff --git a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.test.tsx similarity index 97% rename from frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx rename to frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.test.tsx index c70eb0ee36..6459315d82 100644 --- a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx +++ b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.test.tsx @@ -128,9 +128,6 @@ describe("DepartmentListingPage", () => { it("Has correct page title", async () => { setupApis() renderWithProviders() - await waitFor(() => { - expect(document.title).toBe("Departments | MIT Learn") - }) screen.getByRole("heading", { name: "Browse by Academic Department" }) }) @@ -198,7 +195,8 @@ describe("DepartmentListingPage", () => { const link = await screen.findByRole("link", { name: (name) => name.includes(dept.name), }) - expect(link).toHaveAttribute("href", dept.channel_url) + + expect(link).toHaveAttribute("href", new URL(dept.channel_url!).pathname) }) test("headings", async () => { diff --git a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx similarity index 95% rename from frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx rename to frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx index d4bb1f0a49..32e308a1ea 100644 --- a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx +++ b/frontends/main/src/app-pages/DepartmentListingPage/DepartmentListingPage.tsx @@ -1,3 +1,5 @@ +"use client" + import React from "react" import { Container, @@ -25,7 +27,7 @@ import { RiTerminalBoxLine, } from "@remixicon/react" import { HOME } from "@/common/urls" -import MetaTags from "@/page-components/MetaTags/MetaTags" + import { aggregateProgramCounts, aggregateCourseCounts } from "@/common/utils" import { useChannelCounts } from "api/hooks/channels" @@ -142,9 +144,15 @@ const SchoolDepartments: React.FC = ({ { count: courses, label: pluralize("Course", courses) }, { count: programs, label: pluralize("Program", programs) }, ] + if (!department.channel_url) return null return ( - + { return ( <> - { }) mockedUseFeatureFlagEnabled.mockReturnValue(testCase === "on") - renderTestApp({ - url: commonUrls.ECOMMERCE_CART, - }) - await waitFor(() => { - testCase === "on" - ? expect(document.title).toBe("Shopping Cart | MIT Learn") - : expect(document.title).not.toBe("Shopping Cart | MIT Learn") - }) - }) - }) - - test("Sends to login page when logged out", async () => { - setMockResponse.get(urls.userMe.get(), { - [Permissions.Authenticated]: false, - }) - const expectedUrl = login({ - pathname: "/cart/", - }) - - renderTestApp({ - url: commonUrls.ECOMMERCE_CART, - }) - - await waitFor(() => { - expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) + if (testCase === "off") { + allowConsoleErrors() + expect(() => + renderWithProviders(, { + url: commonUrls.ECOMMERCE_CART, + }), + ).toThrow(ForbiddenError) + } else { + renderWithProviders(, { + url: commonUrls.ECOMMERCE_CART, + }) + } }) }) }) diff --git a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx b/frontends/main/src/app-pages/EcommercePages/CartPage/CartPage.tsx similarity index 88% rename from frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx rename to frontends/main/src/app-pages/EcommercePages/CartPage/CartPage.tsx index 59f9b94dd4..00f56199e6 100644 --- a/frontends/mit-learn/src/pages/EcommercePages/CartPage.tsx +++ b/frontends/main/src/app-pages/EcommercePages/CartPage/CartPage.tsx @@ -1,14 +1,13 @@ +"use client" import React from "react" import { Breadcrumbs, Container, Typography } from "ol-components" import EcommerceFeature from "@/page-components/EcommerceFeature/EcommerceFeature" -import MetaTags from "@/page-components/MetaTags/MetaTags" import * as urls from "@/common/urls" const CartPage: React.FC = () => { return ( - = ({ - title, - children, -}) => { +const ErrorPageTemplate: React.FC = ({ children }) => { return ( - - - {`${title} | ${APP_SETTINGS.SITE_NAME}`} - - + {children} diff --git a/frontends/main/src/app-pages/ErrorPage/FallbackErrorPage.tsx b/frontends/main/src/app-pages/ErrorPage/FallbackErrorPage.tsx new file mode 100644 index 0000000000..50b7984c4a --- /dev/null +++ b/frontends/main/src/app-pages/ErrorPage/FallbackErrorPage.tsx @@ -0,0 +1,18 @@ +"use client" + +import React from "react" +import ErrorPageTemplate from "./ErrorPageTemplate" +import { Typography } from "ol-components" + +const FallbackErrorPage = ({ error }: { error: Pick }) => { + return ( + + + Something went wrong. + + {error.message} + + ) +} + +export default FallbackErrorPage diff --git a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx similarity index 57% rename from frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx rename to frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx index f8c0383b7c..5ba855929f 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx +++ b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx @@ -1,7 +1,6 @@ import React from "react" -import { waitFor } from "@testing-library/react" import { renderWithProviders, screen } from "../../test-utils" -import { HOME, login } from "@/common/urls" +import { HOME } from "@/common/urls" import ForbiddenPage from "./ForbiddenPage" import { setMockResponse, urls } from "api/test-utils" import { Permissions } from "@/common/permissions" @@ -25,19 +24,6 @@ afterAll(() => { window.location = oldWindowLocation }) -test("The ForbiddenPage loads with meta", async () => { - setMockResponse.get(urls.userMe.get(), { - [Permissions.Authenticated]: true, - }) - renderWithProviders() - await waitFor(() => { - expect(document.title).toBe("Not Allowed | MIT Learn") - }) - - const meta = document.head.querySelector('meta[name="robots"]') - expect(meta).toHaveProperty("content", "noindex,noarchive") -}) - test("The ForbiddenPage loads with Correct Title", () => { setMockResponse.get(urls.userMe.get(), { [Permissions.Authenticated]: true, @@ -54,17 +40,3 @@ test("The ForbiddenPage loads with a link that directs to HomePage", () => { const homeLink = screen.getByRole("link", { name: "Home" }) expect(homeLink).toHaveAttribute("href", HOME) }) - -test("Redirects unauthenticated users to login", async () => { - setMockResponse.get(urls.userMe.get(), { - [Permissions.Authenticated]: false, - }) - renderWithProviders(, { url: "/some/url?foo=bar#baz" }) - - const expectedUrl = login({ - pathname: "/some/url", - search: "?foo=bar", - hash: "#baz", - }) - expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) -}) diff --git a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx similarity index 77% rename from frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx rename to frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx index df8efb636b..4079ba5603 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx +++ b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx @@ -2,18 +2,18 @@ import React, { useEffect } from "react" import ErrorPageTemplate from "./ErrorPageTemplate" import { useUserMe } from "api/hooks/user" import { Typography } from "ol-components" -import { login } from "@/common/urls" -import { useLocation } from "react-router" +import { redirect } from "next/navigation" +import * as urls from "@/common/urls" const ForbiddenPage: React.FC = () => { - const location = useLocation() const { data: user } = useUserMe() useEffect(() => { if (!user?.is_authenticated) { - window.location.assign(login(location)) + const loginUrl = urls.login() + redirect(loginUrl) } - }) + }, [user]) return ( diff --git a/frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.test.tsx b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx similarity index 57% rename from frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.test.tsx rename to frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx index 1918565748..83213c82b8 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.test.tsx +++ b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx @@ -1,17 +1,8 @@ import React from "react" -import { waitFor } from "@testing-library/react" -import { renderWithProviders, screen } from "../../test-utils" +import { renderWithProviders, screen } from "@/test-utils" import { HOME } from "@/common/urls" import NotFoundPage from "./NotFoundPage" -test("The NotFoundPage loads with meta", async () => { - renderWithProviders(, {}) - await waitFor(() => { - const meta = document.head.querySelector('meta[name="robots"]') - expect(meta).toHaveProperty("content", "noindex,noarchive") - }) -}) - test("The NotFoundPage loads with Correct Title", () => { renderWithProviders(, {}) screen.getByRole("heading", { name: "404 Not Found Error" }) diff --git a/frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.tsx b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.tsx similarity index 96% rename from frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.tsx rename to frontends/main/src/app-pages/ErrorPage/NotFoundPage.tsx index 294383a2c5..60fcd34d86 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/NotFoundPage.tsx +++ b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.tsx @@ -1,3 +1,5 @@ +"use client" + import React from "react" import ErrorPageTemplate from "./ErrorPageTemplate" import { Typography } from "ol-components" diff --git a/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx b/frontends/main/src/app-pages/HomePage/BrowseTopicsSection.tsx similarity index 91% rename from frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx rename to frontends/main/src/app-pages/HomePage/BrowseTopicsSection.tsx index a15039f9df..a79b995e0e 100644 --- a/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx +++ b/frontends/main/src/app-pages/HomePage/BrowseTopicsSection.tsx @@ -1,4 +1,5 @@ import React from "react" +import Link from "next/link" import { Container, styled, @@ -7,13 +8,12 @@ import { ButtonLink, TypographyProps, } from "ol-components" -import { Link } from "react-router-dom" import { useLearningResourceTopics } from "api/hooks/learningResources" import { RiArrowRightLine } from "@remixicon/react" import RootTopicIcon from "@/components/RootTopicIcon/RootTopicIcon" const Section = styled.section` - background: #fff url("/static/images/open-bg-texture-with-gradient.svg") + background: #fff url("/images/backgrounds/open-bg-texture-with-gradient.svg") no-repeat center left; background-size: 135% auto; padding: 80px 0; @@ -116,7 +116,10 @@ const BrowseTopicsSection: React.FC = () => { {topics?.results.map( ({ id, name, channel_url: channelUrl, icon }) => { return ( - + {name} diff --git a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx b/frontends/main/src/app-pages/HomePage/HomePage.test.tsx similarity index 92% rename from frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx rename to frontends/main/src/app-pages/HomePage/HomePage.test.tsx index 44da718dde..6938d2be05 100644 --- a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx +++ b/frontends/main/src/app-pages/HomePage/HomePage.test.tsx @@ -13,7 +13,7 @@ import { user, within, waitFor, -} from "../../test-utils" +} from "@/test-utils" import invariant from "tiny-invariant" import * as routes from "@/common/urls" import { assertHeadings } from "ol-test-utilities" @@ -101,16 +101,16 @@ describe("Home Page Hero", () => { setupAPIs() renderWithProviders() const expected = [ - { label: "Topic", href: "/topics/" }, - { label: "Recently Added", href: "/search/?sortby=new" }, - { label: "Popular", href: "/search/?sortby=-views" }, - { label: "Upcoming", href: "/search/?sortby=upcoming" }, - { label: "Free", href: "/search/?free=true" }, + { label: "Topic", href: "/topics" }, + { label: "Recently Added", href: "/search?sortby=new" }, + { label: "Popular", href: "/search?sortby=-views" }, + { label: "Upcoming", href: "/search?sortby=upcoming" }, + { label: "Free", href: "/search?free=true" }, { label: "With Certificate", - href: "/search/?certification_type=professional&certification_type=completion&certification_type=micromasters", + href: "/search?certification_type=professional&certification_type=completion&certification_type=micromasters", }, - { label: "Explore All", href: "/search/" }, + { label: "Explore All", href: "/search" }, ] expected.forEach(({ label, href }) => { const link = screen.getByRole("link", { name: label }) @@ -176,7 +176,8 @@ describe("Home Page News and Events", () => { let section await waitFor(() => { section = screen - .getByRole("heading", { name: "Stories" })! + .getAllByRole("heading", { name: "Stories" })! + .at(0)! .closest("section")! }) @@ -227,7 +228,8 @@ describe("Home Page News and Events", () => { let section await waitFor(() => { section = screen - .getByRole("heading", { name: "Events" })! + .getAllByRole("heading", { name: "Events" })! + .at(0)! .closest("section")! }) @@ -263,7 +265,7 @@ describe("Home Page personalize section", () => { ).closest("section") invariant(personalize) const link = within(personalize).getByRole("link") - expect(link).toHaveAttribute("href", "/dashboard/") + expect(link).toHaveAttribute("href", "/dashboard") }) test("Links to login when not authenticated", async () => { @@ -334,6 +336,8 @@ test("Headings", async () => { { level: 2, name: "MIT Stories & Events" }, { level: 3, name: "Stories" }, { level: 3, name: "Events" }, + { level: 3, name: "Stories" }, + { level: 3, name: "Events" }, ]) }) }) diff --git a/frontends/mit-learn/src/pages/HomePage/HomePage.tsx b/frontends/main/src/app-pages/HomePage/HomePage.tsx similarity index 84% rename from frontends/mit-learn/src/pages/HomePage/HomePage.tsx rename to frontends/main/src/app-pages/HomePage/HomePage.tsx index d55d25e523..553c797d70 100644 --- a/frontends/mit-learn/src/pages/HomePage/HomePage.tsx +++ b/frontends/main/src/app-pages/HomePage/HomePage.tsx @@ -1,15 +1,17 @@ +"use client" + import React from "react" -import { Container, styled } from "ol-components" -import HeroSearch from "./HeroSearch" +import { Container, styled, theme } from "ol-components" +import HeroSearch from "@/page-components/HeroSearch/HeroSearch" import BrowseTopicsSection from "./BrowseTopicsSection" import NewsEventsSection from "./NewsEventsSection" import TestimonialsSection from "./TestimonialsSection" import ResourceCarousel from "@/page-components/ResourceCarousel/ResourceCarousel" import PersonalizeSection from "./PersonalizeSection" import * as carousels from "./carousels" -import MetaTags from "@/page-components/MetaTags/MetaTags" +import LearningResourceDrawer from "@/page-components/LearningResourceDrawer/LearningResourceDrawer" -const FullWidthBackground = styled.div(({ theme }) => ({ +const FullWidthBackground = styled.div({ background: "linear-gradient(0deg, #FFF 0%, #E9ECEF 100%);", paddingBottom: "80px", [theme.breakpoints.down("md")]: { @@ -18,7 +20,7 @@ const FullWidthBackground = styled.div(({ theme }) => ({ [theme.breakpoints.down("sm")]: { paddingBottom: "32px", }, -})) +}) const FeaturedCoursesCarousel = styled(ResourceCarousel)(({ theme }) => ({ marginTop: "16px", @@ -39,7 +41,7 @@ const MediaCarousel = styled(ResourceCarousel)(({ theme }) => ({ const HomePage: React.FC = () => { return ( <> - + diff --git a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx similarity index 86% rename from frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx rename to frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx index 4aabd7b0f2..9c6923bd8a 100644 --- a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx +++ b/frontends/main/src/app-pages/HomePage/NewsEventsSection.tsx @@ -5,7 +5,6 @@ import { theme, Typography, Grid, - useMuiBreakpointAtLeast, Card, TypographyProps, } from "ol-components" @@ -63,8 +62,8 @@ const MobileContainer = styled.section` width: 100%; margin: 0 -16px; - h4 { - margin: 0 16px 24px; + h3 { + margin: 0 16px 12px; } ` @@ -101,6 +100,7 @@ const Events = styled.div` const MobileEvents = styled(Events)` padding: 0 16px; + gap: 18px; ` const EventCard = styled(Card)` @@ -167,13 +167,33 @@ const Chevron = styled(RiArrowRightSLine)` justify-content: flex-end; ` +const AboveMdOnly = styled.div(({ theme }) => ({ + [theme.breakpoints.down("md")]: { + display: "none", + }, +})) + +const BelowMdOnly = styled.div(({ theme }) => ({ + [theme.breakpoints.up("md")]: { + display: "none", + }, +})) + +const AboveLgOnly = styled.div(({ theme }) => ({ + [theme.breakpoints.down("lg")]: { + display: "none", + }, +})) + const Story: React.FC<{ item: NewsFeedItem; mobile: boolean }> = ({ item, mobile, }) => { return ( - + {item.image.url ? ( + + ) : null} {item.title} @@ -197,14 +217,11 @@ const NewsEventsSection: React.FC = () => { sortby: "event_date", }) - const isAboveLg = useMuiBreakpointAtLeast("lg") - const isMobile = !useMuiBreakpointAtLeast("md") - if (!news || !events) { return null } - const stories = news!.results?.slice(0, isAboveLg || isMobile ? 6 : 4) || [] + const stories = news!.results?.slice(0, 6) || [] const EventCards = events!.results?.map((item) => ( @@ -239,7 +256,7 @@ const NewsEventsSection: React.FC = () => { See what's happening in the world of learning with the latest news, insights, and upcoming events at MIT. - {isMobile ? ( + @@ -249,7 +266,7 @@ const NewsEventsSection: React.FC = () => { {stories.map((item) => ( ))} @@ -262,7 +279,8 @@ const NewsEventsSection: React.FC = () => { {EventCards} - ) : ( + + @@ -270,9 +288,15 @@ const NewsEventsSection: React.FC = () => { Stories - {stories.map((item) => ( + {stories.map((item, index) => ( - + {index >= 4 ? ( + + + + ) : ( + + )} ))} @@ -285,7 +309,7 @@ const NewsEventsSection: React.FC = () => { - )} + ) } diff --git a/frontends/mit-learn/src/pages/HomePage/PersonalizeSection.tsx b/frontends/main/src/app-pages/HomePage/PersonalizeSection.tsx similarity index 90% rename from frontends/mit-learn/src/pages/HomePage/PersonalizeSection.tsx rename to frontends/main/src/app-pages/HomePage/PersonalizeSection.tsx index 0046645304..93bd620af7 100644 --- a/frontends/mit-learn/src/pages/HomePage/PersonalizeSection.tsx +++ b/frontends/main/src/app-pages/HomePage/PersonalizeSection.tsx @@ -5,8 +5,7 @@ import * as urls from "@/common/urls" const FullWidthBackground = styled.div(({ theme }) => ({ padding: "80px 0", - background: - 'url("/static/images/homepage/personalize-bg.png") center top no-repeat', + background: 'url("/images/homepage/personalize-bg.png") center top no-repeat', backgroundSize: "cover", [theme.breakpoints.down("md")]: { padding: "40px 0", @@ -73,10 +72,8 @@ const AUTH_TEXT_DATA = { text: "As a member, get personalized recommendations, curate learning lists, and follow your areas of interest.", linkProps: { children: "Sign Up for Free", - reloadDocument: true, - href: urls.login({ - pathname: urls.DASHBOARD_HOME, - }), + rawAnchor: true, + href: urls.login({ pathname: urls.DASHBOARD_HOME }), }, }, } @@ -111,10 +108,7 @@ const PersonalizeSection = () => { return ( - + diff --git a/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx b/frontends/main/src/app-pages/HomePage/TestimonialsSection.tsx similarity index 91% rename from frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx rename to frontends/main/src/app-pages/HomePage/TestimonialsSection.tsx index 4cd56ba530..db009a3beb 100644 --- a/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx +++ b/frontends/main/src/app-pages/HomePage/TestimonialsSection.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useEffect, useState } from "react" import _ from "lodash" import { Container, @@ -11,11 +11,11 @@ import { onReInitSlickA11y, } from "ol-components" import { useTestimonialList } from "api/hooks/testimonials" +import type { Attestation } from "api/v0" import { RiArrowRightLine, RiArrowLeftLine } from "@remixicon/react" import Slider from "react-slick" import AttestantBlock from "@/page-components/TestimonialDisplay/AttestantBlock" - -const MARKETING_IMAGE_IDX = _.shuffle([1, 2, 3, 4, 5, 6]) +import Image from "next/image" const HeaderContainer = styled(Container)(({ theme }) => ({ display: "flex", @@ -224,8 +224,18 @@ const TestimonialTruncateText = styled(TruncateText)({ const SlickCarousel = () => { const { data } = useTestimonialList({ position: 1 }) const [slick, setSlick] = React.useState(null) + const [shuffled, setShuffled] = useState() + const [imageSequence, setImageSequence] = useState() + + useEffect(() => { + if (!data) return + setShuffled(_.shuffle(data?.results)) + setImageSequence(_.shuffle([1, 2, 3, 4, 5, 6])) + }, [data]) - if (!data || data.results.length === 0) return null + if (!data?.results?.length || !shuffled?.length) { + return null + } const settings = { ref: setSlick, @@ -248,7 +258,7 @@ const SlickCarousel = () => { return ( - {_.shuffle(data?.results).map((resource, idx) => ( + {shuffled.map((resource, idx) => ( { className="testimonial-card" > - diff --git a/frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx b/frontends/main/src/app-pages/HomePage/UpcomingCoursesSection.tsx similarity index 100% rename from frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx rename to frontends/main/src/app-pages/HomePage/UpcomingCoursesSection.tsx diff --git a/frontends/mit-learn/src/pages/HomePage/carousels.ts b/frontends/main/src/app-pages/HomePage/carousels.ts similarity index 100% rename from frontends/mit-learn/src/pages/HomePage/carousels.ts rename to frontends/main/src/app-pages/HomePage/carousels.ts diff --git a/frontends/mit-learn/src/pages/ListDetailsPage/LearningPathDetailsPage.tsx b/frontends/main/src/app-pages/LearningPathDetailsPage/LearningPathDetailsPage.tsx similarity index 60% rename from frontends/mit-learn/src/pages/ListDetailsPage/LearningPathDetailsPage.tsx rename to frontends/main/src/app-pages/LearningPathDetailsPage/LearningPathDetailsPage.tsx index 6fc95af062..d4db1727ef 100644 --- a/frontends/mit-learn/src/pages/ListDetailsPage/LearningPathDetailsPage.tsx +++ b/frontends/main/src/app-pages/LearningPathDetailsPage/LearningPathDetailsPage.tsx @@ -1,5 +1,7 @@ +"use client" + import React, { useMemo } from "react" -import { useParams } from "react-router" +import { useParams } from "next/navigation" import { useUserMe } from "api/hooks/user" import { useInfiniteLearningPathItems, @@ -7,7 +9,8 @@ import { } from "api/hooks/learningResources" import { ListType } from "api/constants" import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs" -import { ListDetailsPage } from "./ListDetailsPage" +import LearningResourceDrawer from "@/page-components/LearningResourceDrawer/LearningResourceDrawer" +import ListDetailsPage from "./ListDetailsPage" type RouteParams = { id: string @@ -15,8 +18,10 @@ type RouteParams = { const LearningPathDetailsPage: React.FC = () => { const { data: user } = useUserMe() + const params = useParams() + + const id = parseInt(params.id) - const id = Number(useParams().id) const pathQuery = useLearningPathsDetail(id) const itemsQuery = useInfiniteLearningPathItems({ learning_resource_id: id, @@ -38,16 +43,19 @@ const LearningPathDetailsPage: React.FC = () => { }, [pathQuery.data]) return ( - manageListDialogs.upsertLearningPath(pathQuery.data)} - /> + <> + + manageListDialogs.upsertLearningPath(pathQuery.data)} + /> + ) } diff --git a/frontends/mit-learn/src/pages/ListDetailsPage/ListDetailsPage.tsx b/frontends/main/src/app-pages/LearningPathDetailsPage/ListDetailsPage.tsx similarity index 82% rename from frontends/mit-learn/src/pages/ListDetailsPage/ListDetailsPage.tsx rename to frontends/main/src/app-pages/LearningPathDetailsPage/ListDetailsPage.tsx index 72acc97895..9635b3055c 100644 --- a/frontends/mit-learn/src/pages/ListDetailsPage/ListDetailsPage.tsx +++ b/frontends/main/src/app-pages/LearningPathDetailsPage/ListDetailsPage.tsx @@ -1,6 +1,6 @@ import React from "react" import { Container, BannerPage, styled } from "ol-components" -import MetaTags from "@/page-components/MetaTags/MetaTags" +import PrivateTitle from "@/components/PrivateTitle/PrivateTitle" import ItemsListingComponent from "@/page-components/ItemsListing/ItemsListingComponent" import type { ItemsListingComponentProps } from "@/page-components/ItemsListing/ItemsListingComponent" @@ -20,10 +20,10 @@ const ListDetailsPage: React.FC = ({ }) => { return ( - + = ({ ) } -export { ListDetailsPage } +export default ListDetailsPage diff --git a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx b/frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.test.tsx similarity index 89% rename from frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx rename to frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.test.tsx index 193f7f0852..b17660fb81 100644 --- a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx +++ b/frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.test.tsx @@ -8,9 +8,8 @@ import { renderWithProviders, setMockResponse, user, - waitFor, -} from "../../test-utils" -import type { User } from "../../test-utils" +} from "@/test-utils" +import type { User } from "@/test-utils" /** * Set up the mock API responses for lists pages. @@ -23,7 +22,8 @@ const setup = ({ listsCount?: number } = {}) => { const paths = factories.learningResources.learningPaths({ count: listsCount }) - + const userData = factories.user.user({ ...user }) + setMockResponse.get(urls.userMe.get(), userData) setMockResponse.get(urls.learningPaths.list({ limit: 100 }), paths) const { location } = renderWithProviders(, { @@ -37,9 +37,6 @@ describe("LearningPathListingPage", () => { it("Has title 'Learning Paths'", async () => { setup() screen.getByRole("heading", { name: "Learning Paths" }) - await waitFor(() => - expect(document.title).toBe("Learning Paths | MIT Learn"), - ) }) it("Renders a card for each learning path", async () => { @@ -131,18 +128,11 @@ describe("LearningPathListingPage", () => { }) test("Clicking on list title navigates to list page", async () => { - const { location, paths } = setup() + const { paths } = setup() const path = faker.helpers.arrayElement(paths.results) - const listTitle = await screen.findByRole("link", { + const link = await screen.findByRole("link", { name: new RegExp(path.title), }) - await user.click(listTitle) - expect(location.current).toEqual( - expect.objectContaining({ - pathname: `/learningpaths/${path.id}`, - search: "", - hash: "", - }), - ) + expect(link).toHaveAttribute("href", `/learningpaths/${path.id}`) }) }) diff --git a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.tsx b/frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.tsx similarity index 94% rename from frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.tsx rename to frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.tsx index f22220e6ca..94502ce32a 100644 --- a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.tsx +++ b/frontends/main/src/app-pages/LearningPathListingPage/LearningPathListingPage.tsx @@ -1,3 +1,5 @@ +"use client" + import React, { useCallback, useMemo } from "react" import { Button, @@ -23,8 +25,6 @@ import { GridColumn, GridContainer } from "@/components/GridLayout/GridLayout" import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs" import * as urls from "@/common/urls" import { useUserMe } from "api/hooks/user" -import { Helmet } from "react-helmet-async" -import MetaTags from "@/page-components/MetaTags/MetaTags" const ListHeaderGrid = styled(Grid)` margin-top: 1rem; @@ -81,13 +81,14 @@ const LearningPathListingPage: React.FC = () => { return ( - + {/* TODO + */} diff --git a/frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.test.tsx b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx similarity index 99% rename from frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.test.tsx rename to frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx index cc40f6ca71..baa7c26491 100644 --- a/frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.test.tsx +++ b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx @@ -58,7 +58,7 @@ const PROFILES_FOR_STEPS = times(STEPS_DATA.length, profileForStep) const setup = async (profile: Profile) => { allowConsoleErrors() - + setMockResponse.get(urls.userMe.get(), factories.user.user()) setMockResponse.get(urls.profileMe.get(), profile) setMockResponse.patch(urls.profileMe.patch(), (req: Partial) => ({ ...profile, diff --git a/frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.tsx b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx similarity index 97% rename from frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.tsx rename to frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx index 4395489f79..54a6554adc 100644 --- a/frontends/mit-learn/src/pages/OnboardingPage/OnboardingPage.tsx +++ b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx @@ -1,5 +1,7 @@ +"use client" + import React, { useId, useMemo } from "react" -import { useNavigate } from "react-router-dom" +import { useRouter } from "next/navigation" import range from "lodash/range" import { styled, @@ -32,7 +34,6 @@ import { DELIVERY_CHOICES, ProfileSchema, } from "@/common/profile" -import MetaTags from "@/page-components/MetaTags/MetaTags" const NUM_STEPS = 5 @@ -154,7 +155,8 @@ const OnboardingPage: React.FC = () => { const { isLoading: isSaving, mutateAsync } = useProfileMeMutation() const { isLoading: userLoading, data: user } = useUserMe() const [activeStep, setActiveStep] = React.useState(0) - const navigate = useNavigate() + const router = useRouter() + const formik = useFormik({ enableReinitialize: true, initialValues: initialFormData ?? ProfileSchema.getDefault(), @@ -169,7 +171,7 @@ const OnboardingPage: React.FC = () => { if (activeStep < NUM_STEPS - 1) { setActiveStep((prevActiveStep) => prevActiveStep + 1) } else { - navigate(DASHBOARD_HOME) + router.push(DASHBOARD_HOME) } }, validateOnChange: false, @@ -286,7 +288,6 @@ const OnboardingPage: React.FC = () => { return activeStep < NUM_STEPS ? ( -
diff --git a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.test.tsx b/frontends/main/src/app-pages/PrivacyPage/PrivacyPage.test.tsx similarity index 53% rename from frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.test.tsx rename to frontends/main/src/app-pages/PrivacyPage/PrivacyPage.test.tsx index 32b150ddf3..0fb699cee7 100644 --- a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.test.tsx +++ b/frontends/main/src/app-pages/PrivacyPage/PrivacyPage.test.tsx @@ -1,12 +1,8 @@ -import { - renderTestApp, - screen, - waitFor, - setMockResponse, -} from "../../test-utils" +import React from "react" +import { screen, setMockResponse, renderWithProviders } from "@/test-utils" import { urls } from "api/test-utils" -import * as commonUrls from "@/common/urls" import { Permissions } from "@/common/permissions" +import PrivacyPage from "./PrivacyPage" describe("PrivacyPage", () => { test("Renders title", async () => { @@ -14,12 +10,8 @@ describe("PrivacyPage", () => { [Permissions.Authenticated]: true, }) - renderTestApp({ - url: commonUrls.PRIVACY, - }) - await waitFor(() => { - expect(document.title).toBe("Privacy Policy | MIT Learn") - }) + renderWithProviders() + screen.getByRole("heading", { name: "Privacy Policy", }) diff --git a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx b/frontends/main/src/app-pages/PrivacyPage/PrivacyPage.tsx similarity index 98% rename from frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx rename to frontends/main/src/app-pages/PrivacyPage/PrivacyPage.tsx index 2528683377..6c789b4005 100644 --- a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx +++ b/frontends/main/src/app-pages/PrivacyPage/PrivacyPage.tsx @@ -1,3 +1,5 @@ +"use client" + import { Breadcrumbs, Container, @@ -5,7 +7,6 @@ import { TypographyProps, styled, } from "ol-components" -import MetaTags from "@/page-components/MetaTags/MetaTags" import * as urls from "@/common/urls" import React from "react" @@ -62,13 +63,13 @@ const UnorderedList = styled.ul(({ theme }) => ({ ...theme.typography.body1, })) -const { SITE_NAME, MITOL_SUPPORT_EMAIL } = APP_SETTINGS +const SITE_NAME = process.env.NEXT_PUBLIC_SITE_NAME +const MITOL_SUPPORT_EMAIL = process.env.NEXT_PUBLIC_MITOL_SUPPORT_EMAIL const PrivacyPage: React.FC = () => { return ( - { setMockResponse.get( @@ -10,12 +12,13 @@ const setup = ({ programLetter }: { programLetter: ProgramLetter }) => { programLetter, ) setMockResponse.get(urls.userMe.get(), {}) - renderTestApp({ + renderWithProviders(, { url: programLetterView(programLetter.id), }) } describe("ProgramLetterDisplayPage", () => { + // See https://github.com/mitodl/hq/issues/5694 it("Renders a program letter from api", async () => { const programLetter = factory.programLetter() setup({ programLetter }) diff --git a/frontends/mit-learn/src/pages/ProgramLetterPage/ProgramLetterPage.tsx b/frontends/main/src/app-pages/ProgramLetterPage/[id]/view/ProgramLetterPage.tsx similarity index 84% rename from frontends/mit-learn/src/pages/ProgramLetterPage/ProgramLetterPage.tsx rename to frontends/main/src/app-pages/ProgramLetterPage/[id]/view/ProgramLetterPage.tsx index e7b6938ccd..f9a793e40c 100644 --- a/frontends/mit-learn/src/pages/ProgramLetterPage/ProgramLetterPage.tsx +++ b/frontends/main/src/app-pages/ProgramLetterPage/[id]/view/ProgramLetterPage.tsx @@ -1,7 +1,9 @@ +"use client" + import React from "react" import { styled } from "ol-components" import { useProgramLettersDetail } from "api/hooks/programLetters" -import { useParams } from "react-router" +import { useParams } from "next/navigation" import { CkeditorDisplay } from "ol-ckeditor" type RouteParams = { @@ -103,12 +105,28 @@ const ProgramLetterFooter = styled.div` } ` +const ImageContainer = styled.img(({ theme }) => ({ + display: "flex", + alignItems: "end", + minWidth: "0px", + maxWidth: "646px", + [theme.breakpoints.up("sm")]: { + /** + * Flex 1, combined with the maxWidth, was causing the image to be stretched + * on Safari. We don't need flex 1 on the mobile layout, so omit it there. + */ + flex: 1, + }, + [theme.breakpoints.down("sm")]: { + maxWidth: "100%", + }, +})) + const ProgramLetterPage: React.FC = () => { - const id = String(useParams().id) + const { id } = useParams() const programLetter = useProgramLettersDetail(id) const templateFields = programLetter.data?.template_fields const certificateInfo = programLetter.data?.certificate - return ( @@ -120,9 +138,9 @@ const ProgramLetterPage: React.FC = () => { />
-
@@ -137,13 +155,13 @@ const ProgramLetterPage: React.FC = () => { {templateFields?.program_letter_signatories?.map((signatory) => (
- Signature
- {signatory.name},{signatory.title_line_1} + {signatory.name}, {signatory.title_line_1} {signatory.title_line_2 ? (

, {signatory.title_line_2}

) : ( @@ -157,7 +175,7 @@ const ProgramLetterPage: React.FC = () => {
{templateFields?.program_letter_footer ? ( - diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx b/frontends/main/src/app-pages/SearchPage/SearchPage.test.tsx similarity index 99% rename from frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx rename to frontends/main/src/app-pages/SearchPage/SearchPage.test.tsx index 1cb8efb1c7..d37a321e47 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx +++ b/frontends/main/src/app-pages/SearchPage/SearchPage.test.tsx @@ -146,6 +146,7 @@ describe("SearchPage", () => { }, }, }) + const { location } = renderWithProviders(, { url: "?topic=Physics&topic=Chemistry", }) diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx b/frontends/main/src/app-pages/SearchPage/SearchPage.tsx similarity index 95% rename from frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx rename to frontends/main/src/app-pages/SearchPage/SearchPage.tsx index d36f5f6848..1bdedf0c70 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx +++ b/frontends/main/src/app-pages/SearchPage/SearchPage.tsx @@ -1,7 +1,9 @@ +"use client" + import _ from "lodash" import React, { useCallback, useMemo } from "react" import type { FacetManifest } from "@mitodl/course-search-utils" -import { useSearchParams } from "@mitodl/course-search-utils/react-router" +import { useSearchParams } from "@mitodl/course-search-utils/next" import { useResourceSearchParams, UseResourceSearchParamsProps, @@ -14,7 +16,7 @@ import { SearchField } from "@/page-components/SearchField/SearchField" import type { LearningResourceOfferor } from "api" import { useOfferorsList } from "api/hooks/learningResources" import { capitalize } from "ol-utilities" -import MetaTags from "@/page-components/MetaTags/MetaTags" +import LearningResourceDrawer from "@/page-components/LearningResourceDrawer/LearningResourceDrawer" const cssGradient = ` linear-gradient( @@ -29,7 +31,7 @@ const Page = styled.div` ${({ theme }) => theme.breakpoints.up("md")} { background: - url("/static/images/search_page_vector.png") no-repeat top left / 35%, + url("/images/search_page_vector.png") no-repeat top left / 35%, ${cssGradient}; } ` @@ -209,7 +211,7 @@ const SearchPage: React.FC = () => { return ( - +

Search

diff --git a/frontends/mit-learn/src/pages/TermsPage/TermsPage.test.tsx b/frontends/main/src/app-pages/TermsPage/TermsPage.test.tsx similarity index 53% rename from frontends/mit-learn/src/pages/TermsPage/TermsPage.test.tsx rename to frontends/main/src/app-pages/TermsPage/TermsPage.test.tsx index 430db9d912..667979d23d 100644 --- a/frontends/mit-learn/src/pages/TermsPage/TermsPage.test.tsx +++ b/frontends/main/src/app-pages/TermsPage/TermsPage.test.tsx @@ -1,12 +1,8 @@ -import { - renderTestApp, - screen, - waitFor, - setMockResponse, -} from "../../test-utils" +import React from "react" +import { screen, setMockResponse, renderWithProviders } from "@/test-utils" import { urls } from "api/test-utils" -import * as commonUrls from "@/common/urls" import { Permissions } from "@/common/permissions" +import TermsPage from "./TermsPage" describe("TermsPage", () => { test("Renders title", async () => { @@ -14,12 +10,7 @@ describe("TermsPage", () => { [Permissions.Authenticated]: true, }) - renderTestApp({ - url: commonUrls.TERMS, - }) - await waitFor(() => { - expect(document.title).toBe("Terms of Service | MIT Learn") - }) + renderWithProviders() screen.getByRole("heading", { name: "Terms of Service", }) diff --git a/frontends/mit-learn/src/pages/TermsPage/TermsPage.tsx b/frontends/main/src/app-pages/TermsPage/TermsPage.tsx similarity index 98% rename from frontends/mit-learn/src/pages/TermsPage/TermsPage.tsx rename to frontends/main/src/app-pages/TermsPage/TermsPage.tsx index 2acaa50dd4..73e2bc2b21 100644 --- a/frontends/mit-learn/src/pages/TermsPage/TermsPage.tsx +++ b/frontends/main/src/app-pages/TermsPage/TermsPage.tsx @@ -1,3 +1,6 @@ +"use client" + +import React from "react" // Not urrently linked to. See https://github.com/mitodl/hq/issues/4639 import { Breadcrumbs, @@ -6,9 +9,7 @@ import { TypographyProps, styled, } from "ol-components" -import MetaTags from "@/page-components/MetaTags/MetaTags" import * as urls from "@/common/urls" -import React from "react" const PageContainer = styled.div(({ theme }) => ({ display: "flex", @@ -68,7 +69,6 @@ const TermsPage: React.FC = () => { return ( - { it("Has a page title", async () => { setupApis() renderWithProviders() - await waitFor(() => { - expect(document.title).toBe("Topics | MIT Learn") - }) screen.getByRole("heading", { name: "Browse by Topic" }) }) @@ -137,17 +134,17 @@ describe("TopicsListingPage", () => { expect(subtopics1[1]).toHaveTextContent(sortedSubtopics1[0].name) expect(subtopics1[2]).toHaveTextContent(sortedSubtopics1[1].name) expect(subtopics1[3]).toHaveTextContent(sortedSubtopics1[2].name) - expect(subtopics1.map((el) => el.href)).toEqual([ - topics.t1.channel_url, - ...sortedSubtopics1.map((t) => t.channel_url), + expect(subtopics1.map((el) => new URL(el.href).pathname)).toEqual([ + new URL(topics.t1.channel_url!).pathname, + ...sortedSubtopics1.map((t) => new URL(t.channel_url!).pathname), ]) expect(subtopics2[0]).toHaveTextContent(topics.t2.name) expect(subtopics2[1]).toHaveTextContent(sortedSubtopics2[0].name) expect(subtopics2[2]).toHaveTextContent(sortedSubtopics2[1].name) - expect(subtopics2.map((el) => el.href)).toEqual([ - topics.t2.channel_url, - ...sortedSubtopics2.map((t) => t.channel_url), + expect(subtopics2.map((el) => new URL(el.href).pathname)).toEqual([ + new URL(topics.t2.channel_url!).pathname, + ...sortedSubtopics2.map((t) => new URL(t.channel_url!).pathname), ]) }) @@ -162,8 +159,6 @@ describe("TopicsListingPage", () => { await screen.findByRole("heading", { name: topics.t2.name }), ) - await new Promise((res) => setTimeout(res, 200)) - expect(topic1).toHaveTextContent("Courses: 100") expect(topic1).toHaveTextContent("Programs: 10") diff --git a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx b/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx similarity index 96% rename from frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx rename to frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx index 8b3d5dbc38..922cb42f3f 100644 --- a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx +++ b/frontends/main/src/app-pages/TopicsListingPage/TopicsListingPage.tsx @@ -1,3 +1,5 @@ +"use client" + import React, { useMemo } from "react" import { Container, @@ -11,9 +13,8 @@ import { Skeleton, Breadcrumbs, } from "ol-components" -import { Link } from "react-router-dom" +import Link from "next/link" import { propsNotNil } from "ol-utilities" -import MetaTags from "@/page-components/MetaTags/MetaTags" import { useLearningResourceTopics } from "api/hooks/learningResources" import { LearningResourceTopic } from "api" @@ -40,7 +41,7 @@ const TopicBoxHeader = styled( ({ title, icon, href, className }: TopicBoxHeaderProps) => { return ( - +