Skip to content

Commit

Permalink
Portal frontend 53 (#66)
Browse files Browse the repository at this point in the history
* initial page and route

* page

* release button

* fix: duplicate ids

* fix spacing

* new cfl package

* fix disabled state

* small fixes
  • Loading branch information
SKairinos authored Oct 3, 2024
1 parent 3110b18 commit 7a50db4
Show file tree
Hide file tree
Showing 8 changed files with 1,173 additions and 932 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"✅ Do add `devDependencies` below that are `peerDependencies` in the CFL package."
],
"dependencies": {
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.3.6",
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.3.8",
"crypto-js": "^4.2.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/features/footer/RegisterToNewsletterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const RegisterToNewsletterForm: FC<RegisterToNewsletterFormProps> = () => {
>
<Stack spacing={2}>
<forms.EmailField
id="newsletter-email" // Avoid duplicate IDs on pages which have forms with an email field
FormHelperTextProps={{ style: { color: "white" } }}
required
/>
Expand Down
3 changes: 3 additions & 0 deletions src/pages/teacherDashboard/classes/Classes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ClassTable from "./ClassTable"
import CreateClassForm from "./CreateClassForm"
import JoinClassRequest from "./joinClassRequest/JoinClassRequest"
import JoinClassRequestTable from "./JoinClassRequestTable"
import ReleaseStudents from "./releaseStudents/ReleaseStudents"
import ResetStudentsPassword from "./resetStudentsPassword/ResetStudentsPassword"
import { type RetrieveUserResult } from "../../../api/user"
import TransferStudents from "./transferStudents/TransferStudents"
Expand All @@ -20,6 +21,7 @@ export interface ClassesProps {
| "reset-students-password"
| "update-student-user"
| "transfer-students"
| "release-students"
}

const Classes: FC<ClassesProps> = ({ authUser, view }) => {
Expand All @@ -30,6 +32,7 @@ const Classes: FC<ClassesProps> = ({ authUser, view }) => {
"reset-students-password": <ResetStudentsPassword />,
"update-student-user": <UpdateStudentUser />,
"transfer-students": <TransferStudents />,
"release-students": <ReleaseStudents />,
}[view]
}

Expand Down
9 changes: 9 additions & 0 deletions src/pages/teacherDashboard/classes/class/StudentTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SecurityOutlined as SecurityOutlinedIcon } from "@mui/icons-material"
import { generatePath } from "react-router-dom"

import { type ListUsersResult } from "../../../../api/user"
import { type ReleaseStudentsState } from "../releaseStudents/ReleaseStudents"
import ResetStudentsPasswordDialog from "./ResetStudentsPasswordDialog"
import { type TransferStudentsState } from "../transferStudents/TransferStudents"
import { paths } from "../../../../routes"
Expand Down Expand Up @@ -57,6 +58,14 @@ const StudentTable: FC<StudentTableProps> = ({ classId }) => {
),
state: { studentUsers: Object.values(studentUsers) },
})}
{LinkButton<ReleaseStudentsState>({
children: "Release",
to: generatePath(
paths.teacher.dashboard.tab.classes.class.students.release._,
{ classId },
),
state: { studentUsers: Object.values(studentUsers) },
})}
</Stack>
<ResetStudentsPasswordDialog
classId={classId}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as pages from "codeforlife/components/page"
import { type Class, type StudentUser } from "codeforlife/api"
import { Link, Navigate } from "codeforlife/components/router"
import { useLocation, useParams } from "codeforlife/hooks"
import { type FC } from "react"
import { Typography } from "@mui/material"
import { generatePath } from "react-router-dom"
import { handleResultState } from "codeforlife/utils/api"

import { type ListUsersResult } from "../../../../api/user"
import ReleaseStudentsForm from "./ReleaseStudentsForm"
import { classIdSchema } from "../../../../app/schemas"
import { paths } from "../../../../routes"
import { useRetrieveClassQuery } from "../../../../api/klass"

const _ReleaseStudents: FC<
ReleaseStudentsState & {
classId: Class["id"]
classPath: string
}
> = ({ classId, classPath, ...state }) =>
handleResultState(useRetrieveClassQuery(classId), klass => (
<pages.Section>
<Typography align="center" variant="h4">
Release students from class {klass.name} ({klass.id})
</Typography>
<Link className="back-to" to={classPath}>
Class
</Link>
<Typography>Convert students into independent students.</Typography>
<Typography variant="h5">Students to release from school</Typography>
<Typography>
You are about to remove students from your class and set them up as
independent students. Neither you nor your school will be able to manage
them once you have submitted this request.
</Typography>
<Typography>
Email addresses are required for independent student accounts. If a
student is too young to own an email address, a parent or
guardian&apos;s email address will be required.
</Typography>
<Typography>
The email address will have to be validated through a verification email
before the student can log in. The email has to be unique and not used
for other accounts in Code for Life.{" "}
<strong>
Make sure you type the correct email, as otherwise we may not be able
to recover the account
</strong>
.
</Typography>
<Typography>
The students will then log in with their email via the{" "}
<Link className="body" to={paths.login.indy._}>
independent student login
</Link>
. Their passwords will stay the same. Independent students do not need
to provide a class access code.
</Typography>
<ReleaseStudentsForm classPath={classPath} {...state} />
</pages.Section>
))

