From 6e724a9c74ed420fccfae4efd1c2a5b2d5420d07 Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 30 Jun 2025 18:17:29 +0200 Subject: [PATCH 1/2] extract AlertMessage to its own file --- core/static/bundled/utils/alert-message.ts | 38 +++++++++++++++++++ .../bundled/counter/counter-click-index.ts | 26 +++---------- .../bundled/counter/product-type-index.ts | 33 +++++----------- 3 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 core/static/bundled/utils/alert-message.ts diff --git a/core/static/bundled/utils/alert-message.ts b/core/static/bundled/utils/alert-message.ts new file mode 100644 index 00000000..85d72a2e --- /dev/null +++ b/core/static/bundled/utils/alert-message.ts @@ -0,0 +1,38 @@ +interface AlertParams { + success?: boolean; + duration?: number; +} + +export class AlertMessage { + public open: boolean; + public success: boolean; + public content: string; + private timeoutId?: number; + private readonly defaultDuration: number; + + constructor(params?: { defaultDuration: number }) { + this.open = false; + this.content = ""; + this.timeoutId = null; + this.defaultDuration = params?.defaultDuration ?? 2000; + } + + public display(message: string, params: AlertParams) { + this.clear(); + this.open = true; + this.content = message; + this.success = params.success ?? true; + this.timeoutId = setTimeout(() => { + this.open = false; + this.timeoutId = null; + }, params.duration ?? this.defaultDuration); + } + + public clear() { + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + this.timeoutId = null; + } + this.open = false; + } +} diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts index 99f6bdb7..7a51344b 100644 --- a/counter/static/bundled/counter/counter-click-index.ts +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -1,18 +1,14 @@ import { BasketItem } from "#counter:counter/basket"; import type { CounterConfig, ErrorMessage } from "#counter:counter/types"; import type { CounterProductSelect } from "./components/counter-product-select-index.ts"; +import { AlertMessage } from "#core:utils/alert-message"; document.addEventListener("alpine:init", () => { Alpine.data("counter", (config: CounterConfig) => ({ basket: {} as Record, - errors: [], customerBalance: config.customerBalance, codeField: null as CounterProductSelect | null, - alertMessage: { - content: "", - show: false, - timeout: null, - }, + alertMessage: new AlertMessage({ defaultDuration: 2000 }), init() { // Fill the basket with the initial data @@ -77,22 +73,10 @@ document.addEventListener("alpine:init", () => { return total; }, - showAlertMessage(message: string) { - if (this.alertMessage.timeout !== null) { - clearTimeout(this.alertMessage.timeout); - } - this.alertMessage.content = message; - this.alertMessage.show = true; - this.alertMessage.timeout = setTimeout(() => { - this.alertMessage.show = false; - this.alertMessage.timeout = null; - }, 2000); - }, - addToBasketWithMessage(id: string, quantity: number) { const message = this.addToBasket(id, quantity); if (message.length > 0) { - this.showAlertMessage(message); + this.alertMessage.display(message, { success: false }); } }, @@ -109,7 +93,9 @@ document.addEventListener("alpine:init", () => { finish() { if (this.getBasketSize() === 0) { - this.showAlertMessage(gettext("You can't send an empty basket.")); + this.alertMessage.display(gettext("You can't send an empty basket."), { + success: false, + }); return; } this.$refs.basketForm.submit(); diff --git a/counter/static/bundled/counter/product-type-index.ts b/counter/static/bundled/counter/product-type-index.ts index f200e9b2..e576a583 100644 --- a/counter/static/bundled/counter/product-type-index.ts +++ b/counter/static/bundled/counter/product-type-index.ts @@ -1,15 +1,11 @@ import Alpine from "alpinejs"; import { producttypeReorder } from "#openapi"; +import { AlertMessage } from "#core:utils/alert-message"; document.addEventListener("alpine:init", () => { Alpine.data("productTypesList", () => ({ loading: false, - alertMessage: { - open: false, - success: true, - content: "", - timeout: null, - }, + alertMessage: new AlertMessage({ defaultDuration: 2000 }), async reorder(itemId: number, newPosition: number) { // The sort plugin of Alpine doesn't manage dynamic lists with x-sort @@ -41,23 +37,14 @@ document.addEventListener("alpine:init", () => { }, openAlertMessage(response: Response) { - if (response.ok) { - this.alertMessage.success = true; - this.alertMessage.content = gettext("Products types reordered!"); - } else { - this.alertMessage.success = false; - this.alertMessage.content = interpolate( - gettext("Product type reorganisation failed with status code : %d"), - [response.status], - ); - } - this.alertMessage.open = true; - if (this.alertMessage.timeout !== null) { - clearTimeout(this.alertMessage.timeout); - } - this.alertMessage.timeout = setTimeout(() => { - this.alertMessage.open = false; - }, 2000); + const success = response.ok; + const content = response.ok + ? gettext("Products types reordered!") + : interpolate( + gettext("Product type reorganisation failed with status code : %d"), + [response.status], + ); + this.alertMessage.display(content, { success: success }); this.loading = false; }, })); From a3ac04fc9e79e91b4ae0c5225331f8d9ed124609 Mon Sep 17 00:00:00 2001 From: imperosol Date: Mon, 30 Jun 2025 18:34:30 +0200 Subject: [PATCH 2/2] fix TS types --- core/static/bundled/utils/api.ts | 4 ++-- counter/static/bundled/counter/counter-click-index.ts | 2 +- counter/static/bundled/counter/product-list-index.ts | 2 +- counter/static/bundled/counter/product-type-index.ts | 2 +- counter/static/bundled/counter/types.d.ts | 2 +- package-lock.json | 8 ++++++++ package.json | 5 +++-- sas/static/bundled/sas/album-index.ts | 1 - sas/static/bundled/sas/viewer-index.ts | 7 ++++--- tsconfig.json | 1 + 10 files changed, 22 insertions(+), 12 deletions(-) diff --git a/core/static/bundled/utils/api.ts b/core/static/bundled/utils/api.ts index 95a86e1c..6ed4a53a 100644 --- a/core/static/bundled/utils/api.ts +++ b/core/static/bundled/utils/api.ts @@ -1,5 +1,5 @@ -import type { Client, Options, RequestResult, TDataShape } from "@hey-api/client-fetch"; -import { client } from "#openapi"; +import type { Client, RequestResult, TDataShape } from "#openapi:client"; +import { type Options, client } from "#openapi"; export interface PaginatedResponse { count: number; diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts index 7a51344b..f594cdd8 100644 --- a/counter/static/bundled/counter/counter-click-index.ts +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -1,7 +1,7 @@ +import { AlertMessage } from "#core:utils/alert-message"; import { BasketItem } from "#counter:counter/basket"; import type { CounterConfig, ErrorMessage } from "#counter:counter/types"; import type { CounterProductSelect } from "./components/counter-product-select-index.ts"; -import { AlertMessage } from "#core:utils/alert-message"; document.addEventListener("alpine:init", () => { Alpine.data("counter", (config: CounterConfig) => ({ diff --git a/counter/static/bundled/counter/product-list-index.ts b/counter/static/bundled/counter/product-list-index.ts index a7cd3f86..de0381b9 100644 --- a/counter/static/bundled/counter/product-list-index.ts +++ b/counter/static/bundled/counter/product-list-index.ts @@ -167,7 +167,7 @@ document.addEventListener("alpine:init", () => { }); // if products to download are already in-memory, directly take them. // If not, fetch them. - const products = + const products: ProductSchema[] = this.nbPages > 1 ? await paginated(productSearchProductsDetailed, this.getQueryParams()) : Object.values(this.products).flat(); diff --git a/counter/static/bundled/counter/product-type-index.ts b/counter/static/bundled/counter/product-type-index.ts index e576a583..37de445d 100644 --- a/counter/static/bundled/counter/product-type-index.ts +++ b/counter/static/bundled/counter/product-type-index.ts @@ -1,6 +1,6 @@ +import { AlertMessage } from "#core:utils/alert-message"; import Alpine from "alpinejs"; import { producttypeReorder } from "#openapi"; -import { AlertMessage } from "#core:utils/alert-message"; document.addEventListener("alpine:init", () => { Alpine.data("productTypesList", () => ({ diff --git a/counter/static/bundled/counter/types.d.ts b/counter/static/bundled/counter/types.d.ts index 4a22a916..18fea258 100644 --- a/counter/static/bundled/counter/types.d.ts +++ b/counter/static/bundled/counter/types.d.ts @@ -1,4 +1,4 @@ -type ErrorMessage = string; +export type ErrorMessage = string; export interface InitialFormData { /* Used to refill the form when the backend raises an error */ diff --git a/package-lock.json b/package-lock.json index 5ab0eeb9..44e71934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@types/cytoscape-cxtmenu": "^3.4.4", "@types/cytoscape-klay": "^3.1.4", "@types/jquery": "^3.5.31", + "@types/js-cookie": "^3.0.6", "typescript": "^5.8.3", "vite": "^6.2.5", "vite-bundle-visualizer": "^1.2.1", @@ -2865,6 +2866,13 @@ "@types/sizzle": "*" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/package.json b/package.json index 3abaca2b..0e986ab9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "openapi": "openapi-ts", "analyse-dev": "vite-bundle-visualizer --mode development", "analyse-prod": "vite-bundle-visualizer --mode production", - "check": "biome check --write" + "check": "tsc && biome check --write" }, "keywords": [], "author": "", @@ -30,9 +30,10 @@ "@hey-api/openapi-ts": "^0.73.0", "@rollup/plugin-inject": "^5.0.5", "@types/alpinejs": "^3.13.10", - "@types/jquery": "^3.5.31", "@types/cytoscape-cxtmenu": "^3.4.4", "@types/cytoscape-klay": "^3.1.4", + "@types/jquery": "^3.5.31", + "@types/js-cookie": "^3.0.6", "typescript": "^5.8.3", "vite": "^6.2.5", "vite-bundle-visualizer": "^1.2.1", diff --git a/sas/static/bundled/sas/album-index.ts b/sas/static/bundled/sas/album-index.ts index 32f0f02f..b56b8e02 100644 --- a/sas/static/bundled/sas/album-index.ts +++ b/sas/static/bundled/sas/album-index.ts @@ -83,7 +83,6 @@ document.addEventListener("alpine:init", () => { Alpine.data("pictureUpload", (albumId: number) => ({ errors: [] as string[], - pictures: [], sending: false, progress: null as HTMLProgressElement, diff --git a/sas/static/bundled/sas/viewer-index.ts b/sas/static/bundled/sas/viewer-index.ts index 59718b26..0eec9d36 100644 --- a/sas/static/bundled/sas/viewer-index.ts +++ b/sas/static/bundled/sas/viewer-index.ts @@ -1,3 +1,4 @@ +import type { UserAjaxSelect } from "#core:core/components/ajax-select-index"; import { paginated } from "#core:utils/api"; import { exportToHtml } from "#core:utils/globals"; import { History } from "#core:utils/history"; @@ -130,7 +131,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { currentPicture: { // biome-ignore lint/style/useNamingConvention: api is in snake_case is_moderated: true, - id: null, + id: null as number, name: "", // biome-ignore lint/style/useNamingConvention: api is in snake_case display_name: "", @@ -142,7 +143,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { full_size_url: "", owner: "", date: new Date(), - identifications: [], + identifications: [] as IdentifiedUserSchema[], }, /** * The picture which will be displayed next if the user press the "next" button @@ -155,7 +156,7 @@ exportToHtml("loadViewer", (config: ViewerConfig) => { /** * The select2 component used to identify users **/ - selector: undefined, + selector: undefined as UserAjaxSelect, /** * Error message when a moderation operation fails **/ diff --git a/tsconfig.json b/tsconfig.json index 1f47a39e..59a3642f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "types": ["jquery", "alpinejs"], "paths": { "#openapi": ["./staticfiles/generated/openapi/client/index.ts"], + "#openapi:*": ["./staticfiles/generated/openapi/client/*"], "#core:*": ["./core/static/bundled/*"], "#pedagogy:*": ["./pedagogy/static/bundled/*"], "#counter:*": ["./counter/static/bundled/*"],