Skip to content

Update README with HTML structure and scripts#539

Open
lemassade-hash wants to merge 1 commit intofirebase:mainfrom
lemassade-hash:patch-1
Open

Update README with HTML structure and scripts#539
lemassade-hash wants to merge 1 commit intofirebase:mainfrom
lemassade-hash:patch-1

Conversation

@lemassade-hash
Copy link
Copy Markdown

<title>Transmissions Foyer PRO</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-firestore-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-auth-compat.js"></script>

📋 Transmissions - Foyer (Dossier jeune)

Connexion
Déconnexion

➕ Nouvelle transmission
Info Incident Médical Comportement <textarea id="message" placeholder="Transmission..." class="border p-2 w-full mb-2 rounded"></textarea> ✅ Enregistrer
<script> const firebaseConfig = { apiKey: "TON_API_KEY", authDomain: "TON_PROJECT.firebaseapp.com", projectId: "TON_PROJECT_ID", };

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
const auth = firebase.auth();

let allData = [];
let currentJeune = null;

function login() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
auth.signInWithEmailAndPassword(email, password)
.catch(err => alert(err.message));
}

function logout() {
auth.signOut();
}

auth.onAuthStateChanged(user => {
if (user) {
document.getElementById('login').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
loadData();
} else {
document.getElementById('login').classList.remove('hidden');
document.getElementById('app').classList.add('hidden');
}
});

function toggleForm() {
document.getElementById('form').classList.toggle('hidden');
}

function addTransmission() {
const jeune = document.getElementById('jeune').value;
const educ = document.getElementById('educ').value;
const type = document.getElementById('type').value;
const message = document.getElementById('message').value;

if (!jeune || !educ || !message) {
alert("Merci de remplir tous les champs");
return;
}

db.collection("transmissions").add({
jeune,
educ,
type,
message,
date: new Date()
});

document.getElementById('jeune').value = '';
document.getElementById('educ').value = '';
document.getElementById('message').value = '';
}

function populateJeunes() {
const select = document.getElementById('selectJeune');
const names = [...new Set(allData.map(d => d.data().jeune))];

select.innerHTML = '-- Choisir un jeune --';
names.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.innerText = name;
select.appendChild(opt);
});
}

function showJeune() {
const name = document.getElementById('selectJeune').value;
currentJeune = name;

if (!name) return;

const filtered = allData.filter(d => d.data().jeune === name);

document.getElementById('ficheJeune').classList.remove('hidden');
document.getElementById('nomJeune').innerText = name;

let incidents = 0;
filtered.forEach(d => {
if (d.data().type === "Incident") incidents++;
});

document.getElementById('statsJeune').innerText =
Total : ${filtered.length} | Incidents : ${incidents};

render(filtered);
}

function render(data) {
const list = document.getElementById('list');
list.innerHTML = '';

data.forEach(doc => {
const t = doc.data();
const div = document.createElement('div');
div.className = 'bg-white p-3 rounded-2xl shadow';
div.innerHTML = <div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div> <div><strong>${t.jeune}</strong> - ${t.type}</div> <div class="text-sm">👤 ${t.educ}</div> <div class="mt-2">${t.message}</div>;
list.appendChild(div);
});
}

function loadData() {
db.collection("transmissions").orderBy("date", "desc")
.onSnapshot(snapshot => {
allData = snapshot.docs;
populateJeunes();
render(allData);
});
}
</script>

<!DOCTYPE html><html lang="fr">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Transmissions Foyer PRO</title>
  <script src="https://cdn.tailwindcss.com"></script>  <!-- Firebase -->  <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script>  <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-firestore-compat.js"></script>  <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-auth-compat.js"></script></head>
<body class="bg-gray-100 p-4"><div class="max-w-4xl mx-auto">
  <h1 class="text-2xl font-bold mb-4">📋 Transmissions - Foyer (Dossier jeune)</h1>  <!-- LOGIN -->  <div id="login" class="bg-white p-4 rounded-2xl shadow mb-4">
    <input id="email" placeholder="Email" class="border p-2 w-full mb-2 rounded" />
    <input id="password" type="password" placeholder="Mot de passe" class="border p-2 w-full mb-2 rounded" />
    <button onclick="login()" class="bg-blue-500 text-white px-4 py-2 rounded-2xl w-full">Connexion</button>
  </div>  <!-- APP -->  <div id="app" class="hidden">
    <button onclick="logout()" class="bg-red-500 text-white px-4 py-2 rounded-2xl mb-4">Déconnexion</button><!-- SELECTION JEUNE -->
