online_payment
Raz 7 months ago
parent 3ea327cad2
commit 629dfe5120
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 76
      PadelClub/Data/CustomUser.swift
  3. 58
      PadelClub/Data/Gen/BaseCustomUser.swift
  4. 23
      PadelClub/Data/Gen/BasePlayerRegistration.swift
  5. 58
      PadelClub/Data/Gen/BaseTournament.swift
  6. 41
      PadelClub/Data/Gen/CustomUser.json
  7. 16
      PadelClub/Data/Gen/PlayerRegistration.json
  8. 46
      PadelClub/Data/Gen/Tournament.json
  9. 9
      PadelClub/Data/PlayerRegistration.swift
  10. 195
      PadelClub/Data/Tournament.swift
  11. 4
      PadelClub/Utils/URLs.swift
  12. 3
      PadelClub/Views/Cashier/Event/EventCreationView.swift
  13. 29
      PadelClub/Views/Cashier/Event/EventTournamentsView.swift
  14. 4
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  15. 182
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  16. 204
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  17. 89
      PadelClub/Views/Shared/OnlineWaitingListFaqSheetView.swift
  18. 90
      PadelClub/Views/Shared/PaymentInfoSheetView.swift
  19. 4
      PadelClub/Views/Tournament/Screen/Components/EventClubSettingsView.swift
  20. 230
      PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift
  21. 2
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  22. 2
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -970,6 +970,12 @@
FFE103102C366DCD00684FC9 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; FFE103102C366DCD00684FC9 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; };
FFE103122C366E5900684FC9 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; }; FFE103122C366E5900684FC9 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; };
FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; };
FFE8B5B32DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */; };
FFE8B5B42DA848D400BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */; };
FFE8B5B52DA848D400BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */; };
FFE8B5B72DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */; };
FFE8B5B82DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */; };
FFE8B5B92DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */; };
FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; };
FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; };
FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; };
@ -1407,6 +1413,8 @@
FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSharingView.swift; sourceTree = "<group>"; }; FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSharingView.swift; sourceTree = "<group>"; };
FFE103112C366E5900684FC9 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; }; FFE103112C366E5900684FC9 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = "<group>"; }; FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = "<group>"; };
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>"; };
FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonView.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>"; }; 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>"; }; FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -2004,11 +2012,13 @@
FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */, FF4AB6BC2B9256E10002987F /* SelectablePlayerListView.swift */,
FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */, FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */,
FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */, FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */,
FFE8B5B22DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift */,
FFCFC0192BBC5A8500B82851 /* MatchFormatRowView.swift */, FFCFC0192BBC5A8500B82851 /* MatchFormatRowView.swift */,
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */, FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */,
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */, FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */,
FFE103112C366E5900684FC9 /* ImagePickerView.swift */, FFE103112C366E5900684FC9 /* ImagePickerView.swift */,
FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */, FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */,
FFE8B5B62DA8763800BDE966 /* PaymentInfoSheetView.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2767,6 +2777,7 @@
FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */, FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */,
FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */, FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */,
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */,
FFE8B5B72DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */,
FFBFC3962CF05CBB000EBD8D /* DateMenuView.swift in Sources */, FFBFC3962CF05CBB000EBD8D /* DateMenuView.swift in Sources */,
FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */, FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */,
FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */,
@ -2838,6 +2849,7 @@
C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */, C493B37E2C10AD3600862481 /* LoadingViewModifier.swift in Sources */,
FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */, FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */,
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,
FFE8B5B32DA848D300BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */,
FFF1D2CB2C4A22B200C8D33D /* ExportFormat.swift in Sources */, FFF1D2CB2C4A22B200C8D33D /* ExportFormat.swift in Sources */,
C488C8012CC7DCB80082001F /* BaseClub.swift in Sources */, C488C8012CC7DCB80082001F /* BaseClub.swift in Sources */,
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */,
@ -3064,6 +3076,7 @@
FF4CBFC52C996C0600151637 /* CashierSettingsView.swift in Sources */, FF4CBFC52C996C0600151637 /* CashierSettingsView.swift in Sources */,
FF4CBFC62C996C0600151637 /* LoserRoundScheduleEditorView.swift in Sources */, FF4CBFC62C996C0600151637 /* LoserRoundScheduleEditorView.swift in Sources */,
FF4CBFC72C996C0600151637 /* Club.swift in Sources */, FF4CBFC72C996C0600151637 /* Club.swift in Sources */,
FFE8B5B82DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */,
FF4CBFC82C996C0600151637 /* Array+Extensions.swift in Sources */, FF4CBFC82C996C0600151637 /* Array+Extensions.swift in Sources */,
FF4CBFC92C996C0600151637 /* ToolboxView.swift in Sources */, FF4CBFC92C996C0600151637 /* ToolboxView.swift in Sources */,
FF4CBFCA2C996C0600151637 /* Alphabet.swift in Sources */, FF4CBFCA2C996C0600151637 /* Alphabet.swift in Sources */,
@ -3130,6 +3143,7 @@
FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */, FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */,
FF4CBFFD2C996C0600151637 /* CustomUser.swift in Sources */, FF4CBFFD2C996C0600151637 /* CustomUser.swift in Sources */,
FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */, FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */,
FFE8B5B52DA848D400BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */,
FFA252B52CDD2C6C0074E63F /* OngoingDestination.swift in Sources */, FFA252B52CDD2C6C0074E63F /* OngoingDestination.swift in Sources */,
FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */, FF4CBFFF2C996C0600151637 /* TournamentDurationManagerView.swift in Sources */,
FF4CC0002C996C0600151637 /* MockData.swift in Sources */, FF4CC0002C996C0600151637 /* MockData.swift in Sources */,
@ -3358,6 +3372,7 @@
FF70FB442C90584900129CC2 /* CashierSettingsView.swift in Sources */, FF70FB442C90584900129CC2 /* CashierSettingsView.swift in Sources */,
FF70FB452C90584900129CC2 /* LoserRoundScheduleEditorView.swift in Sources */, FF70FB452C90584900129CC2 /* LoserRoundScheduleEditorView.swift in Sources */,
FF70FB462C90584900129CC2 /* Club.swift in Sources */, FF70FB462C90584900129CC2 /* Club.swift in Sources */,
FFE8B5B92DA8763800BDE966 /* PaymentInfoSheetView.swift in Sources */,
FF70FB472C90584900129CC2 /* Array+Extensions.swift in Sources */, FF70FB472C90584900129CC2 /* Array+Extensions.swift in Sources */,
FF70FB482C90584900129CC2 /* ToolboxView.swift in Sources */, FF70FB482C90584900129CC2 /* ToolboxView.swift in Sources */,
FF70FB492C90584900129CC2 /* Alphabet.swift in Sources */, FF70FB492C90584900129CC2 /* Alphabet.swift in Sources */,
@ -3424,6 +3439,7 @@
FF70FB7C2C90584900129CC2 /* CustomUser.swift in Sources */, FF70FB7C2C90584900129CC2 /* CustomUser.swift in Sources */,
C4C33F772C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */, C4C33F772C9B1ED4006316DE /* CodingContainer+Extensions.swift in Sources */,
FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */, FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */,
FFE8B5B42DA848D400BDE966 /* OnlineWaitingListFaqSheetView.swift in Sources */,
FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */, FFA252B72CDD2C6C0074E63F /* OngoingDestination.swift in Sources */,
FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */, FF70FB7E2C90584900129CC2 /* TournamentDurationManagerView.swift in Sources */,
FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */, FF70FB7F2C90584900129CC2 /* MockData.swift in Sources */,

@ -14,6 +14,79 @@ enum UserRight: Int, Codable {
case creation = 2 case creation = 2
} }
enum RegistrationPaymentMode: Int, Codable {
case disabled = 0
case corporate = 1
case noFee = 2
case stripe = 3
func fee() -> Double? {
switch self {
case .disabled:
return nil
case .corporate:
return nil
case .noFee:
return nil
case .stripe:
let fee = 0.0075
return fee
}
}
func canEnableOnlinePayment() -> Bool {
switch self {
case .disabled:
return false
case .corporate:
return true
case .noFee:
return true
case .stripe:
return true
}
}
func localizedRegistrationPaymentFee() -> String? {
switch self {
case .disabled:
return nil
case .corporate:
return nil
case .noFee:
return nil
case .stripe:
if let fee = self.fee() {
return String(format: "%.1f%%", fee * 100)
} else {
return nil
}
}
}
func sample(entryFee: Double) -> String? {
if let fee = self.fee() {
let feeAmount = entryFee * fee
return String(format: "%.2f€", feeAmount)
} else {
return nil
}
}
func requiresStripe() -> Bool {
switch self {
case .disabled:
return false
case .corporate:
return true
case .noFee:
return true
case .stripe:
return true
}
}
}
@Observable @Observable
class CustomUser: BaseCustomUser, UserBase { class CustomUser: BaseCustomUser, UserBase {
@ -138,6 +211,9 @@ class CustomUser: BaseCustomUser, UserBase {
} }
} }
func canEnableOnlinePayment() -> Bool {
registrationPaymentMode.canEnableOnlinePayment()
}
// enum CodingKeys: String, CodingKey { // enum CodingKeys: String, CodingKey {
// case _id = "id" // case _id = "id"
// case _lastUpdate = "lastUpdate" // case _lastUpdate = "lastUpdate"

