Skip to content

Commit b3393aa

Browse files
committed
Refactor
1 parent dbd7578 commit b3393aa

28 files changed

Lines changed: 2187 additions & 1820 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use client"
2+
3+
import { useQuery } from "@tanstack/react-query"
4+
import { useParams, useRouter } from "next/navigation"
5+
import { useEffect } from "react"
6+
7+
import { getCourseDesignerPlanOptions } from "@/generated/api/@tanstack/react-query.generated"
8+
import {
9+
manageCoursePlanScheduleRoute,
10+
manageCoursePlanWorkspaceRoute,
11+
} from "@/shared-module/common/utils/routes"
12+
import { QueryResult } from "@/shared-module/components"
13+
import { optionalGeneratedQueryOptions } from "@/utils/optionalGeneratedQueryOptions"
14+
15+
/** Redirects to schedule wizard or workspace based on plan status. */
16+
export default function CoursePlanHubRedirect() {
17+
const params = useParams<{ id: string }>()
18+
const router = useRouter()
19+
const planId = params.id
20+
21+
const planQuery = useQuery(
22+
optionalGeneratedQueryOptions({
23+
value: planId,
24+
isReady: (value): value is string => Boolean(value),
25+
build: (value) =>
26+
getCourseDesignerPlanOptions({
27+
path: {
28+
plan_id: value,
29+
},
30+
}),
31+
}),
32+
)
33+
34+
useEffect(() => {
35+
if (!planQuery.data) {
36+
return
37+
}
38+
const { plan } = planQuery.data
39+
if (plan.status === "Draft" || plan.status === "Scheduling") {
40+
router.replace(manageCoursePlanScheduleRoute(planId))
41+
return
42+
}
43+
if (plan.status === "InProgress" || plan.status === "Completed") {
44+
router.replace(manageCoursePlanWorkspaceRoute(planId))
45+
}
46+
}, [planQuery.data, planId, router])
47+
48+
return <QueryResult query={planQuery}>{() => null}</QueryResult>
49+
}
Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,8 @@
11
"use client"
22

3-
import { useQuery } from "@tanstack/react-query"
4-
import { useParams, useRouter } from "next/navigation"
5-
import { useEffect } from "react"
3+
import CoursePlanHubRedirect from "./components/CoursePlanHubRedirect"
64

7-
import { getCourseDesignerPlanOptions } from "@/generated/api/@tanstack/react-query.generated"
85
import { withSignedIn } from "@/shared-module/common/contexts/LoginStateContext"
9-
import {
10-
manageCoursePlanScheduleRoute,
11-
manageCoursePlanWorkspaceRoute,
12-
} from "@/shared-module/common/utils/routes"
136
import withErrorBoundary from "@/shared-module/common/utils/withErrorBoundary"
14-
import { QueryResult } from "@/shared-module/components"
15-
import { optionalGeneratedQueryOptions } from "@/utils/optionalGeneratedQueryOptions"
16-
17-
function CoursePlanHubRedirect() {
18-
const params = useParams<{ id: string }>()
19-
const router = useRouter()
20-
const planId = params.id
21-
22-
const planQuery = useQuery(
23-
optionalGeneratedQueryOptions({
24-
value: planId,
25-
isReady: (value): value is string => Boolean(value),
26-
build: (value) =>
27-
getCourseDesignerPlanOptions({
28-
path: {
29-
plan_id: value,
30-
},
31-
}),
32-
}),
33-
)
34-
35-
useEffect(() => {
36-
if (!planQuery.data) {
37-
return
38-
}
39-
const { plan } = planQuery.data
40-
if (plan.status === "Draft" || plan.status === "Scheduling") {
41-
router.replace(manageCoursePlanScheduleRoute(planId))
42-
return
43-
}
44-
if (plan.status === "InProgress" || plan.status === "Completed") {
45-
router.replace(manageCoursePlanWorkspaceRoute(planId))
46-
}
47-
}, [planQuery.data, planId, router])
48-
49-
return <QueryResult query={planQuery}>{() => null}</QueryResult>
50-
}
517

528
export default withErrorBoundary(withSignedIn(CoursePlanHubRedirect))

services/main-frontend/src/app/manage/course-plans/[id]/schedule/components/MonthBlock.tsx

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,13 @@
11
"use client"
22

