diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 9ba3653..a8685f6 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -155,6 +155,12 @@ FF2B51612C7E302C00FFF126 /* local.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = FF2B51602C7E302C00FFF126 /* local.sqlite */; }; FF2B6F5E2C036A1500835EE7 /* EventLinksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */; }; FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */; }; + FF30ACED2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACEE2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACEF2E8D700B008B6006 /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACEC2E8D700B008B6006 /* PaymentService.swift */; }; + FF30ACF12E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; + FF30ACF22E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; + FF30ACF32E8D7078008B6006 /* PaymentRequestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */; }; FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FF3795602B9396D0004EA093 /* PadelClubApp.xcdatamodeld */; }; FF3795662B9399AA004EA093 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3795652B9399AA004EA093 /* Persistence.swift */; }; FF39B6152DC8825E004E10CE /* PadelClubData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49770202DC25A23005CD239 /* PadelClubData.framework */; }; @@ -717,6 +723,9 @@ FFA97C9E2E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; FFA97C9F2E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; FFA97CA02E8A7C080089EA22 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */; }; + FFA97CA22E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; + FFA97CA32E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; + FFA97CA42E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */; }; FFB0FF672E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; FFB0FF682E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; FFB0FF692E81B671009EDEAC /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB0FF662E81B671009EDEAC /* OnboardingView.swift */; }; @@ -1019,6 +1028,8 @@ FF2B51622C7F073100FFF126 /* Model_1_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_1_1.xcdatamodel; sourceTree = ""; }; FF2B6F5D2C036A1400835EE7 /* EventLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLinksView.swift; sourceTree = ""; }; FF2EFBEF2BDE295E0049CE3B /* SendToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToAllView.swift; sourceTree = ""; }; + FF30ACEC2E8D700B008B6006 /* PaymentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentService.swift; sourceTree = ""; }; + FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRequestButton.swift; sourceTree = ""; }; FF3795612B9396D0004EA093 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; FF3795652B9399AA004EA093 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; FF39B60F2DC87FEB004E10CE /* PadelClubData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PadelClubData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1126,6 +1137,7 @@ FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; FFA97C8C2E8A59D00089EA22 /* TournamentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentPickerView.swift; sourceTree = ""; }; FFA97C9D2E8A7C080089EA22 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RankingGroupStageSetupView.swift; sourceTree = ""; }; FFB0FF662E81B671009EDEAC /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; FFB0FF722E841042009EDEAC /* WeekdaySelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekdaySelectionView.swift; sourceTree = ""; }; FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = ""; }; @@ -1762,6 +1774,7 @@ FF4AB6B42B9248200002987F /* NetworkManager.swift */, FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */, FFE8B5BA2DA9896800BDE966 /* RefundService.swift */, + FF30ACEC2E8D700B008B6006 /* PaymentService.swift */, FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */, FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */, FFE8B6392DACEAEC00BDE966 /* ConfigurationService.swift */, @@ -1814,6 +1827,7 @@ FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */, FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */, + FFA97CA12E8BAC880089EA22 /* RankingGroupStageSetupView.swift */, FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3922BE3625200C2E883 /* Shared */, ); @@ -1842,6 +1856,7 @@ FF1162862BD004AD000C4809 /* EditingTeamView.swift */, FF17CA562CC02FEA003C7323 /* CoachListView.swift */, FF7DCD382CC330260041110C /* TeamRestingView.swift */, + FF30ACF02E8D7078008B6006 /* PaymentRequestButton.swift */, FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; @@ -2280,6 +2295,7 @@ C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */, FF17CA4F2CB9243E003C7323 /* FollowUpMatchView.swift in Sources */, + FF30ACEF2E8D700B008B6006 /* PaymentService.swift in Sources */, FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */, FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */, FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */, @@ -2314,6 +2330,7 @@ FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */, FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */, FFCFC00E2BBC3D4600B82851 /* PointSelectionView.swift in Sources */, + FF30ACF22E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */, C40CD2F32C412681000DBD9A /* AppDelegate.swift in Sources */, FFCFC00C2BBC3D1E00B82851 /* EditScoreView.swift in Sources */, @@ -2450,6 +2467,7 @@ FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FFE8B5B32DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, + FFA97CA32E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */, @@ -2549,6 +2567,7 @@ FFA252AF2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF4CBF6F2C996C0600151637 /* ListRowViewModifier.swift in Sources */, FF4CBF702C996C0600151637 /* PresentationContext.swift in Sources */, + FF30ACEE2E8D700B008B6006 /* PaymentService.swift in Sources */, FF4CBF722C996C0600151637 /* SwiftParser.swift in Sources */, FF4CBF732C996C0600151637 /* ChangePasswordView.swift in Sources */, FF4CBF742C996C0600151637 /* TournamentSubscriptionView.swift in Sources */, @@ -2583,6 +2602,7 @@ FF4CBF882C996C0600151637 /* TeamRowView.swift in Sources */, FF4CBF8A2C996C0600151637 /* AppDelegate.swift in Sources */, FF4CBF8C2C996C0600151637 /* EditScoreView.swift in Sources */, + FF30ACF12E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF4CBF8D2C996C0600151637 /* TournamentOrganizerView.swift in Sources */, FF4CBF8F2C996C0600151637 /* TournamentRunningView.swift in Sources */, FF4CBF902C996C0600151637 /* TournamentDatePickerView.swift in Sources */, @@ -2719,6 +2739,7 @@ FF4CC0182C996C0600151637 /* ClubHolder.swift in Sources */, FF4CC0192C996C0600151637 /* EventSettingsView.swift in Sources */, C49771E72DC25F04005CD239 /* Color+Extensions.swift in Sources */, + FFA97CA22E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, C49771E82DC25F04005CD239 /* Badge+Extensions.swift in Sources */, C49771E92DC25F04005CD239 /* SpinDrawable+Extensions.swift in Sources */, FF4CC01A2C996C0600151637 /* InscriptionInfoView.swift in Sources */, @@ -2796,6 +2817,7 @@ FFA252AD2CDB734A0074E63F /* UmpireStatisticView.swift in Sources */, FF70FAEE2C90584900129CC2 /* ListRowViewModifier.swift in Sources */, FF70FAEF2C90584900129CC2 /* PresentationContext.swift in Sources */, + FF30ACED2E8D700B008B6006 /* PaymentService.swift in Sources */, FF70FAF12C90584900129CC2 /* SwiftParser.swift in Sources */, FF70FAF22C90584900129CC2 /* ChangePasswordView.swift in Sources */, FF70FAF32C90584900129CC2 /* TournamentSubscriptionView.swift in Sources */, @@ -2830,6 +2852,7 @@ FF70FB072C90584900129CC2 /* TeamRowView.swift in Sources */, FF70FB092C90584900129CC2 /* AppDelegate.swift in Sources */, FF70FB0B2C90584900129CC2 /* EditScoreView.swift in Sources */, + FF30ACF32E8D7078008B6006 /* PaymentRequestButton.swift in Sources */, FF70FB0C2C90584900129CC2 /* TournamentOrganizerView.swift in Sources */, FF70FB0E2C90584900129CC2 /* TournamentRunningView.swift in Sources */, FF70FB0F2C90584900129CC2 /* TournamentDatePickerView.swift in Sources */, @@ -2966,6 +2989,7 @@ FF70FB972C90584900129CC2 /* ClubHolder.swift in Sources */, FF70FB982C90584900129CC2 /* EventSettingsView.swift in Sources */, C49771E42DC25F04005CD239 /* Color+Extensions.swift in Sources */, + FFA97CA42E8BAC880089EA22 /* RankingGroupStageSetupView.swift in Sources */, C49771E52DC25F04005CD239 /* Badge+Extensions.swift in Sources */, C49771E62DC25F04005CD239 /* SpinDrawable+Extensions.swift in Sources */, FF70FB992C90584900129CC2 /* InscriptionInfoView.swift in Sources */, diff --git a/PadelClub/Utils/Network/PaymentService.swift b/PadelClub/Utils/Network/PaymentService.swift new file mode 100644 index 0000000..1eef4db --- /dev/null +++ b/PadelClub/Utils/Network/PaymentService.swift @@ -0,0 +1,41 @@ +// +// PaymentService.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/10/2025. +// + +import Foundation +import LeStorage +import PadelClubData + +class PaymentService { + static func resendPaymentEmail(teamRegistrationId: String) async throws -> SimpleResponse { + let service = try StoreCenter.main.service() + let urlRequest = try service._baseRequest( + servicePath: "resend-payment-email/\(teamRegistrationId)/", + method: .post, + requiresToken: true + ) + + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PaymentError.requestFailed + } + + return try JSON.decoder.decode(SimpleResponse.self, from: data) + } +} + +enum PaymentError: Error { + case requestFailed + case unauthorized + case unknown +} + +struct SimpleResponse: Codable { + let success: Bool + let message: String +} diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index 8d2ebe9..0ef3dfb 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -126,7 +126,7 @@ struct CallMessageCustomizationView: View { } label: { Text("Valider") } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Cashier/CashierSettingsView.swift b/PadelClub/Views/Cashier/CashierSettingsView.swift index 2baab1c..151c80a 100644 --- a/PadelClub/Views/Cashier/CashierSettingsView.swift +++ b/PadelClub/Views/Cashier/CashierSettingsView.swift @@ -127,49 +127,47 @@ struct CashierSettingsView: View { } } - ToolbarItem(placement: .keyboard) { - HStack { - if focusedField == ._entryFee { - if tournament.isFree() { - ForEach(priceTags, id: \.self) { priceTag in - Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()))) { - entryFee = priceTag - tournament.entryFee = priceTag - focusedField = nil - } - .buttonStyle(.bordered) - } - } else { - Button("Gratuit") { - entryFee = nil - tournament.entryFee = nil + ToolbarItemGroup(placement: .keyboard) { + if focusedField == ._entryFee { + if tournament.isFree() { + ForEach(priceTags, id: \.self) { priceTag in + Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()))) { + entryFee = priceTag + tournament.entryFee = priceTag focusedField = nil } - .buttonStyle(.bordered) - - } - } else if focusedField == ._clubMemberFeeDeduction { - ForEach(deductionTags, id: \.self) { deductionTag in - Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { - clubMemberFeeDeduction = deductionTag - tournament.clubMemberFeeDeduction = deductionTag - focusedField = nil - } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) } + } else { Button("Gratuit") { - clubMemberFeeDeduction = entryFee - tournament.clubMemberFeeDeduction = clubMemberFeeDeduction + entryFee = nil + tournament.entryFee = nil focusedField = nil } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) + } - Spacer() - Button("Valider") { + } else if focusedField == ._clubMemberFeeDeduction { + ForEach(deductionTags, id: \.self) { deductionTag in + Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { + clubMemberFeeDeduction = deductionTag + tournament.clubMemberFeeDeduction = deductionTag + focusedField = nil + } + .buttonStyle(.borderedProminent) + } + Button("Gratuit") { + clubMemberFeeDeduction = entryFee + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction focusedField = nil } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) + } + Spacer() + Button("Valider") { + focusedField = nil } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index ef14271..4fa8d9b 100644 --- a/PadelClub/Views/Cashier/Event/EventCreationView.swift +++ b/PadelClub/Views/Cashier/Event/EventCreationView.swift @@ -73,14 +73,12 @@ struct EventCreationView: View { .focused($textFieldIsFocus) .toolbar { if textFieldIsFocus { - ToolbarItem(placement: .keyboard) { - HStack { - Spacer() - Button("Valider") { - textFieldIsFocus = false - } - .buttonStyle(.bordered) + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Valider") { + textFieldIsFocus = false } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 76a8db2..71e2417 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -187,26 +187,24 @@ struct EventSettingsView: View { } if focusedField != nil { - ToolbarItem(placement: .keyboard) { - HStack { - if focusedField == ._name, eventName.isEmpty == false { - Button("Effacer") { - event.name = nil - eventName = "" - } - .buttonStyle(.borderless) - } else if focusedField == ._information, tournamentInformation.isEmpty == false { - Button("Effacer") { - tournamentInformation = "" - } - .buttonStyle(.borderless) + ToolbarItemGroup(placement: .keyboard) { + if focusedField == ._name, eventName.isEmpty == false { + Button("Effacer") { + event.name = nil + eventName = "" } - Spacer() - Button("Valider") { - focusedField = nil + .buttonStyle(.borderedProminent) + } else if focusedField == ._information, tournamentInformation.isEmpty == false { + Button("Effacer") { + tournamentInformation = "" } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) } + Spacer() + Button("Valider") { + focusedField = nil + } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index 1c0f9eb..bde5d69 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -223,13 +223,12 @@ struct ClubDetailView: View { .navigationBarBackButtonHidden(focusedField != nil) .toolbar(content: { if focusedField != nil { - ToolbarItem(placement: .keyboard) { - HStack { - Button("Fermer", role: .cancel) { - focusedField = nil - } - Spacer() + ToolbarItemGroup(placement: .keyboard) { + Button("Fermer", role: .cancel) { + focusedField = nil } + .buttonStyle(.borderedProminent) + Spacer() } } }) diff --git a/PadelClub/Views/Components/StepperView.swift b/PadelClub/Views/Components/StepperView.swift index 5319f92..7989dfa 100644 --- a/PadelClub/Views/Components/StepperView.swift +++ b/PadelClub/Views/Components/StepperView.swift @@ -67,15 +67,14 @@ struct StepperView: View { } .multilineTextAlignment(.trailing) .toolbar { - ToolbarItem(placement: .keyboard) { + ToolbarItemGroup(placement: .keyboard) { if amountIsFocused { - HStack { - Spacer() - Button("Confirmer") { - amountIsFocused = false - _validate() - } + Spacer() + Button("Confirmer") { + amountIsFocused = false + _validate() } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 93e1132..f1526f0 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -327,30 +327,28 @@ struct UmpireView: View { }) .toolbar { if focusedField != nil { - ToolbarItem(placement: .keyboard) { - HStack { - if focusedField == ._umpireCustomMail, umpireCustomMail.isEmpty == false { - Button("Effacer") { - _deleteUmpireMail() - } - .buttonStyle(.borderless) - } else if focusedField == ._umpireCustomPhone, umpireCustomPhone.isEmpty == false { - Button("Effacer") { - _deleteUmpirePhone() - } - .buttonStyle(.borderless) - } else if focusedField == ._umpireCustomContact, umpireCustomContact.isEmpty == false { - Button("Effacer") { - _deleteUmpireContact() - } - .buttonStyle(.borderless) + ToolbarItemGroup(placement: .keyboard) { + if focusedField == ._umpireCustomMail, umpireCustomMail.isEmpty == false { + Button("Effacer") { + _deleteUmpireMail() } - Spacer() - Button("Valider") { - focusedField = nil + .buttonStyle(.borderedProminent) + } else if focusedField == ._umpireCustomPhone, umpireCustomPhone.isEmpty == false { + Button("Effacer") { + _deleteUmpirePhone() } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) + } else if focusedField == ._umpireCustomContact, umpireCustomContact.isEmpty == false { + Button("Effacer") { + _deleteUmpireContact() + } + .buttonStyle(.borderedProminent) + } + Spacer() + Button("Valider") { + focusedField = nil } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Player/Components/PlayerPopoverView.swift b/PadelClub/Views/Player/Components/PlayerPopoverView.swift index 76758fe..afdcad3 100644 --- a/PadelClub/Views/Player/Components/PlayerPopoverView.swift +++ b/PadelClub/Views/Player/Components/PlayerPopoverView.swift @@ -202,27 +202,25 @@ struct PlayerPopoverView: View { } if licenseIsFocused || amountIsFocused { - ToolbarItem(placement: .keyboard) { - HStack { - Spacer() - Button("Confirmer") { - if licenseIsFocused { - license = license.trimmedMultiline - if requiredField.contains(.license) { - if license.isLicenseNumber { - amountIsFocused = true - } else { - displayWrongLicenceError = true - } - } else { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Confirmer") { + if licenseIsFocused { + license = license.trimmedMultiline + if requiredField.contains(.license) { + if license.isLicenseNumber { amountIsFocused = true + } else { + displayWrongLicenceError = true } } else { - amountIsFocused = false + amountIsFocused = true } + } else { + amountIsFocused = false } - .buttonStyle(.bordered) } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 3b1eaa8..457217b 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -286,7 +286,7 @@ struct PlayerDetailView: View { } LabeledContent { - TextField("Téléphone contact", text: $contactPhoneNumber) + TextField("Téléphone", text: $contactPhoneNumber) .focused($focusedField, equals: ._contactPhoneNumber) .keyboardType(.namePhonePad) .textContentType(nil) @@ -320,7 +320,7 @@ struct PlayerDetailView: View { } LabeledContent { - TextField("Email contact", text: $contactEmail) + TextField("Email", text: $contactEmail) .focused($focusedField, equals: ._contactEmail) .keyboardType(.emailAddress) .textContentType(nil) @@ -401,6 +401,7 @@ struct PlayerDetailView: View { } focusedField = nil } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 5b81bf2..6d10c35 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -140,6 +140,10 @@ struct EditingTeamView: View { } label: { Text("Payé en ligne") } + + if team.hasPaidOnline() == false { + PaymentRequestButton(teamRegistration: team) + } } if let refundMessage, refundMessage.isEmpty == false { diff --git a/PadelClub/Views/Team/PaymentRequestButton.swift b/PadelClub/Views/Team/PaymentRequestButton.swift new file mode 100644 index 0000000..7ce6843 --- /dev/null +++ b/PadelClub/Views/Team/PaymentRequestButton.swift @@ -0,0 +1,51 @@ +// +// PaymentRequestButton.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/10/2025. +// + + +import SwiftUI +import PadelClubData + +struct PaymentRequestButton: View { + let teamRegistration: TeamRegistration + @State private var isLoading = false + @State private var showAlert = false + @State private var alertMessage = "" + + var body: some View { + Button("Renvoyer email de paiement") { + resendEmail() + } + .disabled(isLoading) + .alert("Résultat", isPresented: $showAlert) { + Button("OK") { } + } message: { + Text(alertMessage) + } + } + + private func resendEmail() { + isLoading = true + Task { + do { + let response = try await PaymentService.resendPaymentEmail( + teamRegistrationId: teamRegistration.id + ) + await MainActor.run { + isLoading = false + alertMessage = response.message + showAlert = true + } + } catch { + await MainActor.run { + isLoading = false + alertMessage = "Erreur lors de l'envoi" + showAlert = true + } + } + } + } +} diff --git a/PadelClub/Views/Team/PaymentService.swift b/PadelClub/Views/Team/PaymentService.swift new file mode 100644 index 0000000..59cdfe5 --- /dev/null +++ b/PadelClub/Views/Team/PaymentService.swift @@ -0,0 +1,24 @@ +class PaymentService { + static func resendPaymentEmail(teamRegistrationId: String) async throws -> SimpleResponse { + let service = try StoreCenter.main.service() + let urlRequest = try service._baseRequest( + servicePath: "resend-payment-email/\(teamRegistrationId)/", + method: .post, + requiresToken: true + ) + + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PaymentError.requestFailed + } + + return try JSON.decoder.decode(SimpleResponse.self, from: data) + } +} + +struct SimpleResponse: Codable { + let success: Bool + let message: String +} diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 66f6a1a..3686ac1 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -461,6 +461,7 @@ struct AddTeamView: View { self.editableTextField = pasteString self.focusedField = nil } + .buttonStyle(.borderedProminent) Spacer() Button("Chercher") { if editableTextField.count > 1 { @@ -470,7 +471,7 @@ struct AddTeamView: View { self.displayWarningNotEnoughCharacter = true } } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) } } } header: { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index abdb908..6112cfe 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -217,78 +217,76 @@ struct TournamentGeneralSettingsView: View { .toolbarBackground(.visible, for: .navigationBar) .toolbar { if focusedField != nil { - ToolbarItem(placement: .keyboard) { - HStack { - if focusedField == ._entryFee { - if tournament.isFree() { - ForEach(priceTags, id: \.self) { priceTag in - Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { - entryFee = priceTag - tournament.entryFee = priceTag - focusedField = nil - } - .buttonStyle(.bordered) - } - } else { - Button("Gratuit") { - entryFee = nil - tournament.entryFee = nil - focusedField = nil - } - .buttonStyle(.bordered) - - } - } else if focusedField == ._clubMemberFeeDeduction { - ForEach(deductionTags, id: \.self) { deductionTag in - Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { - clubMemberFeeDeduction = deductionTag - tournament.clubMemberFeeDeduction = deductionTag + ToolbarItemGroup(placement: .keyboard) { + if focusedField == ._entryFee { + if tournament.isFree() { + ForEach(priceTags, id: \.self) { priceTag in + Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { + entryFee = priceTag + tournament.entryFee = priceTag focusedField = nil } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) } + } else { Button("Gratuit") { - clubMemberFeeDeduction = entryFee - tournament.clubMemberFeeDeduction = clubMemberFeeDeduction + entryFee = nil + tournament.entryFee = nil focusedField = nil } - .buttonStyle(.bordered) - } else { - if focusedField == ._name, tournamentName.isEmpty == false { - Button("Effacer") { - tournament.name = nil - tournamentName = "" - } - .buttonStyle(.borderless) - } else if focusedField == ._information, tournamentInformation.isEmpty == false { - Button("Effacer") { - tournament.information = nil - tournamentInformation = "" - } - .buttonStyle(.borderless) - } else if focusedField == ._umpireCustomMail, umpireCustomMail.isEmpty == false { - Button("Effacer") { - _deleteUmpireMail() - } - .buttonStyle(.borderless) - } else if focusedField == ._umpireCustomPhone, umpireCustomPhone.isEmpty == false { - Button("Effacer") { - _deleteUmpirePhone() - } - .buttonStyle(.borderless) - } else if focusedField == ._umpireCustomContact, umpireCustomContact.isEmpty == false { - Button("Effacer") { - _deleteUmpireContact() - } - .buttonStyle(.borderless) + .buttonStyle(.borderedProminent) + + } + } else if focusedField == ._clubMemberFeeDeduction { + ForEach(deductionTags, id: \.self) { deductionTag in + Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) { + clubMemberFeeDeduction = deductionTag + tournament.clubMemberFeeDeduction = deductionTag + focusedField = nil } + .buttonStyle(.borderedProminent) } - Spacer() - Button("Valider") { + Button("Gratuit") { + clubMemberFeeDeduction = entryFee + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction focusedField = nil } - .buttonStyle(.bordered) + .buttonStyle(.borderedProminent) + } else { + if focusedField == ._name, tournamentName.isEmpty == false { + Button("Effacer") { + tournament.name = nil + tournamentName = "" + } + .buttonStyle(.borderless) + } else if focusedField == ._information, tournamentInformation.isEmpty == false { + Button("Effacer") { + tournament.information = nil + tournamentInformation = "" + } + .buttonStyle(.borderedProminent) + } else if focusedField == ._umpireCustomMail, umpireCustomMail.isEmpty == false { + Button("Effacer") { + _deleteUmpireMail() + } + .buttonStyle(.borderedProminent) + } else if focusedField == ._umpireCustomPhone, umpireCustomPhone.isEmpty == false { + Button("Effacer") { + _deleteUmpirePhone() + } + .buttonStyle(.borderedProminent) + } else if focusedField == ._umpireCustomContact, umpireCustomContact.isEmpty == false { + Button("Effacer") { + _deleteUmpireContact() + } + .buttonStyle(.borderedProminent) + } + } + Spacer() + Button("Valider") { + focusedField = nil } + .buttonStyle(.borderedProminent) } } } diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index c62c42a..a0c5470 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -54,7 +54,10 @@ struct InscriptionManagerView: View { @State private var refreshResult: String? = nil @State private var refreshStatus: Bool? @State private var showLegendView: Bool = false - + @State private var isLoading = false + @State private var showAlert = false + @State private var alertMessage = "" + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -335,6 +338,11 @@ struct InscriptionManagerView: View { } .tint(.master) } + .alert("Requête de paiement", isPresented: $showAlert) { + Button("OK") { } + } message: { + Text(alertMessage) + } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { @@ -373,6 +381,40 @@ struct InscriptionManagerView: View { ToolbarItem(placement: .navigationBarTrailing) { Menu { + + #if PRODTEST + if tournament.enableOnlinePayment { + Button { + isLoading = true + Task { + do { + try await selectedSortedTeams.filter { team in + team.hasPaidOnline() == false && team.hasPaid() == false + }.concurrentForEach { team in + _ = try await PaymentService.resendPaymentEmail(teamRegistrationId: team.id) + } + + await MainActor.run { + isLoading = false + alertMessage = "Relance effectuée avec succès" + showAlert = true + } + } catch { + Logger.error(error) + await MainActor.run { + isLoading = false + alertMessage = "Erreur lors de la requête" + showAlert = true + } + } + } + } label: { + Text("Requête de paiement") + } + .disabled(isLoading) + } + #endif + if tournament.inscriptionClosed() == false { Menu { _sortingTypePickerView() @@ -396,7 +438,7 @@ struct InscriptionManagerView: View { if tournament.isAnimation() == false { Divider() - Section { + Menu { Button("+1 en tableau") { tournament.addWildCard(1, .bracket) _setHash() @@ -408,7 +450,7 @@ struct InscriptionManagerView: View { _setHash() } } - } header: { + } label: { Text("Ajout de wildcards") } @@ -421,21 +463,29 @@ struct InscriptionManagerView: View { } - Divider() - - Button { - presentImportView = true + Menu { + Button { + presentImportView = true + } label: { + Label("Importer un fichier", systemImage: "square.and.arrow.down") + } + Link(destination: URLs.beachPadel.url) { + Label("beach-padel.app.fft.fr", systemImage: "safari") + } } label: { - Label("Importer beach-padel", systemImage: "square.and.arrow.down") - } - Link(destination: URLs.beachPadel.url) { - Label("beach-padel.app.fft.fr", systemImage: "safari") + Text("Beach Padel") } if tournament.inscriptionClosed() == false { Divider() - _importTeamsMenuView(title: "Importer des paires") - _sharingTeamsMenuView() + + Menu { + _importTeamsMenuView(title: "Importer des paires d'un autre tournoi") + _sharingTeamsMenuView() + } label: { + Text("Importer / Exporter") + } + } else { _sharingTeamsMenuView() @@ -459,19 +509,20 @@ struct InscriptionManagerView: View { } //rankingDateSourcePickerView(showDateInLabel: true) - - Divider() - - _sharingTeamsMenuView() - Divider() - - _importTeamsMenuView(title: "Importer des paires") - Button { - presentImportView = true + Menu { + _sharingTeamsMenuView() + + _importTeamsMenuView(title: "Importer des paires d'un autre tournoi") + + Button { + presentImportView = true + } label: { + Label("Importer un fichier", systemImage: "square.and.arrow.down") + } } label: { - Label("Importer un fichier", systemImage: "square.and.arrow.down") + Text("Importer / Exporter") } } } label: { @@ -741,7 +792,7 @@ struct InscriptionManagerView: View { .pickerStyle(.menu) if currentRankSourceDate == SourceFileManager.shared.mostRecentDateAvailable { - Button("Rafraîchir") { + Button("Rafraîchir le rang des joueurs") { confirmUpdateRank = true } } diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 4eec9a2..b94984c 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -331,20 +331,18 @@ struct RegistrationSetupView: View { }) .toolbar { if focusedField == ._stripeAccountId, stripeAccountId.isEmpty == false { - ToolbarItem(placement: .keyboard) { - HStack { - Button("Effacer") { - stripeAccountId = "" - stripeAccountIdIsInvalid = nil - tournament.stripeAccountId = nil - } - .buttonStyle(.borderless) - Spacer() - Button("Valider") { - focusedField = nil - } - .buttonStyle(.bordered) + ToolbarItemGroup(placement: .keyboard) { + Button("Effacer") { + stripeAccountId = "" + stripeAccountIdIsInvalid = nil + tournament.stripeAccountId = nil + } + .buttonStyle(.borderedProminent) + Spacer() + Button("Valider") { + focusedField = nil } + .buttonStyle(.borderedProminent) } } }