@ -32,8 +32,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
var groupStageMatchFormatPreference: MatchFormat? = nil var groupStageMatchFormatPreference: MatchFormat? = nil
var loserBracketMatchFormatPreference: MatchFormat? = nil var loserBracketMatchFormatPreference: MatchFormat? = nil
var loserBracketMode: LoserBracketMode = .automatic var loserBracketMode: LoserBracketMode = .automatic
var disableRankingFederalRuling: Bool = false
var deviceId: String? = nil var deviceId: String? = nil
var agents: [String] = [] var agents: [String] = []
var userRole: Int? = nil
var registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled
var umpireCustomMail: String? = nil
var umpireCustomContact: String? = nil
var umpireCustomPhone: String? = nil
var hideUmpireMail: Bool = false
var hideUmpirePhone: Bool = true
init( init(
id: String = Store.randomId(), id: String = Store.randomId(),
@ -57,8 +65,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
groupStageMatchFormatPreference: MatchFormat? = nil, groupStageMatchFormatPreference: MatchFormat? = nil,
loserBracketMatchFormatPreference: MatchFormat? = nil, loserBracketMatchFormatPreference: MatchFormat? = nil,
loserBracketMode: LoserBracketMode = .automatic, loserBracketMode: LoserBracketMode = .automatic,
disableRankingFederalRuling: Bool = false,
deviceId: String? = nil, deviceId: String? = nil,
agents: [String] = [] agents: [String] = [],
userRole: Int? = nil,
registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true
) { ) {
super.init() super.init()
self.id = id self.id = id
@ -82,8 +98,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = groupStageMatchFormatPreference self.groupStageMatchFormatPreference = groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference
self.loserBracketMode = loserBracketMode self.loserBracketMode = loserBracketMode
self.disableRankingFederalRuling = disableRankingFederalRuling
self.deviceId = deviceId self.deviceId = deviceId
self.agents = agents self.agents = agents
self.userRole = userRole
self.registrationPaymentMode = registrationPaymentMode
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.umpireCustomPhone = umpireCustomPhone
self.hideUmpireMail = hideUmpireMail
self.hideUmpirePhone = hideUmpirePhone
} }
required public override init() { required public override init() {
super.init() super.init()
@ -111,8 +135,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference" case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference"
case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference" case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference"
case _loserBracketMode = "loserBracketMode" case _loserBracketMode = "loserBracketMode"
case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _deviceId = "deviceId" case _deviceId = "deviceId"
case _agents = "agents" case _agents = "agents"
case _userRole = "userRole"
case _registrationPaymentMode = "registrationPaymentMode"
case _umpireCustomMail = "umpireCustomMail"
case _umpireCustomContact = "umpireCustomContact"
case _umpireCustomPhone = "umpireCustomPhone"
case _hideUmpireMail = "hideUmpireMail"
case _hideUmpirePhone = "hideUmpirePhone"
} }
required init(from decoder: Decoder) throws { required init(from decoder: Decoder) throws {
@ -138,8 +170,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil
self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil
self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? [] self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? []
self.userRole = try container.decodeIfPresent(Int.self, forKey: ._userRole) ?? nil
self.registrationPaymentMode = try container.decodeIfPresent(RegistrationPaymentMode.self, forKey: ._registrationPaymentMode) ?? RegistrationPaymentMode.disabled
self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil
self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil
self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil
self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -166,8 +206,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference) try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference)
try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference) try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference)
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode) try container.encode(self.loserBracketMode, forKey: ._loserBracketMode)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.deviceId, forKey: ._deviceId) try container.encode(self.deviceId, forKey: ._deviceId)
try container.encode(self.agents, forKey: ._agents) try container.encode(self.agents, forKey: ._agents)
try container.encode(self.userRole, forKey: ._userRole)
try container.encode(self.registrationPaymentMode, forKey: ._registrationPaymentMode)
try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail)
try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact)
try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone)
try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail)
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try super.encode(to: encoder) try super.encode(to: encoder)
} }
@ -194,8 +242,16 @@ class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference
self.loserBracketMode = customuser.loserBracketMode self.loserBracketMode = customuser.loserBracketMode
self.disableRankingFederalRuling = customuser.disableRankingFederalRuling
self.deviceId = customuser.deviceId self.deviceId = customuser.deviceId
self.agents = customuser.agents self.agents = customuser.agents
self.userRole = customuser.userRole
self.registrationPaymentMode = customuser.registrationPaymentMode
self.umpireCustomMail = customuser.umpireCustomMail
self.umpireCustomContact = customuser.umpireCustomContact
self.umpireCustomPhone = customuser.umpireCustomPhone
self.hideUmpireMail = customuser.hideUmpireMail
self.hideUmpirePhone = customuser.hideUmpirePhone
} }
static func relationships() -> [Relationship] { static func relationships() -> [Relationship] {

@ -33,6 +33,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
var coach: Bool = false var coach: Bool = false
var captain: Bool = false var captain: Bool = false
var registeredOnline: Bool = false var registeredOnline: Bool = false
var timeToConfirm: Date? = nil
var registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting
var paymentId: String? = nil
init( init(
id: String = Store.randomId(), id: String = Store.randomId(),
@ -56,7 +59,10 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
hasArrived: Bool = false, hasArrived: Bool = false,
coach: Bool = false, coach: Bool = false,
captain: Bool = false, captain: Bool = false,
registeredOnline: Bool = false registeredOnline: Bool = false,
timeToConfirm: Date? = nil,
registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting,
paymentId: String? = nil
) { ) {
super.init() super.init()
self.id = id self.id = id
@ -81,6 +87,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = coach self.coach = coach
self.captain = captain self.captain = captain
self.registeredOnline = registeredOnline self.registeredOnline = registeredOnline
self.timeToConfirm = timeToConfirm
self.registrationStatus = registrationStatus
self.paymentId = paymentId
} }
required public override init() { required public override init() {
super.init() super.init()
@ -109,6 +118,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
case _coach = "coach" case _coach = "coach"
case _captain = "captain" case _captain = "captain"
case _registeredOnline = "registeredOnline" case _registeredOnline = "registeredOnline"
case _timeToConfirm = "timeToConfirm"
case _registrationStatus = "registrationStatus"
case _paymentId = "paymentId"
} }
required init(from decoder: Decoder) throws { required init(from decoder: Decoder) throws {
@ -135,6 +147,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false
self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false
self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false
self.timeToConfirm = try container.decodeIfPresent(Date.self, forKey: ._timeToConfirm) ?? nil
self.registrationStatus = try container.decodeIfPresent(PlayerRegistration.RegistrationStatus.self, forKey: ._registrationStatus) ?? PlayerRegistration.RegistrationStatus.waiting
self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -162,6 +177,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
try container.encode(self.coach, forKey: ._coach) try container.encode(self.coach, forKey: ._coach)
try container.encode(self.captain, forKey: ._captain) try container.encode(self.captain, forKey: ._captain)
try container.encode(self.registeredOnline, forKey: ._registeredOnline) try container.encode(self.registeredOnline, forKey: ._registeredOnline)
try container.encode(self.timeToConfirm, forKey: ._timeToConfirm)
try container.encode(self.registrationStatus, forKey: ._registrationStatus)
try container.encode(self.paymentId, forKey: ._paymentId)
try super.encode(to: encoder) try super.encode(to: encoder)
} }
@ -194,6 +212,9 @@ class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = playerregistration.coach self.coach = playerregistration.coach
self.captain = playerregistration.captain self.captain = playerregistration.captain
self.registeredOnline = playerregistration.registeredOnline self.registeredOnline = playerregistration.registeredOnline
self.timeToConfirm = playerregistration.timeToConfirm
self.registrationStatus = playerregistration.registrationStatus
self.paymentId = playerregistration.paymentId
} }
static func relationships() -> [Relationship] { static func relationships() -> [Relationship] {

@ -70,6 +70,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
var hideUmpirePhone: Bool = true var hideUmpirePhone: Bool = true
var disableRankingFederalRuling: Bool = false var disableRankingFederalRuling: Bool = false
var teamCountLimit: Bool = true var teamCountLimit: Bool = true
var enableOnlinePayment: Bool = false
var onlinePaymentIsMandatory: Bool = false
var enableOnlinePaymentRefund: Bool = false
var refundDateLimit: Date? = nil
var stripeAccountId: String? = nil
var enableTimeToConfirm: Bool = false
var isCorporateTournament: Bool = false
var isTemplate: Bool = false
init( init(
id: String = Store.randomId(), id: String = Store.randomId(),
@ -130,7 +138,15 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
hideUmpireMail: Bool = false, hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true, hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false, disableRankingFederalRuling: Bool = false,
teamCountLimit: Bool = true teamCountLimit: Bool = true,
enableOnlinePayment: Bool = false,
onlinePaymentIsMandatory: Bool = false,
enableOnlinePaymentRefund: Bool = false,
refundDateLimit: Date? = nil,
stripeAccountId: String? = nil,
enableTimeToConfirm: Bool = false,
isCorporateTournament: Bool = false,
isTemplate: Bool = false
) { ) {
super.init() super.init()
self.id = id self.id = id
@ -192,6 +208,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = hideUmpirePhone self.hideUmpirePhone = hideUmpirePhone
self.disableRankingFederalRuling = disableRankingFederalRuling self.disableRankingFederalRuling = disableRankingFederalRuling
self.teamCountLimit = teamCountLimit self.teamCountLimit = teamCountLimit
self.enableOnlinePayment = enableOnlinePayment
self.onlinePaymentIsMandatory = onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = enableOnlinePaymentRefund
self.refundDateLimit = refundDateLimit
self.stripeAccountId = stripeAccountId
self.enableTimeToConfirm = enableTimeToConfirm
self.isCorporateTournament = isCorporateTournament
self.isTemplate = isTemplate
} }
required public override init() { required public override init() {
super.init() super.init()
@ -259,6 +283,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
case _hideUmpirePhone = "hideUmpirePhone" case _hideUmpirePhone = "hideUmpirePhone"
case _disableRankingFederalRuling = "disableRankingFederalRuling" case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _teamCountLimit = "teamCountLimit" case _teamCountLimit = "teamCountLimit"
case _enableOnlinePayment = "enableOnlinePayment"
case _onlinePaymentIsMandatory = "onlinePaymentIsMandatory"
case _enableOnlinePaymentRefund = "enableOnlinePaymentRefund"
case _refundDateLimit = "refundDateLimit"
case _stripeAccountId = "stripeAccountId"
case _enableTimeToConfirm = "enableTimeToConfirm"
case _isCorporateTournament = "isCorporateTournament"
case _isTemplate = "isTemplate"
} }
private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? { private static func _decodePayment(container: KeyedDecodingContainer<CodingKeys>) throws -> TournamentPayment? {
@ -389,6 +421,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.teamCountLimit = try container.decodeIfPresent(Bool.self, forKey: ._teamCountLimit) ?? true self.teamCountLimit = try container.decodeIfPresent(Bool.self, forKey: ._teamCountLimit) ?? true
self.enableOnlinePayment = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePayment) ?? false
self.onlinePaymentIsMandatory = try container.decodeIfPresent(Bool.self, forKey: ._onlinePaymentIsMandatory) ?? false
self.enableOnlinePaymentRefund = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePaymentRefund) ?? false
self.refundDateLimit = try container.decodeIfPresent(Date.self, forKey: ._refundDateLimit) ?? nil
self.stripeAccountId = try container.decodeIfPresent(String.self, forKey: ._stripeAccountId) ?? nil
self.enableTimeToConfirm = try container.decodeIfPresent(Bool.self, forKey: ._enableTimeToConfirm) ?? false
self.isCorporateTournament = try container.decodeIfPresent(Bool.self, forKey: ._isCorporateTournament) ?? false
self.isTemplate = try container.decodeIfPresent(Bool.self, forKey: ._isTemplate) ?? false
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -453,6 +493,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone) try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling) try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.teamCountLimit, forKey: ._teamCountLimit) try container.encode(self.teamCountLimit, forKey: ._teamCountLimit)
try container.encode(self.enableOnlinePayment, forKey: ._enableOnlinePayment)
try container.encode(self.onlinePaymentIsMandatory, forKey: ._onlinePaymentIsMandatory)
try container.encode(self.enableOnlinePaymentRefund, forKey: ._enableOnlinePaymentRefund)
try container.encode(self.refundDateLimit, forKey: ._refundDateLimit)
try container.encode(self.stripeAccountId, forKey: ._stripeAccountId)
try container.encode(self.enableTimeToConfirm, forKey: ._enableTimeToConfirm)
try container.encode(self.isCorporateTournament, forKey: ._isCorporateTournament)
try container.encode(self.isTemplate, forKey: ._isTemplate)
try super.encode(to: encoder) try super.encode(to: encoder)
} }
@ -522,6 +570,14 @@ class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = tournament.hideUmpirePhone self.hideUmpirePhone = tournament.hideUmpirePhone
self.disableRankingFederalRuling = tournament.disableRankingFederalRuling self.disableRankingFederalRuling = tournament.disableRankingFederalRuling
self.teamCountLimit = tournament.teamCountLimit self.teamCountLimit = tournament.teamCountLimit
self.enableOnlinePayment = tournament.enableOnlinePayment
self.onlinePaymentIsMandatory = tournament.onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = tournament.enableOnlinePaymentRefund
self.refundDateLimit = tournament.refundDateLimit
self.stripeAccountId = tournament.stripeAccountId
self.enableTimeToConfirm = tournament.enableTimeToConfirm
self.isCorporateTournament = tournament.isCorporateTournament
self.isTemplate = tournament.isTemplate
} }
static func relationships() -> [Relationship] { static func relationships() -> [Relationship] {

@ -119,6 +119,11 @@
"type": "LoserBracketMode", "type": "LoserBracketMode",
"defaultValue": ".automatic" "defaultValue": ".automatic"
}, },
{
"name": "disableRankingFederalRuling",
"type": "Bool",
"defaultValue": "false"
},
{ {
"name": "deviceId", "name": "deviceId",
"type": "String", "type": "String",
@ -129,6 +134,42 @@
"name": "agents", "name": "agents",
"type": "[String]", "type": "[String]",
"defaultValue": "[]" "defaultValue": "[]"
},
{
"name": "userRole",
"type": "Int",
"optional": true,
"defaultValue": "nil"
},
{
"name": "registrationPaymentMode",
"type": "RegistrationPaymentMode",
"defaultValue": "RegistrationPaymentMode.disabled"
},
{
"name": "umpireCustomMail",
"type": "String",
"optional": true
},
{
"name": "umpireCustomContact",
"type": "String",
"optional": true
},
{
"name": "umpireCustomPhone",
"type": "String",
"optional": true
},
{
"name": "hideUmpireMail",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "hideUmpirePhone",
"type": "Bool",
"defaultValue": "true"
} }
] ]
} }

