Public Access
feat(freelancer-edit): add LinkedIn/Upwork/Fiverr fields; save the whole record
Two defects, one file. (1) The edit form was missing the three profile links the application form collects. Added as optional type=url fields pre-filled from the record, with the application form's own labels and placeholders, under the LIVE wire keys verbatim: "Linkedin" (capital L), "upwork", "fiverr" — casing never normalised. (2) The save built its payload field-by-field, so Image and the three links were absent from the body and a freelancer saving an edit wiped their own photo and links. The payload now starts from the loaded record (read-modify-write) and overwrites only the fields this form owns; Image rides through untouched. An empty optional link on a record that never had one is left as the record's own value — no invented empty strings. 'has' stays first, method stays PATCH. No second URL check: the only url predicate is the directory card's isLink().
This commit is contained in:
1 parent
7dfd5aec9b
commit
5cf58ffa8a
1 file changed
+63
-4
@@ -182,6 +182,11 @@
|
|||||||
description: ["description", "Description"],
|
description: ["description", "Description"],
|
||||||
Country: ["Country", "country"],
|
Country: ["Country", "country"],
|
||||||
OCA_link: ["OCA_link", "oca_link"],
|
OCA_link: ["OCA_link", "oca_link"],
|
||||||
|
// The three profile links. The primary name is the LIVE wire key, casing and all —
|
||||||
|
// "Linkedin", "upwork", "fiverr" are deliberately mixed and must never be normalised
|
||||||
|
// (a normalised key is the silent-drop class that ate odoo_versions). A same-word
|
||||||
|
// casing variant is the identical fallback, the way Name/name already works.
|
||||||
|
Linkedin: ["Linkedin", "linkedin"], upwork: ["upwork", "Upwork"], fiverr: ["fiverr", "Fiverr"],
|
||||||
functional: ["functional"], system: ["system"], modules: ["modules"],
|
functional: ["functional"], system: ["system"], modules: ["modules"],
|
||||||
integration: ["integration"], ai: ["ai"],
|
integration: ["integration"], ai: ["ai"],
|
||||||
english: ["english"], german: ["german"], spanish: ["spanish"]
|
english: ["english"], german: ["german"], spanish: ["spanish"]
|
||||||
@@ -407,6 +412,26 @@
|
|||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An optional profile link. A real `type="url"` input with the application form's own
|
||||||
|
// label and placeholder, so the two forms ask for a link the same way. No URL check
|
||||||
|
// lives here: the value is passed through untouched, and the ONLY url predicate in the
|
||||||
|
// fleet is the directory card's isLink() (a prefix allowlist) — one check, not two.
|
||||||
|
function linkField(id, name, labelText, value, placeholder) {
|
||||||
|
var wrap = el("div", "field");
|
||||||
|
var label = el("label", "", labelText);
|
||||||
|
label.setAttribute("for", id);
|
||||||
|
label.appendChild(el("span", "hint", " (optional)"));
|
||||||
|
var input = el("input");
|
||||||
|
input.type = "url";
|
||||||
|
input.id = id;
|
||||||
|
input.name = name;
|
||||||
|
input.value = value == null ? "" : String(value);
|
||||||
|
input.placeholder = placeholder;
|
||||||
|
wrap.appendChild(label);
|
||||||
|
wrap.appendChild(input);
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
function rangeField(fieldset, name, labelText, value) {
|
function rangeField(fieldset, name, labelText, value) {
|
||||||
var row = el("div", "row");
|
var row = el("div", "row");
|
||||||
var label = el("label", "", labelText);
|
var label = el("label", "", labelText);
|
||||||
@@ -534,6 +559,17 @@
|
|||||||
form.appendChild(textField("edOcaLink", "OCA member profile link", readField(record, "OCA_link"),
|
form.appendChild(textField("edOcaLink", "OCA member profile link", readField(record, "OCA_link"),
|
||||||
0, "Your OCA profile URL, if you have one: https://apps.odoo-community.org/community/…"));
|
0, "Your OCA profile URL, if you have one: https://apps.odoo-community.org/community/…"));
|
||||||
|
|
||||||
|
// The three profile links — parity with the application form. Keys are the LIVE wire
|
||||||
|
// keys verbatim ("Linkedin" capital L, "upwork" and "fiverr" lower) and the labels and
|
||||||
|
// placeholders are the application form's own. Each is optional and pre-filled from the
|
||||||
|
// record.
|
||||||
|
form.appendChild(linkField("edLinkedin", "Linkedin", "LinkedIn URL",
|
||||||
|
readField(record, "Linkedin"), "https://www.linkedin.com/in/…"));
|
||||||
|
form.appendChild(linkField("edUpwork", "upwork", "Upwork URL",
|
||||||
|
readField(record, "upwork"), "https://www.upwork.com/freelancers/…"));
|
||||||
|
form.appendChild(linkField("edFiverr", "fiverr", "Fiverr URL",
|
||||||
|
readField(record, "fiverr"), "https://www.fiverr.com/…"));
|
||||||
|
|
||||||
// The image is READ-ONLY on purpose: replacing a photo is a different problem from
|
// The image is READ-ONLY on purpose: replacing a photo is a different problem from
|
||||||
// editing text (an upload path, a base64 blob, a size limit). Shown, not editable.
|
// editing text (an upload path, a base64 blob, a size limit). Shown, not editable.
|
||||||
var photoWrap = el("div", "field");
|
var photoWrap = el("div", "field");
|
||||||
@@ -580,10 +616,20 @@
|
|||||||
save.disabled = true;
|
save.disabled = true;
|
||||||
line.className = "status";
|
line.className = "status";
|
||||||
line.textContent = "Saving…";
|
line.textContent = "Saving…";
|
||||||
// The body shape: the credential (the SAME "has" key the delete sends, one
|
// The body shape: READ-MODIFY-WRITE. Start from the record the webhook returned and
|
||||||
// credential path for both) + the record's id + every field this form owns.
|
// overwrite ONLY the fields this form owns. A field-by-field payload is what deletes a
|
||||||
// Method is C.saveMethod ("PATCH").
|
// freelancer's own photo and links on save — a partial payload wipes what it leaves
|
||||||
var payload = { has: hash, id: rec && rec.id !== undefined ? rec.id : undefined };
|
// out, so the whole record goes back and only the edited keys are replaced. The
|
||||||
|
// credential rides first: "has" is the SAME key the delete sends (one credential path
|
||||||
|
// for both), then the record's id. Method is C.saveMethod ("PATCH").
|
||||||
|
var payload = { has: hash };
|
||||||
|
if (rec) {
|
||||||
|
Object.keys(rec).forEach(function (k) {
|
||||||
|
if (k === "has") return; // never let the record's own "has" shadow the credential
|
||||||
|
payload[k] = rec[k]; // Image, the links, createdAt/updatedAt and everything else
|
||||||
|
});
|
||||||
|
}
|
||||||
|
payload.id = rec && rec.id !== undefined ? rec.id : undefined;
|
||||||
payload.Name = document.getElementById("edName").value.trim();
|
payload.Name = document.getElementById("edName").value.trim();
|
||||||
payload.Focus = document.getElementById("edFocus").value.trim();
|
payload.Focus = document.getElementById("edFocus").value.trim();
|
||||||
payload.description = document.getElementById("edDescription").value.trim();
|
payload.description = document.getElementById("edDescription").value.trim();
|
||||||
@@ -591,6 +637,19 @@
|
|||||||
payload.OCA_link = document.getElementById("edOcaLink").value.trim();
|
payload.OCA_link = document.getElementById("edOcaLink").value.trim();
|
||||||
C.focusAxes.forEach(function (axis) { payload[axis] = num(document.getElementById("ed-" + axis).value, 0); });
|
C.focusAxes.forEach(function (axis) { payload[axis] = num(document.getElementById("ed-" + axis).value, 0); });
|
||||||
C.langs.forEach(function (pair) { payload[pair[0]] = num(document.getElementById("ed-" + pair[0]).value, 0); });
|
C.langs.forEach(function (pair) { payload[pair[0]] = num(document.getElementById("ed-" + pair[0]).value, 0); });
|
||||||
|
// The three links, read from their inputs. Only a key we actually have a value for is
|
||||||
|
// touched: an empty optional field must NOT invent an empty string on a record that
|
||||||
|
// never had one — the record's own value (null, or absent) is already in the payload
|
||||||
|
// and is left as it is. A link the record DID carry and the owner cleared is a
|
||||||
|
// deliberate clear and sends "".
|
||||||
|
[["edLinkedin", "Linkedin"], ["edUpwork", "upwork"], ["edFiverr", "fiverr"]].forEach(function (pair) {
|
||||||
|
var input = document.getElementById(pair[0]);
|
||||||
|
if (!input) return;
|
||||||
|
var v = input.value.trim();
|
||||||
|
var had = rec && rec[pair[1]] !== undefined && rec[pair[1]] !== null && rec[pair[1]] !== "";
|
||||||
|
if (v) payload[pair[1]] = v;
|
||||||
|
else if (had) payload[pair[1]] = "";
|
||||||
|
});
|
||||||
|
|
||||||
fetch(C.webhook, {
|
fetch(C.webhook, {
|
||||||
method: C.saveMethod,
|
method: C.saveMethod,
|
||||||
|
|||||||
Reference in new issue
Block a user