export interface ReleaseStudentsState {
studentUsers: Array<StudentUser<ListUsersResult["data"][number]>>
}

export interface ReleaseStudentsProps {}

const ReleaseStudents: FC<ReleaseStudentsProps> = () => {
const params = useParams({ classId: classIdSchema.required() })
const { state } = useLocation<ReleaseStudentsState>()

if (!params)
return <Navigate to={paths.teacher.dashboard.tab.classes._} replace />

const { classId } = params

const classPath = generatePath(paths.teacher.dashboard.tab.classes.class._, {
classId,
})

return !state || !state.studentUsers || !state.studentUsers.length ? (
<Navigate to={classPath} replace />
) : (
<_ReleaseStudents
classId={classId}
classPath={classPath}
studentUsers={state.studentUsers}
/>
)
}

export default ReleaseStudents
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as forms from "codeforlife/components/form"
import {
EmailOutlined as EmailOutlinedIcon,
PersonRemoveAlt1Outlined as PersonRemoveAlt1OutlinedIcon,
} from "@mui/icons-material"
import { InputAdornment, Stack } from "@mui/material"
import { type FC } from "react"
import { LinkButton } from "codeforlife/components/router"
import { type StudentUser } from "codeforlife/api"
import { submitForm } from "codeforlife/utils/form"
import { useNavigate } from "codeforlife/hooks"

import {
type ReleaseStudentsArg,
useReleaseStudentsMutation,
} from "../../../../api/student"
import { type ListUsersResult } from "../../../../api/user"

export interface ReleaseStudentsFormProps {
studentUsers: Array<StudentUser<ListUsersResult["data"][number]>>
classPath: string
}

const ReleaseStudentsForm: FC<ReleaseStudentsFormProps> = ({
studentUsers,
classPath,
}) => {
const [releaseStudents] = useReleaseStudentsMutation()
const navigate = useNavigate()

return (
<forms.Form
initialValues={studentUsers.reduce(
(arg, studentUser) => ({
...arg,
[studentUser.student.id]: {
user: {
original_first_name: studentUser.first_name,
first_name: studentUser.first_name,
email: "",
email_repeat: "",
},
},
}),
{} as ReleaseStudentsArg,
)}
onSubmit={submitForm(releaseStudents, {
exclude: studentUsers.reduce(
(exclude, studentUser) => [
...exclude,
`${studentUser.student.id}.user.original_first_name`,
`${studentUser.student.id}.user.email_repeat`,
],
[] as string[],
),
then: () => {
navigate(classPath, {
state: {
notifications: [
{
props: {
children: "The students have been released successfully",
},
},
],
},
})
},
catch: () => {
navigate(classPath, {
state: {
notifications: [
{
props: {
error: true,
children: "Failed to release students",
},
},
],
},
})
},
})}
>
<Stack gap={6}>
{studentUsers.map(studentUser => (
<Stack key={`user-${studentUser.id}`} gap={2}>
<Stack direction="row" gap={2}>
<forms.FirstNameField
disabled
name={`${studentUser.student.id}.user.original_first_name`}
label="Original student name"
/>
<forms.FirstNameField
required
name={`${studentUser.student.id}.user.first_name`}
label="New student name"
placeholder="Enter student address"
/>
</Stack>
<Stack direction="row" gap={2}>
<forms.EmailField
required
name={`${studentUser.student.id}.user.email`}
label="New email address"
placeholder="Enter email address"
/>
<forms.RepeatField
name={`${studentUser.student.id}.user.email`}
label="Repeat email address"
placeholder="Repeat email address"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<EmailOutlinedIcon />
</InputAdornment>
),
}}
/>
</Stack>
</Stack>
))}
<Stack direction="row" gap={2}>
<LinkButton to={classPath} variant="outlined">
Cancel
</LinkButton>
<forms.SubmitButton
className="alert"
endIcon={<PersonRemoveAlt1OutlinedIcon />}
>
Remove students
</forms.SubmitButton>
</Stack>
</Stack>
</forms.Form>
)
}

export default ReleaseStudentsForm
4 changes: 4 additions & 0 deletions src/routes/teacher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const teacher = (
path={paths.teacher.dashboard.tab.classes.class.students.transfer._}
element={<TeacherDashboard tab="classes" view="transfer-students" />}
/>
<Route
path={paths.teacher.dashboard.tab.classes.class.students.release._}
element={<TeacherDashboard tab="classes" view="release-students" />}
/>
<Route
path={paths.teacher.dashboard.tab.classes.class._}
element={<TeacherDashboard tab="classes" view="class" />}
Expand Down
Loading

0 comments on commit 7a50db4

Please sign in to comment.