Repository: oppia/oppia

Issue:
Migrate Individual Pages from AngularJS to Angular with Lazy Loading
Background:

The goal of the entire process is to migrate individual pages from AngularJS to Angular, modularize the code, and implement lazy loading. This means that each page will have its own Angular module and will only be loaded when necessary, improving the application's performance.

The steps are designed to accomplish the following (more details on each step is present later in the issue):

Create BaseRootComponentClass in core/templates/pages.: This class serves as the base component for all pages in the application. It manages the logic for setting page title and meta tags, subscribing to language changes and cleaning up on destruction.

Add a listing to constants.ts PAGES_REGISTERED_WITH_FRONTEND constant.: This step registers the page with the frontend part of the application.

Create a component [name-of-the-page]-page-root.component.ts and component.html.: For each page, a new Angular component is created. This component will act as the root component for that page, meaning that it will manage the page's layout and connect it with Angular's change detection mechanism.

Manually copy the navbar from the mainpage.html file to the root html file.: The navbar is a common component used across all pages, so it needs to be manually included in the root HTML of the new Angular page.

Update the module.ts file.: This file is updated to remove any references to AngularJS, and the main class is exported. All the AngularJS specific modules are removed and replaced with Angular's CommonModule and RouterModule.

Update app.routing.module.ts.: The application's main routing module is updated to include a lazy-loaded route for the new page. This means that the new page's module will only be loaded when the user navigates to that page.

Replace smartRouterLink with routerLink.: Angular's RouterLink directive is used in place of the custom SmartRouterLink directive. This is done to ensure that the new page works with Angular's router.

Update feconf.py.: This step removes the page's route from the backend configuration file, as the new Angular page will manage its own route using Angular's RouterModule.
_________________________________________________________________________________________________________
Setup (only done once, please look for this file in `core/templates/pages` and only create if not present):

1. Create `BaseRootComponentClass` in `core/templates/pages`. The content should look like this:
```
// Copyright 2023 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Base root component for all pages.
 */

import { OnDestroy, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';

import { PageHeadService } from 'services/page-head.service';

interface MetaTagData {
  readonly PROPERTY_TYPE: string;
  readonly PROPERTY_VALUE: string;
  readonly CONTENT: string;
}

export class BaseRootComponent implements OnInit, OnDestroy {
  directiveSubscriptions = new Subscription();
  constructor(
    private pageHeadService: PageHeadService,
    private translateService: TranslateService,
    private title: string,
    private readonly meta: readonly MetaTagData[]
  ) {}

  setPageTitleAndMetaTags(): void {
    let translatedTitle = this.translateService.instant(this.title);
    this.pageHeadService.updateTitleAndMetaTags(
      translatedTitle,
      this.meta);
  }

  ngOnInit(): void {
    this.directiveSubscriptions.add(
      this.translateService.onLangChange.subscribe(() => {
        this.setPageTitleAndMetaTags();
      })
    );
  }

  ngOnDestroy(): void {
    this.directiveSubscriptions.unsubscribe();
  }
}
```

For each page:

1. Add a listing to the `PAGES_REGISTERED_WITH_FRONTEND` constant in `constants.ts`. You can find most of the pages' metadata at the end of this issue description under references.
2. Create a component, named `[name-of-the-page]-page-root.component.ts` and the corresponding `component.html`. The contents of each page are about 90% identical, only differing where the page name is used in some form across the component. We have a template for both the `.ts` file and the `.html` file. The interpolation dictionary for the placeholders (prefixed by $ in the root component file template and root component html file template below) is defined as follows:
    ```
        interpolation_dict = {
                'fileoverview': page_name.title() + '-page-root component',
                'selector': 'oppia-' + page_name + '-page-root',
                'templateUrl': './' + page_name + '-page-root.component.html',
                'classname': page_name.replace("-", " ").title().replace(" ", "") + 'PageRootComponent',
                'appConstant': page_name.replace("-", "_").upper(),
                'text': page_name,
                'f': comp.split('./core/templates/')[1].split('-root.component.html')[0] + '.module',
                'moduleName': page_name.replace("-", " ").title().replace(" ", "") + 'PageModule',
              }
    ```
        Some examples of `page_name` are as follows "blog-admin", "exploration-editor", "topics-and-skills-dashboard", etc.
        
        The root component file template:
    ```
        // Copyright 2023 The Oppia Authors. All Rights Reserved.
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
        // You may obtain a copy of the License at
        //
        //      http://www.apache.org/licenses/LICENSE-2.0
        //
        // Unless required by applicable law or agreed to in writing, software
        // distributed under the License is distributed on an "AS-IS" BASIS,
        // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        // See the License for the specific language governing permissions and
        // limitations under the License.
        
        /**
         * @fileoverview $fileoverview component.
         */
        
        import { Component } from '@angular/core';
        import { TranslateService } from '@ngx-translate/core';
        import { AppConstants } from 'app.constants';
        import { PageHeadService } from 'services/page-head.service';
        import { BaseRootComponent } from 'pages/BaseRootComponentClass';
        
        
        @Component({
          selector: '$selector',
          templateUrl: '$templateUrl',
        })
        export class $classname extends BaseRootComponent {
          constructor(
              pageHeadService: PageHeadService,
              translateService: TranslateService
          ) {
            super(
              pageHeadService,
              translateService,
              AppConstants.PAGES_REGISTERED_WITH_FRONTEND.$appConstant.TITLE,
              AppConstants.PAGES_REGISTERED_WITH_FRONTEND.$appConstant.META
            );
          }
        }
    ```
        
        Component html:
    ```
        <oppia-angular-root>
            <oppia-base-content>
              <navbar-breadcrumb>
              </navbar-breadcrumb>
              <content>
                <oppia-$text-page></oppia-$text-page>
              </content>
            </oppia-base-content>
        </oppia-angular-root>
    ```