<select id="selectJeune" onchange="showJeune()" class="border p-2 w-full mb-4 rounded"></select>

<!-- DOSSIER JEUNE -->
<div id="ficheJeune" class="bg-white p-4 rounded-xl shadow mb-4 hidden">
  <h2 id="nomJeune" class="text-xl font-bold mb-2"></h2>
  <div id="statsJeune" class="text-sm text-gray-600"></div>
</div>

<button onclick="toggleForm()" class="bg-blue-500 text-white px-4 py-2 rounded-2xl mb-4">
  ➕ Nouvelle transmission
</button>

<div id="form" class="hidden bg-white p-4 rounded-2xl shadow mb-4">
  <input id="jeune" placeholder="Nom du jeune" class="border p-2 w-full mb-2 rounded" />
  <input id="educ" placeholder="Éducateur" class="border p-2 w-full mb-2 rounded" />
  <select id="type" class="border p-2 w-full mb-2 rounded">
    <option>Info</option>
    <option>Incident</option>
    <option>Médical</option>
    <option>Comportement</option>
  </select>
  <textarea id="message" placeholder="Transmission..." class="border p-2 w-full mb-2 rounded"></textarea>
  <button onclick="addTransmission()" class="bg-green-500 text-white px-4 py-2 rounded-2xl">
    ✅ Enregistrer
  </button>
</div>

<div id="list" class="space-y-2"></div>

  </div>
</div><script>
const firebaseConfig = {
  apiKey: "TON_API_KEY",
  authDomain: "TON_PROJECT.firebaseapp.com",
  projectId: "TON_PROJECT_ID",
};

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
const auth = firebase.auth();

let allData = [];
let currentJeune = null;

function login() {
  const email = document.getElementById('email').value;
  const password = document.getElementById('password').value;
  auth.signInWithEmailAndPassword(email, password)
    .catch(err => alert(err.message));
}

function logout() {
  auth.signOut();
}

auth.onAuthStateChanged(user => {
  if (user) {
    document.getElementById('login').classList.add('hidden');
    document.getElementById('app').classList.remove('hidden');
    loadData();
  } else {
    document.getElementById('login').classList.remove('hidden');
    document.getElementById('app').classList.add('hidden');
  }
});

function toggleForm() {
  document.getElementById('form').classList.toggle('hidden');
}

function addTransmission() {
  const jeune = document.getElementById('jeune').value;
  const educ = document.getElementById('educ').value;
  const type = document.getElementById('type').value;
  const message = document.getElementById('message').value;

  if (!jeune || !educ || !message) {
    alert("Merci de remplir tous les champs");
    return;
  }

  db.collection("transmissions").add({
    jeune,
    educ,
    type,
    message,
    date: new Date()
  });

  document.getElementById('jeune').value = '';
  document.getElementById('educ').value = '';
  document.getElementById('message').value = '';
}

function populateJeunes() {
  const select = document.getElementById('selectJeune');
  const names = [...new Set(allData.map(d => d.data().jeune))];

  select.innerHTML = '<option value="">-- Choisir un jeune --</option>';
  names.forEach(name => {
    const opt = document.createElement('option');
    opt.value = name;
    opt.innerText = name;
    select.appendChild(opt);
  });
}

function showJeune() {
  const name = document.getElementById('selectJeune').value;
  currentJeune = name;

  if (!name) return;

  const filtered = allData.filter(d => d.data().jeune === name);

  document.getElementById('ficheJeune').classList.remove('hidden');
  document.getElementById('nomJeune').innerText = name;

  let incidents = 0;
  filtered.forEach(d => {
    if (d.data().type === "Incident") incidents++;
  });

  document.getElementById('statsJeune').innerText =
    `Total : ${filtered.length} | Incidents : ${incidents}`;

  render(filtered);
}

