Skip to content

Commit

Permalink
fix(frontend): fix contact form phone
Browse files Browse the repository at this point in the history
  • Loading branch information
pYassine committed Jan 8, 2025
1 parent 9c40a1f commit e0d22aa
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 91 deletions.
95 changes: 72 additions & 23 deletions packages/backend/src/_common/decorators/IsValidPhoneDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ValidationOptions, registerDecorator } from "class-validator";
import {
ValidationArguments,
ValidationOptions,
registerDecorator,
} from "class-validator";
import { isAnyValidPhone, isValidMobilePhone } from "../../util/phone";
import { BadRequestException } from "@nestjs/common";

export function IsValidPhone(
property: string,
Expand All @@ -16,31 +21,75 @@ export function IsValidPhone(
options: validationOptions,
constraints: [property],
validator: {
validate(value: any) {
if (typeof value === "string") {
try {
value = JSON.parse(value);
} catch (e) {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
validate(value: any, _args: ValidationArguments) {
const result = validatePhoneValue(value, required, mobileOnly);
if (!result.isValid) {
throw new BadRequestException({
message: result.message.replace("{property}", propertyName),
error: "INVALID_PHONE_FORMAT",
field: propertyName,
});
}

if (!value?.numero && required) {
return false;
}

if (
typeof value.numero !== "string" ||
typeof value.countryCode !== "string"
) {
return !required;
}

return mobileOnly
? isValidMobilePhone(value)
: isAnyValidPhone(value);
return result.isValid;
},
},
});
};
}
interface ValidationResult {
isValid: boolean;
message: string;
}

function validatePhoneValue(
value: any,
required: boolean,
mobileOnly: boolean
): ValidationResult {
if (typeof value === "string") {
try {
value = JSON.parse(value);
} catch (e) {
return {
isValid: false,
message:
"Le format du numéro de téléphone pour {property} est invalide",
};
}
}

if (!value?.numero) {
return required
? {
isValid: false,
message: "Le numéro de téléphone est requis pour {property}",
}
: {
isValid: true,
message: "",
};
}

if (
typeof value.numero !== "string" ||
typeof value.countryCode !== "string"
) {
return {
isValid: !required,
message:
"Le format du numéro de téléphone est invalide pour {property}. Il doit contenir un numéro et un code pays correct",
};
}

const isValid = mobileOnly
? isValidMobilePhone(value)
: isAnyValidPhone(value);

return {
isValid,
message: mobileOnly
? "{property} doit être un numéro de téléphone mobile valide"
: "{property} doit être un numéro de téléphone valide",
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class ContactSupportController {
if (!validateUpload("STRUCTURE_DOC", req, file)) {
callback(new Error("INCORRECT_FORMAT"), false);
}

callback(null, true);
},
storage: diskStorage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,4 @@ export class ContactSupportDto {

@IsEmpty()
public attachment!: MessageEmailAttachment;

@ApiProperty({
type: "string",
format: "binary",
required: false,
})
@IsOptional()
@Transform(({ value }: TransformFnParams) => {
return value instanceof File ? value : undefined;
})
file?: Express.Multer.File;
}
1 change: 1 addition & 0 deletions packages/backend/src/util/file-manager/FileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function validateUpload(
const validFileMimeType = SUPPORTED_MIME_TYPES[uploadType].includes(
file.mimetype
);

const validFileExtension = SUPPORTED_FILE_EXTENSIONS[uploadType].includes(
extname(file.originalname).toLowerCase()
);
Expand Down
3 changes: 1 addition & 2 deletions packages/backend/src/util/file-manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@index('./*', f => `export * from '${f.path}'`)
export * from "./file-manager.service";
export * from "./FileManager";
export * from "./FILES_EXTENSIONS.const";
export * from "./FILES_MIME_TYPES.const";
export * from "./FILES_SIZE_LIMIT.const";
1 change: 0 additions & 1 deletion packages/frontend/src/_common/mocks/USAGER_VALIDE.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export const USAGER_VALIDE_MOCK: UsagerLight = {
orientation: true,
orientationDetail: "Test orientation",
situationProDetail: null,

raison: "PRESTATIONS_SOCIALES",
liencommune: "SOCIAL",
liencommuneDetail: "Suivi social",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,13 @@ <h2 class="text-success">
nom&#64;domaine.fr
</p>
</div>
<div class="col-md-6 col-12 form-group required">
<div class="col-md-6 col-12 form-group">
<label for="phone">Numéro de téléphone</label>
<ngx-intl-tel-input
[cssClass]="
submitted && (f.phone.errors || f.phone.invalid)
submitted &&
f.phone.value &&
(f.phone.errors || f.phone.invalid)
? 'form-control is-invalid'
: 'form-control'
"
Expand All @@ -170,7 +172,11 @@ <h2 class="text-success">
name="phone"
></ngx-intl-tel-input>
<p
*ngIf="submitted && (f.phone.errors || f.phone.invalid)"
*ngIf="
submitted &&
f.phone.value &&
(f.phone.errors || f.phone.invalid)
"
class="invalid-feedback-custom"
>
Numéro de téléphone incorrect (Ex: 0606060606)
Expand All @@ -191,20 +197,23 @@ <h2 class="text-success">
formControlName="subject"
subject="subject"
[ngClass]="{
'is-invalid': f.subject.dirty && f.subject.errors
'is-invalid':
(f.subject.dirty || submitted) && f.subject.errors
}"
[attr.aria-invalid]="
f.subject.dirty && f.subject.errors ? true : false
(f.subject.dirty || submitted) && f.subject.errors
? true
: false
"
[attr.aria-describedby]="
f.subject.dirty && f.subject.errors
(f.subject.dirty || submitted) && f.subject.errors
? 'invalid-subject'
: 'subject-help'
"
required
/>
<small
*ngIf="!f.subject.dirty && !f.subject.errors"
*ngIf="!(f.subject.dirty || submitted) && !f.subject.errors"
id="subject-help"
>2 caractères minimum</small
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,7 @@ export class ContactSupportComponent implements OnInit, OnDestroy {
[Validators.required, Validators.minLength(10), NoWhiteSpaceValidator],
],
email: [email, [Validators.required, EmailValidator]],
phone: [
{ countryCode: "fr", number: "" },
[Validators.required, anyPhoneValidator],
],
phone: [{ countryCode: "fr", number: "" }, [anyPhoneValidator]],
file: [""],
fileSource: ["", [validateUpload("STRUCTURE_DOC", false)]],
name: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
import { subMonths, subWeeks } from "date-fns";
import { subDays, subMonths, subWeeks, subYears } from "date-fns";
import { UsagerLight } from "../../../../../../../_common/model";
import { usagerPassageChecker } from "./usagerPassageChecker.service";

describe("usagerPassageChecker ", () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2024-12-30T12:00:00.000Z"));
});

afterEach(() => {
jest.useRealTimers();
});

describe("TWO_MONTHS ", () => {
it("usagerPassageChecker: should return true, last interaction > TWO_MONTHS", () => {
describe("Previous two months ", () => {
it("usagerPassageChecker: should return true, Last Interaction date < Two months ago", () => {
expect(
usagerPassageChecker.check({
usager: {
decision: {
statut: "VALIDE",
},
lastInteraction: {
dateInteraction: subMonths(new Date(), 3),
dateInteraction: subMonths(new Date(), 5),
},
} as UsagerLight,
lastInteractionDate: "PREVIOUS_TWO_MONTHS",
})
).toBeTruthy();

expect(
usagerPassageChecker.check({
usager: {
decision: {
statut: "INSTRUCTION",
},
lastInteraction: {
dateInteraction: new Date("2024-10-30T12:00:00.000Z"),
dateInteraction: subYears(new Date(), 1),
},
} as UsagerLight,
lastInteractionDate: "PREVIOUS_TWO_MONTHS",
})
).toBeTruthy();
});

it("usagerPassageChecker: should return false, last interaction < TWO_MONTHS", () => {
it("usagerPassageChecker: should return false, last interaction > two months ago", () => {
expect(
usagerPassageChecker.check({
usager: {
decision: {
statut: "INSTRUCTION",
},
lastInteraction: {
dateInteraction: new Date("2024-10-31T12:00:00.000Z"),
dateInteraction: subDays(new Date(), 2),
},
} as UsagerLight,
lastInteractionDate: "PREVIOUS_TWO_MONTHS",
Expand All @@ -61,10 +53,10 @@ describe("usagerPassageChecker ", () => {
usagerPassageChecker.check({
usager: {
decision: {
statut: "INSTRUCTION",
statut: "VALIDE",
},
lastInteraction: {
dateInteraction: new Date("2024-11-28T12:00:00.000Z"),
dateInteraction: subMonths(new Date(), 2),
},
} as UsagerLight,
lastInteractionDate: "PREVIOUS_TWO_MONTHS",
Expand All @@ -73,8 +65,8 @@ describe("usagerPassageChecker ", () => {
});
});

describe("usagerPassageChecker THREE_MONTHS", () => {
it("usagerPassageChecker: should return true, last interaction > 3 months", () => {
describe("Previous three months", () => {
it("usagerPassageChecker: should return true, last interaction < 3 months", () => {
expect(
usagerPassageChecker.check({
usager: {
Expand All @@ -90,7 +82,7 @@ describe("usagerPassageChecker ", () => {
).toBeTruthy();
});

it("usagerPassageChecker: should return false, last interaction < 3 months", () => {
it("usagerPassageChecker: should return false, last interaction > 3 months", () => {
expect(
usagerPassageChecker.check({
usager: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function check({
if (lastInteractionDate && USAGER_DEADLINES[lastInteractionDate]) {
const deadlineTime = USAGER_DEADLINES[lastInteractionDate].value;
const interactionTime = new Date(usager.lastInteraction.dateInteraction);
return deadlineTime > interactionTime;
return interactionTime < deadlineTime;
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@

<div class="modal-body">
<div class="col">
<p>
Vous êtes sur le point de domicilier
<strong>{{ usager | usagerNomComplet }}.</strong>
<br />
Veuillez indiquer la date de début de la domiciliation.
</p>
<p *ngIf="lastDecision && usager.decision.typeDom === 'RENOUVELLEMENT'">
<b>Information:</b> la dernière domiciliation de
{{ usager | usagerNomComplet }} était valide du
{{ lastDecision.dateDebut | date : "dd/MM/yyyy" }} au
{{ lastDecision.dateFin | date : "dd/MM/yyyy" }}.
</p>
<form
[formGroup]="valideForm"
class="my-1"
Expand All @@ -31,7 +19,7 @@
</p>
</div>
<div class="form-group col-12 col-md-6 required">
<label for="dateDebutValide">Date de début</label>
<label for="dateDebutValide">Date de début de la domiciliation</label>
<div class="input-group">
<input
class="form-control"
Expand Down Expand Up @@ -86,7 +74,7 @@
</p>
</div>
<div class="form-group col-12 col-md-6 required">
<label for="dateFinValide">Date de fin</label>
<label for="dateFinValide">Date de fin de la domiciliation</label>
<div class="input-group">
<input
class="form-control"
Expand Down Expand Up @@ -151,6 +139,20 @@
</p>
</div>
</div>

<div
class="col-12"
*ngIf="lastDecision && usager.typeDom === 'RENOUVELLEMENT'"
>
<p class="alert alert-info">
La dernière domiciliation connue de
{{ usager | usagerNomComplet }} était valide du&nbsp;
<b>
{{ lastDecision.dateDebut | date : "dd/MM/yyyy" }} au
{{ lastDecision.dateFin | date : "dd/MM/yyyy" }}.</b
>
</p>
</div>
</div>

<div class="row my-2">
Expand Down
Loading

0 comments on commit e0d22aa

Please sign in to comment.