From c50a27bea53dc1e92704f2d3513bdddc7d16a3de Mon Sep 17 00:00:00 2001 From: Olivier <1+olivier@noreply.raspot.in> Date: Tue, 18 Aug 2026 16:15:08 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"first-login-ui/src"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- first-login-ui/src/Main.qml | 373 +++++++++++++++++++++++++++--------- 1 file changed, 278 insertions(+), 95 deletions(-) diff --git a/first-login-ui/src/Main.qml b/first-login-ui/src/Main.qml index 1bd850e..d73ba41 100644 --- a/first-login-ui/src/Main.qml +++ b/first-login-ui/src/Main.qml @@ -19,18 +19,53 @@ Kirigami.ApplicationWindow { { "title": "Terminé", "subtitle": "Poste prêt" } ] + property bool passwordStepValid: + currentPassword.text.trim().length > 0 && + newPassword.text.length > 0 && + confirmPassword.text.length > 0 && + newPassword.text === confirmPassword.text + + property bool pinStepValid: + currentPin.text.length >= 4 && + newPin.text.length >= 4 && + confirmPin.text.length >= 4 && + newPin.text === confirmPin.text + + property bool workflowComplete: passwordStepValid && pinStepValid + property bool closeAttempted: false + function goNext() { - if (currentStep < steps.length - 1) { + // Défense en profondeur : même si un bouton était activé par erreur, + // on ne franchit jamais une étape dont les champs requis sont incomplets. + if (currentStep === 1 && !passwordStepValid) + return + if (currentStep === 2 && !pinStepValid) + return + + if (currentStep < steps.length - 1) currentStep += 1 - } } function goBack() { - if (currentStep > 0) { + if (currentStep > 0) currentStep -= 1 + } + + onClosing: function(close) { + if (!workflowComplete) { + close.accepted = false + closeAttempted = true + closeWarningTimer.restart() } } + Timer { + id: closeWarningTimer + interval: 3500 + repeat: false + onTriggered: root.closeAttempted = false + } + // Indicateur volontairement informatif : il ne bloque jamais // la validation du mot de passe. La longueur est privilégiée, // la diversité des caractères apporte un bonus visuel. @@ -89,11 +124,6 @@ Kirigami.ApplicationWindow { return "Saisissez votre nouveau mot de passe pour obtenir une indication de robustesse." } - Shortcut { - sequence: "Ctrl+Q" - onActivated: Qt.quit() - } - component PrimaryButton: Controls.Button { id: control implicitHeight: 48 @@ -196,6 +226,29 @@ Kirigami.ApplicationWindow { } } + component FieldLabel: RowLayout { + id: fieldLabel + + property string iconName: "dialog-password" + property string labelText: "" + + spacing: 8 + + Kirigami.Icon { + source: fieldLabel.iconName + implicitWidth: 17 + implicitHeight: 17 + color: "#64748b" + } + + Controls.Label { + text: fieldLabel.labelText + color: "#334155" + font.pixelSize: 13 + font.weight: Font.DemiBold + } + } + component StepDot: Rectangle { required property int stepIndex @@ -301,37 +354,49 @@ Kirigami.ApplicationWindow { anchors.fill: parent spacing: 16 - ColumnLayout { - Layout.alignment: Qt.AlignTop - spacing: 0 + // Colonne de largeur fixe : tous les marqueurs et tous + // les titres commencent exactement au même endroit. + Item { + Layout.preferredWidth: 42 + Layout.fillHeight: true - StepDot { stepIndex: index } + StepDot { + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + stepIndex: index + } Rectangle { visible: index < root.steps.length - 1 - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 2 - Layout.preferredHeight: 36 + anchors.top: parent.top + anchors.topMargin: 34 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: 2 color: index < root.currentStep ? "#22c55e" : "#28517e" } } ColumnLayout { Layout.fillWidth: true - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignVCenter spacing: 3 Controls.Label { + Layout.fillWidth: true text: modelData.title color: index === root.currentStep ? "white" : "#c1d3e8" font.pixelSize: 15 font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium + horizontalAlignment: Text.AlignLeft } Controls.Label { + Layout.fillWidth: true text: modelData.subtitle color: "#7699c2" font.pixelSize: 12 + horizontalAlignment: Text.AlignLeft } } } @@ -382,11 +447,22 @@ Kirigami.ApplicationWindow { Item { Layout.preferredHeight: 20 } - Controls.Label { - text: "Ctrl+Q : quitter l'aperçu" - color: "#6289b6" - font.pixelSize: 11 + RowLayout { Layout.alignment: Qt.AlignHCenter + spacing: 6 + + Kirigami.Icon { + source: "lock" + implicitWidth: 14 + implicitHeight: 14 + color: "#6289b6" + } + + Controls.Label { + text: "Assistant obligatoire jusqu'à la fin" + color: "#6289b6" + font.pixelSize: 11 + } } } } @@ -431,9 +507,37 @@ Kirigami.ApplicationWindow { Item { Layout.fillWidth: true } - SecondaryButton { - text: "Quitter l'aperçu" - onClicked: Qt.quit() + Rectangle { + implicitWidth: lockStatusRow.implicitWidth + 24 + implicitHeight: 34 + radius: 17 + color: root.closeAttempted ? "#fef2f2" : "#f8fafc" + border.width: 1 + border.color: root.closeAttempted ? "#fecaca" : "#cbd5e1" + + RowLayout { + id: lockStatusRow + anchors.centerIn: parent + spacing: 7 + + Kirigami.Icon { + source: root.workflowComplete ? "lock-open" : "lock" + implicitWidth: 16 + implicitHeight: 16 + color: root.closeAttempted ? "#dc2626" : "#64748b" + } + + Controls.Label { + text: root.closeAttempted + ? "Terminez la configuration avant de quitter" + : root.workflowComplete + ? "Configuration complète" + : "Configuration en cours" + color: root.closeAttempted ? "#b91c1c" : "#475569" + font.pixelSize: 11 + font.weight: Font.DemiBold + } + } } } @@ -597,20 +701,47 @@ Kirigami.ApplicationWindow { anchors.fill: parent spacing: 18 - Controls.Label { - text: "Mot de passe local" - color: "#0f172a" - font.pixelSize: 29 - font.weight: Font.Bold - } + RowLayout { + Layout.fillWidth: true + spacing: 20 - Controls.Label { - Layout.maximumWidth: 720 - text: "Ce mot de passe reste disponible comme solution de secours si votre YubiKey est momentanément indisponible." - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 15 - lineHeight: 1.2 + Rectangle { + Layout.preferredWidth: 80 + Layout.preferredHeight: 80 + radius: 22 + color: "#eff6ff" + border.width: 1 + border.color: "#bfdbfe" + + Kirigami.Icon { + anchors.centerIn: parent + source: "dialog-password" + implicitWidth: 38 + implicitHeight: 38 + color: "#2563eb" + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 5 + + Controls.Label { + text: "Mot de passe local" + color: "#0f172a" + font.pixelSize: 29 + font.weight: Font.Bold + } + + Controls.Label { + Layout.fillWidth: true + text: "Ce mot de passe reste disponible comme solution de secours si votre YubiKey est momentanément indisponible." + wrapMode: Text.WordWrap + color: "#64748b" + font.pixelSize: 15 + lineHeight: 1.2 + } + } } Rectangle { @@ -626,11 +757,11 @@ Kirigami.ApplicationWindow { anchors.margins: 16 spacing: 12 - Controls.Label { - text: "ℹ" + Kirigami.Icon { + source: "emblem-information" + implicitWidth: 22 + implicitHeight: 22 color: "#2563eb" - font.pixelSize: 22 - font.weight: Font.Bold } Controls.Label { @@ -645,11 +776,9 @@ Kirigami.ApplicationWindow { Item { Layout.preferredHeight: 4 } - Controls.Label { - text: "Mot de passe temporaire actuel" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "dialog-password" + labelText: "Mot de passe temporaire actuel" } SecretField { @@ -657,11 +786,9 @@ Kirigami.ApplicationWindow { placeholderText: "Saisissez le mot de passe temporaire" } - Controls.Label { - text: "Nouveau mot de passe" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "document-edit" + labelText: "Nouveau mot de passe" } SecretField { @@ -690,6 +817,14 @@ Kirigami.ApplicationWindow { RowLayout { Layout.fillWidth: true + spacing: 8 + + Kirigami.Icon { + source: "security-high" + implicitWidth: 17 + implicitHeight: 17 + color: root.passwordStrengthColor(strengthCard.strengthLevel) + } Controls.Label { text: "Robustesse du mot de passe" @@ -747,11 +882,9 @@ Kirigami.ApplicationWindow { font.pixelSize: 11 } - Controls.Label { - text: "Confirmation" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "dialog-ok-apply" + labelText: "Confirmation" } SecretField { @@ -780,7 +913,7 @@ Kirigami.ApplicationWindow { PrimaryButton { text: "Continuer" - enabled: newPassword.text.length > 0 && newPassword.text === confirmPassword.text + enabled: root.passwordStepValid opacity: enabled ? 1.0 : 0.45 onClicked: root.goNext() } @@ -865,11 +998,9 @@ Kirigami.ApplicationWindow { } } - Controls.Label { - text: "PIN temporaire actuel" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "dialog-password" + labelText: "PIN temporaire actuel" } SecretField { @@ -878,11 +1009,9 @@ Kirigami.ApplicationWindow { inputMethodHints: Qt.ImhDigitsOnly } - Controls.Label { - text: "Nouveau PIN" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "security-high" + labelText: "Nouveau PIN" } SecretField { @@ -891,11 +1020,9 @@ Kirigami.ApplicationWindow { inputMethodHints: Qt.ImhDigitsOnly } - Controls.Label { - text: "Confirmation du nouveau PIN" - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold + FieldLabel { + iconName: "dialog-ok-apply" + labelText: "Confirmation du nouveau PIN" } SecretField { @@ -925,7 +1052,7 @@ Kirigami.ApplicationWindow { PrimaryButton { text: "Finaliser" - enabled: newPin.text.length >= 4 && newPin.text === confirmPin.text + enabled: root.pinStepValid opacity: enabled ? 1.0 : 0.45 onClicked: root.goNext() } @@ -952,12 +1079,12 @@ Kirigami.ApplicationWindow { border.width: 1 border.color: "#86efac" - Controls.Label { + Kirigami.Icon { anchors.centerIn: parent - text: "✓" + source: "dialog-ok-apply" + implicitWidth: 52 + implicitHeight: 52 color: "#16a34a" - font.pixelSize: 50 - font.weight: Font.Bold } } @@ -983,28 +1110,82 @@ Kirigami.ApplicationWindow { Item { Layout.preferredHeight: 12 } - Rectangle { - Layout.preferredWidth: 520 - Layout.preferredHeight: 112 + RowLayout { Layout.alignment: Qt.AlignHCenter - radius: 16 - color: "#f8fafc" - border.width: 1 - border.color: "#e2e8f0" + spacing: 12 - ColumnLayout { - anchors.fill: parent - anchors.margins: 18 - spacing: 10 + Repeater { + model: [ + { "icon": "dialog-password", "title": "Mot de passe", "text": "Accès local renseigné" }, + { "icon": "security-high", "title": "YubiKey", "text": "PIN personnel renseigné" }, + { "icon": "computer-laptop", "title": "Poste", "text": "Configuration finalisée" } + ] - RowLayout { - Controls.Label { text: "✓"; color: "#16a34a"; font.pixelSize: 17; font.weight: Font.Bold } - Controls.Label { text: "Mot de passe personnel configuré"; color: "#334155"; font.pixelSize: 14 } - } + Rectangle { + required property var modelData + Layout.preferredWidth: 205 + Layout.preferredHeight: 126 + radius: 16 + color: "#f8fafc" + border.width: 1 + border.color: "#e2e8f0" - RowLayout { - Controls.Label { text: "✓"; color: "#16a34a"; font.pixelSize: 17; font.weight: Font.Bold } - Controls.Label { text: "PIN personnel YubiKey configuré"; color: "#334155"; font.pixelSize: 14 } + ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 7 + + RowLayout { + Layout.fillWidth: true + + Rectangle { + Layout.preferredWidth: 38 + Layout.preferredHeight: 38 + radius: 11 + color: "#eff6ff" + + Kirigami.Icon { + anchors.centerIn: parent + source: modelData.icon + implicitWidth: 21 + implicitHeight: 21 + color: "#2563eb" + } + } + + Item { Layout.fillWidth: true } + + Rectangle { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + radius: 13 + color: "#dcfce7" + + Kirigami.Icon { + anchors.centerIn: parent + source: "dialog-ok-apply" + implicitWidth: 15 + implicitHeight: 15 + color: "#16a34a" + } + } + } + + Controls.Label { + text: modelData.title + color: "#0f172a" + font.pixelSize: 14 + font.weight: Font.DemiBold + } + + Controls.Label { + Layout.fillWidth: true + text: modelData.text + color: "#64748b" + font.pixelSize: 11 + wrapMode: Text.WordWrap + } + } } } } @@ -1021,7 +1202,9 @@ Kirigami.ApplicationWindow { } PrimaryButton { - text: "Fermer l'aperçu" + text: "Terminer" + enabled: root.workflowComplete + opacity: enabled ? 1.0 : 0.45 onClicked: Qt.quit() } }