3. Go to the mainpage.html file and look for the navbar. You will have to manually copy it to the root html file.
    For example:
   `Mainpage.html:`
    ```
    <!DOCTYPE html>
    <html ng-app="oppia" lang="en">
      <head>
        <title>Oppia Blog Admin Panel</title>
        <oppia-blog-admin-navbar></oppia-blog-admin-navbar>
        <!-- Require head styles file using webpack -->
        @load('pages/header_css_libs.html')
        @load('pages/header_js_libs.html')
    
      </head>
    
      <body>
        <oppia-root>
          <oppia-blog-admin-page></oppia-blog-admin-page>
        </oppia-root>
        @load('pages/footer_js_libs.html')
      </body>
    </html>
    ```
    `Rootcomponent Html`:
    ```
    <oppia-angular-root>
        <oppia-base-content>
          <navbar-breadcrumb>
            <oppia-blog-admin-navbar></oppia-blog-admin-navbar>
          </navbar-breadcrumb>
          <content>
            <oppia-blog-admin-page></oppia-blog-admin-page>
          </content>
        </oppia-base-content>
    </oppia-angular-root>
    
    ```
5. Go to the module.ts file: Remove the providers array, remove anything related to angularjs. (these are typically found at the end of the file after the Module Class). Export the only module class in the file (can be done by adding the word `export` before `class`), remove `ngDoBootStrap() {}` and the comment above it. Remove `Browser*Module`, any HTTP module, and everything related to router module.
6. Add `CommonModule` and `RouterModule.forChild([{
      path: '',
      component: $classname
    }]),` to the **imports array** (which is different from file imports). Also add the newly created root component to the declarations array.
7. In `app.routing.module.ts`, add the following code to the end of the routes array:
  ```
    {
    path: (
      AppConstants.PAGES_REGISTERED_WITH_FRONTEND.ADMIN.ROUTE
    ),
    pathMatch: 'full',
    loadChildren: () => import(
      'pages/page_name-page/page_name-page.module')
      .then(m => m.AdminPageModule)
    } 
  ``` 
to the end of the routes array.
8. Replace all instances of `smartRouterLink` with `routerLink`: `[smartRouterLink]` -> `[routerLink]`. This step can be challenging; consider doing it last. When you try to visit the page after making all changes, the console error will direct you to all the places where this replacement is needed on the page.
9. Find the appropriate listing in feconf.py. Given that massive size of the file it is close to impossible to look through it. Here is a small trick. Try to grep the name of `mainpage.html` file across the codebase. You will find a `self.render_template` statement. Look for the class name containing this line. Either search for it again or use the "findAllReferences" function to navigate to the place in `main.py` where the mapping occurs. That will reveal the location of the route in `feconf.py`. Double-check to ensure that the route here matches the one in `constants.ts`. You can then remove the listing
___________________________________________________________________________________________________________
References:

