From 7b4ffd6516e221067fc788f33629ff25079f3cc9 Mon Sep 17 00:00:00 2001
From: Davide <keeshux@gmail.com>
Date: Mon, 10 Feb 2025 22:50:55 +0100
Subject: [PATCH 1/5] Delete profile from editor

---
 .../EnvironmentValues+Extensions.swift        | 13 +++++
 .../Views/App/ProfileContextMenu.swift        |  3 +-
 .../Views/App/ProfileRemoveButton.swift       | 27 ++++++++--
 .../Views/Profile/ModuleViewModifier.swift    |  4 +-
 .../Views/Profile/ProfileActionsSection.swift | 54 +++++++++++++++++++
 .../Views/Profile/ProfileCoordinator.swift    |  3 ++
 .../{UUIDSection.swift => UUIDText.swift}     | 22 ++++----
 .../Profile/iOS/ProfileEditView+iOS.swift     |  7 ++-
 .../macOS/ProfileGeneralView+macOS.swift      |  7 ++-
 .../macOS/ProfileSplitView+macOS.swift        |  4 ++
 10 files changed, 124 insertions(+), 20 deletions(-)
 create mode 100644 Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
 rename Packages/App/Sources/AppUIMain/Views/Profile/{UUIDSection.swift => UUIDText.swift} (72%)

diff --git a/Packages/App/Sources/AppUIMain/Extensions/EnvironmentValues+Extensions.swift b/Packages/App/Sources/AppUIMain/Extensions/EnvironmentValues+Extensions.swift
index c2680f7dd..cf23fc4ac 100644
--- a/Packages/App/Sources/AppUIMain/Extensions/EnvironmentValues+Extensions.swift
+++ b/Packages/App/Sources/AppUIMain/Extensions/EnvironmentValues+Extensions.swift
@@ -34,8 +34,21 @@ extension EnvironmentValues {
             self[NavigationPathKey.self] = newValue
         }
     }
+
+    var dismissProfile: () -> Void {
+        get {
+            self[DismissProfileKey.self]
+        }
+        set {
+            self[DismissProfileKey.self] = newValue
+        }
+    }
 }
 
 private struct NavigationPathKey: EnvironmentKey {
     static let defaultValue: Binding<NavigationPath> = .constant(NavigationPath())
 }
