Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8856cd6
changeset
dcaccamo3ainformatica Sep 8, 2025
e1040c1
This feature allows us to manage dual CTAs when creating/editing a se…
dcaccamo3ainformatica Sep 26, 2025
696bda1
clean dirty code
dcaccamo3ainformatica Sep 26, 2025
86a85bf
cleaned code and restored test adparters
dcaccamo3ainformatica Sep 27, 2025
a788665
create component cta-manager and add new test case
dcaccamo3ainformatica Sep 28, 2025
7f46072
rename a const in cta manager
dcaccamo3ainformatica Sep 28, 2025
5458e35
clean commented line
dcaccamo3ainformatica Sep 29, 2025
35b826d
The second CTA will be inserted into a single button block with a link.
dcaccamo3ainformatica Sep 29, 2025
6085977
update some const name
dcaccamo3ainformatica Sep 29, 2025
fb8f046
adding markdown parsing on form step section wrapper and service extr…
dcaccamo3ainformatica Sep 30, 2025
8d18988
add missing markdown text on common.json en
dcaccamo3ainformatica Sep 30, 2025
14240eb
add Box for insert cta2_section and fix alignment
dcaccamo3ainformatica Sep 30, 2025
02f8204
removed unnecessary check and fix some bug about <p> nested
dcaccamo3ainformatica Sep 30, 2025
4bd1d91
create component button add remove
dcaccamo3ainformatica Oct 1, 2025
a94705a
improved code readability
dcaccamo3ainformatica Oct 5, 2025
3531379
improved code readability and cleanliness
dcaccamo3ainformatica Oct 6, 2025
573d444
Improved code readability in adapters
dcaccamo3ainformatica Oct 6, 2025
1333e39
improvement of the cta object construction function
dcaccamo3ainformatica Oct 6, 2025
d6fef96
renamed a const
dcaccamo3ainformatica Oct 6, 2025
e0e85fe
created constants.ts and improvement on add and remove secondary cta
dcaccamo3ainformatica Oct 6, 2025
ffc5eb5
fixed build fail problem
dcaccamo3ainformatica Oct 6, 2025
082826d
improved readability and code cleanliness
dcaccamo3ainformatica Oct 7, 2025
a8a0e6a
improvement of logic and names
dcaccamo3ainformatica Oct 7, 2025
38049c3
logic improvements
dcaccamo3ainformatica Oct 7, 2025
01a4a71
deleted border rule on banner
dcaccamo3ainformatica Oct 7, 2025
4f7c2a4
unnecessary ternary operators removed
dcaccamo3ainformatica Oct 7, 2025
bd79f3c
updated graphical interface after design review
dcaccamo3ainformatica Oct 8, 2025
6b12c2f
updated email support with a placeholder
dcaccamo3ainformatica Oct 8, 2025
e63f51a
restored banner element
dcaccamo3ainformatica Oct 8, 2025
3e4a732
'added official mail support for double CTA'S
dcaccamo3ainformatica Oct 10, 2025
94e83a2
removal of markdown parsing from the form-step-section-wrapper component
dcaccamo3ainformatica Oct 11, 2025
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
5 changes: 5 additions & 0 deletions .changeset/nasty-teachers-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"io-services-cms-backoffice": minor
---

Added secondary CTA and improved CTA management for created/modified services
12 changes: 11 additions & 1 deletion apps/backoffice/mocks/data/backend-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export const aMockServiceCTADouble = `---\nit:\n cta_1: \n text: \"${faker.l
2,
)}\"\n action: \"iohandledlink://${faker.internet.url()}\"\n---`;

export const aMockServiceCTADoubleDifferentLink = `---\nit:\n cta_1: \n text: \"${faker.lorem.words(
2,
)}\"\n action: \"ioit://${faker.internet.url()}"\n cta_2: \n text: \"${faker.lorem.words(
2,
)}\"\n action: \"iosso://${faker.internet.url()}\"\nen:\n cta_1: \n text: \"${faker.lorem.words(
2,
)}\"\n action: \"ioit://${faker.internet.url()}\"\n cta_2: \n text: \"${faker.lorem.words(
2,
)}\"\n action: \"iosso://${faker.internet.url()}\"\n---`;