function render(data) {
  const list = document.getElementById('list');
  list.innerHTML = '';

  data.forEach(doc => {
    const t = doc.data();
    const div = document.createElement('div');
    div.className = 'bg-white p-3 rounded-2xl shadow';
    div.innerHTML = `
      <div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div>
      <div><strong>${t.jeune}</strong> - ${t.type}</div>
      <div class="text-sm">👤 ${t.educ}</div>
      <div class="mt-2">${t.message}</div>
    `;
    list.appendChild(div);
  });
}

function loadData() {
  db.collection("transmissions").orderBy("date", "desc")
    .onSnapshot(snapshot => {
      allData = snapshot.docs;
      populateJeunes();
      render(allData);
    });
}
</script></body>
</html>
@google-cla
Copy link
Copy Markdown

google-cla bot commented Apr 4, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds a complete HTML and JavaScript application for managing transmissions directly into the README.md file. The review identifies several critical issues, including a Cross-Site Scripting (XSS) vulnerability due to unsafe innerHTML usage, a logic bug where real-time updates reset the UI state, and potential runtime errors related to timestamp handling. It is recommended to move the application to a separate file and implement the suggested improvements for data sanitization and integrity.

Comment on lines +324 to +329
div.innerHTML = `
<div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div>
<div><strong>${t.jeune}</strong> - ${t.type}</div>
<div class="text-sm">👤 ${t.educ}</div>
<div class="mt-2">${t.message}</div>
`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The use of innerHTML to render user-provided data such as t.jeune, t.type, t.educ, and t.message introduces a significant Cross-Site Scripting (XSS) vulnerability. An attacker could inject malicious scripts into these fields which would then execute in the context of other users' browsers. Use textContent or a dedicated sanitization library to safely display user-generated content.

Comment on lines +174 to +343
<!DOCTYPE html><html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Transmissions Foyer PRO</title>
<script src="https://cdn.tailwindcss.com"></script> <!-- Firebase --> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-firestore-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-auth-compat.js"></script></head>
<body class="bg-gray-100 p-4"><div class="max-w-4xl mx-auto">
<h1 class="text-2xl font-bold mb-4">📋 Transmissions - Foyer (Dossier jeune)</h1> <!-- LOGIN --> <div id="login" class="bg-white p-4 rounded-2xl shadow mb-4">
<input id="email" placeholder="Email" class="border p-2 w-full mb-2 rounded" />
<input id="password" type="password" placeholder="Mot de passe" class="border p-2 w-full mb-2 rounded" />
<button onclick="login()" class="bg-blue-500 text-white px-4 py-2 rounded-2xl w-full">Connexion</button>
</div> <!-- APP --> <div id="app" class="hidden">
<button onclick="logout()" class="bg-red-500 text-white px-4 py-2 rounded-2xl mb-4">Déconnexion</button><!-- SELECTION JEUNE -->
<select id="selectJeune" onchange="showJeune()" class="border p-2 w-full mb-4 rounded"></select>

<!-- DOSSIER JEUNE -->
<div id="ficheJeune" class="bg-white p-4 rounded-xl shadow mb-4 hidden">
<h2 id="nomJeune" class="text-xl font-bold mb-2"></h2>
<div id="statsJeune" class="text-sm text-gray-600"></div>
</div>

<button onclick="toggleForm()" class="bg-blue-500 text-white px-4 py-2 rounded-2xl mb-4">
➕ Nouvelle transmission
</button>

<div id="form" class="hidden bg-white p-4 rounded-2xl shadow mb-4">
<input id="jeune" placeholder="Nom du jeune" class="border p-2 w-full mb-2 rounded" />
<input id="educ" placeholder="Éducateur" class="border p-2 w-full mb-2 rounded" />
<select id="type" class="border p-2 w-full mb-2 rounded">
<option>Info</option>
<option>Incident</option>
<option>Médical</option>
<option>Comportement</option>
</select>
<textarea id="message" placeholder="Transmission..." class="border p-2 w-full mb-2 rounded"></textarea>
<button onclick="addTransmission()" class="bg-green-500 text-white px-4 py-2 rounded-2xl">
✅ Enregistrer
</button>
</div>

<div id="list" class="space-y-2"></div>

</div>
</div><script>
const firebaseConfig = {
apiKey: "TON_API_KEY",
authDomain: "TON_PROJECT.firebaseapp.com",
projectId: "TON_PROJECT_ID",
};

firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
const auth = firebase.auth();