Contants.ts
(Maybe be malformed in some places, double check before taking it up).
```
  "ADMIN": {
        "ROUTE": "admin",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          }
        ]
      },
      "BLOG_ADMIN": {
        "ROUTE": "blog-admin",
        "TITLE": "Oppia Blog Admin Panel",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          }
        ]
      },
      "CLASSROOM_ADMIN": {
        "ROUTE": "classroom-admin",
        "TITLE": "Oppia Classroom Admin Panel",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it."
          }
        ]
      },
      "DIAGNOSTIC_TEST_PLAYER": {
        "ROUTE": "diagnostic-test-player",
        "TITLE": "Diagnostic test player",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history, and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it. The Learner Diagnostic test page will allow the learner to test their knowledge and get a set of recommendations for where they should begin learning."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "With Oppia, you can access free lessons on math, physics, statistics, chemistry, music, history, and more from anywhere in the world. Oppia is a nonprofit with the mission of providing high-quality education to those who lack access to it. The Learner Diagnostic test page will allow the learner to test their knowledge and get a set of recommendations for where they should begin learning."
          }
        ]
      },
      "BLOG_DASHBOARD": {
        "ROUTE": "blog-dashboard",
        "TITLE": "Blog Dashboard - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "COLLECTION_EDITOR": {
        "ROUTE": "collection-editor",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          }
        ]
      },
      "COLLECTION_PLAYER": {
        "ROUTE": "collection-player",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          }
        ]
      },
      "CONSOLE_ERRORS.HTM": {
        "ROUTE": "console_errors.htm",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Contact the Oppia team, submit feedback, and learn how to get involved with the Oppia project."
          }
        ]
      },
      "CREATOR_DASHBOARD": {
        "ROUTE": "creator-dashboard",
        "TITLE": "Creator Dashboard | Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "CONTRIBUTOR_DASHBOARD_ADMIN": {
        "ROUTE": "contributor-dashboard-admin",
        "TITLE": "Oppia Contributor Dashbaord Admin Panel",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "CONTRIBUTOR_DASHBOARD": {
        "ROUTE": "contributor-dashboard",
        "TITLE": "Contributor Dashboard - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "EMAIL_DASHBOARD": {
        "ROUTE": "email-dashboard",
        "TITLE": "Email Dashboard - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "EMAIL_DASHBOARD_RESULT.MAI": {
        "ROUTE": "email-dashboard-result.mai",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "ERROR_IFRAMED.MAI": {
        "ROUTE": "error-iframed.mai",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "ERROR_PAGE_400.MAI": {
        "ROUTE": "error-page-400.mai",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "ERROR_PAGE_401.MAI": {
        "ROUTE": "error-page-401.mai",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "ERROR_PAGE_500.MAI": {
        "ROUTE": "error-page-500.mai",
        "TITLE": "",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "EXPLORATION_EDITOR": {
        "ROUTE": "exploration-editor",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Help others learn new things. Create lessons through explorations and share your knowledge with the community."
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Help others learn new things. Create lessons through explorations and share your knowledge with the community."
          }
        ]
      },
      "LEARNER_DASHBOARD": {
        "ROUTE": "learner-dashboard",
        "TITLE": "Learner Dashboard | Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "MAINTENANCE": {
        "ROUTE": "maintenance",
        "TITLE": "Down for Maintenance - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "MODERATOR": {
        "ROUTE": "moderator",
        "TITLE": "Moderator Tools - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "PRACTICE_SESSION": {
        "ROUTE": "practice-session",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "REVIEW_TEST": {
        "ROUTE": "review-test",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "SKILL_EDITOR": {
        "ROUTE": "skill-editor",
        "TITLE": "Skill Editor",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "STORY_EDITOR": {
        "ROUTE": "story-editor",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "SUBTOPIC_VIEWER": {
        "ROUTE": "subtopic-viewer",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "TOPIC_EDITOR": {
        "ROUTE": "topic-editor",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "TOPICS_AND_SKILLS_DASHBOARD": {
        "ROUTE": "topics-and-skills-dashboard",
        "TITLE": "Topics and Skills Dashboard - Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "TOPIC_VIEWER": {
        "ROUTE": "topic-viewer",
        "TITLE": "Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "FACILITATOR_DASHBOARD": {
        "ROUTE": "facilitator-dashboard",
        "TITLE": "Facilitator Dashboard | Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "CREATE_LEARNER_GROUP": {
        "ROUTE": "create-learner-group",
        "TITLE": "Create Learner Group | Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      },
      "EDIT_LEARNER_GROUP": {
        "ROUTE": "edit-learner-group",
        "TITLE": "Edit Learner Group | Oppia",
        "META": [
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          },
          {
            "PROPERTY_TYPE": "itemprop",
            "PROPERTY_VALUE": "og:description",
            // eslint-disable-next-line max-len
            "CONTENT": "Oppia is a free, open-source learning platform. Join the community to create or try an exploration today!"
          }
        ]
      }
```