@ -115,6 +115,22 @@
"name": "registeredOnline", "name": "registeredOnline",
"type": "Bool", "type": "Bool",
"defaultValue": "false" "defaultValue": "false"
},
{
"name": "timeToConfirm",
"type": "Date",
"optional": true
},
{
"name": "registrationStatus",
"type": "PlayerRegistration.RegistrationStatus",
"choices": "PlayerRegistration.RegistrationStatus",
"defaultValue": "PlayerRegistration.RegistrationStatus.waiting"
},
{
"name": "paymentId",
"type": "String",
"optional": true
} }
] ]
} }

@ -295,14 +295,52 @@
{ {
"name": "disableRankingFederalRuling", "name": "disableRankingFederalRuling",
"type": "Bool", "type": "Bool",
"defaultValue": "false", "defaultValue": "false"
"optional": false
}, },
{ {
"name": "teamCountLimit", "name": "teamCountLimit",
"type": "Bool", "type": "Bool",
"defaultValue": "true", "defaultValue": "true"
"optional": false },
{
"name": "enableOnlinePayment",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "onlinePaymentIsMandatory",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "enableOnlinePaymentRefund",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "refundDateLimit",
"type": "Date",
"optional": true
},
{
"name": "stripeAccountId",
"type": "String",
"optional": true
},
{
"name": "enableTimeToConfirm",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "isCorporateTournament",
"type": "Bool",
"defaultValue": "false"
},
{
"name": "isTemplate",
"type": "Bool",
"defaultValue": "false"
} }
] ]
} }