export const getMockServiceLifecycle = (serviceId?: string) => ({
authorized_cidrs: [
...Array.from(Array(faker.number.int({ max: 5, min: 1 })).keys()),
Expand Down Expand Up @@ -139,7 +149,7 @@ export const getMockServiceLifecycle = (serviceId?: string) => ({
category: faker.helpers.arrayElement(["SPECIAL", "STANDARD"]),
cta: faker.helpers.arrayElement([
aMockServiceCTASingle,
aMockServiceCTADouble,
aMockServiceCTADoubleDifferentLink,
undefined,
]),
custom_special_flow: faker.lorem.slug(1),
Expand Down
21 changes: 17 additions & 4 deletions apps/backoffice/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,20 @@
"description": "These features allow you to enrich the service with many additional parameters. Some of these require the signing of an ad hoc contract.",
"arriving": "Arriving",
"cta": {
"label": "Button with external link",
"description": "Add a button that takes you to a web page with more information about the service."
"label": "Button with link",
"description": "Add one or two buttons to the service tab to direct citizens to an external link, an internal link, or directly to a post-login area thanks to Single Sign-On. Find out more",
"addSecondaryButton": "add secondary button",
"removeSecondaryButton": "remove secondary button",
"form": {
"selectLabel": "Button type",
"externalLink": "External link to the app",
"singleSignOn": "Single Sign-on Link",
"internalLink": "Internal link within the app (deeplink)",
"placeholder": "",
"btnWithLink": {
"alertSingleSignOn": "To make the button with the link work in Single Sign-on, you must ensure that you have completed the integration into the institution's systems [Read here for technical specifications](https://docs.pagopa.it/manuale-servizi/come-si-crea-un-servizio/la-scheda-servizio/nome-del-servizio)"
}
}
},
"fims": {
"label": "FIMS Protocol",
Expand Down Expand Up @@ -173,13 +185,14 @@
},
"cta": {
"text": {
"helperText": "",
"helperText": "<a style=\"color: #0073E6;\" href=\"https://google.com\" target=\"_blank\">Read</a> the technical specifications here",
"label": "Button label",
"placeholder": ""
},
"url": {
"helperText": "",
"label": "External URL",
"labelInternal": "internal URL",
"placeholder": ""
}
},
Expand Down Expand Up @@ -673,4 +686,4 @@
}
},
"undefined": "Not defined"
}
}
21 changes: 17 additions & 4 deletions apps/backoffice/public/locales/it/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,20 @@
"description": "Queste funzionalità ti permettono di arricchire il servizio con tanti parametri aggiuntivi. Alcuni di questi richiedono la firma di un contratto ad hoc.",
"arriving": "In arrivo",
"cta": {
"label": "Pulsante con link esterno",
"description": "Aggiungi un pulsante che rimanda ad una pagina web con più informazioni sul servizio."
"label": "Pulsante con link",
"description": "Aggiungi uno o due pulsanti alla scheda servizio, per inidirizzare il cittadino ad un link esterno, interno o direttamente ad un’area di post-login grazie al Single Sign-On. Scopri di più",
"addSecondaryButton": "aggiungi pulsante secondario",
"removeSecondaryButton": "rimuovi pulsante secondario",
"form": {
"selectLabel": "Tipo di pulsante",
"externalLink": "Link esterno all'app",
"singleSignOn": "Link in single Sign-on",
"internalLink": "Link interno all'app (deeplink)",
"placeholder": "",
"btnWithLink": {
"alertSingleSignOn": "Per far funzionare il pulsante con il link in Single Sign-on, devi assicurarti di aver completato l'integrazione nei sistemi dell'ente. [Leggi qui per le specifiche tecniche](https://docs.pagopa.it/manuale-servizi/come-si-crea-un-servizio/la-scheda-servizio/nome-del-servizio)."
}
}
},
"fims": {
"label": "Protocollo FIMS",
Expand Down Expand Up @@ -173,13 +185,14 @@
},
"cta": {
"text": {
"helperText": "",
"helperText": "<a style=\"color: #0073E6;\" href=\"https://google.com\" target=\"_blank\">Leggi quì</a> le specifiche tecniche ",
"label": "Etichetta del pulsante",
"placeholder": ""
},
"url": {
"helperText": "",
"label": "URL esterna",
"labelInternal": "URL interna",
"placeholder": ""
}
},
Expand Down Expand Up @@ -673,4 +686,4 @@
}
},
"undefined": "Non definito"
}
}
16 changes: 14 additions & 2 deletions apps/backoffice/src/components/banner/banner.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { Alert, AlertColor, AlertTitle } from "@mui/material";
import { ReactElement } from "react";

import { MarkdownView } from "../markdown-view";

export interface BannerProps {
description?: string;
icon?: ReactElement;
severity: AlertColor;
title?: string;
}

export const Banner = ({ description, severity, title }: BannerProps) => (
export const Banner = ({ description, icon, severity, title }: BannerProps) => (
<Alert
className="banner"
icon={icon}
severity={severity}
sx={{ marginBottom: 1.5 }}
sx={{
// fix for disable an vertical line of border-color #FFCB46;
"&.MuiPaper-outlined, &.MuiAlert-outlined": { border: "none !important" },
"&::before": { display: "none" },
backgroundImage: "none",
// take into 0 any border
border: "none !important",
borderLeft: "none !important",
mb: 1.5,
}}
variant="standard"
>
<AlertTitle>{title}</AlertTitle>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AddCircleOutline, RemoveCircleOutline } from "@mui/icons-material";
import { Button, Grid } from "@mui/material";
import React from "react";

export interface CtaControlsRowProps {
addLabel: string;
initialStateRemove?: boolean;
onAdd?: () => void;
onRemove?: () => void;
removeLabel: string;
show?: boolean;
}

export const CtaControlsButtons: React.FC<CtaControlsRowProps> = ({
addLabel,
initialStateRemove = false,
onAdd,
onRemove,
removeLabel,
show = true,
}) => {
if (!show) return null;

return (
<>
<Grid item md={6} xs={12}>
{initialStateRemove ? (
<Button
color="error"
onClick={onRemove}
startIcon={<RemoveCircleOutline />}
variant="text"
>
{removeLabel}
</Button>
) : (
<Button
color="primary"
onClick={onAdd}
startIcon={<AddCircleOutline />}
variant="text"
>
{addLabel}
</Button>
)}
</Grid>
<Grid item md={6} xs={12} />
</>
);
};

export default CtaControlsButtons;
154 changes: 154 additions & 0 deletions apps/backoffice/src/components/cta-manager/service-cta-manager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Banner } from "@/components/banner";
import { FormStepSectionWrapper } from "@/components/forms";
import {
SelectController,
TextFieldController,
UrlFieldController,
} from "@/components/forms/controllers";
import { Link, ReportProblemRounded } from "@mui/icons-material";
import { Box, Divider, Grid } from "@mui/material";
import { useTranslation } from "next-i18next";
import React from "react";
import { useFormContext } from "react-hook-form";