let allData = [];
let currentJeune = null;

function login() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
auth.signInWithEmailAndPassword(email, password)
.catch(err => alert(err.message));
}

function logout() {
auth.signOut();
}

auth.onAuthStateChanged(user => {
if (user) {
document.getElementById('login').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
loadData();
} else {
document.getElementById('login').classList.remove('hidden');
document.getElementById('app').classList.add('hidden');
}
});

function toggleForm() {
document.getElementById('form').classList.toggle('hidden');
}

function addTransmission() {
const jeune = document.getElementById('jeune').value;
const educ = document.getElementById('educ').value;
const type = document.getElementById('type').value;
const message = document.getElementById('message').value;

if (!jeune || !educ || !message) {
alert("Merci de remplir tous les champs");
return;
}

db.collection("transmissions").add({
jeune,
educ,
type,
message,
date: new Date()
});

document.getElementById('jeune').value = '';
document.getElementById('educ').value = '';
document.getElementById('message').value = '';
}

function populateJeunes() {
const select = document.getElementById('selectJeune');
const names = [...new Set(allData.map(d => d.data().jeune))];

select.innerHTML = '<option value="">-- Choisir un jeune --</option>';
names.forEach(name => {
const opt = document.createElement('option');
opt.value = name;
opt.innerText = name;
select.appendChild(opt);
});
}

function showJeune() {
const name = document.getElementById('selectJeune').value;
currentJeune = name;

if (!name) return;

const filtered = allData.filter(d => d.data().jeune === name);

document.getElementById('ficheJeune').classList.remove('hidden');
document.getElementById('nomJeune').innerText = name;

let incidents = 0;
filtered.forEach(d => {
if (d.data().type === "Incident") incidents++;
});

document.getElementById('statsJeune').innerText =
`Total : ${filtered.length} | Incidents : ${incidents}`;

render(filtered);
}

function render(data) {
const list = document.getElementById('list');
list.innerHTML = '';

data.forEach(doc => {
const t = doc.data();
const div = document.createElement('div');
div.className = 'bg-white p-3 rounded-2xl shadow';
div.innerHTML = `
<div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div>
<div><strong>${t.jeune}</strong> - ${t.type}</div>
<div class="text-sm">👤 ${t.educ}</div>
<div class="mt-2">${t.message}</div>
`;
list.appendChild(div);
});
}

function loadData() {
db.collection("transmissions").orderBy("date", "desc")
.onSnapshot(snapshot => {
allData = snapshot.docs;
populateJeunes();
render(allData);
});
}
</script></body>
</html>![IMG_20251108_174123](https://github.com/user-attachments/assets/07ca3d0e-8d12-43a3-b764-ba97e5c42ba0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The addition of a complete HTML/JavaScript application directly into the README.md file is unconventional and negatively impacts the maintainability and readability of the documentation. This repository is dedicated to "App Hosting adapters," and this specific application seems out of place. It is recommended to move this code to a separate file (e.g., index.html) or wrap it in a proper Markdown code block if it is intended as a usage example.

educ,
type,
message,
date: new Date()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using new Date() on the client side can lead to inconsistent timestamps due to variations in system clocks or user manipulation. It is recommended to use Firestore's server-side timestamp for better data integrity.

Suggested change
date: new Date()
date: firebase.firestore.FieldValue.serverTimestamp()

.onSnapshot(snapshot => {
allData = snapshot.docs;
populateJeunes();
render(allData);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The onSnapshot listener currently calls render(allData) on every update, which will overwrite any filtered view the user might have selected via showJeune(). Additionally, populateJeunes() will reset the dropdown selection. The application should preserve the user's current selection and filter state when data updates.

const div = document.createElement('div');
div.className = 'bg-white p-3 rounded-2xl shadow';
div.innerHTML = `
<div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Accessing t.date.seconds without a null check can cause a runtime error when using Firestore's serverTimestamp. The field may be null in the local cache before the server-side value is synchronized. Add a check to handle the case where t.date is not yet available.

Suggested change
<div class="text-sm text-gray-500">${new Date(t.date.seconds*1000).toLocaleString()}</div>
<div class="text-sm text-gray-500">${t.date ? new Date(t.date.seconds*1000).toLocaleString() : '...'}</div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant