Merge pull request #858 from ae-utbm/jsstandard

Add biome to format js files
This commit is contained in:
thomas girod
2024-10-08 23:45:20 +02:00
committed by GitHub
43 changed files with 10820 additions and 6681 deletions

View File

@ -1,241 +1,245 @@
#eboutic {
display: flex;
flex-direction: row-reverse;
align-items: flex-start;
column-gap: 20px;
margin: 0 20px 20px;
display: flex;
flex-direction: row-reverse;
align-items: flex-start;
column-gap: 20px;
margin: 0 20px 20px;
}
#eboutic-title {
margin-left: 20px;
margin-left: 20px;
}
#eboutic h3 {
margin-left: 0;
margin-right: 0;
margin-left: 0;
margin-right: 0;
}
#basket {
min-width: 300px;
border-radius: 8px;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
padding: 10px;
min-width: 300px;
border-radius: 8px;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
padding: 10px;
}
#basket h3 {
margin-top: 0;
margin-top: 0;
}
@media screen and (max-width: 765px) {
#eboutic {
flex-direction: column-reverse;
align-items: center;
margin: 10px;
row-gap: 20px;
}
#eboutic-title {
margin-bottom: 20px;
margin-top: 4px;
}
#basket {
width: -webkit-fill-available;
}
#eboutic {
flex-direction: column-reverse;
align-items: center;
margin: 10px;
row-gap: 20px;
}
#eboutic-title {
margin-bottom: 20px;
margin-top: 4px;
}
#basket {
width: -webkit-fill-available;
}
}
#eboutic .item-list {
margin-left: 0;
list-style: none;
margin-left: 0;
list-style: none;
}
#eboutic .item-list li {
display: flex;
align-items: center;
margin-bottom: 10px
display: flex;
align-items: center;
margin-bottom: 10px;
}
#eboutic .item-row {
gap: 10px;
gap: 10px;
}
#eboutic .item-name {
word-break: break-word;
width: 100%;
line-height: 100%;
word-break: break-word;
width: 100%;
line-height: 100%;
}
#eboutic .fa-plus,
#eboutic .fa-minus {
cursor: pointer;
background-color: #354a5f;
color: white;
border-radius: 50%;
padding: 5px;
font-size: 10px;
line-height: 10px;
width: 10px;
text-align: center;
cursor: pointer;
background-color: #354a5f;
color: white;
border-radius: 50%;
padding: 5px;
font-size: 10px;
line-height: 10px;
width: 10px;
text-align: center;
}
#eboutic .item-quantity {
min-width: 65px;
justify-content: space-between;
align-items: center;
display: flex;
gap: 5px;
min-width: 65px;
justify-content: space-between;
align-items: center;
display: flex;
gap: 5px;
}
#eboutic .item-price {
min-width: 65px;
text-align: right;
min-width: 65px;
text-align: right;
}
/* CSS du catalogue */
#eboutic #catalog {
display: flex;
flex-grow: 1;
flex-direction: column;
row-gap: 30px;
display: flex;
flex-grow: 1;
flex-direction: column;
row-gap: 30px;
}
#eboutic .category-header {
margin-bottom: 15px;
margin-bottom: 15px;
}
#eboutic .product-group {
display: flex;
flex-wrap: wrap;
column-gap: 15px;
row-gap: 15px;
display: flex;
flex-wrap: wrap;
column-gap: 15px;
row-gap: 15px;
}
#eboutic .product-button {
position: relative;
box-sizing: border-box;
min-height: 180px;
height: fit-content;
width: 150px;
padding: 15px;
overflow: hidden;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 5px;
justify-content: flex-start;
position: relative;
box-sizing: border-box;
min-height: 180px;
height: fit-content;
width: 150px;
padding: 15px;
overflow: hidden;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 5px;
justify-content: flex-start;
}
#eboutic .product-button.selected {
animation: bg-in-out 1s ease;
background-color: rgb(216, 236, 255);
animation: bg-in-out 1s ease;
background-color: rgb(216, 236, 255);
}
#eboutic .product-button.selected::after {
content: "🛒";
position: absolute;
top: 5px;
right: 5px;
padding: 5px;
border-radius: 50%;
box-shadow: 0 0 12px 2px rgb(0 0 0 / 14%);
background-color: white;
width: 20px;
height: 20px;
font-size: 16px;
line-height: 20px;
content: "🛒";
position: absolute;
top: 5px;
right: 5px;
padding: 5px;
border-radius: 50%;
box-shadow: 0 0 12px 2px rgb(0 0 0 / 14%);
background-color: white;
width: 20px;
height: 20px;
font-size: 16px;
line-height: 20px;
}
#eboutic .product-button:active {
box-shadow: none;
box-shadow: none;
}
#eboutic .product-image {
width: 100%;
height: 100%;
min-height: 70px;
max-height: 70px;
object-fit: contain;
border-radius: 4px;
line-height: 70px;
margin-bottom: 15px;
width: 100%;
height: 100%;
min-height: 70px;
max-height: 70px;
object-fit: contain;
border-radius: 4px;
line-height: 70px;
margin-bottom: 15px;
}
#eboutic i.product-image {
background-color: rgba(173, 173, 173, 0.2);
background-color: rgba(173, 173, 173, 0.2);
}
#eboutic .product-description h4 {
font-size: .75em;
word-break: break-word;
margin: 0 0 5px 0;
font-size: .75em;
word-break: break-word;
margin: 0 0 5px 0;
}
#eboutic .product-button p {
font-size: 13px;
margin: 0;
font-size: 13px;
margin: 0;
}
#eboutic .catalog-buttons {
display: flex;
justify-content: center;
column-gap: 30px;
margin: 30px 0 0;
display: flex;
justify-content: center;
column-gap: 30px;
margin: 30px 0 0;
}
#eboutic input {
all: unset;
all: unset;
}
#eboutic .catalog-buttons button {
min-width: 60px;
min-width: 60px;
}
#eboutic .catalog-buttons form {
margin: 0;
margin: 0;
}
@media screen and (max-width: 765px) {
#eboutic #catalog {
row-gap: 15px;
width: 100%;
}
#eboutic #catalog {
row-gap: 15px;
width: 100%;
}
#eboutic section {
text-align: center;
}
#eboutic section {
text-align: center;
}
#eboutic .product-group {
justify-content: space-around;
flex-direction: column;
}
#eboutic .product-group {
justify-content: space-around;
flex-direction: column;
}
#eboutic .product-group .product-button {
min-height: 100px;
width: 100%;
max-width: 100%;
display: flex;
flex-direction: row;
gap: 10px;
}
#eboutic .product-group .product-button {
min-height: 100px;
width: 100%;
max-width: 100%;
display: flex;
flex-direction: row;
gap: 10px;
}
#eboutic .product-group .product-description {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
#eboutic .product-group .product-description {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
#eboutic .product-description h4 {
text-align: left;
max-width: 90%;
}
#eboutic .product-description h4 {
text-align: left;
max-width: 90%;
}
#eboutic .product-image {
margin-bottom: 0;
max-width: 70px;
}
#eboutic .product-image {
margin-bottom: 0;
max-width: 70px;
}
}
@keyframes bg-in-out {
0% { background-color: white; }
100% { background-color: rgb(216, 236, 255); }
}
0% {
background-color: white;
}
100% {
background-color: rgb(216, 236, 255);
}
}

