Skip to content

Commit

Permalink
Improve learningpath form typing
Browse files Browse the repository at this point in the history
  • Loading branch information
katrinewi committed Dec 20, 2024
1 parent a5a69db commit 8dafad1
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 76 deletions.
15 changes: 8 additions & 7 deletions src/containers/MyNdla/Learningpath/EditLearningpathStepsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Button, Heading, Spinner, Text } from "@ndla/primitives";
import { SafeLinkButton } from "@ndla/safelink";
import { Stack, styled } from "@ndla/styled-system/jsx";
import { HelmetWithTracker, useTracker } from "@ndla/tracker";
import { FormValues, LearningpathStepForm } from "./components/LearningpathStepForm";
import { LearningpathStepForm } from "./components/LearningpathStepForm";
import { useCreateLearningpathStep } from "./learningpathMutations";
import { learningpathQuery, useFetchLearningpath } from "./learningpathQueries";
import { formValuesToGQLInput } from "./utils";
Expand All @@ -26,6 +26,7 @@ import MyNdlaBreadcrumb from "../components/MyNdlaBreadcrumb";
import MyNdlaPageWrapper from "../components/MyNdlaPageWrapper";
import { LearningpathStepListItem } from "./components/LearningpathStepListItem";
import { LearningpathStepper } from "./components/LearningpathStepper";
import { FormValues } from "./types";

