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 5 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
@@ -0,0 +1,19 @@
import { fr } from "@codegouvfr/react-dsfr";
import { css } from "../../../../styled-system/css";

export const FeedbackAnswered = () => {
return (
<div className={container}>
<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>
</div>
);
};

const container = css({
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
display: "flex",
flexDirection: "column",
width: "100%",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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 (
<div className={container}>
<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>
</div>
);
};

const container = css({
display: "flex",
flexDirection: "column",
width: "100%",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 (
<>
<h2 className={fr.cx("fr-h5", "fr-mb-0")}>
Avez-vous trouvé la réponse à votre question ?
</h2>
<div className={fr.cx("fr-ml-3w")}>
<Button
type="button"
priority="secondary"
className={fr.cx("fr-mr-3w")}
onClick={props.onClickNo}
>
Non
</Button>
<Button type="button" priority="secondary" onClick={props.onClickYes}>
Oui
</Button>
</div>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";

import { fr } from "@codegouvfr/react-dsfr";
import { css } from "../../../../styled-system/css";
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-3w"
)} ${highlightContainer}`}
>
{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>
);
};

const highlightContainer = css({
display: "flex",
alignItems: "center",
});
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 @@ -2,13 +2,17 @@ import { fr } from "@codegouvfr/react-dsfr";
import { Footer } from "./Footer";
import { Header } from "./header";
import { NeedMoreInfo } from "./NeedMoreInfo";
import { Feedback } from "./feedback";

export const DsfrLayout = ({ children }: { children: React.ReactNode }) => {
return (
<html>
<body>
<Header />
<main className={fr.cx("fr-container")}>{children}</main>
<main className={fr.cx("fr-container")}>
{children}
<Feedback />
maxgfr marked this conversation as resolved.
Show resolved Hide resolved
</main>
<NeedMoreInfo />
<Footer />
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,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
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