@ -145,7 +145,7 @@ final class PlayerRegistration: BasePlayerRegistration, SideStorable {
func pasteData(_ exportFormat: ExportFormat = .rawText) -> String { func pasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch exportFormat { switch exportFormat {
case .rawText: case .rawText:
return [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: exportFormat.separator()) return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator())
case .csv: case .csv:
return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator()) return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
} }
@ -383,6 +383,13 @@ final class PlayerRegistration: BasePlayerRegistration, SideStorable {
case beachPadel = 1 case beachPadel = 1
} }
enum RegistrationStatus: Int, Codable {
case waiting = 0
case pending = 1
case confirmed = 2
case canceled = 3
}
static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int { static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int {
switch playerRank { switch playerRank {
case 0: return 0 case 0: return 0

@ -28,98 +28,6 @@ final class Tournament: BaseTournament {
@ObservationIgnored @ObservationIgnored
var navigationPath: [Screen] = [] var navigationPath: [Screen] = []
// internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0) {
// super.init()
// }
internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0, enableOnlineRegistration: Bool = false, registrationDateLimit: Date? = nil, openingRegistrationDate: Date? = nil, waitingListLimit: Int? = nil, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil,
umpireCustomMail: String? = nil,
umpireCustomContact: String? = nil,
umpireCustomPhone: String? = nil,
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false,
teamCountLimit: Bool = true
) {
super.init()
self.event = event
self.name = name
self.startDate = startDate
self.endDate = endDate
self.creationDate = creationDate
#if DEBUG
self.isPrivate = false
#else
self.isPrivate = isPrivate
#endif
self.groupStageFormat = groupStageFormat
self.roundFormat = roundFormat
self.loserRoundFormat = loserRoundFormat
self.groupStageSortMode = groupStageSortMode
self.groupStageCount = groupStageCount
self.rankSourceDate = rankSourceDate
self.dayDuration = dayDuration
self.teamCount = teamCount
self.teamSorting = teamSorting ?? federalLevelCategory.defaultTeamSortingType
self.federalCategory = federalCategory
self.federalLevelCategory = federalLevelCategory
self.federalAgeCategory = federalAgeCategory
self.closedRegistrationDate = closedRegistrationDate
self.groupStageAdditionalQualified = groupStageAdditionalQualified
self.courtCount = courtCount
self.prioritizeClubMembers = prioritizeClubMembers
self.qualifiedPerGroupStage = qualifiedPerGroupStage
self.teamsPerGroupStage = teamsPerGroupStage
self.entryFee = entryFee
self.additionalEstimationDuration = additionalEstimationDuration
self.isDeleted = isDeleted
#if DEBUG
self.publishTeams = true
self.publishSummons = true
self.publishBrackets = true
self.publishGroupStages = true
self.publishRankings = true
self.publishTournament = true
#else
self.publishTeams = publishTeams
self.publishSummons = publishSummons
self.publishBrackets = publishBrackets
self.publishGroupStages = publishGroupStages
self.publishRankings = publishRankings
self.publishTournament = publishTournament
#endif
self.shouldVerifyBracket = shouldVerifyBracket
self.shouldVerifyGroupStage = shouldVerifyGroupStage
self.hideTeamsWeight = hideTeamsWeight
self.hidePointsEarned = hidePointsEarned
self.loserBracketMode = loserBracketMode
self.initialSeedRound = initialSeedRound
self.initialSeedCount = initialSeedCount
self.enableOnlineRegistration = enableOnlineRegistration
self.registrationDateLimit = registrationDateLimit
self.openingRegistrationDate = openingRegistrationDate
self.waitingListLimit = waitingListLimit
self.accountIsRequired = accountIsRequired
self.licenseIsRequired = licenseIsRequired
self.minimumPlayerPerTeam = minimumPlayerPerTeam
self.maximumPlayerPerTeam = maximumPlayerPerTeam
self.information = information
self.umpireCustomMail = umpireCustomMail
self.umpireCustomContact = umpireCustomContact
self.disableRankingFederalRuling = disableRankingFederalRuling
self.teamCountLimit = teamCountLimit
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
required public init() {
super.init()
}
var tournamentStore: TournamentStore? { var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.id) return TournamentLibrary.shared.store(tournamentId: self.id)
} }
@ -780,7 +688,7 @@ defer {
func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] { func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] {
var duplicates = [PlayerRegistration]() var duplicates = [PlayerRegistration]()
Set(players.compactMap({ $0.licenceId })).forEach { licenceId in Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
let found = players.filter({ $0.licenceId == licenceId }) let found = players.filter({ $0.licenceId?.strippedLicense == licenceId.strippedLicense })
if found.count > 1 { if found.count > 1 {
duplicates.append(found.first!) duplicates.append(found.first!)
} }
@ -1953,6 +1861,80 @@ defer {
} }
} }
func initSettings(templateTournament: Tournament?) {
setupDefaultPrivateSettings(templateTournament: templateTournament)
setupUmpireSettings(defaultTournament: nil) //default is not template, default is for event sharing settings
if let templateTournament {
setupRegistrationSettings(templateTournament: templateTournament)
}
setupFederalSettings()
}
func setupDefaultPrivateSettings(templateTournament: Tournament?) {
#if DEBUG
self.isPrivate = false
self.publishTeams = true
self.publishSummons = true
self.publishBrackets = true
self.publishGroupStages = true
self.publishRankings = true
self.publishTournament = true
#else
var shouldBePrivate = templateTournament?.isPrivate ?? true
if Guard.main.currentPlan == .monthlyUnlimited {
shouldBePrivate = false
} else if Guard.main.purchasedTransactions.isEmpty == false {
shouldBePrivate = false
}
self.isPrivate = shouldBePrivate
#endif
}
func setupUmpireSettings(defaultTournament: Tournament? = nil) {
if let defaultTournament {
self.umpireCustomMail = defaultTournament.umpireCustomMail
self.umpireCustomPhone = defaultTournament.umpireCustomPhone
self.umpireCustomContact = defaultTournament.umpireCustomContact
self.hideUmpireMail = defaultTournament.hideUmpireMail
self.hideUmpirePhone = defaultTournament.hideUmpirePhone
self.disableRankingFederalRuling = defaultTournament.disableRankingFederalRuling
self.loserBracketMode = defaultTournament.loserBracketMode
} else {
let user = DataStore.shared.user
self.umpireCustomMail = user.umpireCustomMail
self.umpireCustomPhone = user.umpireCustomPhone
self.umpireCustomContact = user.umpireCustomContact
self.hideUmpireMail = user.hideUmpireMail
self.hideUmpirePhone = user.hideUmpirePhone
self.disableRankingFederalRuling = user.disableRankingFederalRuling
self.loserBracketMode = user.loserBracketMode
}
}
func setupRegistrationSettings(templateTournament: Tournament) {
self.enableOnlineRegistration = templateTournament.enableOnlineRegistration
self.accountIsRequired = templateTournament.accountIsRequired
self.licenseIsRequired = templateTournament.licenseIsRequired
self.minimumPlayerPerTeam = templateTournament.minimumPlayerPerTeam
self.maximumPlayerPerTeam = templateTournament.maximumPlayerPerTeam
self.waitingListLimit = templateTournament.waitingListLimit
self.teamCountLimit = templateTournament.teamCountLimit
self.enableOnlinePayment = templateTournament.enableOnlinePayment
self.onlinePaymentIsMandatory = templateTournament.onlinePaymentIsMandatory
self.enableOnlinePaymentRefund = templateTournament.enableOnlinePaymentRefund
self.stripeAccountId = templateTournament.stripeAccountId
self.enableTimeToConfirm = templateTournament.enableTimeToConfirm
self.isCorporateTournament = templateTournament.isCorporateTournament
if self.registrationDateLimit == nil, templateTournament.registrationDateLimit != nil {
self.registrationDateLimit = startDate.truncateMinutesAndSeconds()
}
self.openingRegistrationDate = templateTournament.openingRegistrationDate != nil ? creationDate.truncateMinutesAndSeconds() : nil
self.refundDateLimit = templateTournament.enableOnlinePaymentRefund ? startDate.truncateMinutesAndSeconds() : nil
}
func setupFederalSettings() { func setupFederalSettings() {
teamSorting = tournamentLevel.defaultTeamSortingType teamSorting = tournamentLevel.defaultTeamSortingType
groupStageMatchFormat = groupStageSmartMatchFormat() groupStageMatchFormat = groupStageSmartMatchFormat()
@ -2604,6 +2586,10 @@ extension Tournament: TournamentBuildHolder {
} }
extension Tournament { extension Tournament {
static func getTemplateTournament() -> Tournament? {
return DataStore.shared.tournaments.filter { $0.isTemplate && $0.isDeleted == false }.sorted(by: \.startDate, order: .descending).first
}
static func newEmptyInstance() -> Tournament { static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? { var _mostRecentDateAvailable: Date? {
@ -2612,28 +2598,7 @@ extension Tournament {
} }
let rankSourceDate = _mostRecentDateAvailable let rankSourceDate = _mostRecentDateAvailable
let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil && $0.isDeleted == false }.sorted(by: \.endDate!, order: .descending) return Tournament(rankSourceDate: rankSourceDate)
var shouldBePrivate = tournaments.first?.isPrivate ?? true
if Guard.main.currentPlan == .monthlyUnlimited {
shouldBePrivate = false
} else if Guard.main.purchasedTransactions.isEmpty == false {
shouldBePrivate = false
}
let disableRankingFederalRuling = tournaments.first?.disableRankingFederalRuling ?? false
let umpireCustomMail = tournaments.first?.umpireCustomMail
let umpireCustomPhone = tournaments.first?.umpireCustomPhone
let umpireCustomContact = tournaments.first?.umpireCustomContact
let hideUmpireMail = tournaments.first?.hideUmpireMail ?? false
let hideUmpirePhone = tournaments.first?.hideUmpirePhone ?? true
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
//creator: DataStore.shared.user?.id
return Tournament(isPrivate: shouldBePrivate, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge, loserBracketMode: DataStore.shared.user.loserBracketMode, umpireCustomMail: umpireCustomMail, umpireCustomContact: umpireCustomContact, umpireCustomPhone: umpireCustomPhone, hideUmpireMail: hideUmpireMail, hideUmpirePhone: hideUmpirePhone, disableRankingFederalRuling: disableRankingFederalRuling)
} }
static func fake() -> Tournament { static func fake() -> Tournament {

@ -10,8 +10,8 @@ import Foundation
enum URLs: String, Identifiable { enum URLs: String, Identifiable {
// case httpScheme = "https://" // case httpScheme = "https://"
#if DEBUG #if DEBUG
case activationHost = "xlr.alwaysdata.net" case activationHost = "http://127.0.0.1:8000"
case main = "https://xlr.alwaysdata.net/" case main = "http://127.0.0.1:8000/"
// case api = "https://xlr.alwaysdata.net/roads/" // case api = "https://xlr.alwaysdata.net/roads/"
#elseif TESTFLIGHT #elseif TESTFLIGHT
case activationHost = "xlr.alwaysdata.net" case activationHost = "xlr.alwaysdata.net"

@ -138,12 +138,13 @@ struct EventCreationView: View {
Logger.error(error) Logger.error(error)
} }
let templateTournament = Tournament.getTemplateTournament()
tournaments.forEach { tournament in tournaments.forEach { tournament in
tournament.event = event.id tournament.event = event.id
tournament.courtCount = selectedClub?.courtCount ?? 2 tournament.courtCount = selectedClub?.courtCount ?? 2
tournament.startDate = startingDate tournament.startDate = startingDate
tournament.dayDuration = duration tournament.dayDuration = duration
tournament.setupFederalSettings() tournament.initSettings(templateTournament: templateTournament)
} }
do { do {

@ -13,6 +13,7 @@ struct EventTournamentsView: View {
@Environment(NavigationViewModel.self) private var navigation @Environment(NavigationViewModel.self) private var navigation
let event: Event let event: Event
@State private var newTournament: Tournament? @State private var newTournament: Tournament?
@State private var mainTournament: Tournament?
var presentTournamentCreationView: Binding<Bool> { Binding( var presentTournamentCreationView: Binding<Bool> { Binding(
get: { newTournament != nil }, get: { newTournament != nil },
@ -27,6 +28,7 @@ struct EventTournamentsView: View {
let tournaments = event.tournaments let tournaments = event.tournaments
List { List {
ForEach(tournaments) { tournament in ForEach(tournaments) { tournament in
Section {
NavigationLink { NavigationLink {
TournamentStatusView(tournament: tournament, eventDismiss: true) TournamentStatusView(tournament: tournament, eventDismiss: true)
} label: { } label: {
@ -39,6 +41,25 @@ struct EventTournamentsView: View {
} }
} }
} }
} footer: {
if event.tournaments.count > 1 {
if mainTournament == nil {
FooterButtonView("c'est le tournoi principal") {
self.mainTournament = tournament
}
} else if mainTournament == tournament {
FooterButtonView("ce n'est pas le tournoi principal") {
self.mainTournament = tournament
}
} else if let mainTournament {
FooterButtonView("coller les réglages du tournoi principal") {
tournament.setupUmpireSettings(defaultTournament: mainTournament)
tournament.setupRegistrationSettings(templateTournament: mainTournament)
dataStore.tournaments.addOrUpdate(instance: tournament)
}
}
}
}
} }
} }
.toolbar { .toolbar {
@ -63,13 +84,9 @@ struct EventTournamentsView: View {
newTournament.courtCount = event.eventCourtCount() newTournament.courtCount = event.eventCourtCount()
newTournament.startDate = event.eventStartDate() newTournament.startDate = event.eventStartDate()
newTournament.dayDuration = event.eventDayDuration() newTournament.dayDuration = event.eventDayDuration()
newTournament.setupFederalSettings() newTournament.initSettings(templateTournament: Tournament.getTemplateTournament())
do { dataStore.tournaments.addOrUpdate(instance: newTournament)
try dataStore.tournaments.addOrUpdate(instance: newTournament)
} catch {
Logger.error(error)
}
self.newTournament = nil self.newTournament = nil
} }

@ -72,6 +72,7 @@ struct CalendarView: View {
if federalDataViewModel.ageCategories.isEmpty == false { if federalDataViewModel.ageCategories.isEmpty == false {
tournament.federalTournamentAge = federalDataViewModel.ageCategories.first! tournament.federalTournamentAge = federalDataViewModel.ageCategories.first!
} }
tournament.initSettings(templateTournament: Tournament.getTemplateTournament())
newTournament = tournament newTournament = tournament
} }
@ -173,7 +174,8 @@ struct CalendarView: View {
newTournament.federalTournamentAge = build.age newTournament.federalTournamentAge = build.age
newTournament.dayDuration = federalTournament.dayDuration newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9)
newTournament.setupFederalSettings() newTournament.initSettings(templateTournament: Tournament.getTemplateTournament())
do { do {
try dataStore.tournaments.addOrUpdate(instance: newTournament) try dataStore.tournaments.addOrUpdate(instance: newTournament)
} catch { } catch {

@ -133,7 +133,7 @@ struct EventListView: View {
try FileManager.default.removeItem(at: chunk.url) try FileManager.default.removeItem(at: chunk.url)
} }
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -147,119 +147,103 @@ struct EventListView: View {
Divider() Divider()
} }
Menu { Menu {
if pcTournaments.anySatisfy({ $0.isPrivate == true }) {
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.isPrivate = false tournament.isPrivate = false
} }
do { dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} label: { } label: {
Text("Afficher sur Padel Club") Text("Afficher sur Padel Club")
} }
}
if pcTournaments.anySatisfy({ $0.isPrivate == false }) {
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.isPrivate = true tournament.isPrivate = true
} }
do { dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} label: { } label: {
Text("Masquer sur Padel Club") Text("Masquer sur Padel Club")
} }
}
} label: { } label: {
Text("Visibilité sur Padel Club") Text("Visibilité sur Padel Club")
} }
Divider() Divider()
if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) || pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true && $0.hasEnded() == false }) {
Menu { Menu {
if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) { Button {
Task {
await pcTournaments.concurrentForEach { tournament in
await tournament.refreshTeamList(forced: true)
}
}
} label: {
Text("M-à-j des inscriptions")
}
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
if tournament.onlineRegistrationCanBeEnabled() {
tournament.enableOnlineRegistration = true tournament.enableOnlineRegistration = true
} }
do {
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
} }
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: { } label: {
Text("Activer") Text("Activer")
} }
}
if pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true && $0.hasEnded() == false }) {
Button { Button {
Task { pcTournaments.forEach { tournament in
await pcTournaments.concurrentForEach { tournament in tournament.enableOnlineRegistration = false
await tournament.refreshTeamList(forced: true)
} }
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: {
Text("Désactiver")
} }
} label: { } label: {
Text("M-à-j des inscriptions") Text("Inscription en ligne")
} }
Divider()
if dataStore.user.canEnableOnlinePayment() {
Menu {
Button { Button {
if let templateTournament = Tournament.getTemplateTournament() {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.enableOnlineRegistration = false if tournament.onlineRegistrationCanBeEnabled() {
tournament.setupRegistrationSettings(templateTournament: templateTournament)
} }
do {
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} label: {
Text("Désactiver")
} }
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} }
} label: { } label: {
Text("Inscription en ligne") Text("Utiliser les réglages par défaut")
} }
} label: {
Text("Inscription et paiement en ligne")
} }
Divider() Divider()
}
Menu { Menu {
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.information = nil tournament.information = nil
} }
do { dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} label: { } label: {
Text("Effacer les descriptions") Text("Effacer les descriptions")
} }
Button {
let info = Set(pcTournaments.compactMap { tournament in let info = Set(pcTournaments.compactMap { tournament in
tournament.information?.trimmedMultiline tournament.information?.trimmedMultiline
}).joined(separator: "\n") }).joined(separator: "\n")
if info.isEmpty == false {
Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.information = info tournament.information = info
} }
do { dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} label: { } label: {
Text("Mettre '\(info.trunc(length: 12))'") Text("Mettre la même description")
}
} }
PasteButton(payloadType: String.self) { strings in PasteButton(payloadType: String.self) { strings in
@ -267,11 +251,7 @@ struct EventListView: View {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.information = pasteboard tournament.information = pasteboard
} }
do { dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
} }
} }
} label: { } label: {
@ -284,69 +264,24 @@ struct EventListView: View {
tournament.umpireCustomMail = nil tournament.umpireCustomMail = nil
tournament.umpireCustomPhone = nil tournament.umpireCustomPhone = nil
tournament.umpireCustomContact = nil tournament.umpireCustomContact = nil
tournament.hideUmpireMail = dataStore.user.hideUmpireMail
tournament.hideUmpirePhone = dataStore.user.hideUmpirePhone
} }
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: { } label: {
Text("Effacer les informations du JAP") Text("Retirer les informations personnalisées")
}
let umpireCustomMail = pcTournaments.first(where: { tournament in
tournament.umpireCustomMail != nil
})?.umpireCustomMail
let umpireCustomPhone = pcTournaments.first(where: { tournament in
tournament.umpireCustomPhone != nil
})?.umpireCustomPhone
let umpireCustomContact = pcTournaments.first(where: { tournament in
tournament.umpireCustomContact != nil
})?.umpireCustomContact
Button {
pcTournaments.forEach { tournament in
tournament.umpireCustomMail = umpireCustomMail
tournament.umpireCustomPhone = umpireCustomPhone
tournament.umpireCustomContact = umpireCustomContact
}
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: {
Text("Indiquer le même JAP pour tous")
}
Button {
pcTournaments.forEach { tournament in
tournament.hideUmpireMail = true
}
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: {
Text("Masquer le mail")
} }
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.hideUmpireMail = false tournament.setupUmpireSettings()
} }
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments) dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: { } label: {
Text("Afficher le mail") Text("Utiliser les réglages par défaut")
} }
Button {
pcTournaments.forEach { tournament in
tournament.hideUmpirePhone = true
}
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: { } label: {
Text("Masquer le téléphone") Text("Informations de contact Juge-Arbitre")
}
Button {
pcTournaments.forEach { tournament in
tournament.hideUmpirePhone = false
}
dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} label: {
Text("Afficher le téléphone")
}
} label: {
Text("Infos JAP")
} }
} }
@ -400,7 +335,17 @@ struct EventListView: View {
} }
} }
.listRowView(isActive: tournament.enableOnlineRegistration, color: .green, hideColorVariation: true) .listRowView(isActive: tournament.enableOnlineRegistration, color: .green, hideColorVariation: true)
.onChange(of: tournament.isTemplate) {
dataStore.tournaments.addOrUpdate(instance: tournament)
}
.contextMenu { .contextMenu {
@Bindable var bindableTournament: Tournament = tournament
Toggle(isOn: $bindableTournament.isTemplate) {
Text("Source des réglages d'inscriptions")
}
Divider()
if tournament.hasEnded() == false { if tournament.hasEnded() == false {
Button { Button {
navigation.openTournamentInOrganizer(tournament) navigation.openTournamentInOrganizer(tournament)
@ -441,14 +386,15 @@ struct EventListView: View {
} }
private func _importFederalTournamentBatch(federalTournament: FederalTournament) { private func _importFederalTournamentBatch(federalTournament: FederalTournament) {
federalTournament.tournaments.forEach { tournament in let templateTournament = Tournament.getTemplateTournament()
_create(federalTournament: federalTournament, existingTournament: _event(of: federalTournament)?.existingBuild(tournament), build: tournament) let newTournaments = federalTournament.tournaments.compactMap { tournament in
_create(federalTournament: federalTournament, existingTournament: _event(of: federalTournament)?.existingBuild(tournament), build: tournament, templateTournament: templateTournament)
} }
dataStore.tournaments.addOrUpdate(contentOfs: newTournaments)
} }
private func _create(federalTournament: FederalTournament, existingTournament: Tournament?, build: any TournamentBuildHolder) { private func _create(federalTournament: FederalTournament, existingTournament: Tournament?, build: any TournamentBuildHolder, templateTournament: Tournament?) -> Tournament? {
if let existingTournament { if existingTournament == nil {
} else {
let event = federalTournament.getEvent() let event = federalTournament.getEvent()
let newTournament = Tournament.newEmptyInstance() let newTournament = Tournament.newEmptyInstance()
newTournament.event = event.id newTournament.event = event.id
@ -460,12 +406,10 @@ struct EventListView: View {
newTournament.federalTournamentAge = build.age newTournament.federalTournamentAge = build.age
newTournament.dayDuration = federalTournament.dayDuration newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9)
newTournament.setupFederalSettings() newTournament.initSettings(templateTournament: templateTournament)
do { return newTournament
try dataStore.tournaments.addOrUpdate(instance: newTournament) } else {
} catch { return nil
Logger.error(error)
}
} }
} }
} }

@ -18,9 +18,22 @@ struct UmpireView: View {
@State private var presentSearchView: Bool = false @State private var presentSearchView: Bool = false
@State private var showSubscriptions: Bool = false @State private var showSubscriptions: Bool = false
@State private var showProductIds: Bool = false @State private var showProductIds: Bool = false
@State private var umpireCustomMail: String
@State private var umpireCustomPhone: String
@State private var umpireCustomContact: String
@State private var umpireCustomMailIsInvalid: Bool = false
@State private var umpireCustomPhoneIsInvalid: Bool = false
@FocusState private var focusedField: CustomUser.CodingKeys?
// @State var isConnected: Bool = false // @State var isConnected: Bool = false
init() {
_umpireCustomMail = State(wrappedValue: DataStore.shared.user.umpireCustomMail ?? "")
_umpireCustomPhone = State(wrappedValue: DataStore.shared.user.umpireCustomPhone ?? "")
_umpireCustomContact = State(wrappedValue: DataStore.shared.user.umpireCustomContact ?? "")
}
enum UmpireScreen { enum UmpireScreen {
case login case login
} }
@ -129,6 +142,39 @@ 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)
}
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.")
}
Section {
@Bindable var user = dataStore.user
Toggle(isOn: $user.disableRankingFederalRuling) {
Text("Désactiver la règle fédéral")
}
.onChange(of: user.disableRankingFederalRuling) {
dataStore.saveUser()
}
} header: {
Text("Règle fédérale classement finale")
} footer: {
Text("Dernier de poule ≠ dernier du tournoi")
}
Section { Section {
@Bindable var user = dataStore.user @Bindable var user = dataStore.user
@ -195,6 +241,61 @@ struct UmpireView: View {
} }
#endif #endif
} }
.navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: {
if focusedField != nil {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
focusedField = nil
}
}
}
})
.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)
}
Spacer()
Button("Valider") {
focusedField = nil
}
.buttonStyle(.bordered)
}
}
}
}
.onChange(of: [dataStore.user.umpireCustomMail, dataStore.user.umpireCustomPhone, dataStore.user.umpireCustomContact]) {
self.dataStore.saveUser()
}
.onChange(of: [dataStore.user.hideUmpireMail, dataStore.user.hideUmpirePhone]) {
self.dataStore.saveUser()
}
.onChange(of: focusedField) { old, new in
if old == ._umpireCustomMail {
_confirmUmpireMail()
} else if old == ._umpireCustomPhone {
_confirmUmpirePhone()
} else if old == ._umpireCustomContact {
_confirmUmpireContact()
}
}
.sheet(isPresented: self.$showSubscriptions, content: { .sheet(isPresented: self.$showSubscriptions, content: {
NavigationStack { NavigationStack {
SubscriptionView(isPresented: self.$showSubscriptions) SubscriptionView(isPresented: self.$showSubscriptions)
@ -242,6 +343,108 @@ struct UmpireView: View {
} }
} }
private func _confirmUmpireMail() {
umpireCustomMailIsInvalid = false
if umpireCustomMail.isEmpty {
dataStore.user.umpireCustomMail = nil
} else if umpireCustomMail.isValidEmail() {
dataStore.user.umpireCustomMail = umpireCustomMail
} else {
umpireCustomMailIsInvalid = true
}
}
private func _deleteUmpireMail() {
umpireCustomMailIsInvalid = false
umpireCustomMail = ""
dataStore.user.umpireCustomMail = nil
}
private func _confirmUmpirePhone() {
umpireCustomPhoneIsInvalid = false
if umpireCustomPhone.isEmpty {
dataStore.user.umpireCustomPhone = nil
} else if umpireCustomPhone.isPhoneNumber() {
dataStore.user.umpireCustomPhone = umpireCustomPhone.prefixMultilineTrimmed(15)
} else {
umpireCustomPhoneIsInvalid = true
}
}
private func _deleteUmpirePhone() {
umpireCustomPhoneIsInvalid = false
umpireCustomPhone = ""
dataStore.user.umpireCustomPhone = nil
}
private func _confirmUmpireContact() {
if umpireCustomContact.isEmpty {
dataStore.user.umpireCustomContact = nil
} else {
dataStore.user.umpireCustomContact = umpireCustomContact.prefixMultilineTrimmed(200)
}
}
private func _deleteUmpireContact() {
umpireCustomContact = ""
dataStore.user.umpireCustomContact = nil
}
private func _customUmpireView() -> some View {
Section {
VStack(alignment: .leading) {
TextField(dataStore.user.email, text: $umpireCustomMail)
.frame(maxWidth: .infinity)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.focused($focusedField, equals: ._umpireCustomMail)
.onSubmit {
_confirmUmpireMail()
}
if umpireCustomMailIsInvalid {
Text("Vous n'avez pas indiqué un email valide.").foregroundStyle(.logoRed)
}
}
VStack(alignment: .leading) {
TextField(dataStore.user.phone ?? "Téléphone", text: $umpireCustomPhone)
.frame(maxWidth: .infinity)
.keyboardType(.phonePad)
.focused($focusedField, equals: ._umpireCustomPhone)
.onSubmit {
_confirmUmpirePhone()
}
if umpireCustomPhoneIsInvalid {
Text("Vous n'avez pas indiqué un téléphone valide.").foregroundStyle(.logoRed)
}
}
VStack(alignment: .leading) {
TextField(dataStore.user.fullName(), text: $umpireCustomContact)
.frame(maxWidth: .infinity)
.keyboardType(.default)
.focused($focusedField, equals: ._umpireCustomContact)
.onSubmit {
_confirmUmpireContact()
}
if dataStore.user.getSummonsMessageSignature() != nil, umpireCustomContact != dataStore.user.fullName() {
Text("Attention vous avez une signature personnalisée contenant un contact différent.").foregroundStyle(.logoRed)
FooterButtonView("retirer la personnalisation ?") {
dataStore.user.summonsMessageSignature = nil
self.dataStore.saveUser()
}
} }
} header: {
Text("Juge-arbitre")
} footer: {
Text("Ces informations seront utilisées pour vous contacter. Vous pouvez les modifier si vous souhaitez utiliser les informations de contact différentes de votre compte Padel Club.")
}
}
} }
struct AccountRowView: View { struct AccountRowView: View {
@ -294,6 +497,7 @@ struct ProductIdsView: View {
} }
} }
} }
//#Preview { //#Preview {

@ -0,0 +1,89 @@
//
// OnlineWaitingListFaqSheetView.swift
// PadelClub
//
// Created by razmig on 10/04/2025.
//
import SwiftUI
struct OnlineWaitingListFaqSheetView: View {
@Environment(\.dismiss) private var dismiss
let faqText: String =
"""
FAQ pour les Arbitres - Confirmation des Équipes
Comment fonctionne le délai de confirmation pour les équipes ?
Notre système calcule automatiquement un délai de confirmation adapté pour les équipes en fonction de trois facteurs principaux :
- Proximité du tournoi : Plus le tournoi est proche, plus le délai est court
- Pression de la liste d'attente : Plus il y a d'équipes en attente, plus le délai est court
- Heures ouvrables : Les délais respectent généralement les heures ouvrables (8h-21h)
Quels sont les délais typiques de confirmation ?
- Tournoi dans moins de 24h 30 minutes
- Tournoi dans moins de 48h 60 minutes (1 heure)
- Tournoi dans moins de 72h 120 minutes (2 heures)
- Tournoi dans plus de 72h 240 minutes (4 heures)
Ces délais peuvent être raccourcis en fonction du nombre d'équipes en liste d'attente :
- 30+ équipes en attente 30 minutes
- 20+ équipes en attente 60 minutes (1 heure)
- 10+ équipes en attente 120 minutes (2 heures)
Y a-t-il des exceptions à ces règles ?
Oui, dans les situations urgentes :
- Si le tournoi commence dans moins de 24h, les restrictions d'heures ouvrables sont ignorées
- Si le tournoi commence dans moins de 12h, toutes les restrictions sont assouplies avec un minimum de 30 minutes de délai
Comment les délais sont-ils arrondis ?
Les délais sont toujours arrondis à la demi-heure supérieure pour plus de simplicité.
Que se passe-t-il si le délai tombe en dehors des heures ouvrables ?
Si le délai calculé tombe en dehors des heures ouvrables (avant 8h ou après 21h), il est automatiquement reporté au jour ouvrable suivant à 8h du matin.
"""
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Content sections
ForEach(faqText.components(separatedBy: "\n\n"), id: \.self) { section in
if !section.isEmpty {
VStack(alignment: .leading, spacing: 10) {
if section.contains(":") {
Text(section.components(separatedBy: ":")[0])
.font(.headline)
.foregroundColor(.primary)
let bulletPoints = section.components(separatedBy: "\n-")
if bulletPoints.count > 1 {
ForEach(bulletPoints.dropFirst(), id: \.self) { point in
HStack(alignment: .top) {
Text("")
.padding(.trailing, 5)
Text(point)
.fixedSize(horizontal: false, vertical: true)
}
}
}
} else {
Text(section)
}
}
.padding(.bottom, 10)
}
}
}
.padding()
}
.navigationBarItems(trailing: Button("Fermer") {
dismiss()
})
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("FAQ - Liste d'attente")
}
}
}

@ -0,0 +1,90 @@
//
// PaymentInfoSheetView.swift
// PadelClub
//
// Created by razmig on 15/01/2025.
//
import SwiftUI
struct PaymentInfoSheetView: View {
@Environment(\.dismiss) private var dismiss
let paymentInfoText: String =
"""
Comment fonctionnent les paiements en ligne ?
Les paiements en ligne permettent aux joueurs de régler les frais de tournoi directement via la plateforme. Voici les informations importantes à connaître :
Options de paiement :
- Le paiement en ligne est activé à la discrétion de l'organisateur
- L'organisateur peut rendre le paiement en ligne obligatoire ou optionnel
- Si le paiement n'est pas obligatoire, il est possible de s'inscrire sans payer immédiatement
- Tous les paiements sont traités via Stripe, une plateforme sécurisée de paiement en ligne
Remboursements :
- Les remboursements peuvent être activés ou désactivés par l'organisateur
- Si activés, une date limite de remboursement peut être définie
- Aucun remboursement n'est possible après cette date limite
- Les remboursements sont automatiquement traités via la même méthode de paiement utilisée
Commissions et frais :
- Padel Club prélève une commission de 0,75% sur chaque transaction
- Cette commission couvre les frais de service et de maintenance de la plateforme
- Des frais supplémentaires de Stripe peuvent s'appliquer (environ 1,4% + 0,25 par transaction)
- Le montant total des frais est indiqué clairement avant validation du paiement
Exigences pour les organisateurs :
- L'organisateur doit avoir un compte Stripe valide pour recevoir les paiements
- Le compte Stripe doit être vérifié et connecté à Padel Club
- Sans compte Stripe connecté, l'option de paiement en ligne ne peut pas être activée
- Les fonds sont directement versés sur le compte bancaire associé au compte Stripe de l'organisateur
Sécurité :
- Toutes les transactions sont sécurisées et chiffrées
- Padel Club ne stocke pas les informations de carte bancaire
- La conformité RGPD et PCI-DSS est assurée par Stripe
En cas de problème avec un paiement, veuillez contacter l'organisateur du tournoi ou le support Padel Club.
"""
var body: some View {
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Content sections
ForEach(paymentInfoText.components(separatedBy: "\n\n"), id: \.self) { section in
if !section.isEmpty {
VStack(alignment: .leading, spacing: 10) {
if section.contains(":") {
Text(section.components(separatedBy: ":")[0])
.font(.headline)
.foregroundColor(.primary)
let bulletPoints = section.components(separatedBy: "\n-")
if bulletPoints.count > 1 {
ForEach(bulletPoints.dropFirst(), id: \.self) { point in
HStack(alignment: .top) {
Text("")
.padding(.trailing, 5)
Text(point)
.fixedSize(horizontal: false, vertical: true)
}
}
}
} else {
Text(section)
}
}
.padding(.bottom, 10)
}
}
}
.padding()
}
.navigationBarItems(trailing: Button("Fermer") {
dismiss()
})
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Paiement en ligne")
}
}
}

