-
Notifications
You must be signed in to change notification settings - Fork 1
feat: program dashboard progress page initial layout #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import React from 'react'; | ||
| import { ProgramProgressCoursesProps } from '../data/types'; | ||
|
|
||
| const ProgramProgressCourses: React.FC<ProgramProgressCoursesProps> = ({ | ||
| courseData, | ||
| }) => ( | ||
| <div> | ||
| {courseData.uuid} | ||
| <div> | ||
| || Courses In Progress || | ||
| </div> | ||
| <div> | ||
| || Courses Remaining || | ||
| </div> | ||
| <div> | ||
| || Courses Completed || | ||
| </div> | ||
| </div> | ||
| ); | ||
|
|
||
| export default ProgramProgressCourses; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import React from 'react'; | ||
| import { ProgramProgressHeaderProps } from '../data/types'; | ||
| import { getProgramIcon } from '../data/util'; | ||
|
|
||
| const ProgramProgressHeader: React.FC<ProgramProgressHeaderProps> = ({ | ||
| programTitle, programType, authoringOrganizations, | ||
| }) => { | ||
| const programIcon = getProgramIcon(programType); | ||
|
|
||
| return ( | ||
| <div> | ||
| <p> | ||
| {programTitle} | ||
| </p> | ||
| <p> | ||
| {programType} | ||
| </p> | ||
| <p> | ||
| {authoringOrganizations && authoringOrganizations.length} | ||
| </p> | ||
| <img src={programIcon} alt={`${programType} icon`} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ProgramProgressHeader; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import React from 'react'; | ||
| import { ProgramProgressInfoProps } from '../data/types'; | ||
|
|
||
| const ProgramProgressInfo: React.FC<ProgramProgressInfoProps> = ({ | ||
| allCoursesCompleted, totalCoursesInProgram, | ||
| }) => ( | ||
| allCoursesCompleted | ||
| ? ( | ||
| <div> | ||
| Render all courses completed text | ||
| </div> | ||
| ) | ||
| : ( | ||
| <div> | ||
| Render upgrade button and info about the {totalCoursesInProgram} courses in this program | ||
| </div> | ||
| ) | ||
| ); | ||
|
|
||
| export default ProgramProgressInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import React from 'react'; | ||
|
|
||
| const ProgramProgressSidebar: React.FC = () => ( | ||
| <> | ||
| <div> | ||
| || Program Certificate Progress || | ||
| </div> | ||
| <div> | ||
| || Earned Certificates || | ||
| </div> | ||
| <div> | ||
| || Program Record || | ||
| </div> | ||
| </> | ||
| ); | ||
|
|
||
| export default ProgramProgressSidebar; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import React, { useEffect, useState } from 'react'; | ||
| import { Helmet } from 'react-helmet'; | ||
| import { useParams } from 'react-router-dom'; | ||
| import { Col, Container, Row } from '@openedx/paragon'; | ||
| import { logError } from '@edx/frontend-platform/logging'; | ||
| import { camelCaseObject } from '@edx/frontend-platform/utils'; | ||
| import { getProgramProgressData } from '../data/api'; | ||
| import { ProgramProgressData } from '../data/types'; | ||
| import ProgramProgressCourses from './ProgramProgressCourses'; | ||
| import ProgramProgressHeader from './ProgramProgressHeader'; | ||
| import ProgramProgressSidebar from './ProgramProgressSidebar'; | ||
| import ProgramProgressInfo from './ProgramProgressInfo'; | ||
|
|
||
| const ProgramProgress: React.FC = () => { | ||
| const [programProgressData, setProgramProgressData] = useState<ProgramProgressData>(); | ||
| const [programProgressEndpointError, setProgramProgressEndpointError] = useState(false); | ||
| const hasProgramProgressData : Boolean = programProgressData?.courseData | ||
| && programProgressData.programData | ||
| && programProgressData.urls; | ||
|
|
||
| // TODO: for review: https://stackoverflow.com/questions/75706357/react-useparams-returns-string-undefined | ||
| const { uuid } = useParams() as { uuid: string }; | ||
| useEffect(() => { | ||
| getProgramProgressData(uuid) | ||
| .then(responseData => { | ||
| setProgramProgressData(camelCaseObject(responseData.data)); | ||
| }) | ||
| .catch(err => { | ||
| logError(err); | ||
| setProgramProgressEndpointError(true); | ||
| }); | ||
| }, [uuid]); | ||
|
|
||
| if (programProgressEndpointError) { | ||
| return ( | ||
| <div>Not found page</div> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this placeholder?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is. It will be replaced with an alert similar to what's in the enterprise version |
||
| ); | ||
| } | ||
|
|
||
| if (!hasProgramProgressData) { | ||
| return ( | ||
| <div>Loading...</div> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this the error condition that we would see if there is a request out that hasn't returned yet? Is this placeholder?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is placeholder. I will replace it with the And yes it is what the user will see while the request is pending. It's the first thing that is rendered to the page, then the request is made, the response comes back, updates state variables, which triggers a re-render in React, which will either render the error state (NotFoundPage) or the actual page |
||
| ); | ||
| } | ||
|
|
||
| const programData = programProgressData?.programData; | ||
| const courseData = programProgressData?.courseData; | ||
|
|
||
| const totalCoursesInProgram = (courseData.notStarted?.length || 0) | ||
| + (courseData.completed?.length || 0) | ||
| + (courseData.inProgress?.length || 0); | ||
|
|
||
| const allCoursesCompleted = !courseData.notStarted?.length | ||
| && !courseData.inProgress?.length | ||
| && courseData.completed?.length; | ||
|
|
||
| return ( | ||
| <> | ||
| <Helmet title={`${programData.title}`} /> | ||
| <Container> | ||
| <ProgramProgressHeader | ||
| programTitle={programData?.title} | ||
| programType={programData?.type} | ||
| authoringOrganizations={programData?.authoringOrganizations} | ||
| /> | ||
| <Row> | ||
| <Col> | ||
| <ProgramProgressInfo | ||
| allCoursesCompleted={allCoursesCompleted} | ||
| totalCoursesInProgram={totalCoursesInProgram} | ||
| /> | ||
| <ProgramProgressCourses courseData={courseData} /> | ||
| </Col> | ||
| <Col> | ||
| <ProgramProgressSidebar /> | ||
| </Col> | ||
| </Row> | ||
| </Container> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export default ProgramProgress; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On line 22, there is some typecasting going on. I've never had to do it before but it's the only way I've found to satisfy Typescript when using the
useParamshook from React Router. I put the link to the stackoverflow that gave me the idea so that reviewers could easily see why I did that. But it should probably be removed and just included in the PR description