const StyledOl = styled("ol", {
base: {
Expand Down Expand Up @@ -68,7 +69,11 @@ export const EditLearningpathStepsPage = () => {
await createStep({
variables: {
learningpathId: data.myNdlaLearningpath.id,
params: { ...transformedData, language: i18n.language, showTitle: false },
params: {
...transformedData,
language: i18n.language,
showTitle: false,
},
},
refetchQueries: [{ query: learningpathQuery, variables: { pathId: data.myNdlaLearningpath.id.toString() } }],
});
Expand Down Expand Up @@ -111,11 +116,7 @@ export const EditLearningpathStepsPage = () => {
{t("myNdla.learningpath.form.steps.add")}
</AddButton>
) : (
<LearningpathStepForm
learningpathId={data.myNdlaLearningpath.id}
onClose={() => setIsCreating(false)}
onSave={onSaveStep}
/>
<LearningpathStepForm defaultStepType="text" onClose={() => setIsCreating(false)} onSave={onSaveStep} />
)}
</Stack>
<Stack justify="space-between" direction="row">
Expand Down
19 changes: 6 additions & 13 deletions src/containers/MyNdla/Learningpath/components/ExternalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ import {
FieldLabel,
FieldHelper,
FieldRoot,
Input,
Text,
CheckboxRoot,
CheckboxLabel,
CheckboxControl,
CheckboxIndicator,
CheckboxHiddenInput,
FieldErrorMessage,
FieldInput,
} from "@ndla/primitives";
import { SafeLink } from "@ndla/safelink";
import { Stack, styled } from "@ndla/styled-system/jsx";
import useValidationTranslation from "../../../../util/useValidationTranslation";
import FieldLength from "../../components/FieldLength";
import { useFetchOpengraph } from "../learningpathQueries";
import { FormValuesMap } from "../types";

const StyledCheckboxRoot = styled(CheckboxRoot, {
base: {
Expand All @@ -44,17 +45,9 @@ const CopyrightText = styled(Text, {
const TITLE_MAX_LENGTH = 64;
const INTRODUCTION_MAX_LENGTH = 250;

export interface ExternalFormValues {
type: "external";
title: string;
introduction: string;
url: string;
shareable: boolean;
}

export const ExternalForm = () => {
const { t } = useTranslation();
const { control, setValue, watch } = useFormContext<ExternalFormValues>();
const { control, setValue, watch } = useFormContext<FormValuesMap["external"]>();
const { refetch } = useFetchOpengraph({ skip: true });
const { validationT } = useValidationTranslation();

Expand Down Expand Up @@ -98,7 +91,7 @@ export const ExternalForm = () => {
<FieldLabel>{t("myNdla.learningpath.form.content.external.title.label")}</FieldLabel>
<FieldHelper>{t("myNdla.learningpath.form.content.external.title.labelHelper")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<Input {...field} />
<FieldInput {...field} value={field.value ?? ""} />
<FieldLength value={field.value?.length ?? 0} maxLength={TITLE_MAX_LENGTH} />
</FieldRoot>
)}
Expand All @@ -125,7 +118,7 @@ export const ExternalForm = () => {
<FieldLabel>{t("myNdla.learningpath.form.content.external.introduction.label")}</FieldLabel>
<FieldHelper>{t("myNdla.learningpath.form.content.external.introduction.labelHelper")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<Input {...field} />
<FieldInput {...field} value={field.value ?? ""} />
<FieldLength value={field.value?.length ?? 0} maxLength={INTRODUCTION_MAX_LENGTH} />
</FieldRoot>
)}
Expand All @@ -144,7 +137,7 @@ export const ExternalForm = () => {
<FieldLabel>{t("myNdla.learningpath.form.content.external.content.label")}</FieldLabel>
<FieldHelper>{t("myNdla.learningpath.form.content.external.content.labelHelper")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<Input {...field} />
<FieldInput {...field} value={field.value ?? ""} />
</FieldRoot>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import {
RadioGroupRoot,
} from "@ndla/primitives";
import { HStack, styled } from "@ndla/styled-system/jsx";
import { LearningpathStepDeleteDialog } from "./LearningpathStepDeleteDialog";
import { GQLMyNdlaLearningpathStepFragment } from "../../../../graphqlTypes";
import { formValues, getFormTypeFromStep, getValuesFromStep } from "../utils";
import { ExternalForm } from "./ExternalForm";
import { LearningpathStepDeleteDialog } from "./LearningpathStepDeleteDialog";
import { ResourceForm } from "./ResourceForm";
import { GQLMyNdlaLearningpathStepFragment } from "../../../../graphqlTypes";
import { FormKeys, FormValues } from "../types";
import { toFormValues } from "../utils";
import { TextForm } from "./TextForm";

const ContentForm = styled("form", {
Expand All @@ -42,32 +43,20 @@ const ContentForm = styled("form", {
});

const RADIO_GROUP_OPTIONS = ["text", "resource", "external", "folder"] as const;
export type FormType = (typeof RADIO_GROUP_OPTIONS)[number];

export type FormValues = {
type: string;
title: string;
introduction: string;
description: string;
embedUrl: string;
url: string;
shareable: boolean;
};

interface Props {
learningpathId: number;
step?: GQLMyNdlaLearningpathStepFragment;
defaultStepType: FormKeys;
onClose?: VoidFunction;
onDelete?: (close: VoidFunction) => Promise<void>;
onSave: (data: FormValues) => Promise<void>;
}

export const LearningpathStepForm = ({ step, onClose, onSave, onDelete }: Props) => {
export const LearningpathStepForm = ({ step, defaultStepType, onClose, onSave, onDelete }: Props) => {
const { t } = useTranslation();

const stepType = getFormTypeFromStep(step);
const methods = useForm<FormValues>({
defaultValues: stepType ? getValuesFromStep(stepType, step) : formValues(),
defaultValues: toFormValues(defaultStepType, step),
});
const { handleSubmit, control, watch, reset, formState } = methods;

Expand All @@ -83,7 +72,7 @@ export const LearningpathStepForm = ({ step, onClose, onSave, onDelete }: Props)
<FieldHelper>{t("myNdla.learningpath.form.content.subTitle")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<RadioGroupRoot
onValueChange={(details) => reset(getValuesFromStep(details.value as FormType, step))}
onValueChange={(details) => reset(toFormValues(details.value as FormKeys, step))}
orientation="vertical"
{...field}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { Stack, styled } from "@ndla/styled-system/jsx";
import { GQLMyNdlaLearningpathStepFragment } from "../../../../graphqlTypes";
import { useUpdateLearningpathStep, useDeleteLearningpathStep } from "../learningpathMutations";
import { formValuesToGQLInput, getFormTypeFromStep } from "../utils";
import { FormValues, LearningpathStepForm } from "./LearningpathStepForm";
import { LearningpathStepForm } from "./LearningpathStepForm";
import { learningpathQuery } from "../learningpathQueries";
import { FormValues } from "../types";

const ContentWrapper = styled("div", {
base: {
Expand Down Expand Up @@ -101,7 +102,7 @@ export const LearningpathStepListItem = ({ step, learningpathId }: LearningpathS
)}
</ContentWrapper>
{isEditing ? (
<LearningpathStepForm learningpathId={learningpathId} step={step} onSave={onSave} onDelete={onDelete} />
<LearningpathStepForm step={step} defaultStepType={stepType ?? "text"} onSave={onSave} onDelete={onDelete} />
) : null}
</li>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ import { ContentTypeBadge } from "@ndla/ui";
import { ResourcePicker } from "./ResourcePicker";
import { GQLResourceType } from "../../../../graphqlTypes";
import { contentTypeMapping } from "../../../../util/getContentType";

export interface ResourceFormValues {
type: "resource";
embedUrl: string;
title: string;
}
import { FormValuesMap } from "../types";

interface ResourceFormProps {
resource?: ResourceData;
Expand All @@ -31,7 +26,7 @@ interface ResourceFormProps {
export const ResourceForm = ({ resource }: ResourceFormProps) => {
const { t } = useTranslation();
const [selectedResource, setSelectedResource] = useState<ResourceData | undefined>(resource);
const { setValue } = useFormContext<ResourceFormValues>();
const { setValue } = useFormContext<FormValuesMap["resource"]>();

const onSelectResource = (resource?: ResourceData) => {
setSelectedResource(resource);
Expand Down
16 changes: 5 additions & 11 deletions src/containers/MyNdla/Learningpath/components/TextForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,16 @@ import { Controller, useFormContext } from "react-hook-form";
import { FieldErrorMessage, FieldHelper, FieldInput, FieldLabel, FieldRoot, Spinner } from "@ndla/primitives";
import useValidationTranslation from "../../../../util/useValidationTranslation";
import FieldLength from "../../components/FieldLength";
import { FormValuesMap } from "../types";

const MarkdownEditor = lazy(() => import("../../../../components/MarkdownEditor/MarkdownEditor"));

const TITLE_MAX_LENGTH = 64;
const INTRODUCTION_MAX_LENGTH = 250;

export interface TextFormValues {
type: "text";
title: string;
introduction: string;
description: string;
}

export const TextForm = () => {
const { validationT } = useValidationTranslation();
const { setValue, control } = useFormContext<TextFormValues>();
const { setValue, control } = useFormContext<FormValuesMap["text"]>();

return (
<>
Expand All @@ -53,7 +47,7 @@ export const TextForm = () => {
<FieldLabel>{t("myNdla.learningpath.form.content.text.title.label")}</FieldLabel>
<FieldHelper>{t("myNdla.learningpath.form.content.text.title.labelHelper")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<FieldInput {...field} />
<FieldInput {...field} value={field.value ?? ""} />
<FieldLength value={field.value?.length ?? 0} maxLength={TITLE_MAX_LENGTH} />
</FieldRoot>
)}
Expand All @@ -80,7 +74,7 @@ export const TextForm = () => {
<FieldLabel>{t("myNdla.learningpath.form.content.text.introduction.label")}</FieldLabel>
<FieldHelper>{t("myNdla.learningpath.form.content.text.introduction.labelHelper")}</FieldHelper>
<FieldErrorMessage>{fieldState.error?.message}</FieldErrorMessage>
<FieldInput {...field} />
<FieldInput {...field} value={field.value ?? ""} />
<FieldLength value={field?.value?.length ?? 0} maxLength={INTRODUCTION_MAX_LENGTH} />
</FieldRoot>
)}
Expand Down Expand Up @@ -108,7 +102,7 @@ export const TextForm = () => {
shouldDirty: true,
});
}}
initialValue={field.value}
initialValue={field.value ?? "<p></p>"}
{...field}
/>
</Suspense>
Expand Down
18 changes: 18 additions & 0 deletions src/containers/MyNdla/Learningpath/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2024-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

export interface FormValuesMap {
text: { type: "text"; title: string; introduction: string; description: string };
resource: { type: "resource"; title: string; embedUrl: string };
external: { type: "external"; title: string; url: string; introduction: string; shareable: boolean };
folder: { type: "folder"; title: string };
}

export type FormKeys = keyof FormValuesMap;

export type FormValues = FormValuesMap[keyof FormValuesMap];
46 changes: 29 additions & 17 deletions src/containers/MyNdla/Learningpath/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/

import config from "../../../config";
import { FormType, FormValues } from "./components/LearningpathStepForm";
import { GQLMyNdlaLearningpathStepFragment } from "../../../graphqlTypes";
import { FormValuesMap, FormValues, FormKeys } from "./types";

export const sharedLearningpathLink = (id: number) => `${config.ndlaFrontendDomain}/learningpath/${id}`;

Expand All @@ -19,7 +19,7 @@ export const LEARNINGPATH_SHARED = "UNLISTED";
export const LEARNINGPATH_PRIVATE = "PRIVATE";
export const LEARNINGPATH_READY_FOR_SHARING = "READY_FOR_SHARING";

export const getFormTypeFromStep = (step?: GQLMyNdlaLearningpathStepFragment) => {
export const getFormTypeFromStep = (step?: GQLMyNdlaLearningpathStepFragment): FormKeys | undefined => {
if (!step?.resource && !step?.oembed && !step?.embedUrl) {
return "text";
}
Expand All @@ -34,22 +34,30 @@ export const getFormTypeFromStep = (step?: GQLMyNdlaLearningpathStepFragment) =>
return undefined;
};

export const formValues: (type?: FormType, step?: GQLMyNdlaLearningpathStepFragment) => Partial<FormValues> = (
type?: FormType,
export const toFormValues = <T extends FormKeys>(
type: T,
step?: GQLMyNdlaLearningpathStepFragment,
) => ({
type: type ?? "",
title: step?.title ?? "",
introduction: step?.introduction ?? "",
embedUrl: step?.embedUrl?.url ?? "",
shareable: !!step?.embedUrl?.url,
description: step?.description ?? "<p></p>",
});

export const getValuesFromStep = (type: FormType, step?: GQLMyNdlaLearningpathStepFragment) => {
const formType = getFormTypeFromStep(step);
const isInitialType = formType === type;
return formValues(type, isInitialType ? step : undefined);
): FormValuesMap[T] => {
const baseValues = { title: step?.title ?? "" };
return (
{
text: {
...baseValues,
type: "text",
introduction: step?.introduction ?? "",
description: step?.description ?? "",
},
resource: { ...baseValues, type: "resource", embedUrl: step?.embedUrl?.url ?? "" },
external: {
...baseValues,
type: "external",
url: step?.embedUrl?.url ?? "",
introduction: step?.introduction ?? "",
shareable: !!step?.embedUrl?.url,
},
folder: { ...baseValues, type: "folder" },
} as const
)[type];
};

export const formValuesToGQLInput = (values: FormValues) => {
Expand All @@ -73,6 +81,10 @@ export const formValuesToGQLInput = (values: FormValues) => {
},
};
}
if (values.type === "folder") {
// TODO: Implement once folder form is added
return { type: "TEXT", title: values.title };
}

return {
type: "TEXT",
Expand Down

0 comments on commit 8dafad1

Please sign in to comment.