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(dsfr): ajout du composant "Avez-vous trouvé une réponse à votre question" #6121

Merged
merged 18 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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 @@ -3,6 +3,7 @@ import { fr } from "@codegouvfr/react-dsfr";
import { Tag } from "@codegouvfr/react-dsfr/Tag";
import Html from "../common/Html";
import { ContainerRich } from "../layout/ContainerRich";
import { Feedback } from "../layout/feedback";

type Props = {
metaDescription: string;
Expand Down Expand Up @@ -46,11 +47,12 @@ function ArticleCodeDuTravail({
<Html>{html}</Html>
</div>
{notaHtml && (
<div className={fr.cx("fr-highlight","fr-mb-5w")}>
<div className={fr.cx("fr-highlight", "fr-mb-5w")}>
<p>NOTA</p>
<Html>{notaHtml}</Html>
</div>
)}
<Feedback />
</ContainerRich>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { fr } from "@codegouvfr/react-dsfr";

export const FeedbackAnswered = () => {
return (
<>
<h2 className={fr.cx("fr-h5")}>Merci pour votre réponse.</h2>
<p>
L’équipe du Code du travail numérique vous remercie pour votre réponse.
</p>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { fr } from "@codegouvfr/react-dsfr";
import { Button } from "@codegouvfr/react-dsfr/Button";
import { Checkbox } from "@codegouvfr/react-dsfr/Checkbox";
import { Input } from "@codegouvfr/react-dsfr/Input";
import { css } from "../../../../styled-system/css";
import { MatomoFeedbackEventCategory } from "./tracking";
import { useState } from "react";

type Props = {
onSubmit: (data: FeedbackDataSent) => void;
type: "positive" | "negative";
};

export type FeedbackDataSent = {
suggestion?: string;
categories?: MatomoFeedbackEventCategory[];
};

const MAX_LENGTH = 150;

export const FeedbackContent = (props: Props) => {
const [categories, setCategories] = useState<MatomoFeedbackEventCategory[]>(
[]
);
const [suggestion, setSuggestion] = useState<string | undefined>(undefined);

const onChangeCategories = (e: React.ChangeEvent<HTMLInputElement>) => {
setCategories((prev) => {
if (e.target.checked) {
return [...prev, e.target.value as MatomoFeedbackEventCategory];
}
return prev.filter((category) => category !== e.target.value);
});
};

const onChangeSuggestion = (value: string) => {
if (value.trim().length > MAX_LENGTH) {
return;
} else {
setSuggestion(value);
}
};

const onLocalSubmit = () => {
if (
(!suggestion || suggestion.trim().length <= 0) &&
categories.length === 0
) {
alert("Veuillez renseigner au minimum un champ");
return;
}
props.onSubmit({
categories,
suggestion,
});
};

return (
<>
<h2 className={fr.cx("fr-h5")}>Merci pour votre réponse.</h2>
{props.type === "negative" && (
<Checkbox
legend="Pouvez-vous nous en dire plus ?"
options={[
{
label: "Les informations ne sont pas claires",
nativeInputProps: {
name: "checkboxes-1",
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
value: "unclear" as MatomoFeedbackEventCategory,
onChange: onChangeCategories,
},
},
{
label:
"Cette page ne correspond pas à ma recherche ou à ma situation.",
nativeInputProps: {
name: "checkboxes-2",
value: "unrelated" as MatomoFeedbackEventCategory,
onChange: onChangeCategories,
},
},
{
label: "Je ne suis pas satisfait de cette réglementation.",
nativeInputProps: {
name: "checkboxes-3",
value: "unsatisfied" as MatomoFeedbackEventCategory,
onChange: onChangeCategories,
},
},
{
label: "Les informations me semblent fausses.",
nativeInputProps: {
name: "checkboxes-4",
value: "wrong" as MatomoFeedbackEventCategory,
onChange: onChangeCategories,
},
},
]}
/>
)}
<Input
label="Faire une suggestion pour améliorer cette page"
textArea={true}
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
nativeTextAreaProps={{
onChange: (e) => onChangeSuggestion(e.target.value),
value: suggestion,
}}
/>
<p className={fr.cx("fr-info-text", "fr-mt-0", "fr-mb-3w")}>
{`${MAX_LENGTH} caractères maximum`}
</p>
<Button type="button" priority="secondary" onClick={onLocalSubmit}>
Envoyer
</Button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { fr } from "@codegouvfr/react-dsfr";
import { Button } from "@codegouvfr/react-dsfr/Button";

type Props = {
onClickNo: () => void;
onClickYes: () => void;
};

export const FeedbackDefault = (props: Props) => {
return (
<div className="fr-grid-row">
<div className="fr-col-12 fr-col-md">
<h2 className={fr.cx("fr-h5", "fr-mb-md-0")}>
Avez-vous trouvé la réponse à votre question ?
</h2>
</div>
<div>
<Button
type="button"
priority="secondary"
className={fr.cx("fr-mr-2w")}
onClick={props.onClickNo}
>
Non
</Button>
<Button type="button" priority="secondary" onClick={props.onClickYes}>
Oui
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";

import { fr } from "@codegouvfr/react-dsfr";
import { useState } from "react";
import { FeedbackDefault } from "./FeedbackDefault";
import { FeedbackContent, FeedbackDataSent } from "./FeedbackContent";
import { FeedbackAnswered } from "./FeedbackAnswered";
import { useFeedbackEvents } from "./tracking";
import { usePathname } from "next/navigation";
import { getBaseUrl } from "../../utils";

export const Feedback = () => {
const [viewFeedback, setViewFeedback] = useState<
"yes" | "no" | "default" | "answered"
>("default");
const currentPath = usePathname();
const { emitFeedback, emitFeedbackCategory, emitFeedbackSuggestion } =
useFeedbackEvents();

const onClickNo = () => {
setViewFeedback("no");
if (currentPath) emitFeedback(false, getBaseUrl(currentPath));
};

const onClickYes = () => {
setViewFeedback("yes");
if (currentPath) emitFeedback(true, getBaseUrl(currentPath));
};

const onSubmit = (data: FeedbackDataSent) => {
if (currentPath && data.suggestion)
emitFeedbackSuggestion(data.suggestion, getBaseUrl(currentPath));
if (data.categories && currentPath) {
data.categories.forEach((category) =>
emitFeedbackCategory(category, getBaseUrl(currentPath))
);
}
setViewFeedback("answered");
};

return (
<div className={fr.cx("fr-highlight", "fr-p-2w", "fr-m-0")}>
{viewFeedback === "default" && (
<FeedbackDefault onClickNo={onClickNo} onClickYes={onClickYes} />
)}
{viewFeedback === "no" && (
<FeedbackContent onSubmit={onSubmit} type="negative" />
)}
{viewFeedback === "yes" && (
<FeedbackContent onSubmit={onSubmit} type="positive" />
)}
{viewFeedback === "answered" && <FeedbackAnswered />}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { push as matopush } from "@socialgouv/matomo-next";
import { MatomoBaseEvent } from "../../../lib";

export enum MatomoFeedbackEventSecondary {
FEEDBACK = "feedback",
FEEDBACK_SUGGESTION = "feedback_suggestion",
FEEDBACK_CATEGORY = "feedback_category",
}

export enum MatomoFeedbackEventTertiary {
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
POSITIVE = "positive",
NEGATIVE = "negative",
}

export type MatomoFeedbackEventCategory =
| "unclear"
| "unrelated"
| "unsatisfied"
| "wrong";

export const useFeedbackEvents = () => {
carolineBda marked this conversation as resolved.
Show resolved Hide resolved
const emitFeedback = (isPositive: boolean, baseUrl: string) => {
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
matopush([
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
MatomoBaseEvent.TRACK_EVENT,
MatomoFeedbackEventSecondary.FEEDBACK,
isPositive
? MatomoFeedbackEventTertiary.POSITIVE
: MatomoFeedbackEventTertiary.NEGATIVE,
baseUrl,
]);
};

const emitFeedbackSuggestion = (suggestion: string, baseUrl: string) => {
matopush([
MatomoBaseEvent.TRACK_EVENT,
MatomoFeedbackEventSecondary.FEEDBACK_SUGGESTION,
suggestion,
baseUrl,
]);
};

const emitFeedbackCategory = (
category: MatomoFeedbackEventCategory,
baseUrl: string
) => {
matopush([
MatomoBaseEvent.TRACK_EVENT,
MatomoFeedbackEventSecondary.FEEDBACK_CATEGORY,
category,
baseUrl,
]);
};
return { emitFeedback, emitFeedbackSuggestion, emitFeedbackCategory };
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@ export const MentionsLegales = () => (

<h3>En savoir plus</h3>
<p>
Pour en savoir plus sur la politique d’accessibilité numérique de l’État&nbsp;:{" "}
<a href="http://references.modernisation.gouv.fr/accessibilite-numerique" target="_blank">
Pour en savoir plus sur la politique d’accessibilité numérique de
l’État&nbsp;:{" "}
<a
href="http://references.modernisation.gouv.fr/accessibilite-numerique"
target="_blank"
>
http://references.modernisation.gouv.fr/accessibilite-numerique
</a>
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getBaseUrl } from "../index";

describe("getBaseUrl", () => {
it("should remove query parameters from the URL", () => {
const url = "https://example.com/page?param=value";
const result = getBaseUrl(url);
expect(result).toBe("https://example.com/page");
});

it("should return the same URL if there are no query parameters", () => {
const url = "https://example.com/page";
const result = getBaseUrl(url);
expect(result).toBe(url);
});

it("should handle URLs with multiple query parameters", () => {
const url = "https://example.com/page?param1=value1&param2=value2";
const result = getBaseUrl(url);
expect(result).toBe("https://example.com/page");
});

it("should handle URLs with hash fragments", () => {
const url = "https://example.com/page?param=value#section";
const result = getBaseUrl(url);
expect(result).toBe("https://example.com/page");
});

it("should handle URLs with no path", () => {
const url = "https://example.com?param=value";
const result = getBaseUrl(url);
expect(result).toBe("https://example.com");
});
});
3 changes: 3 additions & 0 deletions packages/code-du-travail-frontend/src/modules/utils/index.ts
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const getBaseUrl = (url: string) => {
return url.replace(/\?.*$/, "");
};
Loading