+
+private struct DismissProfileKey: EnvironmentKey {
+    static let defaultValue: () -> Void = {}
+}
diff --git a/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift b/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
index 5b695cb7c..8fd9a5748 100644
--- a/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
+++ b/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
@@ -124,7 +124,8 @@ private extension ProfileContextMenu {
     var profileRemoveButton: some View {
         ProfileRemoveButton(
             profileManager: profileManager,
-            preview: preview
+            profileId: preview.id,
+            profileName: preview.name
         ) {
             ThemeImageLabel(Strings.Global.Actions.remove, .contextRemove)
         }
diff --git a/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift b/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift
index 41f73a386..ada943a55 100644
--- a/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift
+++ b/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift
@@ -24,22 +24,41 @@
 //
 
 import CommonLibrary
+import PassepartoutKit
 import SwiftUI
 
 struct ProfileRemoveButton<Label>: View where Label: View {
+
+    @Environment(\.dismissProfile)
+    private var dismissProfile
+
     let profileManager: ProfileManager
 
-    let preview: ProfilePreview
+    let profileId: Profile.ID
+
+    let profileName: String
 
     let label: () -> Label
 
+    @State
+    private var isConfirming = false
+
     var body: some View {
         Button(role: .destructive) {
-            Task {
-                await profileManager.remove(withId: preview.id)
-            }
+            isConfirming = true
         } label: {
             label()
         }
+        .themeConfirmation(
+            isPresented: $isConfirming,
+            title: Strings.Global.Actions.delete,
+            isDestructive: true,
+            action: {
+                Task {
+                    dismissProfile()
+                    await profileManager.remove(withId: profileId)
+                }
+            }
+        )
     }
 }
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift
index aa326e2dc..6a242437c 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ModuleViewModifier.swift
@@ -42,7 +42,9 @@ struct ModuleViewModifier<T>: ViewModifier where T: ModuleBuilder & Equatable {
             content
 #if DEBUG
             if !isUITesting {
-                UUIDSection(uuid: draft.id)
+                Section {
+                    UUIDText(uuid: draft.id)
+                }
             }
 #endif
         }
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
new file mode 100644
index 000000000..29de715a4
--- /dev/null
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
@@ -0,0 +1,54 @@
+//
+//  ProfileActionsSection.swift
+//  Passepartout
+//
+//  Created by Davide De Rosa on 2/10/25.
+//  Copyright (c) 2025 Davide De Rosa. All rights reserved.
+//
+//  https://github.com/passepartoutvpn
+//
+//  This file is part of Passepartout.
+//
+//  Passepartout is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  Passepartout is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with Passepartout.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import CommonLibrary
+import PassepartoutKit
+import SwiftUI
+
+struct ProfileActionsSection: View {
+    let profileManager: ProfileManager
+
+    let profileEditor: ProfileEditor
+
+    var body: some View {
+        UUIDText(uuid: profileId)
+            .asSectionWithTrailingContent {
+                if profileManager.profile(withId: profileId) != nil {
+                    ProfileRemoveButton(
+                        profileManager: profileManager,
+                        profileId: profileId,
+                        profileName: profileEditor.profile.name,
+                        label: {
+                            Text(Strings.Global.Actions.delete)
+                        }
+                    )
+                }
+            }
+    }
+
+    private var profileId: Profile.ID {
+        profileEditor.profile.id
+    }
+}
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileCoordinator.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileCoordinator.swift
index c6634d714..f2fe53cd0 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileCoordinator.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileCoordinator.swift
@@ -70,6 +70,7 @@ struct ProfileCoordinator: View {
     var body: some View {
         contentView
             .modifier(PaywallModifier(reason: $paywallReason))
+            .environment(\.dismissProfile, onDismiss)
             .withErrorHandler(errorHandler)
     }
 }
@@ -80,6 +81,7 @@ private extension ProfileCoordinator {
     var contentView: some View {
 #if os(iOS)
         ProfileEditView(
+            profileManager: profileManager,
             profileEditor: profileEditor,
             initialModuleId: initialModuleId,
             moduleViewFactory: moduleViewFactory,
@@ -95,6 +97,7 @@ private extension ProfileCoordinator {
         .themeNavigationStack(path: $path)
 #else
         ProfileSplitView(
+            profileManager: profileManager,
             profileEditor: profileEditor,
             initialModuleId: initialModuleId,
             moduleViewFactory: moduleViewFactory,
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/UUIDSection.swift b/Packages/App/Sources/AppUIMain/Views/Profile/UUIDText.swift
similarity index 72%
rename from Packages/App/Sources/AppUIMain/Views/Profile/UUIDSection.swift
rename to Packages/App/Sources/AppUIMain/Views/Profile/UUIDText.swift
index 5fa5c5122..12ad0ef91 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/UUIDSection.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/UUIDText.swift
@@ -1,5 +1,5 @@
 //
-//  UUIDSection.swift
+//  UUIDText.swift
 //  Passepartout
 //
 //  Created by Davide De Rosa on 11/4/24.
@@ -25,19 +25,17 @@
 
 import SwiftUI
 
-struct UUIDSection: View {
+struct UUIDText: View {
     let uuid: UUID
 
     var body: some View {
-        Section {
-            ThemeCopiableText(
-                title: Strings.Unlocalized.uuid,
-                value: uuid,
-                valueView: {
-                    Text($0.flatString.localizedDescription(style: .quartets))
-                        .monospaced()
-                }
-            )
-        }
+        ThemeCopiableText(
+            title: Strings.Unlocalized.uuid,
+            value: uuid,
+            valueView: {
+                Text($0.flatString.localizedDescription(style: .quartets))
+                    .monospaced()
+            }
+        )
     }
 }
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift b/Packages/App/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift
index 620e78e51..d8200b1cf 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/iOS/ProfileEditView+iOS.swift
@@ -31,6 +31,7 @@ import PassepartoutKit
 import SwiftUI
 
 struct ProfileEditView: View, Routable {
+    let profileManager: ProfileManager
 
     @ObservedObject
     var profileEditor: ProfileEditor
@@ -62,7 +63,10 @@ struct ProfileEditView: View, Routable {
                 paywallReason: $paywallReason
             )
             ProfileBehaviorSection(profileEditor: profileEditor)
-            UUIDSection(uuid: profileEditor.profile.id)
+            ProfileActionsSection(
+                profileManager: profileManager,
+                profileEditor: profileEditor
+            )
         }
         .toolbar(content: toolbarContent)
         .navigationTitle(Strings.Global.Nouns.profile)
@@ -187,6 +191,7 @@ private extension ProfileEditView {
 #Preview {
     NavigationStack {
         ProfileEditView(
+            profileManager: .forPreviews,
             profileEditor: ProfileEditor(profile: .newMockProfile()),
             initialModuleId: nil,
             moduleViewFactory: DefaultModuleViewFactory(registry: Registry()),
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileGeneralView+macOS.swift b/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileGeneralView+macOS.swift
index dcecfb120..06f1a5527 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileGeneralView+macOS.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileGeneralView+macOS.swift
@@ -29,6 +29,7 @@ import CommonLibrary
 import SwiftUI
 
 struct ProfileGeneralView: View {
+    let profileManager: ProfileManager
 
     @ObservedObject
     var profileEditor: ProfileEditor
@@ -44,7 +45,10 @@ struct ProfileGeneralView: View {
                 paywallReason: $paywallReason
             )
             ProfileBehaviorSection(profileEditor: profileEditor)
-            UUIDSection(uuid: profileEditor.profile.id)
+            ProfileActionsSection(
+                profileManager: profileManager,
+                profileEditor: profileEditor
+            )
         }
         .themeForm()
     }
@@ -52,6 +56,7 @@ struct ProfileGeneralView: View {
 
 #Preview {
     ProfileGeneralView(
+        profileManager: .forPreviews,
         profileEditor: ProfileEditor(),
         paywallReason: .constant(nil)
     )
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileSplitView+macOS.swift b/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileSplitView+macOS.swift
index 34663e464..895ddf665 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileSplitView+macOS.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/macOS/ProfileSplitView+macOS.swift
@@ -31,6 +31,8 @@ import PassepartoutKit
 import SwiftUI
 
 struct ProfileSplitView: View, Routable {
+    let profileManager: ProfileManager
+
     let profileEditor: ProfileEditor
 
     let initialModuleId: UUID?
@@ -124,6 +126,7 @@ private extension ProfileSplitView {
         switch detail {
         case .general:
             ProfileGeneralView(
+                profileManager: profileManager,
                 profileEditor: profileEditor,
                 paywallReason: $paywallReason
             )
@@ -140,6 +143,7 @@ private extension ProfileSplitView {
 
 #Preview {
     ProfileSplitView(
+        profileManager: .forPreviews,
         profileEditor: ProfileEditor(profile: .newMockProfile()),
         initialModuleId: nil,
         moduleViewFactory: DefaultModuleViewFactory(registry: Registry()),

From 699a2db487f269b197ab8319563a1dbf7e06e7a7 Mon Sep 17 00:00:00 2001
From: Davide <keeshux@gmail.com>
Date: Mon, 10 Feb 2025 23:33:13 +0100
Subject: [PATCH 2/5] Translate

---
 .../Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift | 2 +-
 Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift      | 2 ++
 .../Sources/UILibrary/Resources/de.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/el.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/en.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/es.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/fr.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/it.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/nl.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/pl.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/pt.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/ru.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/sv.lproj/Localizable.strings    | 1 +
 .../Sources/UILibrary/Resources/uk.lproj/Localizable.strings    | 1 +
 .../UILibrary/Resources/zh-Hans.lproj/Localizable.strings       | 1 +
 15 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
index 29de715a4..8245f25d9 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
@@ -41,7 +41,7 @@ struct ProfileActionsSection: View {
                         profileId: profileId,
                         profileName: profileEditor.profile.name,
                         label: {
-                            Text(Strings.Global.Actions.delete)
+                            Text(Strings.Views.Profile.Rows.deleteProfile)
                         }
                     )
                 }
diff --git a/Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift b/Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift
index cc22c28dc..57853eee3 100644
--- a/Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift
+++ b/Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift
@@ -863,6 +863,8 @@ public enum Strings {
       public enum Rows {
         /// Add module
         public static let addModule = Strings.tr("Localizable", "views.profile.rows.add_module", fallback: "Add module")
+        /// Delete profile
+        public static let deleteProfile = Strings.tr("Localizable", "views.profile.rows.delete_profile", fallback: "Delete profile")
       }
       public enum Sections {
         public enum Name {
diff --git a/Packages/App/Sources/UILibrary/Resources/de.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/de.lproj/Localizable.strings
index 01eeed2c6..7a4c7b590 100644
--- a/Packages/App/Sources/UILibrary/Resources/de.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/de.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Trotzdem speichern";
 "views.profile.module_list.section.footer" = "Tippen Sie auf Module, um ihre Einstellungen zu bearbeiten. Module können gezogen werden, um die Priorität festzulegen.";
 "views.profile.rows.add_module" = "Modul hinzufügen";
+"views.profile.rows.delete_profile" = "Profil löschen";
 "views.profile.sections.name.footer" = "Verwenden Sie diesen Namen, um Ihre VPN-Automationen in der Kurzbefehle-App zu erstellen.";
 "views.providers.clear_filters" = "Filter löschen";
 "views.providers.last_updated" = "Zuletzt aktualisiert am %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/el.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/el.lproj/Localizable.strings
index 293a96a3c..2b63cf074 100644
--- a/Packages/App/Sources/UILibrary/Resources/el.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/el.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Αποθήκευση ούτως ή άλλως";
 "views.profile.module_list.section.footer" = "Πατήστε σε ένα module για να επεξεργαστείτε τις ρυθμίσεις του. Τα modules μπορούν να μετακινηθούν για να καθορίσετε την προτεραιότητά τους.";
 "views.profile.rows.add_module" = "Προσθήκη μονάδας";
+"views.profile.rows.delete_profile" = "Διαγραφή προφίλ";
 "views.profile.sections.name.footer" = "Χρησιμοποιήστε αυτό το όνομα για να δημιουργήσετε αυτοματισμούς VPN στην εφαρμογή Συντομεύσεις.";
 "views.providers.clear_filters" = "Καθαρισμός φίλτρων";
 "views.providers.last_updated" = "Τελευταία ενημέρωση στις %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/en.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/en.lproj/Localizable.strings
index ab3c90081..649099ace 100644
--- a/Packages/App/Sources/UILibrary/Resources/en.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/en.lproj/Localizable.strings
@@ -110,6 +110,7 @@
 
 "views.profile.sections.name.footer" = "Use this name to create your VPN automations from the Shortcuts app.";
 "views.profile.rows.add_module" = "Add module";
+"views.profile.rows.delete_profile" = "Delete profile";
 "views.profile.module_list.section.footer" = "Tap modules to edit their settings. Modules may be dragged to determine priority.";
 "views.profile.alerts.purchase.buttons.ok" = "Save anyway";
 
diff --git a/Packages/App/Sources/UILibrary/Resources/es.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/es.lproj/Localizable.strings
index 344d60d68..54848c954 100644
--- a/Packages/App/Sources/UILibrary/Resources/es.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/es.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Guardar de todos modos";
 "views.profile.module_list.section.footer" = "Toca los módulos para editar su configuración. Se pueden arrastrar para establecer la prioridad.";
 "views.profile.rows.add_module" = "Añadir módulo";
+"views.profile.rows.delete_profile" = "Eliminar perfil";
 "views.profile.sections.name.footer" = "Usa este nombre para crear automatizaciones VPN en la app Atajos.";
 "views.providers.clear_filters" = "Borrar filtros";
 "views.providers.last_updated" = "Última actualización el %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/fr.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/fr.lproj/Localizable.strings
index 83dd5529a..054239173 100644
--- a/Packages/App/Sources/UILibrary/Resources/fr.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/fr.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Enregistrer quand même";
 "views.profile.module_list.section.footer" = "Touchez les modules pour modifier leurs paramètres. Ils peuvent être glissés pour définir leur priorité.";
 "views.profile.rows.add_module" = "Ajouter un module";
+"views.profile.rows.delete_profile" = "Supprimer le profil";
 "views.profile.sections.name.footer" = "Utilisez ce nom pour créer vos automatisations VPN dans l'app Raccourcis.";
 "views.providers.clear_filters" = "Effacer les filtres";
 "views.providers.last_updated" = "Dernière mise à jour le %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/it.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/it.lproj/Localizable.strings
index 125949598..e547c214c 100644
--- a/Packages/App/Sources/UILibrary/Resources/it.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/it.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Salva comunque";
 "views.profile.module_list.section.footer" = "Tocca i moduli per modificarne le impostazioni. Possono essere trascinati per determinare la priorità.";
 "views.profile.rows.add_module" = "Aggiungi modulo";
+"views.profile.rows.delete_profile" = "Elimina profilo";
 "views.profile.sections.name.footer" = "Usa questo nome per creare le automazioni VPN nell'app Comandi Rapidi.";
 "views.providers.clear_filters" = "Cancella filtri";
 "views.providers.last_updated" = "Ultimo aggiornamento il %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/nl.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/nl.lproj/Localizable.strings
index 38381a596..e38adf6d6 100644
--- a/Packages/App/Sources/UILibrary/Resources/nl.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/nl.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Toch opslaan";
 "views.profile.module_list.section.footer" = "Tik op modules om hun instellingen te bewerken. Modules kunnen worden versleept om de prioriteit te bepalen.";
 "views.profile.rows.add_module" = "Module toevoegen";
+"views.profile.rows.delete_profile" = "Verwijder profiel";
 "views.profile.sections.name.footer" = "Gebruik deze naam om uw VPN-automatiseringen in de app Opdrachten te maken.";
 "views.providers.clear_filters" = "Filters wissen";
 "views.providers.last_updated" = "Laatst bijgewerkt op %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/pl.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/pl.lproj/Localizable.strings
index 909da4f25..f59816c87 100644
--- a/Packages/App/Sources/UILibrary/Resources/pl.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/pl.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Zapisz mimo to";
 "views.profile.module_list.section.footer" = "Stuknij moduły, aby edytować ich ustawienia. Można je przeciągnąć, aby ustalić priorytet.";
 "views.profile.rows.add_module" = "Dodaj moduł";
+"views.profile.rows.delete_profile" = "Usuń profil";
 "views.profile.sections.name.footer" = "Użyj tej nazwy, aby tworzyć automatyzacje VPN w aplikacji Skróty.";
 "views.providers.clear_filters" = "Wyczyść filtry";
 "views.providers.last_updated" = "Ostatnia aktualizacja: %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/pt.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/pt.lproj/Localizable.strings
index a46ad65c2..c247705ee 100644
--- a/Packages/App/Sources/UILibrary/Resources/pt.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/pt.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Salvar assim mesmo";
 "views.profile.module_list.section.footer" = "Toque nos módulos para editar suas configurações. Eles podem ser arrastados para definir a prioridade.";
 "views.profile.rows.add_module" = "Adicionar módulo";
+"views.profile.rows.delete_profile" = "Excluir perfil";
 "views.profile.sections.name.footer" = "Use este nome para criar automações VPN no app Atalhos.";
 "views.providers.clear_filters" = "Limpar filtros";
 "views.providers.last_updated" = "Última atualização em %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/ru.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/ru.lproj/Localizable.strings
index 8cafd468b..f345df962 100644
--- a/Packages/App/Sources/UILibrary/Resources/ru.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/ru.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Сохранить в любом случае";
 "views.profile.module_list.section.footer" = "Нажмите на модули, чтобы изменить их настройки. Их можно перетаскивать для установки приоритета.";
 "views.profile.rows.add_module" = "Добавить модуль";
+"views.profile.rows.delete_profile" = "Удалить профиль";
 "views.profile.sections.name.footer" = "Используйте это имя для создания автоматизаций VPN в приложении Команды.";
 "views.providers.clear_filters" = "Очистить фильтры";
 "views.providers.last_updated" = "Последнее обновление %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/sv.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/sv.lproj/Localizable.strings
index 7429c8384..8bb27dc98 100644
--- a/Packages/App/Sources/UILibrary/Resources/sv.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/sv.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Spara ändå";
 "views.profile.module_list.section.footer" = "Tryck på moduler för att redigera inställningarna. Moduler kan dras för att bestämma prioritet.";
 "views.profile.rows.add_module" = "Lägg till modul";
+"views.profile.rows.delete_profile" = "Ta bort profil";
 "views.profile.sections.name.footer" = "Använd detta namn för att skapa VPN-automatiseringar i appen Genvägar.";
 "views.providers.clear_filters" = "Rensa filter";
 "views.providers.last_updated" = "Senast uppdaterad %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/uk.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/uk.lproj/Localizable.strings
index 57aca8001..fca1d1c67 100644
--- a/Packages/App/Sources/UILibrary/Resources/uk.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/uk.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "Зберегти все одно";
 "views.profile.module_list.section.footer" = "Торкніться модулів, щоб змінити їхні налаштування. Їх можна перетягувати для визначення пріоритету.";
 "views.profile.rows.add_module" = "Додати модуль";
+"views.profile.rows.delete_profile" = "Видалити профіль";
 "views.profile.sections.name.footer" = "Використовуйте цю назву для створення автоматизацій VPN у додатку Команди.";
 "views.providers.clear_filters" = "Очистити фільтри";
 "views.providers.last_updated" = "Останнє оновлення %@";
diff --git a/Packages/App/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings b/Packages/App/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings
index 08d33ea62..07cd15f8d 100644
--- a/Packages/App/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings
+++ b/Packages/App/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings
@@ -284,6 +284,7 @@
 "views.profile.alerts.purchase.buttons.ok" = "仍然保存";
 "views.profile.module_list.section.footer" = "点击模块以编辑其设置。可以拖动模块来确定优先级。";
 "views.profile.rows.add_module" = "添加模块";
+"views.profile.rows.delete_profile" = "删除配置文件";
 "views.profile.sections.name.footer" = "使用此名称在快捷指令应用中创建您的 VPN 自动化。";
 "views.providers.clear_filters" = "清除筛选";
 "views.providers.last_updated" = "最近更新于 %@";

From 384fe21cb611f02823ae759ae5d507fd84199c19 Mon Sep 17 00:00:00 2001
From: Davide <keeshux@gmail.com>
Date: Mon, 10 Feb 2025 23:35:03 +0100
Subject: [PATCH 3/5] Add missing hide icon in context menu

---
 .../Sources/UILibrary/Views/UI/PinActiveProfileToggle.swift   | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Packages/App/Sources/UILibrary/Views/UI/PinActiveProfileToggle.swift b/Packages/App/Sources/UILibrary/Views/UI/PinActiveProfileToggle.swift
index a8df7faac..da8c27d23 100644
--- a/Packages/App/Sources/UILibrary/Views/UI/PinActiveProfileToggle.swift
+++ b/Packages/App/Sources/UILibrary/Views/UI/PinActiveProfileToggle.swift
@@ -47,10 +47,12 @@ public struct HideActiveProfileButton: View {
     }
 
     public var body: some View {
-        Button(Strings.Global.Actions.hide) {
+        Button {
             withAnimation {
                 pinsActiveProfile = false
             }
+        } label: {
+            ThemeImageLabel(Strings.Global.Actions.hide, .hide)
         }
     }
 }

From c70b45ff1560f34ebf2d5d6e4a3097119050f066 Mon Sep 17 00:00:00 2001
From: Davide <keeshux@gmail.com>
Date: Mon, 10 Feb 2025 23:49:09 +0100
Subject: [PATCH 4/5] Split remove buttons

Require confirmation in profile.
---
 .../Views/App/ProfileContextMenu.swift        | 10 +--
 .../Views/App/ProfileRemoveButton.swift       | 64 -------------------
 .../Views/Profile/ProfileActionsSection.swift | 39 ++++++++---
 3 files changed, 35 insertions(+), 78 deletions(-)
 delete mode 100644 Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift

diff --git a/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift b/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
index 8fd9a5748..c5130ba89 100644
--- a/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
+++ b/Packages/App/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
@@ -122,11 +122,11 @@ private extension ProfileContextMenu {
     }
 
     var profileRemoveButton: some View {
-        ProfileRemoveButton(
-            profileManager: profileManager,
-            profileId: preview.id,
-            profileName: preview.name
-        ) {
+        Button(role: .destructive) {
+            Task {
+                await profileManager.remove(withId: preview.id)
+            }
+        } label: {
             ThemeImageLabel(Strings.Global.Actions.remove, .contextRemove)
         }
     }
diff --git a/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift b/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift
deleted file mode 100644
index ada943a55..000000000
--- a/Packages/App/Sources/AppUIMain/Views/App/ProfileRemoveButton.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-//  ProfileRemoveButton.swift
-//  Passepartout
-//
-//  Created by Davide De Rosa on 9/6/24.
-//  Copyright (c) 2025 Davide De Rosa. All rights reserved.
-//
-//  https://github.com/passepartoutvpn
-//
-//  This file is part of Passepartout.
-//
-//  Passepartout is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  Passepartout is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with Passepartout.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import CommonLibrary
-import PassepartoutKit
-import SwiftUI
-
-struct ProfileRemoveButton<Label>: View where Label: View {
-
-    @Environment(\.dismissProfile)
-    private var dismissProfile
-
-    let profileManager: ProfileManager
-
-    let profileId: Profile.ID
-
-    let profileName: String
-
-    let label: () -> Label
-
-    @State
-    private var isConfirming = false
-
-    var body: some View {
-        Button(role: .destructive) {
-            isConfirming = true
-        } label: {
-            label()
-        }
-        .themeConfirmation(
-            isPresented: $isConfirming,
-            title: Strings.Global.Actions.delete,
-            isDestructive: true,
-            action: {
-                Task {
-                    dismissProfile()
-                    await profileManager.remove(withId: profileId)
-                }
-            }
-        )
-    }
-}
diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
index 8245f25d9..f5a69c822 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
@@ -28,27 +28,48 @@ import PassepartoutKit
 import SwiftUI
 
 struct ProfileActionsSection: View {
+
+    @Environment(\.dismissProfile)
+    private var dismissProfile
+
     let profileManager: ProfileManager
 
     let profileEditor: ProfileEditor
 
+    @State
+    private var isConfirmingDeletion = false
+
     var body: some View {
         UUIDText(uuid: profileId)
             .asSectionWithTrailingContent {
                 if profileManager.profile(withId: profileId) != nil {
-                    ProfileRemoveButton(
-                        profileManager: profileManager,
-                        profileId: profileId,
-                        profileName: profileEditor.profile.name,
-                        label: {
-                            Text(Strings.Views.Profile.Rows.deleteProfile)
-                        }
-                    )
+                    removeButton
+                        .themeConfirmation(
+                            isPresented: $isConfirmingDeletion,
+                            title: Strings.Global.Actions.delete,
+                            isDestructive: true,
+                            action: {
+                                Task {
+                                    dismissProfile()
+                                    await profileManager.remove(withId: profileId)
+                                }
+                            }
+                        )
                 }
             }
     }
+}
+
+private extension ProfileActionsSection {
+    var removeButton: some View {
+        Button(Strings.Views.Profile.Rows.deleteProfile, role: .destructive) {
+            isConfirmingDeletion = true
+        }
+    }
+}
 
-    private var profileId: Profile.ID {
+private extension ProfileActionsSection {
+    var profileId: Profile.ID {
         profileEditor.profile.id
     }
 }

From 48da2a310bd9060e58a9b4e4b20b1b403773930c Mon Sep 17 00:00:00 2001
From: Davide <keeshux@gmail.com>
Date: Mon, 10 Feb 2025 23:53:41 +0100
Subject: [PATCH 5/5] Render differently on iOS/macOS

---
 .../Views/Profile/ProfileActionsSection.swift | 43 +++++++++++++------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
index f5a69c822..4d3891dd2 100644
--- a/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
+++ b/Packages/App/Sources/AppUIMain/Views/Profile/ProfileActionsSection.swift
@@ -40,27 +40,42 @@ struct ProfileActionsSection: View {
     private var isConfirmingDeletion = false
 
     var body: some View {
+#if os(iOS)
+        Section {
+            UUIDText(uuid: profileId)
+        }
+        Section {
+            removeContent
+                .frame(maxWidth: .infinity, alignment: .center)
+        }
+#else
         UUIDText(uuid: profileId)
             .asSectionWithTrailingContent {
-                if profileManager.profile(withId: profileId) != nil {
-                    removeButton
-                        .themeConfirmation(
-                            isPresented: $isConfirmingDeletion,
-                            title: Strings.Global.Actions.delete,
-                            isDestructive: true,
-                            action: {
-                                Task {
-                                    dismissProfile()
-                                    await profileManager.remove(withId: profileId)
-                                }
-                            }
-                        )
-                }
+                removeContent
             }
+#endif
     }
 }
 
 private extension ProfileActionsSection {
+    var removeContent: some View {
+        profileManager.profile(withId: profileId)
+            .map { _ in
+                removeButton
+                    .themeConfirmation(
+                        isPresented: $isConfirmingDeletion,
+                        title: Strings.Global.Actions.delete,
+                        isDestructive: true,
+                        action: {
+                            Task {
+                                dismissProfile()
+                                await profileManager.remove(withId: profileId)
+                            }
+                        }
+                    )
+            }
+    }
+
     var removeButton: some View {
         Button(Strings.Views.Profile.Rows.deleteProfile, role: .destructive) {
             isConfirmingDeletion = true