View File

@ -14,131 +14,134 @@ const BASKET_ITEMS_COOKIE_NAME = "basket_items";
* @returns {string|null|undefined} the value of the cookie or null if it does not exist, undefined if not found
*/
function getCookie(name) {
if (!document.cookie || document.cookie.length === 0) return null;
// biome-ignore lint/style/useBlockStatements: <explanation>
if (!document.cookie || document.cookie.length === 0) return null;
let found = document.cookie
.split(';')
.map(c => c.trim())
.find(c => c.startsWith(name + '='));
const found = document.cookie
.split(";")
.map((c) => c.trim())
.find((c) => c.startsWith(`${name}=`));
return found === undefined ? undefined : decodeURIComponent(found.split('=')[1]);
return found === undefined ? undefined : decodeURIComponent(found.split("=")[1]);
}
/**
* Fetch the basket items from the associated cookie
* @returns {BasketItem[]|[]} the items in the basket
*/
function get_starting_items() {
const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME);
if (!cookie) {
return []
}
// Django cookie backend converts `,` to `\054`
let parsed = JSON.parse(cookie.replace(/\\054/g, ','));
if (typeof parsed === "string") {
// In some conditions, a second parsing is needed
parsed = JSON.parse(parsed);
}
const res = Array.isArray(parsed) ? parsed : [];
return res.filter((i) => !!document.getElementById(i.id))
function getStartingItems() {
const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME);
if (!cookie) {
return [];
}
// Django cookie backend converts `,` to `\054`
let parsed = JSON.parse(cookie.replace(/\\054/g, ","));
if (typeof parsed === "string") {
// In some conditions, a second parsing is needed
parsed = JSON.parse(parsed);
}
const res = Array.isArray(parsed) ? parsed : [];
return res.filter((i) => !!document.getElementById(i.id));
}
document.addEventListener('alpine:init', () => {
Alpine.data('basket', () => ({
items: get_starting_items(),
document.addEventListener("alpine:init", () => {
Alpine.data("basket", () => ({
items: getStartingItems(),
/**
* Get the total price of the basket
* @returns {number} The total price of the basket
*/
get_total() {
return this.items
.reduce((acc, item) => acc + item["quantity"] * item["unit_price"], 0);
},
/**
* Get the total price of the basket
* @returns {number} The total price of the basket
*/
getTotal() {
return this.items.reduce((acc, item) => acc + item.quantity * item.unit_price, 0);
},
/**
* Add 1 to the quantity of an item in the basket
* @param {BasketItem} item
*/
add(item) {
item.quantity++;
this.set_cookies();
},
/**
* Add 1 to the quantity of an item in the basket
* @param {BasketItem} item
*/
add(item) {
item.quantity++;
this.setCookies();
},
/**
* Remove 1 to the quantity of an item in the basket
* @param {BasketItem} item_id
*/
remove(item_id) {
const index = this.items.findIndex(e => e.id === item_id);
/**
* Remove 1 to the quantity of an item in the basket
* @param {BasketItem} item_id
*/
remove(itemId) {
const index = this.items.findIndex((e) => e.id === itemId);
if (index < 0) return;
this.items[index].quantity -= 1;
if (index < 0) {
return;
}
this.items[index].quantity -= 1;
if (this.items[index].quantity === 0) {
this.items = this.items.filter((e) => e.id !== this.items[index].id);
}
this.set_cookies();
},
if (this.items[index].quantity === 0) {
this.items = this.items.filter((e) => e.id !== this.items[index].id);
}
this.setCookies();
},
/**
* Remove all the items from the basket & cleans the catalog CSS classes
*/
clear_basket() {
this.items = [];
this.set_cookies();
},
/**
* Remove all the items from the basket & cleans the catalog CSS classes
*/
clearBasket() {
this.items = [];
this.setCookies();
},
/**
* Set the cookie in the browser with the basket items
* ! the cookie survives an hour
*/
set_cookies() {
if (this.items.length === 0) {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`;
} else {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.items))};Max-Age=3600`;
}
},
/**
* Set the cookie in the browser with the basket items
* ! the cookie survives an hour
*/
setCookies() {
if (this.items.length === 0) {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`;
} else {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.items))};Max-Age=3600`;
}
},
/**
* Create an item in the basket if it was not already in
* @param {number} id The id of the product to add
* @param {string} name The name of the product
* @param {number} price The unit price of the product
* @returns {BasketItem} The created item
*/
create_item(id, name, price) {
let new_item = {
id: id,
name: name,
quantity: 0,
unit_price: price
};
/**
* Create an item in the basket if it was not already in
* @param {number} id The id of the product to add
* @param {string} name The name of the product
* @param {number} price The unit price of the product
* @returns {BasketItem} The created item
*/
createItem(id, name, price) {
const newItem = {
id,
name,
quantity: 0,
// biome-ignore lint/style/useNamingConvention: used by django backend
unit_price: price,
};
this.items.push(new_item);
this.add(new_item);
this.items.push(newItem);
this.add(newItem);
return new_item;
},
return newItem;
},
/**
* Add an item to the basket.
* This is called when the user click on a button in the catalog
* @param {number} id The id of the product to add
* @param {string} name The name of the product
* @param {number} price The unit price of the product
*/
add_from_catalog(id, name, price) {
let item = this.items.find(e => e.id === id)
/**
* Add an item to the basket.
* This is called when the user click on a button in the catalog
* @param {number} id The id of the product to add
* @param {string} name The name of the product
* @param {number} price The unit price of the product
*/
addFromCatalog(id, name, price) {
let item = this.items.find((e) => e.id === id);
// if the item is not in the basket, we create it
// else we add + 1 to it
if (!item) {
item = this.create_item(id, name, price);
} else {
this.add(item);
}
},
}))
})
// if the item is not in the basket, we create it
// else we add + 1 to it
if (item) {
this.add(item);
} else {
item = this.createItem(id, name, price);
}
},
}));
});