33
import { css } from "@emotion/css"
4-
import {
5-
Berries,
6-
Cabin,
7-
Campfire,
8-
CandleLight,
9-
Leaf,
10-
MapleLeaf,
11-
MistyCloud,
12-
PineTree,
13-
Sleigh,
14-
Sunrise,
15-
WaterLiquid,
16-
WinterSnowflake,
17-
} from "@vectopus/atlas-icons-react"
184
import { format } from "date-fns"
195
import { motion } from "motion/react"
206
import { forwardRef } from "react"
217

228
import { StageMonth } from "../scheduleMappers"
239

24-
const MONTH_ICONS = [
25-
WinterSnowflake,
26-
Sleigh,
27-
Sunrise,
28-
WaterLiquid,
29-
Leaf,
30-
Campfire,
31-
Cabin,
32-
Berries,
33-
MapleLeaf,
34-
MistyCloud,
35-
CandleLight,
36-
PineTree,
37-
] as const
10+
import { COURSE_PLAN_MONTH_ICONS } from "@/app/manage/course-plans/monthIcons"
3811

3912
const stageMonthBlockStyles = css`
4013
min-width: 84px;
@@ -81,7 +54,7 @@ const MonthBlock = forwardRef<HTMLDivElement, MonthBlockProps>(function MonthBlo
8154
{ month, reduceMotion, layoutId },
8255
ref,
8356
) {
84-
const MonthIcon = MONTH_ICONS[month.date.getMonth()]
57+
const MonthIcon = COURSE_PLAN_MONTH_ICONS[month.date.getMonth()]
8558

8659
return (
8760
<motion.div

services/main-frontend/src/app/manage/course-plans/[id]/schedule/components/steps/NameStep.tsx

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use client"
22

33
import { css } from "@emotion/css"
4-
import { useEffect } from "react"
5-
import { useForm } from "react-hook-form"
64
import { useTranslation } from "react-i18next"
75

6+
import { useWizardTextField } from "../../hooks/useWizardStepFields"
7+
88
import { baseTheme } from "@/shared-module/common/styles"
99
import { Button, TextField } from "@/shared-module/components"
1010

@@ -35,23 +35,8 @@ interface NameStepProps {
3535

3636
export default function NameStep({ planName, onPlanNameChange, onContinue }: NameStepProps) {
3737
const { t } = useTranslation()
38-
const { control, setValue, watch } = useForm<{ planName: string }>({
39-
defaultValues: { planName },
40-
})
41-
42-
useEffect(() => {
43-
setValue("planName", planName)
44-
}, [planName, setValue])
45-
46-
useEffect(() => {
47-
const subscription = watch((values, meta) => {
48-
if (meta.name === "planName") {
49-
onPlanNameChange(values.planName ?? "")
50-
}
51-
})
52-
53-
return () => subscription.unsubscribe()
54-
}, [onPlanNameChange, watch])
38+
// eslint-disable-next-line i18next/no-literal-string
39+
const { control, fieldName } = useWizardTextField("planName", planName, onPlanNameChange)
5540

5641
return (
5742
<>
@@ -60,8 +45,7 @@ export default function NameStep({ planName, onPlanNameChange, onContinue }: Nam
6045
<div className={fieldStyles}>
6146
<TextField
6247
id="course-plan-name"
63-
// eslint-disable-next-line i18next/no-literal-string
64-
name="planName"
48+
name={fieldName}
6549
control={control}
6650
label={t("course-plans-plan-name-label")}
6751
placeholder={t("course-plans-untitled-plan")}

services/main-frontend/src/app/manage/course-plans/[id]/schedule/components/steps/SetupStep.tsx

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"use client"
22

33
import { css } from "@emotion/css"
4-
import { useEffect, useMemo } from "react"
5-
import { useForm } from "react-hook-form"
4+
import { useMemo } from "react"
65
import { useTranslation } from "react-i18next"
76

7+
import { useSetupStepFields } from "../../hooks/useWizardStepFields"
8+
89
import { CourseDesignerCourseSize } from "@/generated/api/types.generated"
910
import { Button, Select, YearMonthField } from "@/shared-module/components"
1011

@@ -50,14 +51,12 @@ export default function SetupStep({
5051
onContinue,
5152
}: SetupStepProps) {
5253
const { t } = useTranslation()
53-
const { control, setValue, watch } = useForm<{
54-
courseSize: CourseDesignerCourseSize
55-
startsOnMonth: string
56-
}>({
57-
defaultValues: {
58-
courseSize,
59-
startsOnMonth,
60-
},
54+
55+
const { control } = useSetupStepFields({
56+
courseSize,
57+
startsOnMonth,
58+
onCourseSizeChange,
59+
onStartsOnMonthChange,
6160
})
6261

6362
const courseSizeOptions = useMemo(
@@ -72,27 +71,6 @@ export default function SetupStep({
7271
[t],
7372
)
7473

75-
useEffect(() => {
76-
setValue("courseSize", courseSize)
77-
}, [courseSize, setValue])
78-
79-
useEffect(() => {
80-
setValue("startsOnMonth", startsOnMonth)
81-
}, [startsOnMonth, setValue])
82-
83-
useEffect(() => {
84-
const subscription = watch((values, meta) => {
85-
if (meta.name === "courseSize" && values.courseSize) {
86-
onCourseSizeChange(values.courseSize)
87-
}
88-
if (meta.name === "startsOnMonth") {
89-
onStartsOnMonthChange(values.startsOnMonth ?? "")
90-
}
91-
})
92-
93-
return () => subscription.unsubscribe()
94-
}, [onCourseSizeChange, onStartsOnMonthChange, watch])
95-
9674
return (
9775
<>
9876
<h2>{t("course-plans-wizard-step-size-and-date")}</h2>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { useEffect } from "react"
2+
import { useForm } from "react-hook-form"
3+
4+
/** Keeps a single RHF text field in sync with external wizard controller state. */
5+
export function useWizardTextField(
6+
fieldName: string,
7+
externalValue: string,
8+
onExternalChange: (value: string) => void,
9+
) {
10+
const { control, setValue, watch } = useForm<Record<string, string>>({
11+
defaultValues: { [fieldName]: externalValue },
12+
})
13+
14+
useEffect(() => {
15+
setValue(fieldName, externalValue)
16+
}, [externalValue, fieldName, setValue])
17+
18+
useEffect(() => {
19+
const subscription = watch((values, meta) => {
20+
if (meta.name === fieldName) {
21+
onExternalChange(values[fieldName] ?? "")
22+
}
23+
})
24+
return () => subscription.unsubscribe()
25+
}, [fieldName, onExternalChange, watch])
26+
27+
return { control, fieldName }
28+
}
29+
30+
import type { CourseDesignerCourseSize } from "@/generated/api/types.generated"
31+
32+
type SetupStepFormValues = {
33+
courseSize: CourseDesignerCourseSize
34+
startsOnMonth: string
35+
}
36+
37+
/** Keeps setup-step RHF fields in sync with the wizard controller. */
38+
export function useSetupStepFields(options: {
39+
courseSize: CourseDesignerCourseSize
40+
startsOnMonth: string
41+
onCourseSizeChange: (value: CourseDesignerCourseSize) => void
42+
onStartsOnMonthChange: (value: string) => void
43+
}) {
44+
const { courseSize, startsOnMonth, onCourseSizeChange, onStartsOnMonthChange } = options
45+
const { control, setValue, watch } = useForm<SetupStepFormValues>({
46+
defaultValues: { courseSize, startsOnMonth },
47+
})
48+
49+
useEffect(() => {
50+
setValue("courseSize", courseSize)
51+
}, [courseSize, setValue])
52+
53+
useEffect(() => {
54+
setValue("startsOnMonth", startsOnMonth)
55+
}, [setValue, startsOnMonth])
56+
57+
useEffect(() => {
58+
const subscription = watch((formValues, meta) => {
59+
if (meta.name === "courseSize" && formValues.courseSize) {
60+
onCourseSizeChange(formValues.courseSize)
61+
}
62+
if (meta.name === "startsOnMonth") {
63+
onStartsOnMonthChange(formValues.startsOnMonth ?? "")
64+
}
65+
})
66+
return () => subscription.unsubscribe()
67+
}, [onCourseSizeChange, onStartsOnMonthChange, watch])
68+
69+
return { control }
70+
}

0 commit comments

Comments
 (0)