fix and clean up

online_payment
Raz 7 months ago
parent b95c7acb83
commit f2f6e88d8a
  1. 32
      PadelClub.xcodeproj/project.pbxproj
  2. 6
      PadelClub/Data/CustomUser.swift
  3. 2
      PadelClub/Data/Gen/BaseTeamRegistration.swift
  4. 44
      PadelClub/Data/Gen/BaseTournament.swift
  5. 29
      PadelClub/Data/Gen/Tournament.json
  6. 4
      PadelClub/Data/Tournament.swift
  7. 5
      PadelClub/Utils/Network/RefundService.swift
  8. 68
      PadelClub/Utils/Network/StripeValidationService.swift
  9. 62
      PadelClub/Utils/Network/XlsToCsvService.swift
  10. 33
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  11. 2
      PadelClub/Views/Tournament/FileImportView.swift
  12. 117
      PadelClub/Views/Tournament/Screen/Components/RefundResultsView.swift
  13. 181
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  14. 63
      PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift
  15. 27
      PadelClub/Views/Tournament/TournamentInitView.swift

@ -423,7 +423,6 @@
FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; };
FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; };
FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; };
FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; };
FF4CBFEC2C996C0600151637 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; };
@ -735,7 +734,6 @@
FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1F4B6C2BF9E60B000B4573 /* TournamentBuildView.swift */; };
FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; };
FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; };
FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */; };
FF70FB6B2C90584900129CC2 /* TournamentCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9268062BCE94D90080F940 /* TournamentCallView.swift */; };
@ -910,7 +908,6 @@
FFA252B62CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; };
FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252B42CDD2C630074E63F /* OngoingDestination.swift */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; };
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */; };
FFB378342D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; };
FFB378352D672ED200EE82E9 /* MatchFormatGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */; };
@ -979,6 +976,15 @@
FFE8B5BB2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; };
FFE8B5BC2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; };
FFE8B5BD2DA9896800BDE966 /* RefundService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BA2DA9896800BDE966 /* RefundService.swift */; };
FFE8B5BF2DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; };
FFE8B5C02DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; };
FFE8B5C12DAA325400BDE966 /* RefundResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */; };
FFE8B5C72DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; };
FFE8B5C82DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; };
FFE8B5C92DAA390900BDE966 /* StripeValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */; };
FFE8B5CB2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; };
FFE8B5CC2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; };
FFE8B5CD2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */; };
FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; };
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; };
@ -1366,7 +1372,6 @@
FFA252B02CDD2C080074E63F /* OngoingContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingContainerView.swift; sourceTree = "<group>"; };
FFA252B42CDD2C630074E63F /* OngoingDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingDestination.swift; sourceTree = "<group>"; };
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
FFA6D78A2BB0BEB3003A31F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
FFB1C98A2C10255100B154A7 /* TournamentBroadcastRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentBroadcastRowView.swift; sourceTree = "<group>"; };
FFB378332D672ED100EE82E9 /* MatchFormatGuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchFormatGuideView.swift; sourceTree = "<group>"; };
@ -1419,6 +1424,9 @@
FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineWaitingListFaqSheetView.swift; sourceTree = "<group>"; };
FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentInfoSheetView.swift; sourceTree = "<group>"; };
FFE8B5BA2DA9896800BDE966 /* RefundService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefundService.swift; sourceTree = "<group>"; };
FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefundResultsView.swift; sourceTree = "<group>"; };
FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeValidationService.swift; sourceTree = "<group>"; };
FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XlsToCsvService.swift; sourceTree = "<group>"; };
FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonView.swift; sourceTree = "<group>"; };
FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = "<group>"; };
FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -2056,6 +2064,8 @@
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */,
FF6EC9052B947A1000EA7F5A /* NetworkManagerError.swift */,
FFE8B5BA2DA9896800BDE966 /* RefundService.swift */,
FFE8B5C62DAA390000BDE966 /* StripeValidationService.swift */,
FFE8B5CA2DAA429E00BDE966 /* XlsToCsvService.swift */,
);
path = Network;
sourceTree = "<group>";
@ -2075,6 +2085,7 @@
FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */,
FF4623CA2D1340D200CB57B5 /* TournamentCategorySettingsView.swift */,
FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */,
FFE8B5BE2DAA325400BDE966 /* RefundResultsView.swift */,
FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */,
);
path = Components;
@ -2242,7 +2253,6 @@
children = (
FF6EC9072B947A1E00EA7F5A /* Network */,
FF8E1CE52C006E0200184680 /* Alphabet.swift */,
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */,
FF92680A2BCEE3E10080F940 /* ContactManager.swift */,
FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */,
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */,
@ -2634,6 +2644,7 @@
FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */,
FFB1C98B2C10255100B154A7 /* TournamentBroadcastRowView.swift in Sources */,
FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */,
FFE8B5C12DAA325400BDE966 /* RefundResultsView.swift in Sources */,
FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */,
FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */,
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */,
@ -2740,6 +2751,7 @@
FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */,
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */,
FF558C632C6CDD020071F9AE /* UnderlineView.swift in Sources */,
FFE8B5CD2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */,
FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */,
FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */,
FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */,
@ -2802,7 +2814,6 @@
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */,
FF1F4B6D2BF9E60B000B4573 /* TournamentBuildView.swift in Sources */,
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */,
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */,
FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */,
FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */,
FFB39B352D8E8B05008E0C89 /* MatchSpot.swift in Sources */,
@ -2851,6 +2862,7 @@
FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */,
FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */,
FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */,
FFE8B5C82DAA390900BDE966 /* StripeValidationService.swift in Sources */,
FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */,
C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */,
FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */,
@ -3056,6 +3068,7 @@
FF4CBFAD2C996C0600151637 /* RoundScheduleEditorView.swift in Sources */,
FF4CBFAE2C996C0600151637 /* EventListView.swift in Sources */,
FF4CBFAF2C996C0600151637 /* TournamentConfiguratorView.swift in Sources */,
FFE8B5C72DAA390900BDE966 /* StripeValidationService.swift in Sources */,
FF4CBFB02C996C0600151637 /* ClubImportView.swift in Sources */,
FF4CBFB12C996C0600151637 /* UnderlineView.swift in Sources */,
FF4CBFB22C996C0600151637 /* MatchScheduler.swift in Sources */,
@ -3121,7 +3134,6 @@
FF4CBFE62C996C0600151637 /* MenuWarningView.swift in Sources */,
FF4CBFE72C996C0600151637 /* TournamentBuildView.swift in Sources */,
FF4CBFE82C996C0600151637 /* TeamPickerView.swift in Sources */,
FF4CBFE92C996C0600151637 /* CloudConvert.swift in Sources */,
FF4CBFEA2C996C0600151637 /* EventTournamentsView.swift in Sources */,
FF4CBFEB2C996C0600151637 /* DisplayContext.swift in Sources */,
FFB39B342D8E8B05008E0C89 /* MatchSpot.swift in Sources */,
@ -3130,6 +3142,7 @@
FF4CBFED2C996C0600151637 /* LoserRoundsView.swift in Sources */,
FF4CBFEE2C996C0600151637 /* GroupStagesView.swift in Sources */,
FF4CBFEF2C996C0600151637 /* PadelClubView.swift in Sources */,
FFE8B5CC2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */,
FF3A73F52D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */,
FF4CBFF02C996C0600151637 /* URLs.swift in Sources */,
FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */,
@ -3194,6 +3207,7 @@
FF4CC0212C996C0600151637 /* GroupStageScheduleEditorView.swift in Sources */,
FF4CC0222C996C0600151637 /* EventView.swift in Sources */,
FF4CC0232C996C0600151637 /* LoginView.swift in Sources */,
FFE8B5BF2DAA325400BDE966 /* RefundResultsView.swift in Sources */,
FF4CC0242C996C0600151637 /* Court.swift in Sources */,
FF4CC0252C996C0600151637 /* Labels.swift in Sources */,
FF4CC0262C996C0600151637 /* CopyPasteButtonView.swift in Sources */,
@ -3353,6 +3367,7 @@
FF70FB2C2C90584900129CC2 /* RoundScheduleEditorView.swift in Sources */,
FF70FB2D2C90584900129CC2 /* EventListView.swift in Sources */,
FF70FB2E2C90584900129CC2 /* TournamentConfiguratorView.swift in Sources */,
FFE8B5C92DAA390900BDE966 /* StripeValidationService.swift in Sources */,
FF70FB2F2C90584900129CC2 /* ClubImportView.swift in Sources */,
FF70FB302C90584900129CC2 /* UnderlineView.swift in Sources */,
FF70FB312C90584900129CC2 /* MatchScheduler.swift in Sources */,
@ -3418,7 +3433,6 @@
FF70FB652C90584900129CC2 /* MenuWarningView.swift in Sources */,
FF70FB662C90584900129CC2 /* TournamentBuildView.swift in Sources */,
FF70FB672C90584900129CC2 /* TeamPickerView.swift in Sources */,
FF70FB682C90584900129CC2 /* CloudConvert.swift in Sources */,
FF70FB692C90584900129CC2 /* EventTournamentsView.swift in Sources */,
FF70FB6A2C90584900129CC2 /* DisplayContext.swift in Sources */,
FFB39B362D8E8B05008E0C89 /* MatchSpot.swift in Sources */,
@ -3427,6 +3441,7 @@
FF70FB6C2C90584900129CC2 /* LoserRoundsView.swift in Sources */,
FF70FB6D2C90584900129CC2 /* GroupStagesView.swift in Sources */,
FF70FB6E2C90584900129CC2 /* PadelClubView.swift in Sources */,
FFE8B5CB2DAA42A000BDE966 /* XlsToCsvService.swift in Sources */,
FF3A73F42D37C34D007E3032 /* RegistrationInfoSheetView.swift in Sources */,
FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */,
FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */,
@ -3491,6 +3506,7 @@
FF70FBA02C90584900129CC2 /* GroupStageScheduleEditorView.swift in Sources */,
FF70FBA12C90584900129CC2 /* EventView.swift in Sources */,
FF70FBA22C90584900129CC2 /* LoginView.swift in Sources */,
FFE8B5C02DAA325400BDE966 /* RefundResultsView.swift in Sources */,
FF70FBA32C90584900129CC2 /* Court.swift in Sources */,
FF70FBA42C90584900129CC2 /* Labels.swift in Sources */,
FF70FBA52C90584900129CC2 /* CopyPasteButtonView.swift in Sources */,

@ -64,12 +64,12 @@ enum RegistrationPaymentMode: Int, Codable {
}
}
func sample(entryFee: Double) -> String? {
func sample(entryFee: Double) -> String {
if let fee = self.fee() {
let feeAmount = entryFee * fee
return String(format: "%.2f", feeAmount)
return String(format: "%.2f", feeAmount)
} else {
return nil
return "0"
}
}

@ -196,4 +196,4 @@ class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
]
}
}
}