import CtaControlsButtons from "./cta-controls-buttons";

export const ServiceCtaManager: React.FC = () => {
const { t } = useTranslation();
const { clearErrors, setValue, watch } = useFormContext();

const hasCta2PreUrl = watch("metadata.cta.cta_2.preUrl");
const initialStateBtn = hasCta2PreUrl !== "" ? true : false;
const selectItems = [
{
label: t("forms.service.extraConfig.cta.form.externalLink"),
value: "iohandledlink://",
},
{
label: t("forms.service.extraConfig.cta.form.singleSignOn"),
value: "iosso://",
},
{
label: t("forms.service.extraConfig.cta.form.internalLink"),
value: "ioit://",
},
];

const linkCtaType = ["iohandledlink://", "iosso://", "ioit://"];

const renderCtaSection = (slot: "cta_1" | "cta_2") => {
//let us observe the value of the select stored referring to the current slot
const kind = watch(`metadata.cta.${slot}.preUrl`);

const helperCtaInternal =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the need to do a kind check in the const declaration rather than in the component logic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind === linkCtaType[2] ? (
<span
dangerouslySetInnerHTML={{
__html: t("forms.service.metadata.cta.text.helperText"),
}}
/>
) : undefined;

const labelCtaInternal =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question as helperCtaInternal

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind === linkCtaType[2]
? t("forms.service.metadata.cta.url.labelInternal")
: t("forms.service.metadata.cta.url.label");

return (
<>
<Grid columnSpacing={2} container rowSpacing={1}>
<Grid item md={6} xs={12}>
<SelectController
displayEmpty
helperText={helperCtaInternal}
items={selectItems}
label={t("forms.service.extraConfig.cta.form.selectLabel")}
name={`metadata.cta.${slot}.preUrl`}
/>
</Grid>

<Grid item md={6} xs={12} />

{kind === linkCtaType[1] && (
<Grid item xs={12}>
<Banner
description={t(
"forms.service.extraConfig.cta.form.btnWithLink.alertSingleSignOn",
)}
icon={<ReportProblemRounded />}
severity="warning"
/>
</Grid>
)}

<Grid item md={6} xs={12}>
<TextFieldController
label={t("forms.service.metadata.cta.text.label")}
name={`metadata.cta.${slot}.text`}
placeholder={t("forms.service.metadata.cta.text.placeholder")}
/>
</Grid>

<Grid item md={6} xs={12}>
<UrlFieldController
hideCheckUrl={kind === linkCtaType[0] ? false : true}
label={labelCtaInternal}
name={`metadata.cta.${slot}.url`}
placeholder={t("forms.service.metadata.cta.url.placeholder")}
/>
</Grid>
{
<CtaControlsButtons
addLabel={t("forms.service.extraConfig.cta.addSecondaryButton")}
initialStateRemove={initialStateBtn}
onAdd={addSecondary}
onRemove={removeSecondary}
removeLabel={t(
"forms.service.extraConfig.cta.removeSecondaryButton",
)}
show={slot === "cta_1" && hasCta2PreUrl !== "" ? false : true}
/>
}
</Grid>
</>
);
};

const addSecondary = () => {
setValue(
"metadata.cta.cta_2",
{
preUrl: "iohandledlink://",
text: "",
url: "",
},
{ shouldDirty: true, shouldValidate: true },
);
};

const removeSecondary = () => {
setValue("metadata.cta.cta_2.preUrl", "");
setValue("metadata.cta.cta_2.text", "");
setValue("metadata.cta.cta_2.url", "");
clearErrors(["metadata.cta.cta_2"]);
};
return (
<Box>
<FormStepSectionWrapper
description={t("forms.service.extraConfig.cta.description")}
icon={<Link />}
title={t("forms.service.extraConfig.cta.label")}
>
{renderCtaSection("cta_1")}
{hasCta2PreUrl !== "" ? (
<Box mt={2}>
<Divider />
{renderCtaSection("cta_2")}
</Box>
) : null}
</FormStepSectionWrapper>
</Box>
);
};

export default ServiceCtaManager;
Loading
Loading