Skip to content
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

feat: teacher onboarding #83

Merged
merged 14 commits into from
Jan 17, 2025
Prev Previous commit
Next Next commit
fixes
SKairinos committed Jan 9, 2025
commit e99f21ff099a7309a5c8b17e105cfd47e1191ff0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@
],
"dependencies": {
"@react-pdf/renderer": "^4.0.0",
"codeforlife": "2.6.3",
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#portal-frontend-44",
"crypto-js": "^4.2.0"
},
"devDependencies": {
2 changes: 1 addition & 1 deletion src/components/form/ClassNameField.tsx
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ export type ClassNameFieldProps = Omit<

const ClassNameField: FC<ClassNameFieldProps> = ({
name = "name",
label = "Last name",
label = "Class name",
placeholder = "Enter a class name",
InputProps = {},
...otherTextFieldProps
109 changes: 51 additions & 58 deletions src/pages/teacherDashboard/TeacherDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import * as page from "codeforlife/components/page"
import { type FC, useEffect } from "react"
import { type SessionMetadata, useNavigate } from "codeforlife/hooks"
import { type FC } from "react"
import { Navigate } from "codeforlife/components/router"
import { type SchoolTeacherUser } from "codeforlife/api"
import { type SessionMetadata } from "codeforlife/hooks"
import { getParam } from "codeforlife/utils/router"
import { handleResultState } from "codeforlife/utils/api"

import Account, { type AccountProps } from "./account/Account"
import Classes, { type ClassesProps } from "./classes/Classes"
import { type RetrieveUserResult, useRetrieveUserQuery } from "../../api/user"
import School, { type SchoolProps } from "./school/School"
import { paths } from "../../routes"
import { useRetrieveUserQuery } from "../../api/user"

export type TeacherDashboardProps =
| {
@@ -29,64 +30,56 @@ const Tabs: FC<TeacherDashboardProps & SessionMetadata> = ({
tab,
view,
user_id,
}) => {
const result = useRetrieveUserQuery(user_id)
const navigate = useNavigate()

const authUser = result.data
const isNonSchoolTeacher = authUser && !authUser.teacher?.school

useEffect(() => {
if (isNonSchoolTeacher) navigate(paths.teacher.onboarding._)
}, [isNonSchoolTeacher, navigate])

if (isNonSchoolTeacher) return <></>
}) =>
handleResultState(useRetrieveUserQuery(user_id), authUser => {
if (!authUser.teacher!.school) {
return <Navigate to={paths.teacher.onboarding._} />
}

const authSchoolTeacherUser =
authUser as SchoolTeacherUser<RetrieveUserResult>
const authSchoolTeacherUser = authUser as SchoolTeacherUser<typeof authUser>

const tabs: page.TabBarProps["tabs"] = [
{
label: "Your school",
children: (
<School
authUser={authSchoolTeacherUser}
view={view as SchoolProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.school, "tab"),
},
{
label: "Your classes",
children: (
<Classes
authUser={authSchoolTeacherUser}
view={view as ClassesProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.classes, "tab"),
},
{
label: "Your account",
children: (
<Account
authUser={authSchoolTeacherUser}
view={view as AccountProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.account, "tab"),
},
]
const tabs: page.TabBarProps["tabs"] = [
{
label: "Your school",
children: (
<School
authUser={authSchoolTeacherUser}
view={view as SchoolProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.school, "tab"),
},
{
label: "Your classes",
children: (
<Classes
authUser={authSchoolTeacherUser}
view={view as ClassesProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.classes, "tab"),
},
{
label: "Your account",
children: (
<Account
authUser={authSchoolTeacherUser}
view={view as AccountProps["view"]}
/>
),
path: getParam(paths.teacher.dashboard.tab.account, "tab"),
},
]

return handleResultState(result, authUser => (
<page.TabBar
header={`Welcome back, ${authUser.first_name} ${authUser.last_name}`}
originalPath={paths.teacher.dashboard.tab._}
value={tabs.findIndex(t => t.path === tab)}
tabs={tabs}
/>
))
}
return (
<page.TabBar
header={`Welcome back, ${authUser.first_name} ${authUser.last_name}`}
originalPath={paths.teacher.dashboard.tab._}
value={tabs.findIndex(t => t.path === tab)}
tabs={tabs}
/>
)
})

const TeacherDashboard: FC<TeacherDashboardProps> = props => (
<page.Page session={{ userType: "teacher" }}>
27 changes: 17 additions & 10 deletions src/pages/teacherOnboarding/CreateSchoolForm.tsx
Original file line number Diff line number Diff line change
@@ -11,10 +11,9 @@ import {
import { SchoolNameField } from "../../components/form"

export interface CreateSchoolFormProps {
submitOptions: SubmitFormOptions<
CreateSchoolArg,
CreateSchoolArg,
CreateSchoolResult
submitOptions: Omit<
SubmitFormOptions<CreateSchoolArg, CreateSchoolArg, CreateSchoolResult>,
"clean"
>
}

@@ -25,13 +24,21 @@ const CreateSchoolForm: FC<CreateSchoolFormProps> = ({ submitOptions }) => (
Life, by default, you become the organisation&apos;s administrator.
</Typography>
<forms.Form
initialValues={{
name: "",
country: undefined,
uk_county: undefined,
}}
initialValues={
{
name: "",
country: undefined,
uk_county: undefined,
} as CreateSchoolArg
}
useMutation={useCreateSchoolMutation}
submitOptions={submitOptions}
submitOptions={{
...submitOptions,
clean: ({ uk_county, ...required }) =>
required.country === "GB" && uk_county
? { uk_county, ...required }
: required,
}}
>
{form => (
<>
78 changes: 43 additions & 35 deletions src/pages/teacherOnboarding/TeacherOnboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as pages from "codeforlife/components/page"
import { type Class, type School } from "codeforlife/api"
import { type FC, type ReactNode, useState } from "react"
import { MobileStepper, Typography, mobileStepperClasses } from "@mui/material"
import { type Class } from "codeforlife/api"
import { Navigate } from "codeforlife/components/router"
import { type SessionMetadata } from "codeforlife/hooks"
import { handleResultState } from "codeforlife/utils/api"

@@ -10,6 +11,7 @@ import CreateSchoolForm from "./CreateSchoolForm"
import { CreateStudentsForm } from "../../components/form"
import { type CreateStudentsResult } from "../../api/student"
import StudentCredentialsTable from "./StudentCredentialsTable"
import { paths } from "../../routes"
import { useRetrieveUserQuery } from "../../api/user"

export interface TeacherOnboardingProps {}
@@ -19,14 +21,14 @@ const _TeacherOnboarding: FC<TeacherOnboardingProps & SessionMetadata> = ({
}) => {
const [activeStep, setActiveStep] = useState<{
index: number
schoolId?: School["id"]
classId?: Class["id"]
students?: CreateStudentsResult
}>({ index: 0 })

function onSubmit(state: Omit<typeof activeStep, "index">): void {
setActiveStep(previousState => ({
index: previousState.index + 1,
setActiveStep(({ index, ...previous }) => ({
index: index + 1,
...previous,
...state,
}))
}
@@ -42,8 +44,8 @@ const _TeacherOnboarding: FC<TeacherOnboardingProps & SessionMetadata> = ({
<CreateSchoolForm
key={generateKey(0)}
submitOptions={{
then: school => {
onSubmit({ schoolId: school.id })
then: () => {
onSubmit({})
},
}}
/>
@@ -88,35 +90,41 @@ const _TeacherOnboarding: FC<TeacherOnboardingProps & SessionMetadata> = ({
},
]

return handleResultState(useRetrieveUserQuery(user_id), authUser => (
<>
<pages.Banner
header={`Welcome, ${authUser.first_name} ${authUser.last_name}`}
subheader="Everything you need to start coding with your class is here. Let's set you up."
/>
<Typography variant="h4">{steps[activeStep.index].header}</Typography>
<Typography>
Progress &lt; {activeStep.index + 1} of {steps.length} &gt;
</Typography>
<MobileStepper
variant="progress"
position="static"
steps={steps.length + 1}
activeStep={activeStep.index + 1}
nextButton={undefined}
backButton={undefined}
sx={{
padding: 0,
marginBottom: 3,
[`.${mobileStepperClasses.progress}`]: {
width: "100%",
height: "7px",
},
}}
/>
{steps[activeStep.index].element}
</>
))
return handleResultState(useRetrieveUserQuery(user_id), authUser =>
authUser.teacher!.school ? (
<Navigate to={paths.teacher.dashboard.tab.school._} />
) : (
<>
<pages.Banner
header={`Welcome, ${authUser.first_name} ${authUser.last_name}`}
subheader="Everything you need to start coding with your class is here. Let's set you up."
/>
<pages.Section>
<Typography variant="h4">{steps[activeStep.index].header}</Typography>
<Typography>
Progress &lt; {activeStep.index + 1} of {steps.length} &gt;
</Typography>
<MobileStepper
variant="progress"
position="static"
steps={steps.length + 1}
activeStep={activeStep.index + 1}
nextButton={undefined}
backButton={undefined}
sx={{
padding: 0,
marginBottom: 3,
[`.${mobileStepperClasses.progress}`]: {
width: "100%",
height: "7px",
},
}}
/>
{steps[activeStep.index].element}
</pages.Section>
</>
),
)
}

const TeacherOnboarding: FC<TeacherOnboardingProps> = props => (
Loading