@ -23,20 +23,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
var roundFormat: MatchFormat? = nil
var loserRoundFormat: MatchFormat? = nil
var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake
var groupStageCount: Int = 0
var groupStageCount: Int = 4
var rankSourceDate: Date? = nil
var dayDuration: Int = 0
var teamCount: Int = 0
var dayDuration: Int = 1
var teamCount: Int = 24
var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate
var federalCategory: TournamentCategory = TournamentCategory.men
var federalLevelCategory: TournamentLevel = TournamentLevel.unlisted
var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted
var federalLevelCategory: TournamentLevel = TournamentLevel.p100
var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior
var closedRegistrationDate: Date? = nil
var groupStageAdditionalQualified: Int = 0
var courtCount: Int = 2
var prioritizeClubMembers: Bool = false
var qualifiedPerGroupStage: Int = 0
var teamsPerGroupStage: Int = 0
var qualifiedPerGroupStage: Int = 1
var teamsPerGroupStage: Int = 4
var entryFee: Double? = nil
var payment: TournamentPayment? = nil
var additionalEstimationDuration: Int = 0
@ -92,20 +92,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
roundFormat: MatchFormat? = nil,
loserRoundFormat: MatchFormat? = nil,
groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake,
groupStageCount: Int = 0,
groupStageCount: Int = 4,
rankSourceDate: Date? = nil,
dayDuration: Int = 0,
teamCount: Int = 0,
dayDuration: Int = 1,
teamCount: Int = 24,
teamSorting: TeamSortingType = TeamSortingType.inscriptionDate,
federalCategory: TournamentCategory = TournamentCategory.men,
federalLevelCategory: TournamentLevel = TournamentLevel.unlisted,
federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted,
federalLevelCategory: TournamentLevel = TournamentLevel.p100,
federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior,
closedRegistrationDate: Date? = nil,
groupStageAdditionalQualified: Int = 0,
courtCount: Int = 2,
prioritizeClubMembers: Bool = false,
qualifiedPerGroupStage: Int = 0,
teamsPerGroupStage: Int = 0,
qualifiedPerGroupStage: Int = 1,
teamsPerGroupStage: Int = 4,
entryFee: Double? = nil,
payment: TournamentPayment? = nil,
additionalEstimationDuration: Int = 0,
@ -374,20 +374,20 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil
self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil
self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake
self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 0
self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 4
self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil
self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 0
self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 0
self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 1
self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 24
self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate
self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men
self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.unlisted
self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.unlisted
self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.p100
self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.senior
self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil
self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2
self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false
self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 0
self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 0
self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 1
self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 4
self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil
self.payment = try Self._decodePayment(container: container)
self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0
@ -587,4 +587,4 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
]
}
}
}