View File

@ -3,77 +3,85 @@
* @enum {number}
*/
const BillingInfoReqState = {
SUCCESS: 1,
FAILURE: 2,
SENDING: 3,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
SUCCESS: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
FAILURE: 2,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
SENDING: 3,
};
document.addEventListener("alpine:init", () => {
Alpine.store("billing_inputs", {
data: et_data,
Alpine.store("billing_inputs", {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
data: etData,
async fill() {
document.getElementById("bank-submit-button").disabled = true;
const res = await fetch(et_data_url);
if (res.ok) {
this.data = await res.json();
document.getElementById("bank-submit-button").disabled = false;
}
},
});
async fill() {
document.getElementById("bank-submit-button").disabled = true;
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
const res = await fetch(etDataUrl);
if (res.ok) {
this.data = await res.json();
document.getElementById("bank-submit-button").disabled = false;
}
},
});
Alpine.data("billing_infos", () => ({
/** @type {BillingInfoReqState | null} */
req_state: null,
Alpine.data("billing_infos", () => ({
/** @type {BillingInfoReqState | null} */
reqState: null,
async send_form() {
this.req_state = BillingInfoReqState.SENDING;
const form = document.getElementById("billing_info_form");
document.getElementById("bank-submit-button").disabled = true;
let payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select"))
.filter((elem) => elem.type !== "submit" && elem.value)
.map((elem) => [elem.name, elem.value]),
);
const res = await fetch(billing_info_url, {
method: "PUT",
body: JSON.stringify(payload),
});
this.req_state = res.ok
? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE;
if (res.status === 422) {
const errors = (await res.json())["detail"].map((err) => err["loc"]).flat();
Array.from(form.querySelectorAll("input"))
.filter((elem) => errors.includes(elem.name))
.forEach((elem) => {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
});
} else if (res.ok) {
Alpine.store("billing_inputs").fill();
}
},
async sendForm() {
this.reqState = BillingInfoReqState.SENDING;
const form = document.getElementById("billing_info_form");
document.getElementById("bank-submit-button").disabled = true;
const payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select"))
.filter((elem) => elem.type !== "submit" && elem.value)
.map((elem) => [elem.name, elem.value]),
);
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
const res = await fetch(billingInfoUrl, {
method: "PUT",
body: JSON.stringify(payload),
});
this.reqState = res.ok
? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE;
if (res.status === 422) {
const errors = (await res.json()).detail.flatMap((err) => err.loc);
for (const elem of Array.from(form.querySelectorAll("input")).filter((elem) =>
errors.includes(elem.name),
)) {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
}
} else if (res.ok) {
Alpine.store("billing_inputs").fill();
}
},
get_alert_color() {
if (this.req_state === BillingInfoReqState.SUCCESS) {
return "green";
}
if (this.req_state === BillingInfoReqState.FAILURE) {
return "red";
}
return "";
},
getAlertColor() {
if (this.reqState === BillingInfoReqState.SUCCESS) {
return "green";
}
if (this.reqState === BillingInfoReqState.FAILURE) {
return "red";
}
return "";
},
get_alert_message() {
if (this.req_state === BillingInfoReqState.SUCCESS) {
return billing_info_success_message;
}
if (this.req_state === BillingInfoReqState.FAILURE) {
return billing_info_failure_message;
}
return "";
},
}));
getAlertMessage() {
if (this.reqState === BillingInfoReqState.SUCCESS) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
return billingInfoSuccessMessage;
}
if (this.reqState === BillingInfoReqState.FAILURE) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined in eboutic_makecommand.jinja
return billingInfoFailureMessage;
}
return "";
},
}));
});

