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 account tab #82

Merged
merged 29 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@
}

export interface DeleteAccountFormProps {
user: RetrieveUserResult
authUser: RetrieveUserResult
}

const DeleteAccountForm: FC<DeleteAccountFormProps> = ({ user }) => {
const DeleteAccountForm: FC<DeleteAccountFormProps> = ({ authUser }) => {

Check warning on line 77 in src/components/form/DeleteAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/DeleteAccountForm.tsx#L77

Added line #L77 was not covered by tests
const [confirmDialog, setConfirmDialog] = useState<{
open: boolean
destroyIndyUserArg?: DestroyIndependentUserArg
Expand All @@ -98,7 +98,7 @@
<Typography fontWeight="bold">This can&apos;t be reversed.</Typography>
<forms.Form
initialValues={{
id: user.id,
id: authUser.id,
password: "",
remove_from_newsletter: false,
}}
Expand Down Expand Up @@ -132,7 +132,7 @@
<forms.SubmitButton
className="alert"
endIcon={<DeleteOutlineIcon />}
sx={theme => ({ marginTop: theme.spacing(3) })}
sx={{ marginTop: 3 }}
>
Delete account
</forms.SubmitButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
import * as forms from "codeforlife/components/form"
import { Stack, Typography } from "@mui/material"
import { getDirty, isDirty } from "codeforlife/utils/form"
import { type FC } from "react"
import { LinkButton } from "codeforlife/components/router"
import { Typography } from "@mui/material"
import { useNavigate } from "codeforlife/hooks"

import {
type RetrieveUserResult,
type UpdateUserArg,
type UpdateUserResult,
useUpdateUserMutation,
} from "../../api/user"
import { indyPasswordSchema, studentPasswordSchema } from "../../app/schemas"
import { LastNameField } from "../../components/form"
import {
indyPasswordSchema,
studentPasswordSchema,
teacherPasswordSchema,
} from "../../app/schemas"
import { LastNameField } from "./index"

export interface UpdateAccountFormProps {
user: RetrieveUserResult
authUser: RetrieveUserResult
}

const UpdateAccountForm: FC<UpdateAccountFormProps> = ({ user }) => {
// TODO: Split this form into two or three forms. Needs UX work
const UpdateAccountForm: FC<UpdateAccountFormProps> = ({ authUser }) => {

Check warning on line 24 in src/components/form/UpdateAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/UpdateAccountForm.tsx#L24

Added line #L24 was not covered by tests
const navigate = useNavigate()

const initialValues = user.student
const initialValues = authUser.student
? {
id: user.id,
id: authUser.id,
password: "",
password_repeat: "",
current_password: "",
}
: {
id: user.id,
password: "",
password_repeat: "",
current_password: "",
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
id: authUser.id,
password: undefined as string | undefined,
password_repeat: undefined as string | undefined,
current_password: undefined as string | undefined,
first_name: authUser.first_name,
last_name: authUser.last_name,
email: authUser.email,
}

return (
<>
{user.student ? (
{authUser.student ? (
<>
<Typography align="center" variant="h4">
Update your password
</Typography>
<Typography variant="h5">Update your password</Typography>
<Typography>
You may edit your password below. It must be long enough and hard
enough to stop your friends guessing it and stealing all of your
Expand All @@ -56,9 +57,7 @@
</>
) : (
<>
<Typography align="center" variant="h4">
Update your account details
</Typography>
<Typography variant="h5">Update your account details</Typography>
<Typography>You can update your account details below.</Typography>
<Typography>
Please note: If you change your email address, you will need to
Expand All @@ -74,41 +73,35 @@
exclude: ["password_repeat"],
clean: (values: typeof initialValues) => {
const arg: UpdateUserArg = { id: values.id }
if (user.student || isDirty(values, initialValues, "password")) {
if (isDirty(values, initialValues, "password")) {
arg.password = values.password
arg.current_password = values.current_password
} else if (isDirty(values, initialValues, "email")) {
}
if (isDirty(values, initialValues, "email")) {
arg.email = values.email
arg.current_password = values.current_password
} else if (isDirty(values, initialValues, "first_name")) {
}
if (isDirty(values, initialValues, "first_name")) {
arg.first_name = values.first_name
} else if (isDirty(values, initialValues, "last_name")) {
}
if (isDirty(values, initialValues, "last_name")) {
arg.last_name = values.last_name
}

return arg
},
then: (_: UpdateUserResult, values: typeof initialValues) => {
const messages = [
"Your account details have been changed successfully.",
]
if (isDirty(values, initialValues, "email")) {
// TODO: implement this behavior on the backend.
messages.push(
"Your email will be changed once you have verified it, until then you can still log in with your old email.",
)
}
if (isDirty(values, initialValues, "password")) {
messages.push(
"Going forward, please login using your new password.",
)
}

// TODO: Update backend to log user out and show a message if credential fields were updated
then: () => {

Check warning on line 94 in src/components/form/UpdateAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/UpdateAccountForm.tsx#L94

Added line #L94 was not covered by tests
navigate(".", {
state: {
notifications: messages.map(message => ({
props: { children: message },
})),
notifications: [
{
props: {
children:
"Your account details have been changed successfully.",
},
},
],
},
})
},
Expand All @@ -120,9 +113,14 @@
"password",
])

let passwordSchema = user.student
? studentPasswordSchema
: indyPasswordSchema
let passwordSchema = indyPasswordSchema

Check warning on line 116 in src/components/form/UpdateAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/UpdateAccountForm.tsx#L116

Added line #L116 was not covered by tests

if (authUser.student) {
passwordSchema = studentPasswordSchema

Check warning on line 119 in src/components/form/UpdateAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/UpdateAccountForm.tsx#L119

Added line #L119 was not covered by tests
} else if (authUser.teacher) {
passwordSchema = teacherPasswordSchema

Check warning on line 121 in src/components/form/UpdateAccountForm.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/form/UpdateAccountForm.tsx#L121

Added line #L121 was not covered by tests
}

if (isDirty(form.values, initialValues, "current_password")) {
passwordSchema = passwordSchema.notOneOf(
[form.values.current_password],
Expand All @@ -132,34 +130,31 @@

return (
<>
{!user.student && (
{!authUser.student && (
<>
<forms.FirstNameField />
<LastNameField />
<forms.EmailField />
</>
)}
<forms.PasswordField
required={Boolean(user.student)}
required={Boolean(authUser.student)}
label="New password"
repeatFieldProps={{ label: "Repeat new password" }}
withRepeatField={Boolean(user.student) || dirty.password}
withRepeatField={Boolean(authUser.student) || dirty.password}
schema={passwordSchema}
/>
{(Boolean(user.student) || dirty.email || dirty.password) && (
{(Boolean(authUser.student) || dirty.email || dirty.password) && (
<forms.PasswordField
required
name="current_password"
label="Current password"
placeholder="Enter your current password"
/>
)}
<Stack direction="row" spacing={2} paddingY={3}>
<LinkButton variant="outlined" to={-1}>
Cancel
</LinkButton>
<forms.SubmitButton>Update details</forms.SubmitButton>
</Stack>
<forms.SubmitButton sx={{ marginTop: 3 }}>
Update details
</forms.SubmitButton>
</>
)
}}
Expand Down
4 changes: 4 additions & 0 deletions src/components/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from "./CreateClassForm"
export { default as CreateClassForm } from "./CreateClassForm"
export * from "./CreateStudentsForm"
export { default as CreateStudentsForm } from "./CreateStudentsForm"
export * from "./DeleteAccountForm"
export { default as DeleteAccountForm } from "./DeleteAccountForm"
export * from "./LastNameField"
export { default as LastNameField } from "./LastNameField"
export * from "./NewPasswordField"
Expand All @@ -16,3 +18,5 @@ export * from "./SchoolNameField"
export { default as SchoolNameField } from "./SchoolNameField"
export * from "./TeacherAutocompleteField"
export { default as TeacherAutocompleteField } from "./TeacherAutocompleteField"
export * from "./UpdateAccountForm"
export { default as UpdateAccountForm } from "./UpdateAccountForm"
11 changes: 5 additions & 6 deletions src/pages/studentAccount/StudentAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import { Typography } from "@mui/material"
import { handleResultState } from "codeforlife/utils/api"

import DeleteAccountForm from "./DeleteAccountForm"
import UpdateAccountForm from "./UpdateAccountForm"
import { DeleteAccountForm, UpdateAccountForm } from "../../components/form"
import { paths } from "../../routes"
import { useRetrieveUserQuery } from "../../api/user"

Expand All @@ -15,15 +14,15 @@
}

const _StudentAccount: FC<SessionMetadata> = ({ user_type, user_id }) =>
handleResultState(useRetrieveUserQuery(user_id), user => (
handleResultState(useRetrieveUserQuery(user_id), authUser => (

Check warning on line 17 in src/pages/studentAccount/StudentAccount.tsx

View check run for this annotation

Codecov / codecov/patch

src/pages/studentAccount/StudentAccount.tsx#L17

Added line #L17 was not covered by tests
<>
<page.Banner
header={`Welcome, ${user.first_name}`}
header={`Welcome, ${authUser.first_name}`}
textAlign="center"
bgcolor={user_type === "student" ? "tertiary" : "secondary"}
/>
<page.Section>
<UpdateAccountForm user={user} />
<UpdateAccountForm authUser={authUser} />
</page.Section>
{user_type === "indy" && (
<>
Expand All @@ -36,7 +35,7 @@
<LinkButton to={paths.indy.dashboard.joinClass._}>Join</LinkButton>
</page.Section>
<page.Section>
<DeleteAccountForm user={user} />
<DeleteAccountForm authUser={authUser} />
</page.Section>
</>
)}
Expand Down
72 changes: 37 additions & 35 deletions src/pages/studentJoinClass/StudentJoinClass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,43 @@ const _StudentJoinClass: FC<SessionMetadata> = ({ user_id }) => {
const user = authUser as IndependentUser<RetrieveUserResult>

return (
<page.Section>
<Typography align="center" variant="h4">
Join a school or club
</Typography>
{user.requesting_to_join_class ? (
<RequestPending user={user} />
) : (
<>
<Typography variant="h5">
Request to join a school or club
</Typography>
<Typography>
If you want to link your Code For Life account with a school or
club, ask a teacher to enable external requests and provide you
with the Class Access Code for the class you want to join. Simply
add the Class Access Code to the form below and submit.
</Typography>
<Typography>
<strong>Warning:</strong> once the teacher accepts you to their
class, that teacher and the school or club will manage your
account.
</Typography>
<Typography>
If successful, the teacher will contact you with your new login
details.
</Typography>
<RequestToJoinClassForm
indyUser={{
id: user.id,
requesting_to_join_class: user.requesting_to_join_class!.id,
}}
/>
</>
)}
</page.Section>
<>
<page.Banner
header={`Welcome, ${authUser.first_name}`}
textAlign="center"
bgcolor={"secondary"}
/>
<page.Section>
<Typography align="center" variant="h4">
Join a school or club
</Typography>
{user.requesting_to_join_class ? (
<RequestPending user={user} />
) : (
<>
<Typography variant="h5">
Request to join a school or club
</Typography>
<Typography>
If you want to link your Code For Life account with a school or
club, ask a teacher to enable external requests and provide you
with the Class Access Code for the class you want to join.
Simply add the Class Access Code to the form below and submit.
</Typography>
<Typography>
<strong>Warning:</strong> once the teacher accepts you to their
class, that teacher and the school or club will manage your
account.
</Typography>
<Typography>
If successful, the teacher will contact you with your new login
details.
</Typography>
<RequestToJoinClassForm indyUser={{ id: user.id }} />
</>
)}
</page.Section>
</>
)
})
}
Expand Down
Loading