@ -66,7 +66,8 @@
},
{
"name": "groupStageCount",
"type": "Int"
"type": "Int",
"defaultValue": "4"
},
{
"name": "rankSourceDate",
@ -75,11 +76,13 @@
},
{
"name": "dayDuration",
"type": "Int"
"type": "Int",
"defaultValue": "1"
},
{
"name": "teamCount",
"type": "Int"
"type": "Int",
"defaultValue": "24"
},
{
"name": "teamSorting",
@ -94,12 +97,12 @@
{
"name": "federalLevelCategory",
"type": "TournamentLevel",
"defaultValue": "TournamentLevel.unlisted"
"defaultValue": "TournamentLevel.p100"
},
{
"name": "federalAgeCategory",
"type": "FederalTournamentAge",
"defaultValue": "FederalTournamentAge.unlisted"
"defaultValue": "FederalTournamentAge.senior"
},
{
"name": "closedRegistrationDate",
@ -108,7 +111,8 @@
},
{
"name": "groupStageAdditionalQualified",
"type": "Int"
"type": "Int",
"defaultValue": "0"
},
{
"name": "courtCount",
@ -117,15 +121,18 @@
},
{
"name": "prioritizeClubMembers",
"type": "Bool"
"type": "Bool",
"defaultValue": "false"
},
{
"name": "qualifiedPerGroupStage",
"type": "Int"
"type": "Int",
"defaultValue": "1"
},
{
"name": "teamsPerGroupStage",
"type": "Int"
"type": "Int",
"defaultValue": "4"
},
{
"name": "entryFee",
@ -255,12 +262,12 @@
{
"name": "minimumPlayerPerTeam",
"type": "Int",
"defaultValue": 2
"defaultValue": "2"
},
{
"name": "maximumPlayerPerTeam",
"type": "Int",
"defaultValue": 2
"defaultValue": "2"
},
{
"name": "information",

@ -2382,10 +2382,6 @@ defer {
func paidOnlineTeams() -> [TeamRegistration] {
unsortedTeams().filter({ $0.hasPaidOnline() })
}
func refundTeams() {
}
func shouldWarnOnlineRegistrationUpdates() -> Bool {
enableOnlineRegistration && onlineTeams().isEmpty == false && hasEnded() == false && hasStarted() == false

@ -42,3 +42,8 @@ enum RefundError: Error {
case unauthorized
case unknown
}
struct RefundResult {
let team: TeamRegistration
let response: Result<RefundResponse, Error>
}

@ -0,0 +1,68 @@
//
// StripeValidationService.swift
// PadelClub
//
// Created by razmig on 12/04/2025.
//
import Foundation
import LeStorage
class StripeValidationService {
static func validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse {
let service = try StoreCenter.main.service()
var urlRequest = try service._baseRequest(servicePath: "validate-stripe-account/", method: .post, requiresToken: true)
let body = ["account_id": accountID]
urlRequest.httpBody = try JSONEncoder().encode(body)
do {
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw ValidationError.invalidResponse
}
switch httpResponse.statusCode {
case 200...299:
let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return decodedResponse
case 400:
// Handle bad request
let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return errorResponse
case 403:
// Handle permission error
let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return errorResponse
default:
throw ValidationError.invalidResponse
}
} catch let error as ValidationError {
throw error
} catch {
throw ValidationError.networkError(error)
}
}
}
struct ValidationResponse: Codable {
let valid: Bool
let account: AccountDetails?
let error: String?
}
struct AccountDetails: Codable {
let id: String
enum CodingKeys: String, CodingKey {
case id
}
}
enum ValidationError: Error {
case invalidResponse
case networkError(Error)
case invalidData
}

@ -1,44 +1,18 @@
//
// CloudConvert.swift
// Padel Tournament
// XlsToCsvService.swift
// PadelClub
//
// Created by Razmig Sarkissian on 14/09/2023.
// Created by razmig on 12/04/2025.
//
import Foundation
import LeStorage
class CloudConvert {
class XlsToCsvService {
enum CloudConvertionError: LocalizedError {
case unknownError
case serviceError(ErrorResponse)
case urlNotFound(String)
var errorDescription: String? {
switch self {
case .unknownError:
return "Erreur"
case .serviceError(let errorResponse):
return errorResponse.error
case .urlNotFound(let url):
return "L'URL [\(url)] n'est pas valide"
}
}
}
static let manager = CloudConvert()
func uploadFile(_ url: URL) async throws -> String {
return try await createJob(url)
}
func createJob(_ url: URL) async throws -> String {
let apiPath = "https://\(URLs.activationHost.rawValue)/utils/xls-to-csv/"
guard let taskURL = URL(string: apiPath) else {
throw CloudConvertionError.urlNotFound(apiPath)
}
var request: URLRequest = URLRequest(url: taskURL)
request.httpMethod = "POST"
static func exportToCsv(url: URL) async throws -> String {
let service = try StoreCenter.main.service()
var request = try service._baseRequest(servicePath: "xls-to-csv/", method: .post, requiresToken: true)
// Create the boundary string for multipart/form-data
let boundary = UUID().uuidString
@ -81,13 +55,29 @@ class CloudConvert {
return responseString
} else {
let error = ErrorResponse(code: 1, status: "Encodage", error: "Encodage des données de classement invalide")
throw CloudConvertionError.serviceError(error)
throw ConvertionError.serviceError(error)
}
}
}
// MARK: - ErrorResponse
struct ErrorResponse: Decodable {
let code: Int
let status, error: String
}
enum ConvertionError: LocalizedError {
case unknownError
case serviceError(ErrorResponse)
case urlNotFound(String)
var errorDescription: String? {
switch self {
case .unknownError:
return "Erreur"
case .serviceError(let errorResponse):
return errorResponse.error
case .urlNotFound(let url):
return "L'URL [\(url)] n'est pas valide"
}
}
}

@ -142,25 +142,26 @@ struct UmpireView: View {
// }
// }
//
_customUmpireView()
Section {
@Bindable var user = dataStore.user
if dataStore.user.hideUmpireMail, dataStore.user.hideUmpirePhone {
Text("Attention, les emails envoyés automatiquement au regard des inscriptions en ligne ne contiendront aucun moyen de vous contacter.").foregroundStyle(.logoRed)
}
if StoreCenter.main.isAuthenticated {
_customUmpireView()
Toggle(isOn: $user.hideUmpireMail) {
Text("Masquer l'email")
}
Toggle(isOn: $user.hideUmpirePhone) {
Text("Masquer le téléphone")
Section {
@Bindable var user = dataStore.user
if dataStore.user.hideUmpireMail, dataStore.user.hideUmpirePhone {
Text("Attention, les emails envoyés automatiquement au regard des inscriptions en ligne ne contiendront aucun moyen de vous contacter.").foregroundStyle(.logoRed)
}
Toggle(isOn: $user.hideUmpireMail) {
Text("Masquer l'email")
}
Toggle(isOn: $user.hideUmpirePhone) {
Text("Masquer le téléphone")
}
} footer: {
Text("Ces informations ne seront pas affichées sur la page d'information des tournois sur Padel Club et dans les emails envoyés automatiquement au regard des inscriptions en lignes.")
}
} footer: {
Text("Ces informations ne seront pas affichées sur la page d'information des tournois sur Padel Club et dans les emails envoyés automatiquement au regard des inscriptions en lignes.")
}
Section {
@Bindable var user = dataStore.user

@ -421,7 +421,7 @@ struct FileImportView: View {
do {
if selectedFile.lastPathComponent.hasSuffix("xls") || selectedFile.lastPathComponent.hasSuffix("xlsx") {
convertingFile = true
fileContent = try await CloudConvert.manager.uploadFile(selectedFile)
fileContent = try await XlsToCsvService.exportToCsv(url: selectedFile)
convertingFile = false
} else {
fileContent = try String(contentsOf: selectedFile)

@ -0,0 +1,117 @@
//
// RefundResultsView.swift
// PadelClub
//
// Created by razmig on 12/04/2025.
//
import SwiftUI
import LeStorage
struct RefundResultsView: View {
@Binding var results: [RefundResult]
let tournament: Tournament
@Environment(\.dismiss) private var dismiss
@State private var processingRetry: Set<String> = []
private var hasErrors: Bool {
failedRefundResult.isEmpty == false
}
private var failedRefundResult: [RefundResult] {
results.filter { result in
if case .failure = result.response {
return true
}
return false
}
}
private var errorCount: Int {
failedRefundResult.count
}
var body: some View {
List {
if hasErrors {
RowButtonView("Réessayer tous les remboursements échoués", role: .destructive) {
// Implement retry logic for failed refunds
await _retryFailedRefund()
}
}
let sorted = results.sorted(by: \.team.weight)
ForEach(sorted, id: \.team.id) { result in
LabeledContent {
if processingRetry.contains(result.team.id) {
ProgressView()
} else {
Button {
Task {
await _retryResult(result: result)
}
} label: {
Label("refaire", systemImage: "arrow.2.circlepath.circle").labelStyle(.iconOnly)
}
.buttonStyle(.borderedProminent)
}
} label: {
Text(result.team.teamLabel(twoLines: true))
switch result.response {
case .success(let response):
Text(response.message)
.foregroundColor(response.success ? .green : .logoRed)
case .failure(let error):
Text(error.localizedDescription)
.foregroundColor(.logoRed)
}
}
}
}
.navigationTitle("Résultats des remboursements")
.toolbarBackground(.visible, for: .bottomBar, .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
if hasErrors {
Label("\(errorCount) erreur\(errorCount.pluralSuffix)", systemImage: "exclamationmark.triangle")
.foregroundColor(.red)
} else {
Label("Remboursements effectués", systemImage: "checkmark.circle.fill")
.foregroundColor(.green)
}
}
}
}
private func _retryResult(result: RefundResult) async {
processingRetry.insert(result.team.id)
do {
let response = try await RefundService.processRefund(teamRegistrationId: result.team.id)
results.removeAll(where: { $0.team.id == result.team.id })
results.append(RefundResult(team: result.team, response: .success(response)))
} catch {
results.removeAll(where: { $0.team.id == result.team.id })
results.append(RefundResult(team: result.team, response: .failure(error)))
}
processingRetry.remove(result.team.id)
}
private func _retryFailedRefund() async {
let failed = failedRefundResult
await failed.concurrentForEach { result in
results.removeAll(where: { $0.team.id == result.team.id })
do {
let response = try await RefundService.processRefund(teamRegistrationId: result.team.id)
if let players = response.players {
tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
}
results.append(RefundResult(team: result.team, response: .success(response)))
} catch {
results.append(RefundResult(team: result.team, response: .failure(error)))
}
}
}
}

@ -13,10 +13,12 @@ struct TournamentStatusView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore
@Bindable var tournament: Tournament
@State private var refundResults: [RefundResult] = []
var eventDismiss: Bool = false
var body: some View {
Form {
List {
#if DEBUG
RowButtonView("debug: Un-delete le tournoi") {
tournament.endDate = nil
@ -44,13 +46,28 @@ struct TournamentStatusView: View {
let paidOnlineTeams = tournament.paidOnlineTeams()
let onlineTeams = tournament.onlineTeams()
if onlineTeams.isEmpty == false {
Section {
LabeledContent {
Text(onlineTeams.count.formatted())
} label: {
Text("Équipes inscrites ligne")
}
Text("Vous avez des équipes inscrites en ligne, en effacant ou supprimant votre tournoi, Padel Club saura prévenir les équipes inscrites en ligne automatiquement.")
.foregroundStyle(.logoRed)
}
}
Section {
RowButtonView("Supprimer le tournoi", role: .destructive) {
if tournament.payment == nil {
let event = tournament.eventObject()
let isLastTournament = event?.tournaments.count == 1
if tournament.onlineTeams().isEmpty == false {
if onlineTeams.isEmpty == false {
tournament.isDeleted = true
dataStore.tournaments.addOrUpdate(instance: tournament)
} else {
@ -88,82 +105,103 @@ struct TournamentStatusView: View {
}
.disabled(paidOnlineTeams.isEmpty == false)
if tournament.hasEnded() == false && tournament.isCanceled == false {
Section {
Section {
@Bindable var bindableTournament: Tournament = tournament
if paidOnlineTeams.isEmpty == false {
LabeledContent {
Text(paidOnlineTeams.count.formatted())
} label: {
Text("Équipes ayant payé en ligne")
}
@Bindable var bindableTournament: Tournament = tournament
if paidOnlineTeams.isEmpty == false {
LabeledContent {
Text(paidOnlineTeams.count.formatted())
} label: {
Text("Équipes ayant payé en ligne")
}
Toggle(isOn: $bindableTournament.enableOnlinePaymentRefund) {
Text("Remboursement possible")
}
Toggle(isOn: $bindableTournament.enableOnlinePaymentRefund) {
Text("Remboursement possible")
}
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
LabeledContent {
Text(refundDateLimit.formatted())
} label: {
Text("Date limite")
}
if refundDateLimit.isEarlierThan(Date()) == false {
Text("Le remboursement est toujours possible")
} else {
Text("La date limite a été dépassé")
FooterButtonView("Retirer la date limite ?", role: .destructive) {
tournament.refundDateLimit = nil
_save()
}
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
LabeledContent {
Text(refundDateLimit.formatted())
} label: {
Text("Date limite")
}
if refundDateLimit.isEarlierThan(Date()) == false {
Text("Le remboursement est toujours possible")
} else {
Text("La date limite a été dépassé")
FooterButtonView("Retirer la date limite ?", role: .destructive) {
tournament.refundDateLimit = nil
_save()
}
}
}
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
if refundDateLimit.isEarlierThan(Date()) == false {
Text("\(paidOnlineTeams.count) équipe\(paidOnlineTeams.count.pluralSuffix) seront remboursée\(paidOnlineTeams.count.pluralSuffix)")
} else {
Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées car la date limite a été dépassé")
}
}
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
if refundDateLimit.isEarlierThan(Date()) == false {
Text("\(paidOnlineTeams.count) équipe\(paidOnlineTeams.count.pluralSuffix) seront remboursée\(paidOnlineTeams.count.pluralSuffix)")
} else {
Text("Les équipes ayant payé en ligne seront remboursées")
Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées car la date limite a été dépassé")
}
} else {
Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées vous n'avez pas autorisé le remboursement.")
Text("Les équipes ayant payé en ligne seront remboursées")
}
Text("Si vous annulez ce tournoi vous pouvez toujours gérer les remboursements au cas par cas dans la vue gestion des inscriptions du tournoi.")
} else {
Text("Les équipes ayant payé en ligne ne seront pas automatiquement remboursées vous n'avez pas autorisé le remboursement.")
}
RowButtonView("Annuler le tournoi", role: .destructive) {
if paidOnlineTeams.isEmpty == false {
Text("Si vous annulez ce tournoi vous pouvez toujours gérer les remboursements au cas par cas dans la vue gestion des inscriptions du tournoi.")
}
if refundResults.isEmpty == false {
NavigationLink {
RefundResultsView(results: $refundResults, tournament: tournament)
} label: {
LabeledContent {
Text("Statut des remboursements")
} label: {
let errorCount = refundResults.filter { result in
if case .failure = result.response { return true }
return false
}.count
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
if refundDateLimit.isEarlierThan(Date()) == false {
tournament.refundTeams()
}
} else {
tournament.refundTeams()
if errorCount > 0 {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.logoRed)
} else {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
}
}
}
}
RowButtonView("Annuler le tournoi", role: .destructive) {
if paidOnlineTeams.isEmpty == false {
if tournament.enableOnlinePaymentRefund {
if let refundDateLimit = tournament.refundDateLimit {
if refundDateLimit.isEarlierThan(Date()) == false {
await _refundTeams()
}
} else {
await _refundTeams()
}
}
tournament.endDate = Date()
tournament.isCanceled = true
_save()
dismiss()
}
} footer: {
Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé dans vos achats. Toutes les données du tournoi seront conservées. Le tournoi reste visible sur [Padel Club](\(URLs.main.rawValue))"))
tournament.endDate = Date()
tournament.isCanceled = true
_save()
dismiss()
}
.disabled(tournament.hasEnded() || tournament.isCanceled)
} footer: {
Text(.init("Si votre tournoi n'a pas pu aboutir à cause de la météo ou autre, vous pouvez l'annuler et il ne sera pas comptabilisé dans vos achats. Toutes les données du tournoi seront conservées. Le tournoi reste visible sur [Padel Club](\(URLs.main.rawValue))"))
}
// Section {
@ -190,11 +228,26 @@ struct TournamentStatusView: View {
}
}
private func _refundTeams() async {
let paidOnlineTeams = tournament.paidOnlineTeams()
var results: [RefundResult] = []
await paidOnlineTeams.concurrentForEach { team in
do {
let response = try await RefundService.processRefund(teamRegistrationId: team.id)
if let players = response.players {
tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
}
results.append(RefundResult(team: team, response: .success(response)))
} catch {
results.append(RefundResult(team: team, response: .failure(error)))
}
}
self.refundResults = results
}
private func _save() {
dataStore.tournaments.addOrUpdate(instance: tournament)
}
}
//#Preview {
// TournamentStatusView()
//}

@ -377,7 +377,8 @@ struct RegistrationSetupView: View {
if let fee = dataStore.user.registrationPaymentMode.localizedRegistrationPaymentFee() {
let entryFee = tournament.entryFee ?? 20
Text("Cette fonction entraîne un coût supplémentaire, en effet Padel Club touchera une commission de \(fee) par paiement en ligne. Soit \(dataStore.user.registrationPaymentMode.sample(entryFee: entryFee)) centimes pour une inscription de \(entryFee)€ par exemple.").foregroundStyle(.logoRed).bold()
let sample = dataStore.user.registrationPaymentMode.sample(entryFee: entryFee)
Text("Cette fonction entraîne un coût supplémentaire, en effet Padel Club touchera une commission de \(fee) par paiement en ligne. Soit \(sample) centimes pour une inscription de \(entryFee)€ par exemple.").foregroundStyle(.logoRed).bold()
}
Toggle(isOn: $onlinePaymentIsMandatory) {
@ -474,7 +475,7 @@ struct RegistrationSetupView: View {
Task {
isValidating = true
do {
let response = try await _validateStripeAccountID(accId)
let response = try await StripeValidationService.validateStripeAccountID(accId)
stripeAccountId = accId
stripeAccountIdIsInvalid = response.valid == false
enableOnlinePayment = response.valid
@ -486,64 +487,6 @@ struct RegistrationSetupView: View {
}
}
private func _validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse {
let apiPath = "\(URLs.activationHost.rawValue)/utils/validate-stripe-account/"
let url = URL(string: apiPath)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = ["account_id": accountID]
request.httpBody = try JSONEncoder().encode(body)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw ValidationError.invalidResponse
}
switch httpResponse.statusCode {
case 200...299:
let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return decodedResponse
case 400:
// Handle bad request
let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return errorResponse
case 403:
// Handle permission error
let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data)
return errorResponse
default:
throw ValidationError.invalidResponse
}
} catch let error as ValidationError {
throw error
} catch {
throw ValidationError.networkError(error)
}
}
struct ValidationResponse: Codable {
let valid: Bool
let account: AccountDetails?
let error: String?
}
struct AccountDetails: Codable {
let id: String
enum CodingKeys: String, CodingKey {
case id
}
}
enum ValidationError: Error {
case invalidResponse
case networkError(Error)
case invalidData
}
private func _hasChanged() {
hasChanges = true
}

@ -11,9 +11,36 @@ import LeStorage
struct TournamentInitView: View {
@EnvironmentObject var dataStore: DataStore
var tournament: Tournament
@State private var cashierStatus: Tournament.TournamentStatus?
@ViewBuilder
var body: some View {
if tournament.paidOnlineTeams().isEmpty == false {
Section {
NavigationLink(value: Screen.cashier) {
let tournamentStatus = cashierStatus
LabeledContent {
if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: {
Text(tournament.isFree() ? "Présence" : "Encaissement")
if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
}
}
.task(priority: .background) {
cashierStatus = await tournament.cashierStatus()
}
}
}
Section {
if let event = tournament.eventObject() {

Loading…
Cancel
Save