Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4c1cfb3
fix(AgreementForm): set tokenUnitPrice to 1 when switching to non-tok…
iDome89 May 21, 2025
eba93aa
Merge pull request #178 from AppQuality/UN-1203
iDome89 Jun 10, 2025
f3ed8e1
feat(CampaignForm): add gender requirements field to campaign form
iacopolea Jun 11, 2025
1393ae0
WIP(GenderRequirements): correct gender requirements state handling i…
iacopolea Jun 11, 2025
ee1fde8
feat(FormProvider): update genderRequirements to require options array
Kariamos Jun 11, 2025
4018bce
feat(CampaignForm): update question to specify gender requirements
Kariamos Jun 11, 2025
62f4530
feat(GenderRequirements): update gender options and improve structure
Kariamos Jun 11, 2025
8064a7d
refactor(FormProvider): remove unused import and comments
Kariamos Jun 11, 2025
7eb6e8e
feat(GenderRequirements): update gender options to use descriptive va…
Kariamos Jun 12, 2025
7185eb7
chore: updated api and schema
Kariamos Jun 12, 2025
cd1eb75
feat(FormProvider): update gender requirements handling and validation
Kariamos Jun 12, 2025
aef9c7e
feat(schema): update visibility criteria structure and types
Kariamos Jun 12, 2025
905bfe7
feat(FormProvider): update genderRequirements to use numeric options
Kariamos Jun 12, 2025
570eb62
feat(GenderRequirements): update gender options to use numeric values
Kariamos Jun 12, 2025
53ac87e
refactor(GenderRequirements): improve styling and structure of gender…
d-beezee Jun 12, 2025
305f598
Merge pull request #179 from AppQuality/UN-1478-gender-distribution
d-beezee Jun 12, 2025
a7303ec
feat: Add basic cuf structure
d-beezee Jun 12, 2025
47e55f1
feat(CufCriteria): implement dynamic handling of custom user fields a…
d-beezee Jun 12, 2025
f291521
feat(CufCriteria): add error handling for CUF selection and improve l…
d-beezee Jun 12, 2025
26b1eda
feat(CufCriteria): add onBlur handling to set field touched for selec…
d-beezee Jun 12, 2025
6f6d8ad
Merge pull request #180 from AppQuality/UN-1481
d-beezee Jun 12, 2025
71d8cef
feat(campaignForm): add age requirements field and integrate into form
Kariamos Jun 12, 2025
99a3a53
wip(agerequirements): added agerequirements component
Kariamos Jun 12, 2025
68bcb09
feat(campaignForm): add age requirements handling in form validation
Kariamos Jun 12, 2025
ba6d14b
feat(campaignForm): refactor age requirements handling with FieldArray
Kariamos Jun 12, 2025
894c0f6
style(campaignForm): improve styling of age requirements component
Kariamos Jun 13, 2025
b5516f8
style(campaignForm): adjust button size for adding age range
Kariamos Jun 13, 2025
e8c9219
style(AgeRequirements): fix gap styling in age requirements component
Kariamos Jun 13, 2025
c481422
Merge pull request #181 from AppQuality/UN-1485-age-requirement
d-beezee Jun 13, 2025
7aa3e72
feat(AgeRequirements): refactor age input handling and improve error …
d-beezee Jun 13, 2025
c95acc1
feat(AgeRequirements): set minimum age to 14 for age input fields
d-beezee Jun 16, 2025
737c042
refactor(GenderRequirements): remove customerChoice field and related…
d-beezee Jun 16, 2025
63c8815
chore: add comuni-province-regioni package version 0.4.3 to yarn.lock
d-beezee Jun 16, 2025
f6db4fe
chore: Update api
d-beezee Jun 16, 2025
f99e384
feat(ProvinceSelect): add province selection field to campaign form
d-beezee Jun 16, 2025
e4781cb
Merge pull request #182 from AppQuality/add-province-criteria
d-beezee Jun 17, 2025
9355ce9
feat(CampaignForm): move additional requirements textarea
d-beezee Jun 18, 2025
1bbaf81
fix(AgeRequirements): update minimum age requirement from 14 to 16
sinatragianpaolo Jun 18, 2025
0a646c6
feat(FormProvider): add validation for country selection based on pro…
sinatragianpaolo Jun 18, 2025
e9aeed9
fix(FormProvider): improve validation message for province selection …
sinatragianpaolo Jun 18, 2025
7f56a4a
fix(FormProvider): update minimum age requirement to 16 and improve f…
d-beezee Jun 18, 2025
9efe48c
Merge pull request #184 from AppQuality/fix-province-with-country-no-…
d-beezee Jun 18, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@zendeskgarden/react-dropdowns": "^8.75.0",
"@zendeskgarden/react-theming": "^8.75.0",
"analytics": "^0.8.1",
"comuni-province-regioni": "^0.4.3",
"husky": "^7.0.4",
"i18n-iso-countries": "^7.11.0",
"lzutf8": "^0.6.0",
Expand Down
2 changes: 2 additions & 0 deletions src/pages/agreements/components/AgreementForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const AgreementForm = ({ agreement, onSubmit }: AgreementFormProps) => {
field.onChange(e);
// if agreement change to not token based, calculate number of tokens based on amount
if (e.target.checked === false) {
form.setFieldValue("tokenUnitPrice", 1, true);
form.setFieldValue(
"tokens",
roundNumberTwoDecimals(
Expand All @@ -118,6 +119,7 @@ const AgreementForm = ({ agreement, onSubmit }: AgreementFormProps) => {
<FormLabel htmlFor={field.name} label="Token Unit Price" />
<div className="input-group">
<Input
disabled={!isTokenBased}
value={field.value}
id={field.name}
type="number"
Expand Down
137 changes: 136 additions & 1 deletion src/pages/campaigns/components/campaignForm/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "src/services/tryberApi";
import { useAppDispatch } from "src/store";
import * as yup from "yup";
import { useAllFieldsByCuf } from "./fields/CufCriteria";
import { dateTimeToISO, formatDate, formatTime } from "./formatDate";
import { getPm, getResearcher, getTl } from "./getAssistantIdByRole";

Expand Down Expand Up @@ -50,13 +51,48 @@ export interface NewCampaignValues {
deviceRequirements?: string;
targetNotes?: string;
targetSize?: string;
genderRequirements?: {
options: number[];
};
ageRequirements?: {
min: number;
max: number;
}[];
targetCap?: string;
checkboxCap?: boolean;
browsersList?: string[];
productType?: string;
notes?: string;
cuf?: { id: string; value: string[] }[];
provinces?: string[];
}

const useGetInitialCufCriteria = ({
dossier,
}: {
dossier: FormProviderInterface["dossier"];
}) => {
const getAllFieldsByCuf = useAllFieldsByCuf();
if (!dossier) return [];
if (!dossier.visibilityCriteria || !dossier.visibilityCriteria.cuf) {
return [];
}

return dossier.visibilityCriteria.cuf.map((cuf) => {
const cufFields = getAllFieldsByCuf(cuf.cufId);
if (cufFields.every((field) => cuf.cufValueIds.includes(field.id))) {
return {
id: cuf.cufId.toString(),
value: ["-1"],
};
}
return {
id: cuf.cufId.toString(),
value: cuf.cufValueIds.map((value) => value.toString()),
};
});
};

const FormProvider = ({
children,
dossier,
Expand All @@ -67,10 +103,12 @@ const FormProvider = ({
const history = useHistory();
const [postDossiers] = usePostDossiersMutation();
const [putDossiers] = usePutDossiersByCampaignMutation();
const getAllFieldsByCuf = useAllFieldsByCuf();
const { data, isLoading } = useGetUsersMeQuery({ fields: "id" });
const { data: devices } = useGetDevicesByDeviceTypeOperatingSystemsQuery({
deviceType: "all",
});
const initialCufCriteria = useGetInitialCufCriteria({ dossier });
const selectedTypes = useMemo(() => {
if (!dossier) return ["Smartphone", "PC"];

Expand Down Expand Up @@ -139,6 +177,16 @@ const FormProvider = ({
dossier?.browsers?.map((browser) => browser.id.toString()) || [],
productType: dossier?.productType?.id.toString() || "",
notes: dossier?.notes || "",
genderRequirements: {
options: dossier?.visibilityCriteria?.gender || [],
},
ageRequirements:
dossier?.visibilityCriteria?.ageRanges?.map((r) => ({
min: r.min,
max: r.max,
})) || [],
cuf: initialCufCriteria,
provinces: dossier?.visibilityCriteria?.province || [],
};

const validationSchema = yup.object({
Expand Down Expand Up @@ -193,10 +241,62 @@ const FormProvider = ({
return true;
}
),
countries: yup.array(),
genderRequirements: yup.object().shape({
options: yup.array().of(yup.number().oneOf([-1, 0, 1, 2])),
}),
countries: yup
.array()
.test(
"contry-no-italy-with-provinces",
"You cannot select an Italian province if you have chosen another country. Please modify your selection to find available testers",
function (value) {
const { provinces } = this.parent;
if (
value &&
value?.length > 0 &&
provinces.length > 0 &&
(value.length > 1 || value[0] !== "IT")
)
return false;
return true;
}
),
languages: yup.array(),
targetNotes: yup.string(),
notes: yup.string(),
cuf: yup.array().of(
yup.object().shape({
id: yup
.number()
.required("CUF ID is required")
.min(1, "CUF ID is required"),
value: yup.array().min(1, "Almeno un elemento è richiesto"),
})
),
ageRequirements: yup.array().of(
yup.object().shape({
min: yup
.number()
.typeError("Min age must be a number")
.min(16, "Min age must be at least 16")
.nullable(),
max: yup
.number()
.typeError("Max age must be a number")
.nullable()
.test(
"is-greater-or-equal",
"Max age must be greater than min age",
function (value) {
const { min } = this.parent;
if (min === null || min === "" || value === null || !value)
return true;
return value >= min;
}
),
})
),
provinces: yup.array().of(yup.string()),
});
return (
<Formik
Expand Down Expand Up @@ -254,6 +354,41 @@ const FormProvider = ({
? parseInt(values.productType, 10)
: undefined,
notes: values.notes,
visibilityCriteria: {
gender: values.genderRequirements?.options || [],
cuf: values.cuf
? values.cuf.map((cuf) => {
if (cuf.value.includes("-1")) {
return {
cufId: parseInt(cuf.id, 10),
cufValueIds: getAllFieldsByCuf(Number(cuf.id)).map(
(field) => field.id
),
};
}

return {
cufId: Number(cuf.id),
cufValueIds: cuf.value.map((value) => Number(value)),
};
})
: [],
ageRanges: values.ageRequirements
? values.ageRequirements
.filter(
(age) =>
age.min !== null &&
age.min !== undefined &&
age.max !== null &&
age.max !== undefined
)
.map((age) => ({
min: Number(age.min),
max: Number(age.max),
}))
: [],
provinces: values.provinces || [],
},
};

if (isEdit) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
Button,
ErrorMessage,
Input,
Text,
} from "@appquality/appquality-design-system";
import {
Field as FormikField,
FieldArray,
FieldProps,
useFormikContext,
} from "formik";
import { ReactComponent as DeleteIcon } from "src/assets/trash.svg";
import styled from "styled-components";
import { NewCampaignValues } from "../FormProvider";

const StyledRow = styled.div`
.container {
display: flex;
gap: ${({ theme }) => theme.grid.sizes[4]};
align-items: center;
flex-direction: row;
}
margin-bottom: ${({ theme }) => theme.grid.sizes[3]};
`;

const AgeRequirements = () => {
const { values, errors, touched } = useFormikContext<NewCampaignValues>();
const ageRanges: NonNullable<NewCampaignValues["ageRequirements"]> =
values.ageRequirements || [];

return (
<FieldArray name="ageRequirements">
{({ remove, push }) => (
<>
{ageRanges.map((age, index) => (
<StyledRow key={index}>
<div className="container">
<FormikField name={`ageRequirements[${index}].min`}>
{({ form, field }: FieldProps) => (
<>
<Text style={{ fontWeight: "bold" }}>From</Text>
<Input
id={field.name}
type="number"
extra={{
min: 16,
max: age.max || undefined,
onBlur: () => form.setFieldTouched(field.name),
}}
value={field.value || ""}
onChange={(val) => form.setFieldValue(field.name, val)}
placeholder="Min"
/>
</>
)}
</FormikField>
<FormikField name={`ageRequirements[${index}].max`}>
{({ form, field }: FieldProps) => (
<>
<Text style={{ fontWeight: "bold" }}>to</Text>
<Input
id={field.name}
type="number"
extra={{
min: age.min || 14,
onBlur: () => form.setFieldTouched(field.name),
}}
value={field.value || ""}
onChange={(val) => form.setFieldValue(field.name, val)}
placeholder="Max"
/>
</>
)}
</FormikField>
<Button size="sm" kind="danger" onClick={() => remove(index)}>
<DeleteIcon />
</Button>
</div>
<div>
<ErrorMessage name={`ageRequirements[${index}].min`} />
<ErrorMessage name={`ageRequirements[${index}].max`} />
</div>
</StyledRow>
))}
<Button
size="sm"
kind="secondary"
onClick={() => push({ min: "", max: "" })}
>
+ Add age range
</Button>
</>
)}
</FieldArray>
);
};

export default AgeRequirements;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
FormGroup,
FormLabel,
} from "@appquality/appquality-design-system";
import { FieldProps, Field as FormikField, useFormikContext } from "formik";
import { Field as FormikField, FieldProps, useFormikContext } from "formik";
import countryList from "i18n-iso-countries";
import { useCallback, useMemo } from "react";
import { NewCampaignValues } from "../FormProvider";
Expand Down Expand Up @@ -71,11 +71,12 @@ const CountrySelect = () => {

return (
<FormikField name="countries">
{({ field }: FieldProps<NewCampaignValues["countries"]>) => (
{({ field, form }: FieldProps<NewCampaignValues["countries"]>) => (
<FormGroup>
<FormLabel htmlFor={field.name} label="Countries" />
<Dropdown
isMulti
onBlur={() => form.setFieldTouched(field.name, true)}
name={field.name}
id={field.name}
options={getOptions()}
Expand Down
Loading
Loading