@ -30,6 +30,10 @@ struct EventClubSettingsView: View {
Text("Lieu de l'événement") Text("Lieu de l'événement")
} footer: { } footer: {
HStack { HStack {
if let clubURL = selectedClub.shareURL() {
ShareLink(item: clubURL)
}
Spacer() Spacer()
FooterButtonView("détails du club") { FooterButtonView("détails du club") {
showClubDetail = selectedClub showClubDetail = selectedClub

@ -5,8 +5,8 @@
// Created by razmig on 20/11/2024. // Created by razmig on 20/11/2024.
// //
import SwiftUI
import LeStorage import LeStorage
import SwiftUI
struct RegistrationSetupView: View { struct RegistrationSetupView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -24,7 +24,22 @@ struct RegistrationSetupView: View {
@State private var licenseIsRequired: Bool @State private var licenseIsRequired: Bool
@State private var minPlayerPerTeam: Int @State private var minPlayerPerTeam: Int
@State private var maxPlayerPerTeam: Int @State private var maxPlayerPerTeam: Int
@State private var showMoreInfos: Bool = false @State private var showMoreRegistrationInfos: Bool = false
@State private var showMoreOnlineWaitingListInfos: Bool = false
@State private var showMorePaymentInfos: Bool = false
@State private var enableTimeToConfirm: Bool
@State private var isTemplate: Bool
@State private var isCorporateTournament: Bool
// Online Payment
@State private var enableOnlinePayment: Bool
@State private var onlinePaymentIsMandatory: Bool
@State private var enableOnlinePaymentRefund: Bool
@State private var refundDateLimit: Date
@State private var refundDateLimitEnabled: Bool
@State private var stripeAccountId: String
@State private var stripeAccountIdIsInvalid: Bool = false
@FocusState private var focusedField: Tournament.CodingKeys?
@State private var hasChanges: Bool = false @State private var hasChanges: Bool = false
@ -33,7 +48,8 @@ struct RegistrationSetupView: View {
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
_enableOnlineRegistration = .init(wrappedValue: tournament.enableOnlineRegistration) _enableOnlineRegistration = .init(wrappedValue: tournament.enableOnlineRegistration)
_isTemplate = .init(wrappedValue: tournament.isTemplate)
_isCorporateTournament = .init(wrappedValue: tournament.isCorporateTournament)
// Registration Date Limit // Registration Date Limit
if let registrationDateLimit = tournament.registrationDateLimit { if let registrationDateLimit = tournament.registrationDateLimit {
_registrationDateLimit = .init(wrappedValue: registrationDateLimit) _registrationDateLimit = .init(wrappedValue: registrationDateLimit)
@ -70,6 +86,21 @@ struct RegistrationSetupView: View {
_maxPlayerPerTeam = .init(wrappedValue: tournament.maximumPlayerPerTeam) _maxPlayerPerTeam = .init(wrappedValue: tournament.maximumPlayerPerTeam)
_minPlayerPerTeam = .init(wrappedValue: tournament.minimumPlayerPerTeam) _minPlayerPerTeam = .init(wrappedValue: tournament.minimumPlayerPerTeam)
// Online Payment
_enableOnlinePayment = .init(wrappedValue: tournament.enableOnlinePayment)
_onlinePaymentIsMandatory = .init(wrappedValue: tournament.onlinePaymentIsMandatory)
_enableOnlinePaymentRefund = .init(wrappedValue: tournament.enableOnlinePaymentRefund)
_stripeAccountId = .init(wrappedValue: tournament.stripeAccountId ?? "")
_enableTimeToConfirm = .init(wrappedValue: tournament.enableTimeToConfirm)
// Refund Date Limit
if let refundDateLimit = tournament.refundDateLimit {
_refundDateLimit = .init(wrappedValue: refundDateLimit)
_refundDateLimitEnabled = .init(wrappedValue: true)
} else {
_refundDateLimit = .init(wrappedValue: tournament.startDate.truncateMinutesAndSeconds())
_refundDateLimitEnabled = .init(wrappedValue: false)
}
} }
func displayWarning() -> Bool { func displayWarning() -> Bool {
@ -88,13 +119,22 @@ struct RegistrationSetupView: View {
Text("Les inscriptions en ligne permettent à des joueurs de s'inscrire à votre tournoi en passant par le site Padel Club. Vous verrez alors votre liste d'inscription s'agrandir dans la vue Gestion des Inscriptions de l'application.") Text("Les inscriptions en ligne permettent à des joueurs de s'inscrire à votre tournoi en passant par le site Padel Club. Vous verrez alors votre liste d'inscription s'agrandir dans la vue Gestion des Inscriptions de l'application.")
FooterButtonView("En savoir plus") { FooterButtonView("En savoir plus") {
self.showMoreInfos = true self.showMoreRegistrationInfos = true
} }
} }
} }
if enableOnlineRegistration { if enableOnlineRegistration {
Section {
Toggle(isOn: $isTemplate) {
Text("Définir en tant que réglages par défaut")
}
} footer: {
Text("Définisser ce tournoi comme la source de vos réglages concernant l'inscription en ligne. Tous les tournois crées après celui-ci utiliseront ces réglages.")
}
if let shareURL = tournament.shareURL(.info) { if let shareURL = tournament.shareURL(.info) {
Section { Section {
Link(destination: shareURL) { Link(destination: shareURL) {
@ -113,6 +153,23 @@ struct RegistrationSetupView: View {
} }
} }
if dataStore.user.canEnableOnlinePayment() {
Section {
Toggle(isOn: $enableTimeToConfirm) {
Text("Automatique")
}
} header: {
Text("Gestion des confirmations")
} footer: {
VStack(alignment: .leading) {
Text("Activer la gestion automatique des confirmations pour ne plus vous occuper de la gestion de la file d'attente.")
FooterButtonView("En savoir plus") {
self.showMoreOnlineWaitingListInfos = true
}
}
}
}
Section { Section {
Toggle(isOn: $openingRegistrationDateEnabled) { Toggle(isOn: $openingRegistrationDateEnabled) {
Text("Définir une date") Text("Définir une date")
@ -179,6 +236,10 @@ struct RegistrationSetupView: View {
Text("Si une limite à la liste d'attente existe, les inscriptions ne seront plus possibles une fois la liste d'attente pleine. Si aucune limite de liste d'attente n'est active, alors les inscriptions seront toujours possibles. Les joueurs auront une indication comme quoi ils sont en liste d'attente.") Text("Si une limite à la liste d'attente existe, les inscriptions ne seront plus possibles une fois la liste d'attente pleine. Si aucune limite de liste d'attente n'est active, alors les inscriptions seront toujours possibles. Les joueurs auront une indication comme quoi ils sont en liste d'attente.")
} }
if dataStore.user.canEnableOnlinePayment() {
_onlinePaymentsView()
}
if tournament.isAnimation() { if tournament.isAnimation() {
Section { Section {
// Toggle(isOn: $userAccountIsRequired) { // Toggle(isOn: $userAccountIsRequired) {
@ -210,9 +271,16 @@ struct RegistrationSetupView: View {
) )
} }
} }
.sheet(isPresented: $showMoreInfos) { .sheet(isPresented: $showMoreRegistrationInfos) {
RegistrationInfoSheetView() RegistrationInfoSheetView()
} }
.sheet(isPresented: $showMoreOnlineWaitingListInfos) {
OnlineWaitingListFaqSheetView()
}
.sheet(isPresented: $showMorePaymentInfos) {
PaymentInfoSheetView()
}
.toolbar(content: { .toolbar(content: {
if hasChanges { if hasChanges {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
@ -229,6 +297,36 @@ struct RegistrationSetupView: View {
} }
} }
}) })
.toolbar(content: {
if focusedField != nil {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
focusedField = nil
}
}
}
})
.toolbar {
if focusedField != nil {
ToolbarItem(placement: .keyboard) {
HStack {
if focusedField == ._stripeAccountId, stripeAccountId.isEmpty == false {
Button("Effacer") {
stripeAccountId = ""
tournament.stripeAccountId = nil
}
.buttonStyle(.borderless)
Spacer()
Button("Valider") {
focusedField = nil
}
.buttonStyle(.bordered)
}
}
}
}
}
.toolbarRole(.editor) .toolbarRole(.editor)
.headerProminence(.increased) .headerProminence(.increased)
.navigationTitle("Inscription en ligne") .navigationTitle("Inscription en ligne")
@ -273,11 +371,102 @@ struct RegistrationSetupView: View {
.onChange(of: [minPlayerPerTeam, maxPlayerPerTeam]) { .onChange(of: [minPlayerPerTeam, maxPlayerPerTeam]) {
_hasChanged() _hasChanged()
} }
.onChange(of: [userAccountIsRequired, licenseIsRequired]) { .onChange(of: [isTemplate, userAccountIsRequired, licenseIsRequired, enableTimeToConfirm, isCorporateTournament]) {
_hasChanged() _hasChanged()
} }
} }
private func _onlinePaymentsView() -> some View {
Section {
Toggle(isOn: $enableOnlinePayment) {
Text("Activer le paiement en ligne")
}
if enableOnlinePayment {
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()
}
Toggle(isOn: $onlinePaymentIsMandatory) {
Text("Paiement obligatoire")
}
}
Toggle(isOn: $enableOnlinePaymentRefund) {
Text("Autoriser les remboursements")
}
if enableOnlinePaymentRefund {
Toggle(isOn: $refundDateLimitEnabled) {
Text("Définir une date limite")
}
if refundDateLimitEnabled {
DatePicker(selection: $refundDateLimit) {
DateMenuView(date: $refundDateLimit)
}
}
}
if dataStore.user.registrationPaymentMode == .corporate {
Toggle(isOn: $isCorporateTournament) {
Text("Revenu Padel Club")
}
}
if isCorporateTournament == false, dataStore.user.registrationPaymentMode.requiresStripe() {
VStack(alignment: .leading) {
TextField("Identifiant du compte Stripe", text: $stripeAccountId)
.frame(maxWidth: .infinity)
.keyboardType(.default)
.focused($focusedField, equals: ._stripeAccountId)
if stripeAccountIdIsInvalid {
Text("Format d'identifiant Stripe invalide.").foregroundStyle(.logoRed)
}
}
}
} header: {
Text("Paiement en ligne")
} footer: {
VStack(alignment: .leading) {
Text("Permettez aux joueurs de payer leur inscription en ligne. Vous devez connecter un compte Stripe pour recevoir les paiements.")
FooterButtonView("En savoir plus") {
self.showMorePaymentInfos = true
}
}
}
.onChange(of: [enableOnlinePayment, onlinePaymentIsMandatory, enableOnlinePaymentRefund]) {
_hasChanged()
}
.onChange(of: refundDateLimitEnabled) {
_hasChanged()
}
.onChange(of: refundDateLimit) {
_hasChanged()
}
.onChange(of: focusedField) { old, new in
if old == ._stripeAccountId {
_hasChanged()
}
}
}
private func _confirmStripeAccountId() {
stripeAccountIdIsInvalid = false
if stripeAccountId.isEmpty {
tournament.stripeAccountId = nil
} else if stripeAccountId.count >= 5, stripeAccountId.starts(with: "acct_") {
tournament.stripeAccountId = stripeAccountId.prefixMultilineTrimmed(255)
} else {
stripeAccountIdIsInvalid = true
}
}
private func _hasChanged() { private func _hasChanged() {
hasChanges = true hasChanges = true
} }
@ -286,17 +475,40 @@ struct RegistrationSetupView: View {
hasChanges = false hasChanges = false
tournament.enableOnlineRegistration = enableOnlineRegistration tournament.enableOnlineRegistration = enableOnlineRegistration
tournament.isTemplate = isTemplate
tournament.isCorporateTournament = isCorporateTournament
if enableOnlineRegistration { if enableOnlineRegistration {
tournament.accountIsRequired = userAccountIsRequired tournament.accountIsRequired = userAccountIsRequired
tournament.licenseIsRequired = licenseIsRequired tournament.licenseIsRequired = licenseIsRequired
tournament.minimumPlayerPerTeam = minPlayerPerTeam tournament.minimumPlayerPerTeam = minPlayerPerTeam
tournament.maximumPlayerPerTeam = maxPlayerPerTeam tournament.maximumPlayerPerTeam = maxPlayerPerTeam
// Online Payment
tournament.enableOnlinePayment = enableOnlinePayment
tournament.onlinePaymentIsMandatory = onlinePaymentIsMandatory
tournament.enableOnlinePaymentRefund = enableOnlinePaymentRefund
if refundDateLimitEnabled == false {
tournament.refundDateLimit = nil
} else {
tournament.refundDateLimit = refundDateLimit
}
tournament.enableTimeToConfirm = enableTimeToConfirm
_confirmStripeAccountId()
} else { } else {
tournament.accountIsRequired = true tournament.accountIsRequired = true
tournament.licenseIsRequired = true tournament.licenseIsRequired = true
tournament.minimumPlayerPerTeam = 2 tournament.minimumPlayerPerTeam = 2
tournament.maximumPlayerPerTeam = 2 tournament.maximumPlayerPerTeam = 2
tournament.enableTimeToConfirm = false
// When online registration is disabled, also disable online payment
tournament.enableOnlinePayment = false
tournament.onlinePaymentIsMandatory = false
tournament.enableOnlinePaymentRefund = false
tournament.refundDateLimit = nil
tournament.stripeAccountId = nil
} }
if openingRegistrationDateEnabled == false { if openingRegistrationDateEnabled == false {
@ -320,11 +532,7 @@ struct RegistrationSetupView: View {
tournament.waitingListLimit = waitingListLimit tournament.waitingListLimit = waitingListLimit
} }
do { self.dataStore.tournaments.addOrUpdate(instance: tournament)
try self.dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
dismiss() dismiss()
} }

@ -84,7 +84,7 @@ struct TournamentRankView: View {
Toggle(isOn: $tournament.disableRankingFederalRuling) { Toggle(isOn: $tournament.disableRankingFederalRuling) {
Text("Désactiver la règle fédéral") Text("Désactiver la règle fédéral")
Text("Dernier de poule ≠ derner du tournoi") Text("Dernier de poule ≠ dernier du tournoi")
} }
.onChange(of: tournament.disableRankingFederalRuling) { .onChange(of: tournament.disableRankingFederalRuling) {
dataStore.tournaments.addOrUpdate(instance: tournament) dataStore.tournaments.addOrUpdate(instance: tournament)

@ -202,7 +202,7 @@ struct TournamentCellView: View {
newTournament.federalTournamentAge = build.age newTournament.federalTournamentAge = build.age
newTournament.dayDuration = federalTournament.dayDuration newTournament.dayDuration = federalTournament.dayDuration
newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9)
newTournament.setupFederalSettings() newTournament.initSettings(templateTournament: Tournament.getTemplateTournament())
do { do {
try dataStore.tournaments.addOrUpdate(instance: newTournament) try dataStore.tournaments.addOrUpdate(instance: newTournament)
} catch { } catch {

Loading…
Cancel
Save