Discussion:
@prafulbbandre Can you please tell me who is working on it?
(And assign them in the issue)

> @prafulbbandre Can you please tell me who is working on it? (And assign them in the issue)
Hi @Rijuta-s,
@srijanreddy98 is working on this.
@srijanreddy98 I can take up the migration of some of these pages. I need some high level overview on how our migration/routing is structured and how these steps help us migrate these pages. A quick call will be really helpful

Resolving patch:
diff --git a/core/controllers/admin.py b/core/controllers/admin.py
--- a/core/controllers/admin.py
+++ b/core/controllers/admin.py
@@ -99,21 +99,6 @@ class ClassroomPageDataDict(TypedDict):
 ]
 
 
-class AdminPage(
-    base.BaseHandler[Dict[str, str], Dict[str, str]]
-):
-    """Admin page shown in the App Engine admin console."""
-
-    URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
-    HANDLER_ARGS_SCHEMAS: Dict[str, Dict[str, str]] = {'GET': {}}
-
-    @acl_decorators.can_access_admin_page
-    def get(self) -> None:
-        """Renders the admin page."""
-
-        self.render_template('admin-page.mainpage.html')
-
-
 class AdminHandlerNormalizePayloadDict(TypedDict):
     """Dict representation of AdminHandler's normalized_payload
     dictionary.
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -271,7 +271,7 @@ def get_redirect_route(
         feconf.ACCESS_VALIDATION_HANDLER_PREFIX,
         access_validators.ViewLearnerGroupPageAccessValidationHandler),
 
-    get_redirect_route(r'%s' % feconf.ADMIN_URL, admin.AdminPage),
+    get_redirect_route(r'%s' % feconf.ADMIN_URL, oppia_root.OppiaRootPage),
     get_redirect_route(r'/adminhandler', admin.AdminHandler),
     get_redirect_route(r'/adminrolehandler', admin.AdminRoleHandler),
     get_redirect_route(r'/bannedusershandler', admin.BannedUsersHandler),


Test patch:
diff --git a/core/controllers/admin_test.py b/core/controllers/admin_test.py
--- a/core/controllers/admin_test.py
+++ b/core/controllers/admin_test.py
@@ -92,20 +92,9 @@ def setUp(self) -> None:
         self.admin_id = self.get_user_id_from_email(self.CURRICULUM_ADMIN_EMAIL)
         self.prod_mode_swap = self.swap(constants, 'DEV_MODE', False)
 
-    def test_admin_page_rights(self) -> None:
-        """Test access rights to the admin page."""
-
-        self.get_html_response('/admin', expected_status_int=302)
-
-        # Login as a non-admin.
-        self.login(self.EDITOR_EMAIL)
-        self.get_html_response('/admin', expected_status_int=401)
-        self.logout()
-
-        # Login as an admin.
-        self.login(self.CURRICULUM_ADMIN_EMAIL, is_super_admin=True)
-        self.get_html_response('/admin')
-        self.logout()
+    def test_admin_get(self) -> None:
+        """Test `/admin` returns a 200 response."""
+        self.get_html_response('/admin', expected_status_int=200)
 
     def test_change_configuration_property(self) -> None:
         """Test that configuration properties can be changed."""
diff --git a/core/templates/tests/unit-test-utils.ajs.ts b/core/templates/tests/unit-test-utils.ajs.ts
--- a/core/templates/tests/unit-test-utils.ajs.ts
+++ b/core/templates/tests/unit-test-utils.ajs.ts
@@ -22,6 +22,7 @@ import { AngularFireAuth } from '@angular/fire/auth';
 import { TranslateService } from '@ngx-translate/core';
 import { CookieModule, CookieService } from 'ngx-cookie';
 import { TranslateCacheService, TranslateCacheSettings } from 'ngx-translate-cache';
+import { RouterTestingModule } from '@angular/router/testing';
 
 import { angularServices } from 'services/angular-services.index';
 
@@ -34,6 +35,7 @@ export const importAllAngularServices = (): void => {
       imports: [
         HttpClientTestingModule,
         CookieModule.forRoot(),
+        RouterTestingModule,
       ],
       providers: [
         CookieService,