betagouv/service-national-universel

View on GitHub
app/src/utils/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
import PasswordValidator from "password-validator";
import { YOUNG_STATUS, YOUNG_STATUS_PHASE1, YOUNG_STATUS_PHASE2, YOUNG_STATUS_PHASE3, REGLEMENT_INTERIEUR_VERSION, isCohortTooOld } from "snu-lib";
export * from "snu-lib";
import slugify from "slugify";
import { isCohortDone } from "./cohorts";

function addOneDay(date) {
  const newDate = new Date(date);
  newDate.setDate(newDate.getDate() + 1);
  return newDate;
}

/*  
  Un jeune doit réaccepter le Reglement intérieur si le départ de sa cohorte est 
  apres la date de mise en place du dernier reglement (et s'il ne l'a pas déja 
  accepté).
*/
export function shouldReAcceptRI(young, cohort) {
  const newRiDate = new Date(REGLEMENT_INTERIEUR_VERSION);
  const cohortStartDate = new Date(cohort?.dateStart);
  if (permissionPhase1(young) && cohortStartDate >= newRiDate && young?.acceptRI != REGLEMENT_INTERIEUR_VERSION) {
    return true;
  }
  return false;
}

export function getPasswordErrorMessage(v) {
  if (!v) return "Ce champ est obligatoire";
  const schema = new PasswordValidator();
  schema
    .is()
    .min(12) // Minimum length 12
    .has()
    .uppercase() // Must have uppercase letters
    .has()
    .lowercase() // Must have lowercase letters
    .has()
    .digits() // Must have digits
    .has()
    .symbols(); // Must have symbols

  if (!schema.validate(v)) {
    return "Votre mot de passe doit contenir au moins 12 caractères, dont une majuscule, une minuscule, un chiffre et un symbole";
  }
}

const permissionApp = (y) => {
  if (!y) return false;
  if (y.status === YOUNG_STATUS.ABANDONED) return false;
  return y?.status !== YOUNG_STATUS.REFUSED;
};

export function wasYoungExcluded(y) {
  return y?.departSejourMotif === "Exclusion";
}

export function permissionChangeCohort(y, date) {
  if (!permissionApp(y)) return false;
  if (wasYoungExcluded(y)) return false;
  const now = new Date();
  const limit = addOneDay(date);
  if (y.statusPhase1 === YOUNG_STATUS_PHASE1.NOT_DONE && now < limit) return false;
  return true;
}

export function permissionPhase1(y) {
  if (!permissionApp(y)) return false;
  if (y.cohort === "à venir") return false;
  return (
    (![YOUNG_STATUS.WAITING_VALIDATION, YOUNG_STATUS.WAITING_CORRECTION, YOUNG_STATUS.WAITING_LIST, YOUNG_STATUS.WITHDRAWN].includes(y.status) &&
      y.statusPhase1 !== YOUNG_STATUS_PHASE1.NOT_DONE &&
      !wasYoungExcluded(y)) ||
    y.statusPhase1 === YOUNG_STATUS_PHASE1.DONE
  );
}

export function hasAccessToPhase2(young) {
  if (!young) return false;
  if (young.statusPhase2 === "VALIDATED") return true;
  if (young.status === YOUNG_STATUS.WITHDRAWN) return false;
  const userIsDoingAMission = young.phase2ApplicationStatus.some((status) => ["VALIDATED", "IN_PROGRESS"].includes(status));

  if (isCohortTooOld(young) && !userIsDoingAMission) {
    return false;
  }
  if (wasYoungExcluded(young)) return false;
  return true;
}

export function permissionPhase2(y) {
  if (wasYoungExcluded(y)) return false;
  if (y.statusPhase2OpenedAt && new Date(y.statusPhase2OpenedAt) < new Date()) return true;
  if (!hasAccessToPhase2(y)) return false;

  // If young has validated phase 2
  if (y.statusPhase2 === YOUNG_STATUS_PHASE2.VALIDATED) return true;
  // If young has done phase 1 or was exempted.
  if ([YOUNG_STATUS_PHASE1.DONE, YOUNG_STATUS_PHASE1.EXEMPTED].includes(y.statusPhase1)) return true;

  return false;
}

export function permissionPhase3(y) {
  if (!permissionApp(y)) return false;
  return (y.status !== YOUNG_STATUS.WITHDRAWN && y.statusPhase2 === YOUNG_STATUS_PHASE2.VALIDATED) || y.statusPhase3 === YOUNG_STATUS_PHASE3.VALIDATED;
}

// from the end of the cohort's last day
export function isYoungCanApplyToPhase2Missions(young) {
  if (young.statusPhase2OpenedAt && new Date(young.statusPhase2OpenedAt) < new Date()) return true;
  const hasYoungPhase1DoneOrExempted = [YOUNG_STATUS_PHASE1.DONE, YOUNG_STATUS_PHASE1.EXEMPTED].includes(young.statusPhase1);
  return isCohortDone(young.cohort, 1) && hasYoungPhase1DoneOrExempted;
}

export const HERO_IMAGES_LIST = ["login.jpg", "phase3.jpg", "rang.jpeg"];

export const ENABLE_PM = true;

export function urlWithScheme(url) {
  if (!/^https?:\/\//i.test(url)) return `http://${url}`;
  return url;
}

export const copyToClipboard = (text) => {
  if (navigator.clipboard && window.isSecureContext) {
    return navigator.clipboard.writeText(text);
  } else {
    const textArea = document.createElement("textarea");
    textArea.value = text;
    textArea.style.position = "absolute";
    textArea.style.opacity = 0;
    document.body.appendChild(textArea);
    textArea.select();
    return new Promise((res, rej) => {
      // here the magic happens
      document.execCommand("copy") ? res() : rej();
      textArea.remove();
    });
  }
};

export function slugifyFileName(str) {
  return slugify(str, { replacement: "-", remove: /[*+~.()'"!:@]/g });
}

function toRadians(degrees) {
  return (degrees * Math.PI) / 180;
}

// Calculer la distance haversine entre deux points géographiques
export function getDistance(lat1, lon1, lat2, lon2) {
  const earthRadiusKm = 6371; // Rayon de la Terre en kilomètres
  const dLat = toRadians(lat2 - lat1);
  const dLon = toRadians(lon2 - lon1);

  const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = earthRadiusKm * c;

  return distance;
}

export const regexPhoneFrenchCountries = /^((00|\+)(33|590|594|262|596|269|687|689|508|681)|0)[1-9]?(\d{8})$/;

export const debounce = (fn, delay) => {
  let timeOutId;
  return function (...args) {
    if (timeOutId) {
      clearTimeout(timeOutId);
    }
    timeOutId = setTimeout(() => {
      fn(...args);
    }, delay);
  };
};

export const validateId = (id) => {
  const idRegex = /^[0-9a-fA-F]{24}$/;
  return idRegex.test(id);
};

export const desktopBreakpoint = 768;