View File

@ -56,11 +56,11 @@
{# Total price #}
<li style="margin-top: 20px">
<span class="item-name"><strong>{% trans %}Basket amount: {% endtrans %}</strong></span>
<span x-text="get_total().toFixed(2) + ' €'" class="item-price"></span>
<span x-text="getTotal().toFixed(2) + ' €'" class="item-price"></span>
</li>
</ul>
<div class="catalog-buttons">
<button @click="clear_basket()" class="btn btn-grey">
<button @click="clearBasket()" class="btn btn-grey">
<i class="fa fa-trash"></i>
{% trans %}Clear{% endtrans %}
</button>
@ -106,7 +106,7 @@
id="{{ p.id }}"
class="product-button"
:class="{selected: items.some((i) => i.id === {{ p.id }})}"
@click='add_from_catalog({{ p.id }}, {{ p.name|tojson }}, {{ p.selling_price }})'
@click='addFromCatalog({{ p.id }}, {{ p.name|tojson }}, {{ p.selling_price }})'
>
{% if p.icon %}
<img class="product-image" src="{{ p.icon.url }}"

View File

@ -56,7 +56,7 @@
<div
class="collapse"
:class="{'shadow': collapsed}"
x-data="{collapsed: !billing_info_exist}"
x-data="{collapsed: !billingInfoExist}"
x-cloak
>
<div class="collapse-header clickable" @click="collapsed = !collapsed">
@ -73,27 +73,27 @@
x-data="billing_infos"
x-show="collapsed"
x-transition.scale.origin.top
@submit.prevent="await send_form()"
@submit.prevent="await sendForm()"
>
{% csrf_token %}
{{ billing_form }}
<br>
<br>
<div
x-show="[BillingInfoReqState.SUCCESS, BillingInfoReqState.FAILURE].includes(req_state)"
x-show="[BillingInfoReqState.SUCCESS, BillingInfoReqState.FAILURE].includes(reqState)"
class="alert"
:class="'alert-' + get_alert_color()"
:class="'alert-' + getAlertColor()"
x-transition
>
<div class="alert-main" x-text="get_alert_message()"></div>
<div class="clickable" @click="req_state = null">
<div class="alert-main" x-text="getAlertMessage()"></div>
<div class="clickable" @click="reqState = null">
<i class="fa fa-close"></i>
</div>
</div>
<input
type="submit" class="btn btn-blue clickable"
value="{% trans %}Validate{% endtrans %}"
:disabled="req_state === BillingInfoReqState.SENDING"
:disabled="reqState === BillingInfoReqState.SENDING"
>
</form>
</div>
@ -141,16 +141,16 @@
{% block script %}
<script>
const billing_info_url = '{{ url("api:put_billing_info", user_id=request.user.id) }}';
const et_data_url = '{{ url("api:etransaction_data") }}';
const billing_info_exist = {{ "true" if billing_infos else "false" }};
const billing_info_success_message = "{% trans %}Billing info registration success{% endtrans %}";
const billing_info_failure_message = "{% trans %}Billing info registration failure{% endtrans %}";
const billingInfoUrl = '{{ url("api:put_billing_info", user_id=request.user.id) }}';
const etDataUrl = '{{ url("api:etransaction_data") }}';
const billingInfoExist = {{ "true" if billing_infos else "false" }};
const billingInfoSuccessMessage = "{% trans %}Billing info registration success{% endtrans %}";
const billingInfoFailureMessage = "{% trans %}Billing info registration failure{% endtrans %}";
{% if billing_infos %}
const et_data = {{ billing_infos|safe }}
const etData = {{ billing_infos|safe }}
{% else %}
const et_data = {}
const etData = {}
{% endif %}
</script>
{{ super() }}