updated code

This commit is contained in:
umer
2025-04-23 20:14:15 +05:00
commit 88b392e5e2
104 changed files with 6879 additions and 0 deletions
@@ -0,0 +1,365 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = ACAC2DEA2D9F2C1900869E5C /* CalendarKit */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
ACAC2DCF2D9F271300869E5C /* Club Portal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Club Portal.app"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
ACAC2DD12D9F271300869E5C /* Club Portal */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "Club Portal";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
ACAC2DCC2D9F271300869E5C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
ACAC2DC62D9F271300869E5C = {
isa = PBXGroup;
children = (
ACAC2DD12D9F271300869E5C /* Club Portal */,
ACAC2DD02D9F271300869E5C /* Products */,
);
sourceTree = "<group>";
};
ACAC2DD02D9F271300869E5C /* Products */ = {
isa = PBXGroup;
children = (
ACAC2DCF2D9F271300869E5C /* Club Portal.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
ACAC2DCE2D9F271300869E5C /* Club Portal */ = {
isa = PBXNativeTarget;
buildConfigurationList = ACAC2DDD2D9F271500869E5C /* Build configuration list for PBXNativeTarget "Club Portal" */;
buildPhases = (
ACAC2DCB2D9F271300869E5C /* Sources */,
ACAC2DCC2D9F271300869E5C /* Frameworks */,
ACAC2DCD2D9F271300869E5C /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
ACAC2DD12D9F271300869E5C /* Club Portal */,
);
name = "Club Portal";
packageProductDependencies = (
ACAC2DEA2D9F2C1900869E5C /* CalendarKit */,
);
productName = "Club Portal";
productReference = ACAC2DCF2D9F271300869E5C /* Club Portal.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
ACAC2DC72D9F271300869E5C /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1600;
TargetAttributes = {
ACAC2DCE2D9F271300869E5C = {
CreatedOnToolsVersion = 16.0;
};
};
};
buildConfigurationList = ACAC2DCA2D9F271300869E5C /* Build configuration list for PBXProject "Club Portal" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = ACAC2DC62D9F271300869E5C;
minimizedProjectReferenceProxies = 1;
packageReferences = (
ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = ACAC2DD02D9F271300869E5C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
ACAC2DCE2D9F271300869E5C /* Club Portal */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
ACAC2DCD2D9F271300869E5C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
ACAC2DCB2D9F271300869E5C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
ACAC2DDB2D9F271500869E5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
ACAC2DDC2D9F271500869E5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
ACAC2DDE2D9F271500869E5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.app.Club-Portal";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
ACAC2DDF2D9F271500869E5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.app.Club-Portal";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
ACAC2DCA2D9F271300869E5C /* Build configuration list for PBXProject "Club Portal" */ = {
isa = XCConfigurationList;
buildConfigurations = (
ACAC2DDB2D9F271500869E5C /* Debug */,
ACAC2DDC2D9F271500869E5C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
ACAC2DDD2D9F271500869E5C /* Build configuration list for PBXNativeTarget "Club Portal" */ = {
isa = XCConfigurationList;
buildConfigurations = (
ACAC2DDE2D9F271500869E5C /* Debug */,
ACAC2DDF2D9F271500869E5C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/richardtop/CalendarKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.11;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
ACAC2DEA2D9F2C1900869E5C /* CalendarKit */ = {
isa = XCSwiftPackageProductDependency;
package = ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */;
productName = CalendarKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = ACAC2DC72D9F271300869E5C /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,15 @@
{
"originHash" : "67a1c5e835fc1360427d7e9a548368960eed766e429f7c9f11a95a0e99d682d9",
"pins" : [
{
"identity" : "calendarkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/richardtop/CalendarKit",
"state" : {
"revision" : "89e02a1472e9ad815ae72b62fd15430144fd1c4c",
"version" : "1.1.11"
}
}
],
"version" : 3
}
@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "1F5171A1-FFCD-454D-84B8-82B7D49580A5"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "D5681314-32C1-4998-BEBF-D4691C886BC2"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "56"
endingLineNumber = "56"
landmarkName = "getDailySched(clubId:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "3F8EDB4D-6640-447D-B0A2-56F9010C4D9E"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/SideMenu/SideMenuView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "59"
endingLineNumber = "59"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "659BB8BA-C1CE-41C8-9E17-554AF478E345"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Availbility/TabSelectorView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "10"
endingLineNumber = "10"
landmarkName = "TabSelectorView"
landmarkType = "14">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "99B40998-6781-4339-ACB5-F75FF0BC38A7"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "69"
endingLineNumber = "69"
landmarkName = "getAvailability()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "40F6EF95-AF67-4F44-8D78-9C818848D826"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Availbility/AvailabilityCardView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "74"
endingLineNumber = "74"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "9DDEE31F-4183-4E3A-8568-5DBD8C04570A"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "84"
endingLineNumber = "84"
landmarkName = "getProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6A2DA2F1-CA9B-4BCE-8ACA-D6D14E858CDA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "77"
endingLineNumber = "77"
landmarkName = "getProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "77A0E8D4-D5E6-4609-9344-05A557E18094"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Dashboard/AnalyticsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "92"
endingLineNumber = "92"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "C83F37A1-9F08-46E4-AE95-CF8DB346F8D4"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Dashboard/AnalyticsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "88"
endingLineNumber = "88"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "002CCE4B-DBB4-4BE0-82C0-03D8524CC2DA"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "34"
endingLineNumber = "34"
landmarkName = "getStats(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E1F2BC55-D1D6-40CE-90D8-2CC0E1866C12"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Dashboard/AnalyticsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "33"
endingLineNumber = "33"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "C120EA6E-E899-43F2-92E6-438C52F26A83"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "98"
endingLineNumber = "98"
landmarkName = "getClubProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E284DADA-1EA8-4DBD-89DA-714E86264DCF"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/DailySecheduler/ScheduleView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "76"
endingLineNumber = "76"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1047CF2A-7B0C-44CA-AB60-933AFFF88D49"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "41"
endingLineNumber = "41"
landmarkName = "getStats(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1D973B51-0BF4-4CAE-9D3D-08BB2000E001"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Dashboard/DashboardView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "65"
endingLineNumber = "65"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "3774B7A6-3139-453B-AF3D-5FB4085D83C3"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/AuthViewModel1.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "61"
endingLineNumber = "61"
landmarkName = "login(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "692D9FC1-2C27-4BD9-BF6C-1817B389E36A"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/AuthViewModel1.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "67"
endingLineNumber = "67"
landmarkName = "login(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x64",
"green" : "0x26",
"red" : "0x16"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x64",
"green" : "0x26",
"red" : "0x16"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x48",
"green" : "0x64",
"red" : "0x17"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x48",
"green" : "0x64",
"red" : "0x17"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "Custom Icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Custom Icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "clock.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Frame 11.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "filter.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Compact Button [1.0] (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Custom Icon (1).png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon-right.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Compact Button [1.0].pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Frame 4 (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Frame 595 (2).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Frame 595.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "calendar-clock, date-time.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "calendar-clock, date-time (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Frame 595 (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame 595.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,36 @@
//
// APIConstants.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
enum APIConstants {
static let baseURL = "https://courtmatchup.mkdlabs.com"
static let xProject = "Y291cnRtYXRjaHVwOmczbmp1OTlpZjR3enk4Z3V0bHEwYmUwZDI1Nzk1MTNsbTg="
static var commonHeaders: [String: String] {
var headers = ["Content-Type": "application/json",
"x-project": "Y291cnRtYXRjaHVwOmczbmp1OTlpZjR3enk4Z3V0bHEwYmUwZDI1Nzk1MTNsbTg="
]
if let token = AuthToken.current {
headers["Authorization"] = "Bearer \(token)"
}
return headers
}
}
struct AuthToken {
static var current: String? {
// Replace with Keychain token
return AppSettings.token
}
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}
@@ -0,0 +1,122 @@
import Foundation
import SwiftUI
enum APIError: Error, CustomStringConvertible {
case invalidURL
case invalidResponse(Int, String?) // Include status code and optional error message
case decodingError(Error)
case serverError(String)
case unknownError
case logout
var description: String {
switch self {
case .invalidURL:
return "Invalid URL"
case .invalidResponse(let statusCode, let message):
return "Invalid response with status code: \(statusCode)\nMessage: \(message ?? "No additional message")"
case .decodingError(let error):
return "Decoding error: \(error.localizedDescription)"
case .serverError(let message):
return "Server error: \(message)"
case .unknownError:
return "An unknown error occurred"
case .logout:
return "Token expired"
}
}
}
//struct Endpoint {
// let urlRequest: URLRequest?
// let isMultipart: Bool
// let body: Data?
//}
final class APIService {
static let shared = APIService()
private init() {}
func request<T: Decodable>(_ endpoint: Endpoint, responseType: T.Type) async throws -> T {
guard let request = endpoint.urlRequest else {
throw APIError.invalidURL
}
print("Request URL: \(request.url?.absoluteString ?? "Invalid URL")")
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(0, "No HTTP response received")
}
print("HTTP Status Code: \(httpResponse.statusCode)")
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
} else {
print("Failed to convert response data to string")
}
if !(200..<300).contains(httpResponse.statusCode) {
if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
if errorResponse.message == "TOKEN_EXPIRED" {
// Perform logout
throw APIError.logout
}
throw APIError.serverError(errorResponse.message)
} else {
throw APIError.invalidResponse(httpResponse.statusCode, "Unknown server error")
}
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw APIError.decodingError(error)
}
}
private func uploadMultipart<T: Decodable>(endpoint: Endpoint, responseType: T.Type) async throws -> T {
guard var request = endpoint.urlRequest else { throw APIError.invalidURL }
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpBody = endpoint.body
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(0, "No HTTP response received")
}
print("HTTP Status Code: \(httpResponse.statusCode)")
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
} else {
print("Failed to convert response data to string")
}
guard (200..<300).contains(httpResponse.statusCode) else {
if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
throw APIError.serverError(errorResponse.message)
} else {
throw APIError.invalidResponse(httpResponse.statusCode, "Unknown server error")
}
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw APIError.decodingError(error)
}
}
}
struct ErrorResponse: Codable {
let message: String
}
@@ -0,0 +1,26 @@
//
// AvailabilityListEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 15/04/2025.
//
import SwiftUI
struct AvailabilityListEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String {
"/v3/api/custom/courtmatchup/club/reservations/availability"
}
var method: HTTPMethod { .get }
var customHeaders: [String: String]? { nil }
var body: Data? { nil }
var isMultipart: Bool { false }
}
@@ -0,0 +1,73 @@
//
// ClubStatisticsEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 11/04/2025.
//
import SwiftUI
struct ClubStatisticsEndpoint: Endpoint {
var path: String {
"/v3/api/custom/courtmatchup/club/statistics"
}
var method: HTTPMethod { .get }
let startDate: String
let endDate: String
let startTime: String
let endTime: String
var customHeaders: [String: String]? { nil }
var urlQueryItems: [URLQueryItem] {
[
URLQueryItem(name: "start_date", value: startDate),
URLQueryItem(name: "end_date", value: endDate),
URLQueryItem(name: "start_time", value: startTime),
URLQueryItem(name: "end_time", value: endTime)
]
}
var body: Data? { nil }
var isMultipart: Bool { false }
}
struct GetClubEndpoint: Endpoint {
var path: String {
"/v3/api/custom/courtmatchup/user/club/\(clubId)"
}
var method: HTTPMethod { .get }
let clubId: String
var customHeaders: [String: String]? { nil }
var urlQueryItems: [URLQueryItem] { [] } // no query items needed
var body: Data? { nil }
var isMultipart: Bool { false }
}
struct GetprofileEndpoint: Endpoint {
var path: String {
"/v3/api/custom/courtmatchup/club/profile"
}
var method: HTTPMethod { .get }
var customHeaders: [String: String]? { nil }
var urlQueryItems: [URLQueryItem] {
[
]
}
var body: Data? { nil }
var isMultipart: Bool { false }
}
@@ -0,0 +1,29 @@
//
// ForgotPasswordEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct ForgotPasswordEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String { "/v2/api/lambda/mobile/forgot" }
var method: HTTPMethod { .post }
let email: String
let role: String
var customHeaders: [String: String]? { nil }
var body: Data? {
try? JSONEncoder().encode([
"email": email,
"role": role
])
}
var isMultipart: Bool { false }
}
@@ -0,0 +1,33 @@
//
// LoginEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
struct LoginEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String { "/v2/api/lambda/login" }
var method: HTTPMethod { .post }
let email: String
let password: String
let role: String = "club"
let isRefresh: Bool
var customHeaders: [String: String]? { nil }
var body: Data? {
try? JSONEncoder().encode([
"email": email,
"password": password,
"role": role,
"is_refresh": isRefresh ? "true" : "false" // Convert Bool to String
])
}
var isMultipart: Bool { false }
}
@@ -0,0 +1,38 @@
//
// ReservationListEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct ReservationListEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String {
"/v4/api/records/reservation?join=booking|booking_id&join=user|user_id&join=buddy|buddy_id&join=clubs|club_id&order=id,desc&filter=courtmatchup_booking.date,cs,\(clubID)"
}
var method: HTTPMethod { .get }
//let date: String
let clubID : Int
// var queryItems: [URLQueryItem]? {
// [
// URLQueryItem(name: "join", value: "booking|booking_id"),
// URLQueryItem(name: "join", value: "user|user_id"),
// URLQueryItem(name: "join", value: "buddy|buddy_id"),
// URLQueryItem(name: "join", value: "clubs|club_id"),
// URLQueryItem(name: "order", value: "id,desc"),
// URLQueryItem(name: "filter", value: "courtmatchup_booking.date,cs,\(date)")
// ]
// }
var customHeaders: [String: String]? { nil }
var body: Data? { nil }
var isMultipart: Bool { false }
}
@@ -0,0 +1,31 @@
//
// ResetPasswordEndpoint.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct ResetPasswordEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String { "/v2/api/lambda/mobile/reset" }
var method: HTTPMethod { .post }
let email: String
let code: String
let password: String
var customHeaders: [String: String]? { nil }
var body: Data? {
try? JSONEncoder().encode([
"email": email,
"code": code,
"password": password
])
}
var isMultipart: Bool { false }
}
@@ -0,0 +1,47 @@
//
// Endpoint.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
protocol Endpoint {
var path: String { get }
var method: HTTPMethod { get }
var customHeaders: [String: String]? { get }
var body: Data? { get }
var isMultipart: Bool { get }
var urlQueryItems: [URLQueryItem] { get } // 👈 Add this
var urlRequest: URLRequest? { get }
}
extension Endpoint {
var urlRequest: URLRequest? {
// Build full URL with query parameters
guard var components = URLComponents(string: APIConstants.baseURL + path) else {
return nil
}
if !urlQueryItems.isEmpty {
components.queryItems = urlQueryItems
}
guard let url = components.url else {
return nil
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
// Merge headers
var headers = APIConstants.commonHeaders
customHeaders?.forEach { headers[$0] = $1 }
request.allHTTPHeaderFields = headers
request.httpBody = body
return request
}
}
@@ -0,0 +1,444 @@
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let availabliltyRsp = try? JSONDecoder().decode(AvailabliltyRsp.self, from: jsonData)
import Foundation
// MARK: - AvailabliltyRsp
struct AvailabliltyRsp: Codable {
let error: Bool?
let courts: Courts?
let hours: Hours?
let coaches: Coaches?
let staff: Staff?
let sports: [JSONAny]?
let dateRange, timeRange: Range?
enum CodingKeys: String, CodingKey {
case error, courts, hours, coaches, staff, sports
case dateRange = "date_range"
case timeRange = "time_range"
}
}
// MARK: - Coaches
struct Coaches: Codable {
let available: [CoachesAvailable]?
let unavailable: [CoachesAvailable]?
let total: Int?
}
// MARK: - CoachesAvailable
struct CoachesAvailable: Codable , Identifiable, Hashable {
let id: Int?
let coachID, userID: Int?
let bio, hourlyRate, availability, firstName: String?
let lastName, phone, email: String?
let photo: String?
let coachSports: String?
let sports: [Sport]?
let availabilityData: AvailabilityData?
enum CodingKeys: String, CodingKey {
case coachID = "coach_id"
case userID = "user_id"
case bio
case hourlyRate = "hourly_rate"
case availability
case firstName = "first_name"
case lastName = "last_name"
case email, photo, phone
case coachSports = "coach_sports"
case sports, availabilityData
case id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: CoachesAvailable, rhs: CoachesAvailable) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - AvailabilityData
struct AvailabilityData: Codable, Identifiable, Hashable {
let id: Int?
let availability: [Availability]?
let unavailability: [JSONAny]?
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: AvailabilityData, rhs: AvailabilityData) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Availability
struct Availability: Codable, Identifiable, Hashable {
let start, end, date, time: String?
let id: Int?
}
// MARK: - Sport
struct Sport: Codable, Identifiable, Hashable{
let id: Int?
let sportID: Int?
let type, subType: String?
let price: Int?
let sportName: String?
enum CodingKeys: String, CodingKey {
case sportID = "sport_id"
case type
case subType = "sub_type"
case price
case sportName = "sport_name"
case id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Sport, rhs: Sport) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Courts
struct Courts: Codable {
let available: [CourtsAvailable]?
let unavailable: [JSONAny]?
let total: Int?
}
// MARK: - CourtsAvailable
struct CourtsAvailable: Codable, Identifiable , Hashable{
let id: Int?
let name, type, subtype: String?
let sportID, surfaceID: Int?
let availability, sportName: String?
let surfaceName: String?
let availabilityData: AvailabilityData?
enum CodingKeys: String, CodingKey {
case id, name, type, subtype
case sportID = "sport_id"
case surfaceID = "surface_id"
case availability
case sportName = "sport_name"
case surfaceName = "surface_name"
case availabilityData
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: CourtsAvailable, rhs: CourtsAvailable) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Range
struct Range: Codable {
let from, until: String?
}
// MARK: - Hours
struct Hours: Codable {
let available: [String]?
let total: Int?
let range: Range?
let byDate: [ByDate]?
}
// MARK: - ByDate
struct ByDate: Codable {
let date: String?
let slots: [String]?
let from, until: String?
}
// MARK: - Staff
struct Staff: Codable, Identifiable, Hashable {
var id: Int?
let available: [StaffAvailable]?
let unavailable: [JSONAny]?
let total: Int?
static func == (lhs: Staff, rhs: Staff) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - StaffAvailable
struct StaffAvailable: Codable, Identifiable, Hashable {
var id: Int?
let staffID, userID: Int?
let availability, staffRole, firstName, lastName: String?
let email, phone, photo: String?
let availabilityData: AvailabilityData?
enum CodingKeys: String, CodingKey {
case staffID = "staff_id"
case userID = "user_id"
case availability
case staffRole = "staff_role"
case firstName = "first_name"
case lastName = "last_name"
case email, photo, availabilityData, phone
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
@@ -0,0 +1,62 @@
//
// ClubDetailsResponse.swift
// Club Portal
//
// Created by Umer Tahir on 19/04/2025.
//
import Foundation
// MARK: - Main Response
struct ClubDetailsResponse: Codable {
let error: Bool
let model: ClubDetailsModel
}
// MARK: - Club Details Model
struct ClubDetailsModel: Codable {
let user: ClubUser
let club: Club
let courts: [Court]
let sports: [Sport]
let pricing: [Pricing]
}
// MARK: - Club User
struct ClubUser: Codable {
let id: Int
let oauth: String?
let role: String
let firstName, lastName, email, password: String
let type: Int
let alternativePhone, ageGroup, familyRole, defaultPaymentMethod: String?
let guardian, passwordLogin, verify: Int
let clubID: Int?
let phone, photo, bio, refer, stripeUID, paypalUID, twoFactorAuthentication: String?
let status: Int
let createAt, updateAt: String
enum CodingKeys: String, CodingKey {
case id, oauth, role
case firstName = "first_name"
case lastName = "last_name"
case email, password, type
case alternativePhone = "alternative_phone"
case ageGroup = "age_group"
case familyRole = "family_role"
case defaultPaymentMethod = "default_payment_method"
case guardian
case passwordLogin = "password_login"
case verify
case clubID = "club_id"
case phone, photo, bio, refer
case stripeUID = "stripe_uid"
case paypalUID = "paypal_uid"
case twoFactorAuthentication = "two_factor_authentication"
case status
case createAt = "create_at"
case updateAt = "update_at"
}
}
@@ -0,0 +1,200 @@
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let clubDetail = try? JSONDecoder().decode(ClubDetail.self, from: jsonData)
import Foundation
// MARK: - ClubDetail
struct ClubDetail: Codable {
let error: Bool?
let model: Model?
let sports: [Club]?
let courts: [Court]?
let pricing: [Pricing]?
}
// MARK: - Court
struct Court: Codable, Identifiable, Hashable {
let id, clubID: Int?
let name: String?
let sportID: Int?
let type: String?
let surfaceID: Int?
let subType, availability: String?
let createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id
case clubID = "club_id"
case name
case sportID = "sport_id"
case type
case surfaceID = "surface_id"
case subType = "sub_type"
case availability
case createAt = "create_at"
case updateAt = "update_at"
}
}
enum TypeEnum: String, Codable {
case empty = ""
case indoor = "Indoor"
case outdoor = "Outdoor"
case typeOutdoor = "outdoor"
}
// MARK: - Model
struct Model: Codable {
let id: Int?
let name, slug, title, description: String?
let userID, completed: Int?
let bio: JSONNull?
let openingTime, closingTime, times, splashScreen: String?
let showClinic, showNotification: Int?
let clubLogo: String?
let feeSettings: String?
let customFields: [CustomField]?
let homeImage: String?
let showBuddy: Int?
let exceptions, clubLocation: String?
let showCoach, showGroups, showCourt: Int?
let buddyDescription, courtDescription, coachDescription, clinicDescription: String?
let lessonDescription: String?
let customRequestThreshold, clubFee, serviceFee, maxPlayers: Int?
let accountDetails, accountSettings, membershipSettings, dailyBreaks: String?
let daysOff, createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id, name, slug, title, description
case userID = "user_id"
case completed, bio
case openingTime = "opening_time"
case closingTime = "closing_time"
case times
case splashScreen = "splash_screen"
case showClinic = "show_clinic"
case showNotification = "show_notification"
case clubLogo = "club_logo"
case feeSettings = "fee_settings"
case customFields = "custom_fields"
case homeImage = "home_image"
case showBuddy = "show_buddy"
case exceptions
case clubLocation = "club_location"
case showCoach = "show_coach"
case showGroups = "show_groups"
case showCourt = "show_court"
case buddyDescription = "buddy_description"
case courtDescription = "court_description"
case coachDescription = "coach_description"
case clinicDescription = "clinic_description"
case lessonDescription = "lesson_description"
case customRequestThreshold = "custom_request_threshold"
case clubFee = "club_fee"
case serviceFee = "service_fee"
case maxPlayers = "max_players"
case accountDetails = "account_details"
case accountSettings = "account_settings"
case membershipSettings = "membership_settings"
case dailyBreaks = "daily_breaks"
case daysOff = "days_off"
case createAt = "create_at"
case updateAt = "update_at"
}
}
// MARK: - CustomField
struct CustomField: Codable {
let value, label, type: String?
let customFieldRequired: Bool?
let options: [Option]?
let min, max, step: String?
enum CodingKeys: String, CodingKey {
case value, label, type
case customFieldRequired = "required"
case options, min, max, step
}
}
enum Option: Codable {
case double(Double)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Double.self) {
self = .double(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Option.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Option"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
// MARK: - Pricing
struct Pricing: Codable {
let id: Int?
let surfaceID: Int?
let clubID: Int?
let type, subtype: String?
let sportID: Int?
let status, isGeneral, generalRate: Int?
let priceByHours, createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id
case surfaceID = "surface_id"
case clubID = "club_id"
case type, subtype
case sportID = "sport_id"
case status
case isGeneral = "is_general"
case generalRate = "general_rate"
case priceByHours = "price_by_hours"
case createAt = "create_at"
case updateAt = "update_at"
}
}
// MARK: - Sport
struct ClubSport: Codable {
let id, status: Int?
let name: String?
let clubID: Int?
let sportTypes: [SportType]?
enum CodingKeys: String, CodingKey {
case id, status, name
case clubID = "club_id"
case sportTypes = "sport_types"
}
}
// MARK: - SportType
struct SportType: Codable {
let type: String?
let subtype: [String]?
let clubSportTypeID: String?
enum CodingKeys: String, CodingKey {
case type, subtype
case clubSportTypeID = "club_sport_type_id"
}
}
@@ -0,0 +1,68 @@
//
// ClubStatisticsResponse.swift
// Club Portal
//
// Created by Umer Tahir on 11/04/2025.
//
struct ClubStatisticsResponse: Decodable {
let error: Bool
let model: ClubStatisticsModel
}
struct ClubStatisticsModel: Decodable {
let clinicReservations: [ReservationStats]
let coachReservations: [ReservationStats]
let totalReservations: [ReservationStats]
let revenueHeatMap: [RevenueHeatMap]
let revenueByDateRange: [RevenueByDateRange]
let revenueByModule: [RevenueByModule]
let buddyStatistics: [ReservationStats]
let totalExpenses: [ExpenseStats]
let totalRevenue: [RevenueStats]
let totalProfit: [ProfitStats]
let courtUtilization: [CourtUtilizationStats]
let lessonReservations: [ReservationStats]
}
struct ReservationStats: Decodable {
let total_hours: Double?
let total_revenue: Double?
}
struct RevenueHeatMap: Decodable {
let date: String
let total_revenue: Double
}
struct RevenueByDateRange: Decodable {
// Add fields if needed
}
struct RevenueByModule: Decodable {
let module: String
let revenue: Double?
}
struct ExpenseStats: Decodable {
let total_hours: Double?
let total_expense: Double?
}
struct RevenueStats: Decodable {
let total_hours: Double?
let total_revenue: Double?
}
struct ProfitStats: Decodable {
let total_profit: Double?
let total_revenue: Double?
let total_expense: Double?
}
struct CourtUtilizationStats: Decodable {
let utilization_percentage: Double?
let total_used_hours: Double?
let total_available_hours: Double?
}
@@ -0,0 +1,12 @@
//
// ForgotPasswordResponse.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
struct ForgotPasswordResponse: Codable {
let error: Bool
let message: String
}
@@ -0,0 +1,31 @@
//
// LoginResponse.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
struct LoginResponse: Decodable {
let error: Bool
let role: String
let token: String
let expire_at: Int
let userId: Int
let first_name: String?
let last_name: String?
let photo: String?
let two_factor_enabled: Bool
enum CodingKeys: String, CodingKey {
case error
case role
case token
case expire_at
case userId = "user_id" // Mapping JSON key "user_id" to "userId"
case first_name
case last_name
case photo
case two_factor_enabled
}
}
@@ -0,0 +1,332 @@
//
// Profile.swift
// Club Portal
//
// Created by Umer Tahir on 22/04/2025.
//
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let profile = try? JSONDecoder().decode(Profile.self, from: jsonData)
import Foundation
// MARK: - ProfileRsp
class ProfileRsp: Codable {
let error: Bool?
let model: ProfileModel?
init(error: Bool?, model: ProfileModel?) {
self.error = error
self.model = model
}
}
// MARK: - Model
class ProfileModel: Codable {
let user: User1?
let club: Club1?
let courts: [Court1]?
let sports: [Sport1]?
let pricing: [Pricing1]?
init(user: User1?, club: Club1?, courts: [Court1]?, sports: [Sport1]?, pricing: [Pricing1]?) {
self.user = user
self.club = club
self.courts = courts
self.sports = sports
self.pricing = pricing
}
}
// MARK: - Club
class Club1: Codable {
let id: Int?
let name, slug: String?
let title, description: String?
let userID, completed: Int?
let bio: String?
let openingTime, closingTime, times, splashScreen: String?
let showClinic, showNotification: Int?
let clubLogo, feeSettings, customFields, homeImage: String?
let showBuddy: Int?
let exceptions: String?
let clubLocation: String?
let showCoach, showGroups, showCourt: Int?
let buddyDescription, courtDescription, coachDescription, clinicDescription: String?
let lessonDescription: String?
let customRequestThreshold, clubFee, serviceFee, maxPlayers: Int?
let accountDetails: String?
let accountSettings, membershipSettings, dailyBreaks: String?
let daysOff, createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id, name, slug, title, description
case userID = "user_id"
case completed, bio
case openingTime = "opening_time"
case closingTime = "closing_time"
case times
case splashScreen = "splash_screen"
case showClinic = "show_clinic"
case showNotification = "show_notification"
case clubLogo = "club_logo"
case feeSettings = "fee_settings"
case customFields = "custom_fields"
case homeImage = "home_image"
case showBuddy = "show_buddy"
case exceptions
case clubLocation = "club_location"
case showCoach = "show_coach"
case showGroups = "show_groups"
case showCourt = "show_court"
case buddyDescription = "buddy_description"
case courtDescription = "court_description"
case coachDescription = "coach_description"
case clinicDescription = "clinic_description"
case lessonDescription = "lesson_description"
case customRequestThreshold = "custom_request_threshold"
case clubFee = "club_fee"
case serviceFee = "service_fee"
case maxPlayers = "max_players"
case accountDetails = "account_details"
case accountSettings = "account_settings"
case membershipSettings = "membership_settings"
case dailyBreaks = "daily_breaks"
case daysOff = "days_off"
case createAt = "create_at"
case updateAt = "update_at"
}
init(id: Int?, name: String?, slug: String?, title: String?, description: String?, userID: Int?, completed: Int?, bio: String?, openingTime: String?, closingTime: String?, times: String?, splashScreen: String?, showClinic: Int?, showNotification: Int?, clubLogo: String?, feeSettings: String?, customFields: String?, homeImage: String?, showBuddy: Int?, exceptions: String?, clubLocation: String?, showCoach: Int?, showGroups: Int?, showCourt: Int?, buddyDescription: String?, courtDescription: String?, coachDescription: String?, clinicDescription: String?, lessonDescription: String?, customRequestThreshold: Int?, clubFee: Int?, serviceFee: Int?, maxPlayers: Int?, accountDetails: String?, accountSettings: String?, membershipSettings: String?, dailyBreaks: String?, daysOff: String?, createAt: String?, updateAt: String?) {
self.id = id
self.name = name
self.slug = slug
self.title = title
self.description = description
self.userID = userID
self.completed = completed
self.bio = bio
self.openingTime = openingTime
self.closingTime = closingTime
self.times = times
self.splashScreen = splashScreen
self.showClinic = showClinic
self.showNotification = showNotification
self.clubLogo = clubLogo
self.feeSettings = feeSettings
self.customFields = customFields
self.homeImage = homeImage
self.showBuddy = showBuddy
self.exceptions = exceptions
self.clubLocation = clubLocation
self.showCoach = showCoach
self.showGroups = showGroups
self.showCourt = showCourt
self.buddyDescription = buddyDescription
self.courtDescription = courtDescription
self.coachDescription = coachDescription
self.clinicDescription = clinicDescription
self.lessonDescription = lessonDescription
self.customRequestThreshold = customRequestThreshold
self.clubFee = clubFee
self.serviceFee = serviceFee
self.maxPlayers = maxPlayers
self.accountDetails = accountDetails
self.accountSettings = accountSettings
self.membershipSettings = membershipSettings
self.dailyBreaks = dailyBreaks
self.daysOff = daysOff
self.createAt = createAt
self.updateAt = updateAt
}
}
// MARK: - Court
class Court1: Codable {
let id, clubID: Int?
let name: String?
let sportID: Int?
let type: String?
let surfaceID: Int?
let subType: String?
let availability: String?
let createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id
case clubID = "club_id"
case name
case sportID = "sport_id"
case type
case surfaceID = "surface_id"
case subType = "sub_type"
case availability
case createAt = "create_at"
case updateAt = "update_at"
}
init(id: Int?, clubID: Int?, name: String?, sportID: Int?, type: String?, surfaceID: Int?, subType: String?, availability: String?, createAt: String?, updateAt: String?) {
self.id = id
self.clubID = clubID
self.name = name
self.sportID = sportID
self.type = type
self.surfaceID = surfaceID
self.subType = subType
self.availability = availability
self.createAt = createAt
self.updateAt = updateAt
}
}
// MARK: - Pricing
class Pricing1: Codable {
let id: Int?
let surfaceID: String?
let clubID: Int?
let type, subtype: String?
let sportID, status, isGeneral, generalRate: Int?
let priceByHours, createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id
case surfaceID = "surface_id"
case clubID = "club_id"
case type, subtype
case sportID = "sport_id"
case status
case isGeneral = "is_general"
case generalRate = "general_rate"
case priceByHours = "price_by_hours"
case createAt = "create_at"
case updateAt = "update_at"
}
init(id: Int?, surfaceID: String?, clubID: Int?, type: String?, subtype: String?, sportID: Int?, status: Int?, isGeneral: Int?, generalRate: Int?, priceByHours: String?, createAt: String?, updateAt: String?) {
self.id = id
self.surfaceID = surfaceID
self.clubID = clubID
self.type = type
self.subtype = subtype
self.sportID = sportID
self.status = status
self.isGeneral = isGeneral
self.generalRate = generalRate
self.priceByHours = priceByHours
self.createAt = createAt
self.updateAt = updateAt
}
}
// MARK: - Sport
class Sport1: Codable {
let id, status: Int?
let name: String?
let clubID: Int?
let sportTypes: [SportType]?
enum CodingKeys: String, CodingKey {
case id, status, name
case clubID = "club_id"
case sportTypes = "sport_types"
}
init(id: Int?, status: Int?, name: String?, clubID: Int?, sportTypes: [SportType]?) {
self.id = id
self.status = status
self.name = name
self.clubID = clubID
self.sportTypes = sportTypes
}
}
// MARK: - SportType
class SportType1: Codable {
let type: String?
let subtype: [String]?
let clubSportTypeID: String?
enum CodingKeys: String, CodingKey {
case type, subtype
case clubSportTypeID = "club_sport_type_id"
}
init(type: String?, subtype: [String]?, clubSportTypeID: String?) {
self.type = type
self.subtype = subtype
self.clubSportTypeID = clubSportTypeID
}
}
// MARK: - User
class User1: Codable {
let id: Int?
let oauth: String?
let role, firstName, lastName, email: String?
let password: String?
let type: Int?
let alternativePhone, ageGroup, familyRole, defaultPaymentMethod: String?
let guardian, passwordLogin, verify: Int?
let clubID, phone, photo, bio: String?
let refer, stripeUid, paypalUid, twoFactorAuthentication: String?
let status: Int?
let createAt, updateAt: String?
enum CodingKeys: String, CodingKey {
case id, oauth, role
case firstName = "first_name"
case lastName = "last_name"
case email, password, type
case alternativePhone = "alternative_phone"
case ageGroup = "age_group"
case familyRole = "family_role"
case defaultPaymentMethod = "default_payment_method"
case guardian
case passwordLogin = "password_login"
case verify
case clubID = "club_id"
case phone, photo, bio, refer
case stripeUid = "stripe_uid"
case paypalUid = "paypal_uid"
case twoFactorAuthentication = "two_factor_authentication"
case status
case createAt = "create_at"
case updateAt = "update_at"
}
init(id: Int?, oauth: String?, role: String?, firstName: String?, lastName: String?, email: String?, password: String?, type: Int?, alternativePhone: String?, ageGroup: String?, familyRole: String?, defaultPaymentMethod: String?, guardian: Int?, passwordLogin: Int?, verify: Int?, clubID: String?, phone: String?, photo: String?, bio: String?, refer: String?, stripeUid: String?, paypalUid: String?, twoFactorAuthentication: String?, status: Int?, createAt: String?, updateAt: String?) {
self.id = id
self.oauth = oauth
self.role = role
self.firstName = firstName
self.lastName = lastName
self.email = email
self.password = password
self.type = type
self.alternativePhone = alternativePhone
self.ageGroup = ageGroup
self.familyRole = familyRole
self.defaultPaymentMethod = defaultPaymentMethod
self.guardian = guardian
self.passwordLogin = passwordLogin
self.verify = verify
self.clubID = clubID
self.phone = phone
self.photo = photo
self.bio = bio
self.refer = refer
self.stripeUid = stripeUid
self.paypalUid = paypalUid
self.twoFactorAuthentication = twoFactorAuthentication
self.status = status
self.createAt = createAt
self.updateAt = updateAt
}
}
// MARK: - Encode/decode helpers
@@ -0,0 +1,146 @@
import Foundation
// MARK: - Root
struct ReservationRsp: Codable {
let list: [Reservation]
}
// MARK: - Reservation
struct Reservation: Codable {
let id: Int
let bookingID: Int
let buddyID: Int?
let spaceAssigned: String
let userID: Int
let clubID: Int
let reservationType: Int?
let status: Int
let checkIn: Int
let recurring: Int
let notes: String
let createAt: String
let updateAt: String
let booking: Booking
let user: User
let buddy: [String?]
let clubs: Club
}
// MARK: - Booking
struct Booking: Codable {
let id: Int
let userID: Int
let sportID: Int
let type: String
let receiptID: String
let subtype: String
let status: Int
let clinicID: Int?
let minNtrp: Int
let maxNtrp: Int
let coachID: Int?
let reservationType: Int
let courtID: Int
let courtIDs: String
let customRequest: Int
let price: Double
let clubFee: Double
let serviceFee: Double
let clinicFee: Double
let last4: Int
let clubID: Int?
let coachFee: Double
let lessonID: Int?
let duration: Int
let paymentIntent: String?
let notes: String?
let spaceAssigned: String?
let playerIDs: String
let courtFee: Double?
let playerID: Int?
let date: String
let startTime: String
let endTime: String
let createAt: String
let updateAt: String
let coachIDs: String
}
// MARK: - User
struct User: Codable {
let id: Int
let oauth: String?
let role: String
let firstName: String
let lastName: String
let email: String
let password: String
let type: Int
let alternativePhone: String?
let ageGroup: String?
let familyRole: String?
let defaultPaymentMethod: String?
let guardian: Int
let passwordLogin: String
let verify: Int
let clubID: Int?
let phone: String?
let photo: String?
let bio: String?
let refer: String?
let stripeUID: String?
let paypalUID: String?
let twoFactorAuthentication: String?
let status: Int
let createAt: String
let updateAt: String
}
// MARK: - Buddy
struct Buddy: Codable {
// Add properties if needed
}
// MARK: - Club
struct Club: Codable {
let id: Int
let name: String
let slug: String
let title: String
let description: String
let userID: Int
let completed: Int
let bio: String?
let openingTime: String
let closingTime: String
let times: String
let splashScreen: String
let showClinic: Int
let showNotification: Int
let clubLogo: String
let feeSettings: String
let customFields: String
let homeImage: String
let showBuddy: Int
let exceptions: String
let clubLocation: String
let showCoach: Int
let showGroups: Int
let showCourt: Int
let buddyDescription: String
let courtDescription: String
let coachDescription: String
let clinicDescription: String
let lessonDescription: String
let customRequestThreshold: Int
let clubFee: Double
let serviceFee: Double
let maxPlayers: Int
let accountDetails: String
let accountSettings: String
let membershipSettings: String
let dailyBreaks: String
let daysOff: String
let createAt: String
let updateAt: String
}
@@ -0,0 +1,16 @@
//
// AppleLoginRequest.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
struct AppleLoginRequest: Encodable {
let first_name : String
let last_name : String
let identityToken : String
let apple_id : String
let role: String
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,112 @@
//
// AvailabilityCardView.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct AvailabilityCardView: View {
var tab: AvailabilityTab
var courts: [CourtsAvailable] = []
var hours: [String]?
var coaches: [CoachesAvailable]?
var stsff: [StaffAvailable]?
@State var name : String = ""
@State var email : String = ""
@State var phone : String = ""
@State var showSheet = false
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Label("Available \(getCount())", systemImage: "calendar")
Spacer()
Text("\(getCount())")
.font(.subheadline)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(Colorr.themeBlueColor)
.foregroundColor(.white)
.clipShape(Capsule())
}
switch tab {
case .courts:
ForEach(courts , id: \.self) { court in
HStack {
Text("Court")
Text("\(courts.count)")
.foregroundColor(.gray)
Spacer()
}
Divider()
}
case .hours:
ForEach(hours ?? [] , id: \.self) { hour in
Text("\(hour)")
.onTapGesture {
showSheet.toggle()
}
Divider()
}
case .coaches:
ForEach(coaches ?? [], id: \.self) { coach in
HStack {
ProfileImageView(imageUrl: coach.photo ?? "", size: 30)
Text(coach.firstName ?? "")
Spacer()
Image("send")
.foregroundColor(.gray)
.onTapGesture {
name = coach.firstName ?? ""
email = coach.email ?? ""
phone = coach.phone ?? ""
showSheet.toggle()
}
}
Divider()
}
case .staff:
ForEach(self.stsff ?? [], id: \.self) { coach in
HStack {
ProfileImageView(imageUrl: coach.photo ?? "", size: 30)
Text( coach.firstName ?? "")
Spacer()
Image("send")
.foregroundColor(.gray)
.onTapGesture {
name = coach.firstName ?? ""
email = coach.email ?? ""
phone = coach.phone ?? ""
showSheet.toggle()
}
}
Divider()
}
}
}
.padding()
.background(Color.white)
.cornerRadius(14)
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
.padding(.horizontal)
.sheet(isPresented: $showSheet) {
ContactInfoBottomSheet(name: $name, email: $email, phone: $phone)
.presentationDetents([.height(250)])
.presentationDragIndicator(.visible)
}
}
func getCount() -> Int {
switch tab {
case .courts: return courts.count
case .hours: return hours?.count ?? 0
case .coaches: return coaches?.count ?? 0
case .staff: return stsff?.count ?? 0
}
}
}
@@ -0,0 +1,42 @@
//
// AvailabilityScreen.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct AvailabilityScreen: View {
@State private var selectedTab: AvailabilityTab = .courts
@Binding var presentSideMenu: Bool // Optional for side menu
@EnvironmentObject var viewModel : DashViewModel
var body: some View {
VStack(spacing: 16) {
HeaderView(presentSideMenu: $presentSideMenu)
DateSelectorView()
TabSelectorView(selectedTab: $selectedTab)
ScrollView {
AvailabilityCardView(
tab: selectedTab,
courts: viewModel.availRsp?.courts?.available ?? [] ,
hours: viewModel.availRsp?.hours?.available ?? [] ,
coaches: viewModel.availRsp?.coaches?.available ?? [],
stsff: viewModel.availRsp?.staff?.available ?? []
)
}
Spacer()
}
.background(Color(.systemGray6).ignoresSafeArea())
.onAppear(){
viewModel.getAvailability()
}
}
}
@@ -0,0 +1,31 @@
//
// AvailabilityTab.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
enum AvailabilityTab: String, CaseIterable {
case courts = "Courts"
case hours = "Hours"
case coaches = "Coaches"
case staff = "Staff"
}
//
//struct Court: Identifiable {
// var id = UUID()
// var number: String
//}
//
//struct Hour: Identifiable {
// var id = UUID()
// var hour: String
//}
//
//struct Coach: Identifiable {
// var id = UUID()
// var name: String
// var image: String // image name in Assets
//}
@@ -0,0 +1,58 @@
//
// DateSelectorView.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct DateSelectorView: View {
@State private var startDate: Date = Date()
@State private var endDate: Date = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date()
@State private var startTime: Date = Date()
@State private var endTime: Date = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) ?? Date()
var body: some View {
VStack(spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "calendar")
Text("Date Range")
.fontWeight(.semibold)
Spacer()
}
HStack {
DatePicker("From", selection: $startDate, displayedComponents: [.date])
.labelsHidden()
DatePicker("Until", selection: $endDate, in: startDate..., displayedComponents: [.date])
.labelsHidden()
}
}
.padding()
.background(Color(.white))
.cornerRadius(10)
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "clock")
Text("Time Range")
.fontWeight(.semibold)
Spacer()
}
HStack {
DatePicker("From", selection: $startTime, displayedComponents: [.hourAndMinute])
.labelsHidden()
DatePicker("Until", selection: $endTime, displayedComponents: [.hourAndMinute])
.labelsHidden()
}
}
.padding()
.background(Color(.white))
.cornerRadius(10)
}
.padding(.horizontal)
}
}
@@ -0,0 +1,24 @@
//
// HeaderView.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct HeaderViewAv: View {
var body: some View {
HStack {
Image(systemName: "person.crop.circle.fill")
.resizable()
.frame(width: 30, height: 30)
Spacer()
Text("The Royal Club")
.font(.headline)
Spacer()
Image(systemName: "arrowshape.turn.up.backward")
}
.padding(.horizontal)
}
}
@@ -0,0 +1,68 @@
//
// ContactInfoBottomSheet.swift
// Club Portal
//
// Created by Umer Tahir on 16/04/2025.
//
import SwiftUI
struct ContactInfoBottomSheet: View {
@Binding var name : String
@Binding var email : String
@Binding var phone : String
@Environment(\.dismiss) var dismiss
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Text(name)
.font(.title3.bold())
VStack(alignment: .leading, spacing: 8) {
Text("EMAIL")
.font(.caption)
.foregroundColor(.gray)
HStack {
Text(email)
.underline()
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button {
UIPasteboard.general.string = email
} label: {
Image(systemName: "doc.on.doc")
}
}
}
VStack(alignment: .leading, spacing: 8) {
Text("PHONE")
.font(.caption)
.foregroundColor(.gray)
HStack {
Text(phone)
.underline()
Spacer()
Button {
UIPasteboard.general.string = phone
} label: {
Image(systemName: "doc.on.doc")
}
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(20)
.padding()
}
}
@@ -0,0 +1,67 @@
//
// TabSelectorView.swift
// Club Portal
//
// Created by Umer Tahir on 12/04/2025.
//
import SwiftUI
struct TabSelectorView: View {
@Binding var selectedTab: AvailabilityTab
var body: some View {
HStack(spacing: 0) {
// ForEach(AvailabilityTab.allCases, id: \.self) { tab in
// Button(action: {
// selectedTab = tab
// }) {
// Text(tab.rawValue)
// .fontWeight(selectedTab == tab ? .bold : .regular)
// .foregroundColor(.black)
// .frame(maxWidth: .infinity)
// .padding(.vertical, 10)
// .background(selectedTab == tab ? Color.white : Color.clear)
// }
// }
// }
// .background(Color(.systemGray5))
// .clipShape(Capsule())
// .padding(.horizontal)
Picker(selection: $selectedTab, label: Text("Options")) {
ForEach(AvailabilityTab.allCases, id: \.self) { index in
Text(index.rawValue).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
.tint(.gray.opacity(0.5))
}
}
}
//struct SegmentedControlView: View {
// @Binding var selectedTab : Int
// let options = ["Summary", "Analytics"]
//
// @EnvironmentObject var viewModel : DashViewModel
//
// var body: some View {
// Picker(selection: $selectedTab, label: Text("Options")) {
// ForEach(options.indices, id: \.self) { index in
// Text(self.options[index]).tag(index)
// }
//
// }
// .pickerStyle(SegmentedPickerStyle())
// .padding(.horizontal)
// .tint(.gray.opacity(0.5))
//
// Divider()
//
//
// }
//}
@@ -0,0 +1,35 @@
//
// CalendarKitView.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
import CalendarKit
//struct CalendarKitView: UIViewControllerRepresentable {
// func makeUIViewController(context: Context) -> DayViewController {
// let dayViewController = DayViewController()
// dayViewController.dataSource = context.coordinator
// return dayViewController
// }
//
// func updateUIViewController(_ uiViewController: DayViewController, context: Context) {}
//
// func makeCoordinator() -> Coordinator {
// return Coordinator()
// }
//
// class Coordinator: NSObject, EventDataSource {
// func eventsForDate(_ date: Date) -> [EventDescriptor] {
// let event = Event()
// // event.startDate = date
// // event.endDate = Calendar.current.date(byAdding: .hour, value: 1, to: date)!
// event.text = "Sample Event"
// event.color = .blue
// return [event]
// }
// }
//}
@@ -0,0 +1,36 @@
//
// CoachesTab.swift
// Club Portal
//
// Created by Umer Tahir on 16/04/2025.
//
import SwiftUI
struct CoachesTab: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(0..<3, id: \.self) { _ in
HStack(spacing: 12) {
ProfileImageView(imageUrl: "", size: 40)
VStack(alignment: .leading, spacing: 4) {
Text("Steve Jobs")
.font(.headline)
Text("Hours playing: 9:00 AM 10:00 AM")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
}
}
.padding()
}
}
}
@@ -0,0 +1,52 @@
//
// DetailsTab.swift
// Club Portal
//
// Created by Umer Tahir on 16/04/2025.
//
import SwiftUI
struct DetailRow: View {
let title: String
let value: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.subheadline)
.foregroundColor(.secondary)
Text(value)
.font(.body)
.foregroundColor(.primary)
Divider()
}
}
}
struct DetailsTab: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Group {
DetailRow(title: "Reservation date", value: "16 April, 2024")
DetailRow(title: "Time", value: "9:00 AM 10:00 AM")
DetailRow(title: "Event type", value: "Lesson")
DetailRow(title: "Sport/type", value: "Tennis • Indoors • Clay")
DetailRow(title: "Repeating", value: "Every week")
DetailRow(title: "Space assigned", value: "Court #5")
DetailRow(title: "Court fee", value: "$50.0")
DetailRow(title: "Court fee status", value: "Paid")
DetailRow(title: "Note", value: "{content.note}")
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(.systemBackground))
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
)
.padding()
}
}
}
@@ -0,0 +1,54 @@
//
// EventCalendarView.swift
// Club Portal
//
// Created by Umer Tahir on 16/04/2025.
//
import SwiftUI
import CalendarKit
//struct EventCalendarView: View {
// @Binding var selectedDate: Date
// @EnvironmentObject var viewModel: DashViewModel
// @StateObject private var eventStore = CalendarEventStore()
//
// var body: some View {
// CalendarView(eventStore: eventStore)
// .frame(height: 400)
// .cornerRadius(12)
// .onAppear {
// loadEvents()
// }
// .onChange(of: selectedDate) { _ in
// loadEvents()
// }
// }
//
// private func loadEvents() {
// guard let stats = viewModel.dailyReservations?.first else { return }
// eventStore.events.removeAll()
//
// func addEvent(title: String, duration: Double) {
// guard duration > 0 else { return }
//
// let start = Calendar.current.date(bySettingHour: 9, minute: 0, second: 0, of: selectedDate)!
// let end = Calendar.current.date(byAdding: .minute, value: Int(duration * 60), to: start)!
//
// let event = Event()
// event.dateInterval = DateInterval(start: start, end: end)
// event.text = "\(title)"
// event.color = .blue.withAlphaComponent(0.6)
// event.lineBreakMode = .byTruncatingTail
//
// eventStore.events.append(event)
// }
//
// if let lesson = stats.booking {
// addEvent(title: "Lesson", duration: lesson.duration)
// }
//
// eventStore.objectWillChange.send()
// }
//}
@@ -0,0 +1,101 @@
//
// FilterView.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
struct DailyFilterView: View {
@Environment(\.presentationMode) var presentationMode
@State private var selectedSport: String? = "Tennis"
@State private var isDropdownOpen = false
let sports = ["Tennis", "Pickleball", "Sport 3", "Sport 4"]
var body: some View {
VStack {
// Close and Title
HStack {
Button("Close") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Text("Filter")
.font(.headline)
Spacer()
}
.padding()
// Dropdown Menu
VStack(alignment: .leading, spacing: 10) {
Button(action: {
withAnimation {
isDropdownOpen.toggle()
}
}) {
HStack {
Text("Sport")
.font(.subheadline)
Spacer()
Image(systemName: isDropdownOpen ? "chevron.up" : "chevron.down")
}
.padding()
}
.background(Color(.systemGray6))
.cornerRadius(8)
if isDropdownOpen {
VStack(spacing: 8) {
ForEach(sports, id: \..self) { sport in
HStack {
Text(sport)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
if selectedSport == sport {
Image(systemName: "checkmark.square.fill")
.foregroundColor(.blue)
} else {
Image(systemName: "square")
}
}
.background(Color.white)
.cornerRadius(8)
.onTapGesture {
selectedSport = sport
}
}
}
.padding(.horizontal)
}
}
.padding()
Spacer()
// Apply Button
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Apply and close")
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
.background(Color(.systemGray6).opacity(0.2))
.edgesIgnoringSafeArea(.bottom)
}
}
//struct FilterView_Previews: PreviewProvider {
// static var previews: some View {
// FilterView()
// }
//}
@@ -0,0 +1,106 @@
//
// FilterScreen.swift
// Club Portal
//
// Created by Umer Tahir on 15/04/2025.
//
import SwiftUI
struct ScheduleFilterView: View {
@Environment(\.dismiss) var dismiss
@Binding var navigationPaths: [String]
@State private var isClubExpanded = true
@State private var selectedClubs: Set<String> = [] // For storing selected clubs
@State var filterBlock: ((Int) -> Void)?
@EnvironmentObject var dasModel: DashViewModel
var body: some View {
VStack(spacing: 0) {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Club Filter Section
DisclosureGroup(isExpanded: $isClubExpanded) {
VStack(spacing: 10) {
if let clubs = dasModel.club?.courts {
ForEach(clubs, id: \.self) { court in
if let clubName = court.name {
ClubCheckboxRow(clubName: clubName, isSelected: selectedClubs.contains(clubName)) {
if selectedClubs.contains(clubName) {
selectedClubs.remove(clubName)
} else {
selectedClubs.insert(clubName)
}
}
}
}
}
}
.padding(.top, 10)
} label: {
Text("Club")
.font(.headline)
}
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill(Color(.systemGray6)))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.systemGray4), lineWidth: 1)
)
.padding(.horizontal)
}
.padding(.top)
}
Spacer()
Button(action: {
// Handle apply logic
self.navigationPaths.removeLast()
}) {
Text("Apply and close")
.fontWeight(.semibold)
.frame(maxWidth: .infinity)
.padding()
.background(Colorr.themeBlueColor)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.horizontal)
}
.padding(.vertical, 10)
.background(Color.white)
}
.navigationTitle("Filter")
.navigationBarTitleDisplayMode(.inline)
.background(Color(.systemGray6).ignoresSafeArea())
}
}
// MARK: - Club Checkbox Row
struct ClubCheckboxRow: View {
let clubName: String
let isSelected: Bool
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
HStack {
Text(clubName)
.foregroundColor(.primary)
Spacer()
Image(systemName: isSelected ? "checkmark.square.fill" : "square")
.foregroundColor(isSelected ? Colorr.themeBlueColor : .gray)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? Colorr.themeBlueColor : Color.gray.opacity(0.3), lineWidth: 1)
)
}
}
}
@@ -0,0 +1,44 @@
//
// PlayersTab.swift
// Club Portal
//
// Created by Umer Tahir on 16/04/2025.
//
import SwiftUI
struct PlayersTab: View {
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(0..<5, id: \.self) { index in
HStack(spacing: 12) {
ProfileImageView(imageUrl: "", size: 40)
VStack(alignment: .leading, spacing: 2) {
Text("Steve Jobs")
.font(.headline)
if index == 0 {
Text("Guardian sign-in: Steve Jobs")
.font(.caption)
.foregroundColor(.gray)
}
}
Spacer()
if index == 0 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.green)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
}
}
.padding()
}
}
}
@@ -0,0 +1,34 @@
import SwiftUI
struct ReservationDetailsView: View {
@State private var selectedTab = "Details"
let tabs = ["Details", "Players", "Coaches"]
var body: some View {
VStack(spacing: 0) {
Picker("", selection: $selectedTab) {
ForEach(tabs, id: \.self) { tab in
Text(tab).tag(tab)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()
switch selectedTab {
case "Details":
DetailsTab()
case "Players":
PlayersTab()
case "Coaches":
CoachesTab()
default:
Spacer()
Text("Unknown Tab")
Spacer()
}
}
.navigationBarTitle("Reservation details", displayMode: .inline)
}
}
@@ -0,0 +1,306 @@
import SwiftUI
struct Event: Identifiable {
let id = UUID()
let title: String
let startTime: Date
let endTime: Date
let color: Color
}
struct ScheduleView: View {
@State var navigationPaths: [String] = []
@State private var selectedDate = Date()
@State private var displayedMonth = Date()
@State private var events: [Event] = []
@Binding var presentSideMenu: Bool // Optional for side menu
let hours = Array(8...20)
let calendar = Calendar.current
@EnvironmentObject var viewModel : DashViewModel
var body: some View {
NavigationStack(path: $navigationPaths) {
VStack(spacing: 0) {
// Top Header
HeaderView(presentSideMenu: $presentSideMenu)
Divider()
VStack {
// Search + Filter
searchBar
.padding(.top, 5)
// Month + Horizontal Dates
VStack(spacing: 10) {
Divider()
monthHeader
Divider()
dateScrollView
}
.padding(.horizontal)
.padding(.bottom)
Divider()
// Time Slots with Events
ScrollView {
VStack(spacing: 0) {
ForEach(hours, id: \.self) { hour in
timeSlotRow(hour: hour)
}
}
.padding(.horizontal)
.padding(.bottom, 20)
}
}
}
.onAppear {
// loadDummyEvents()
viewModel.getDailySched(clubId: 10)
if let club = self.viewModel.club {
print(club.courts?.count)
}else {
viewModel.getClubProfile()
}
}
.navigationDestination(for: String.self) { value in
if value == "DailyFilterView" {
ScheduleFilterView(navigationPaths: $navigationPaths) { filter in
viewModel.getDailySched(clubId: filter)
}
}else if value == "ReservationDetailsView" {
ReservationDetailsView()
}
}
}
}
// MARK: - Components
var headerBar: some View {
HStack {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 36, height: 36)
Spacer()
Text("The Royal Club")
.font(.headline)
Spacer()
Image(systemName: "arrow.right")
}
.padding()
}
var searchBar: some View {
HStack {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("search player", text: .constant(""))
.foregroundColor(.primary)
}
.padding(5)
.background(Color(.white))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.systemGray4), lineWidth: 1)
)
//.padding(.horizontal)
Button(action: {
navigationPaths.append("DailyFilterView")
}) {
HStack(spacing: 4) {
Image("filter 1")
}
}
}
.padding(.horizontal)
}
var monthHeader: some View {
HStack {
Button(action: {
displayedMonth = calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? displayedMonth
}) {
Image("left_arrow")
}
Spacer()
Text(formattedMonth(displayedMonth))
.font(.subheadline)
Spacer()
Button(action: {
displayedMonth = calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? displayedMonth
}) {
Image("right_arrow")
}
}
.padding(.horizontal)
.background(Colorr.lightColor)
}
var dateScrollView: some View {
let days = getDatesForMonth(from: displayedMonth)
return ScrollViewReader { scrollProxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(days, id: \.self) { date in
VStack(spacing: 4) {
Text(shortDayName(from: date))
.font(.caption)
.foregroundColor(.gray)
Text(dayNumber(from: date))
.fontWeight(isSameDay(date, selectedDate) ? .bold : .regular)
.frame(width: 36, height: 36)
.background(isSameDay(date, selectedDate) ? Colorr.themeBlueColor : Color.clear)
.foregroundColor(isSameDay(date, selectedDate) ? .white : .primary)
.clipShape(RoundedCorner())
}
.onTapGesture {
selectedDate = date
}
.id(calendar.startOfDay(for: date))
}
}
.padding(.vertical, 4)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
scrollProxy.scrollTo(calendar.startOfDay(for: selectedDate), anchor: .center)
}
}
}
}
}
func timeSlotRow(hour: Int) -> some View {
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .top) {
Text(timeLabel(for: hour))
.font(.caption)
.frame(width: 50, alignment: .leading)
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color(.systemGray6))
.frame(height: 60)
eventOverlay(for: hour)
}
}
Divider()
}
}
func eventOverlay(for hour: Int) -> some View {
VStack(spacing: 4) {
ForEach(events.filter {
calendar.isDate($0.startTime, inSameDayAs: selectedDate)
&& calendar.component(.hour, from: $0.startTime) == hour
}) { event in
VStack(alignment: .leading, spacing: 2) {
Text(event.title)
.font(.subheadline)
.bold()
Text("\(timeOnly(event.startTime)) - \(timeOnly(event.endTime))")
.font(.caption)
.foregroundColor(.gray)
}
.padding(8)
.background(event.color.opacity(0.2))
.foregroundColor(event.color)
.cornerRadius(12)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Color.clear)
.onTapGesture {
self.navigationPaths.append("ReservationDetailsView")
}
}
}
.padding(.top, 4)
.frame(maxWidth: .infinity)
}
// MARK: - Utility
func loadDummyEvents() {
let baseDate = calendar.startOfDay(for: Date())
events = [
Event(
title: "Court reserved",
startTime: calendar.date(bySettingHour: 9, minute: 0, second: 0, of: baseDate)!,
endTime: calendar.date(bySettingHour: 10, minute: 0, second: 0, of: baseDate)!,
color: .green
),
Event(
title: "Clinic",
startTime: calendar.date(bySettingHour: 11, minute: 30, second: 0, of: baseDate)!,
endTime: calendar.date(bySettingHour: 13, minute: 0, second: 0, of: baseDate)!,
color: .purple
)
]
}
func getDatesForMonth(from date: Date) -> [Date] {
let range = calendar.range(of: .day, in: .month, for: date)!
let start = calendar.date(from: calendar.dateComponents([.year, .month], from: date))!
return range.compactMap { calendar.date(byAdding: .day, value: $0 - 1, to: start) }
}
func formattedMonth(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
return formatter.string(from: date)
}
func dayNumber(from date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd"
return formatter.string(from: date)
}
func shortDayName(from date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "EEE"
return formatter.string(from: date)
}
func timeOnly(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "h:mm a"
return formatter.string(from: date)
}
func timeLabel(for hour: Int) -> String {
let date = calendar.date(bySettingHour: hour, minute: 0, second: 0, of: Date())!
let formatter = DateFormatter()
formatter.dateFormat = "h a"
return formatter.string(from: date)
}
func isSameDay(_ a: Date, _ b: Date) -> Bool {
calendar.isDate(a, inSameDayAs: b)
}
}
@@ -0,0 +1,241 @@
//
// AnalyticsView.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
import Charts
struct AnalyticsView: View {
@State private var selectedTab = 1
@EnvironmentObject var viewModel : DashViewModel
var body: some View {
NavigationView {
VStack {
//HeaderView()
// SegmentedControlView()
ScrollView {
VStack(spacing: 15) {
// PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? [])
// BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? [])
PieChartCard1()
BarChartCard1()
}
.padding(.horizontal)
}
}
.background(Color(.systemGray6))
.navigationBarHidden(true)
.onAppear() {
if let data = self.viewModel.statResponse?.revenueHeatMap {
print(data.count)
}
}
}
}
}
// MARK: - Pie Chart Card
struct PieChartCard1: View {
var pieData: [RevenueByModule]?
let data: [(name: String, value: Double, color: Color)] = [
("Module A", 30, Colorr.pieBlue),
("Module B", 70, Colorr.pieGreen)
]
var body: some View {
CardView(title: "% made through each module") {
VStack {
HStack {
LegendView(color: Colorr.pieBlue, text: "Module A")
LegendView(color: Colorr.pieGreen, text: "Module B")
}
.padding(.top, 10)
Chart(data, id: \.name) { item in
SectorMark(
angle: .value("Value", item.value),
innerRadius: .ratio(0.5),
angularInset: 2
)
.foregroundStyle(item.color)
}
.chartLegend(.hidden)
.frame(height: 180)
}
}
.onAppear() {
if let d = self.pieData {
d.forEach { r in
// let data = (r.module.)
}
}
}
}
}
struct PieChartCard: View {
let data: [RevenueByModule]
var body: some View {
let chartData = data.map {
(name: $0.module, value: $0.revenue ?? 0, color: randomColor())
}
CardView(title: "% made through each module") {
VStack {
HStack {
ForEach(chartData, id: \.name) { item in
LegendView(color: item.color, text: item.name)
}
.padding(.top, 5)
}
Chart(chartData, id: \.name) { item in
SectorMark(
angle: .value("Value", item.value),
innerRadius: .ratio(0.5),
angularInset: 2
)
.foregroundStyle(item.color)
}
.chartLegend(.hidden)
.frame(height: 180)
}
}
}
func randomColor() -> Color {
let colors: [Color] = [Colorr.pieBlue, Colorr.pieGreen, .orange, .purple, .pink, .mint]
return colors.randomElement() ?? .blue
}
}
struct BarChartCard1: View {
var data: [RevenueByModule]?
let barData: [ModuleRevenue] = [
ModuleRevenue(day: "Wed", module: "Module A", value: 200),
ModuleRevenue(day: "Wed", module: "Module B", value: 500),
ModuleRevenue(day: "Thu", module: "Module A", value: 50),
ModuleRevenue(day: "Thu", module: "Module B", value: 300),
ModuleRevenue(day: "Fri", module: "Module A", value: 30),
ModuleRevenue(day: "Fri", module: "Module B", value: 200),
ModuleRevenue(day: "Sat", module: "Module A", value: 400),
ModuleRevenue(day: "Sat", module: "Module B", value: 250),
ModuleRevenue(day: "Sun", module: "Module A", value: 180),
ModuleRevenue(day: "Sun", module: "Module B", value: 600)
]
var body: some View {
CardView(title: "Revenue from each module") {
Chart(barData) { data in
BarMark(
x: .value("Day", data.day),
y: .value("Revenue", data.value)
)
.foregroundStyle(data.module == "Module A" ? Colorr.pieBlue : Colorr.pieGreen)
.position(by: .value("Module", data.module))
}
.chartYAxis {
AxisMarks(position: .leading)
}
.frame(height: 200)
}
}
}
struct BarChartCard: View {
let data: [RevenueHeatMap]
var body: some View {
let barData: [ModuleRevenue] = data.map {
ModuleRevenue(day: $0.date.toWeekday()!, module: $0.date.toWeekday()!, value: $0.total_revenue )
}
CardView(title: "Revenue from each module") {
Chart(barData) { data in
BarMark(
x: .value("Day", data.day),
y: .value("Revenue", data.value)
)
.foregroundStyle(
data.module == "Module A" ? Colorr.pieBlue : Colorr.pieGreen
)
}
.chartYAxis {
AxisMarks(position: .leading)
}
.frame(height: 200)
}
}
}
// MARK: - Common Components
// Reusable card for charts
struct CardView<Content: View>: View {
let title: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(title)
.font(.subheadline)
.bold()
Spacer()
// Image(systemName: "arrow.up.right.square")
}
.padding(.bottom, 5)
content
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.2), radius: 5)
}
}
// Legend for pie chart
struct LegendView: View {
let color: Color
let text: String
var body: some View {
HStack {
Circle()
.fill(color)
.frame(width: 12, height: 12)
Text(text)
.font(.footnote)
}
}
}
// MARK: - Preview
struct AnalyticsView_Previews: PreviewProvider {
static var previews: some View {
AnalyticsView()
}
}
// MARK: - Model
struct ModuleRevenue: Identifiable, Hashable {
var id = UUID()
var day: String
var module: String
var value: Double
}
@@ -0,0 +1,280 @@
//
// SummaryView.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
struct SummaryView: View {
//@Binding var selectedTab: Int
@State var navigationPaths: [String] = []
@State var selectedTab = 0
@EnvironmentObject var viewModel : DashViewModel
@Binding var presentSideMenu: Bool // Optional for side menu
@State var filterCount = 0
var body: some View {
NavigationStack(path: $navigationPaths) {
VStack {
VStack {
HeaderView(presentSideMenu: $presentSideMenu)
SegmentedControlView(selectedTab: $selectedTab)
}
.background(Color(.white))
if selectedTab == 0 {
FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount)
CourtUtilizationView()
ScrollView {
LazyVStack(spacing: 10) {
ReservationCard(title: "Court reservations", hours: "\(self.viewModel.statResponse?.coachReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.coachReservations.first?.total_revenue ?? 0 )")
ReservationCard(title: "Clinic reservations", hours: "\(self.viewModel.statResponse?.clinicReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.clinicReservations.first?.total_revenue ?? 0 )")
ReservationCard(title: "Find a Buddy", hours: "\(self.viewModel.statResponse?.buddyStatistics.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.buddyStatistics.first?.total_revenue ?? 0 )")
ReservationCard(title: "Lesson reservation", hours: "\(self.viewModel.statResponse?.lessonReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.lessonReservations.first?.total_revenue ?? 0 )")
ReservationCard(title: "Total expenses", hours: "\(self.viewModel.statResponse?.totalExpenses.first?.total_hours ?? 0 )", revenue:"$\(self.viewModel.statResponse?.totalExpenses.first?.total_expense ?? 0 )")
ReservationCard(title: "Total profit", hours: "\(self.viewModel.statResponse?.totalProfit.first?.total_profit ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalProfit.first?.total_revenue ?? 0 )")
ReservationCard(title: "Total revenue", hours: "\(self.viewModel.statResponse?.totalRevenue.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalRevenue.first?.total_revenue ?? 0 )")
}
.padding(.horizontal)
}
}else {
AnalyticsView()
}
// .background(Color(UIColor.systemGroupedBackground))
}
.background(Color(.systemGray6))
.navigationBarHidden(true)
.navigationDestination(for: String.self) { value in
if value == "FilterScreen" {
FilterScreen(navigationPaths: $navigationPaths) { filter, filterCount in
self.filterCount = filterCount
viewModel.getStats(completion: {})
}
}
}
.onAppear(){
viewModel.getStats {
if viewModel.sessionExpired {
AppSettings.token = ""
presentSideMenu = false
}
}
// viewModel.getClubProfile()
// viewModel.getProfile()
}
}
}
}
// MARK: - Header
struct HeaderView: View {
@Binding var presentSideMenu: Bool
@State private var showLogoutConfirmation = false
var body: some View {
HStack {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 32, height: 32)
.onTapGesture {
presentSideMenu.toggle()
}
Spacer()
Text("The Royal Club")
.font(.headline)
Spacer()
Image("logout")
.onTapGesture {
showLogoutConfirmation = true
}
}
.padding()
.alert("Are you sure you want to logout?", isPresented: $showLogoutConfirmation) {
Button("Logout", role: .destructive) {
// Your logout logic here
print("User logged out")
AppSettings.token = ""
}
Button("Cancel", role: .cancel) { }
}
}
}
// MARK: - Segmented Control
struct SegmentedControlView: View {
@Binding var selectedTab : Int
let options = ["Summary", "Analytics"]
@EnvironmentObject var viewModel : DashViewModel
var body: some View {
Picker(selection: $selectedTab, label: Text("Options")) {
ForEach(options.indices, id: \.self) { index in
Text(self.options[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
.tint(.gray.opacity(0.5))
Divider()
}
}
// MARK: - Filter View
struct FilterView: View {
@Binding var navigationPaths: [String]
@State var isFilterScreenPresented = false
@Binding var filterCount : Int
@EnvironmentObject var viewModel : DashViewModel
var body: some View {
VStack(alignment: .leading) {
HStack {
Spacer()
.frame(width: 20)
Button(action: { isFilterScreenPresented = true }) {
HStack(spacing: 5) {
Image("filter")
Text("Filter")
.font(.footnote)
.foregroundColor(.gray)
Text("\(self.filterCount ) ")
.font(.caption)
.foregroundColor(.white)
.padding(.horizontal, 8) // Horizontal padding for better width
.padding(.vertical, 4) // Vertical padding for height
.background(
Capsule()
.fill(Colorr.themeBlueColor) // Use your custom color
)
}
.padding(8) // Padding inside the button
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 2)
.onTapGesture {
self.navigationPaths.append("FilterScreen")
}
}
Text("Clear all")
.onTapGesture {
filterCount = 0
viewModel.dashboardfilters = ""
}
Spacer()
}
.padding(.bottom, 5)
}
Divider()
}
}
// MARK: - Court Utilization
struct CourtUtilizationView: View {
var body: some View {
HStack {
Text("Court utilization")
.font(.subheadline)
.bold()
Spacer()
Text("66%")
.font(.subheadline)
.bold()
}
.padding()
.background(Color.white)
.cornerRadius(10)
.padding(.horizontal)
}
}
// MARK: - Reservation Card
struct ReservationCard: View {
let title: String
let hours: String
let revenue: String
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(title)
.font(.body)
// .padding(.top)
HStack {
Spacer()
InfoView(icon: "clock", label: "Hours", value: hours)
.padding(.leading)
Spacer()
.frame(width: 150)
InfoView(icon: "cash", label: "Revenue", value: revenue)
.padding(.trailing)
Spacer()
}
.padding(.bottom)
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(color: .gray.opacity(0.2), radius: 5)
}
}
// MARK: - Info View
struct InfoView: View {
let icon: String
let label: String
let value: String
var body: some View {
VStack {
Image( icon)
.font(.title2)
.foregroundColor(.blue)
Text(label)
.font(.footnote)
.foregroundColor(.gray)
Text(value)
.font(.headline)
.bold()
}
}
}
// MARK: - Preview
//struct SummaryView_Previews: PreviewProvider {
// static var previews: some View {
// SummaryView()
// }
//}
@@ -0,0 +1,95 @@
//
// FilterScreen.swift
// Club Portal
//
// Created by Umer Tahir on 14/04/2025.
//
import SwiftUI
struct FilterScreen: View {
@Binding var navigationPaths: [String]
@State private var startTime: Date = Date()
@State private var endTime: Date = Date()
@State private var startDate: Date = Date()
@State private var endDate: Date = Date()
@State private var selectedDuration: String = "This Day"
let durationOptions = ["This Day", "This Week", "This Month", "This Year"]
@State var filterBlock : ((String, Int) ->Void)?
var body: some View {
NavigationView {
Form {
Section(header: Text("Time Range")) {
DatePicker("Start Time", selection: $startTime, displayedComponents: .hourAndMinute)
DatePicker("End Time", selection: $endTime, displayedComponents: .hourAndMinute)
}
Section(header: Text("Date Range")) {
DatePicker("Start Date", selection: $startDate, displayedComponents: .date)
DatePicker("End Date", selection: $endDate, displayedComponents: .date)
}
Section(header: Text("Duration")) {
Picker("Duration", selection: $selectedDuration) {
ForEach(durationOptions, id: \.self) { duration in
Text(duration)
}
}
.pickerStyle(MenuPickerStyle())
}
Section {
Button(action: {
applyFilter()
}) {
HStack {
Spacer()
Text("Apply")
.fontWeight(.bold)
Spacer()
}
}
}
}
.navigationTitle("Filter")
}
}
private func applyFilter() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss"
let startDateStr = dateFormatter.string(from: startDate)
let endDateStr = dateFormatter.string(from: endDate)
let startTimeStr = timeFormatter.string(from: startTime)
let endTimeStr = timeFormatter.string(from: endTime)
let query = """
start_date=\(startDateStr)&\
end_date=\(endDateStr)&\
start_time=\(startTimeStr)&\
end_time=\(endTimeStr)
"""
// 🔢 Count how many filters are applied
var filterCount = 0
if !Calendar.current.isDateInToday(startDate) { filterCount += 1 }
if !Calendar.current.isDateInToday(endDate) { filterCount += 1 }
if !Calendar.current.isDate(startTime, equalTo: Date(), toGranularity: .minute) { filterCount += 1 }
if !Calendar.current.isDate(endTime, equalTo: Date(), toGranularity: .minute) { filterCount += 1 }
if selectedDuration != "This Day" { filterCount += 1 }
print("Generated Filter Query: \(query)")
print("Filter count: \(filterCount)")
self.filterBlock?(query, filterCount)
self.navigationPaths.removeLast()
}
}
@@ -0,0 +1,23 @@
//
// Club_PortalApp.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
@main
struct Club_PortalApp: App {
var body: some Scene {
WindowGroup {
WelcomeView()
.environmentObject(AuthViewModel())
.environmentObject(DashViewModel())
.preferredColorScheme(.light) // or `.light or` `nil` to use the current scheme
}
}
}
@@ -0,0 +1,24 @@
//
// ContentView.swift
// Club Portal
//
// Created by Umer Tahir on 04/04/2025.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
@@ -0,0 +1,105 @@
//
// ForgotPasswordView.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct ForgotPasswordView: View {
@Binding var navigationPaths: [String]
@State private var showAlert = false
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var viewModel: AuthViewModel
@State private var navigateToOtp = false
@State var showError = false
var body: some View {
// NavigationStack { // Change NavigationView to NavigationStack
HStack(spacing: 10) {
Image(systemName: "arrow.left")
.foregroundColor(.black)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Text("Forgot password")
.bold()
Spacer()
}
.padding()
ScrollView {
VStack(spacing: 20) {
// Title
Image("lock")
.resizable()
.frame(width: 80, height: 80)
.foregroundColor(.green)
.padding(.top, 40)
// Phone Number Input
VStack(alignment: .leading) {
Text("Email")
.foregroundColor(.black)
.padding(.leading, 10)
TextField("Email", text: $viewModel.email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(viewModel.email.isEmpty || viewModel.isEmailValid ? Color.gray : Color.red, lineWidth: 1)
)
.cornerRadius(8)
}
// Description Text
Text("Enter the email associated with your account and we will send you a code to reset your password.")
.font(.footnote)
.foregroundColor(.gray)
.padding(.top, 5)
}
.padding(.horizontal, 10)
// Request Password Reset Code Button
Button(action: {
viewModel.forgotPassword { isSuccess in
if isSuccess {
self.navigationPaths.append("OtpView")
} else {
showError = true
}
}
}) {
Text("Request password reset code")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color("green"))
.cornerRadius(8)
}
.disabled(!viewModel.isEmailValid)
.opacity(viewModel.isEmailValid ? 1 : 0.5)
Spacer()
}
.padding(.horizontal, 20)
.toast(message: viewModel.toastMessage, isShowing: $viewModel.showToast)
.overlay(
Group {
if viewModel.isLoading {
LoadingView()
}
}
)
.navigationBarBackButtonHidden(true)
}
}
@@ -0,0 +1,159 @@
//
// OtpView.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct OtpView: View {
@EnvironmentObject var viewModel : AuthViewModel
@Binding var navPaths : [String]
@State private var code = ["", "", "", "", "",""] // Array to hold each digit of the OTP
// Accept phone number as a parameter
@State private var showAlert = false
@State private var navigateToNext = false // State to control navigation
@Environment(\.presentationMode) var presentationMode
// @State var otpCode = ""
@FocusState private var focusedField: Int?
var body: some View {
HStack(spacing: 10) {
Image(systemName: "arrow.left")
.foregroundColor(.black)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Text("Verify security code")
.bold()
Spacer()
}
.padding()
ScrollView {
VStack(spacing: 20) {
// Title
Image("otp")
.resizable()
.frame(width: 80, height: 80)
.foregroundColor(.green)
.padding(.top, 40)
// Security Code Description
VStack(alignment: .leading) {
Text("Security code")
.foregroundColor(.black)
.padding(.leading, 10)
Text("Weve sent a verification code to: \(viewModel.email)")
.font(.footnote)
.foregroundColor(.gray)
.padding(.top, 5)
}
.padding(.horizontal, 20)
// if viewModel.isFetching {
// ProgressView("Login...")
// }
// OTP Input Fields
HStack(spacing: 10) {
ForEach(0..<6) { index in
TextField("", text: $code[index])
.keyboardType(.numberPad)
.frame(width: 50, height: 50)
//.background(Color.gray.opacity(0.2))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.cornerRadius(8)
.multilineTextAlignment(.center)
.focused($focusedField, equals: index) // Bind focus state
.onChange(of: code[index]) { newValue in
if newValue.count > 1 {
code[index] = String(newValue.prefix(1)) // Limit to one character
}
if !newValue.isEmpty && index < 5 {
// Move to the next field if not the last one
focusedField = index + 1
} else if newValue.isEmpty && index > 0 {
// Move focus back if the user deletes the input
focusedField = index - 1
}
}
}
}
.padding(.horizontal, 20)
.onAppear {
// Set initial focus to the first text field
focusedField = 0
}
// Resend Code Link
HStack {
Text("Didnt get the text?")
.foregroundColor(.gray)
Button(action: {
// Handle resend code action
// viewModel.forgotUsingPhoneNumber { isSuccess in
// if isSuccess {
// DispatchQueue.main.async {
// // navigateToOtp = true
// // viewModel.phoneNumber = "\(viewModel.countryCode) \(viewModel.phone)"
// }
// }else {
// }
// }
}) {
Text("Resend")
.foregroundColor(Color("green"))
.underline() // Underline the text
}
}
.padding(.leading, 10)
// Continue Button
Button(action: {
let otpCode = code.joined()
viewModel.otpCode = otpCode
navPaths.append("SetNewPasswordView")
}) {
Text("Continue")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color("green"))
.cornerRadius(8)
}
.disabled(code.joined().count != 6)
.opacity(code.joined().count == 6 ? 1 : 0.5)
.padding( 10)
.alert(isPresented: $showAlert) {
Alert(title: Text("Incomplete OTP"),
message: Text("Please enter all digits of the OTP."),
dismissButton: .default(Text("OK")))
}
Spacer()
}
.padding(.horizontal, 20)
}
.navigationTitle("")
// .toast(isPresented: $viewModel.isError, message: viewModel.errorMessage)
.navigationBarBackButtonHidden(true)
}
func verifyCode(){
// viewModel.reset {
// if viewModel.isError == false {
// navPaths.append("SetNewPasswordView")
// }
// }
}
}
@@ -0,0 +1,167 @@
//
// SetNewPasswordView.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct SetNewPasswordView: View {
@Binding var navigationPaths: [String]
@State private var showNewPassword = false
@State private var showConfirmPassword = false
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var viewModel: AuthViewModel
@State private var showAlert = false
@State private var navigateToSignIn = false // State to control navigation to SignInView
var body: some View {
HStack(spacing: 10) {
Image(systemName: "arrow.left")
.foregroundColor(.black)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Text("Set new password")
.bold()
Spacer()
}
.padding()
ScrollView {
VStack(spacing: 20) {
// Title
Image("lock")
.resizable()
.frame(width: 80, height: 80)
.foregroundColor(.green)
.padding(.top, 40)
// New Password Field
VStack(alignment: .leading) {
Text("Set new password")
.foregroundColor(.black)
.padding(.leading, 10)
ZStack {
if showNewPassword {
TextField("New Password", text: $viewModel.newPassword)
} else {
SecureField("New Password", text: $viewModel.newPassword)
}
}
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.cornerRadius(8)
.overlay(
Button(action: {
showNewPassword.toggle()
}) {
Image(systemName: showNewPassword ? "eye.slash" : "eye")
.foregroundColor(.gray)
.padding(.trailing, 10)
}, alignment: .trailing
)
}
.padding(.horizontal, 20)
// Confirm Password Field
VStack(alignment: .leading) {
Text("Confirm new password")
.foregroundColor(.black)
.padding(.leading, 10)
ZStack {
if showConfirmPassword {
TextField("Confirm Password", text: $viewModel.confirmPassword)
} else {
SecureField("Confirm Password", text: $viewModel.confirmPassword)
}
}
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.cornerRadius(8)
.overlay(
Button(action: {
showConfirmPassword.toggle()
}) {
Image(systemName: showConfirmPassword ? "eye.slash" : "eye")
.foregroundColor(.gray)
.padding(.trailing, 10)
}, alignment: .trailing
)
}
.padding(.horizontal, 20)
Button(action: {
viewModel.resetPassword { success in
if success {
self.navigationPaths.removeAll()
}
}
}) {
Text("Set new password")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color("green"))
.cornerRadius(8)
}
.disabled(!viewModel.canSubmitNewPassword)
.opacity(viewModel.canSubmitNewPassword ? 1 : 0.5)
.padding(.vertical, 10)
Spacer()
}
.padding(.horizontal, 20)
// if viewModel.isFetching {
// ProgressView("Loading...")
// }
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true) // Hide the back button
.alert(isPresented: $showAlert) {
Alert(
title: Text("Alert"),
message: Text(viewModel.showToast ? viewModel.toastMessage : viewModel.toastMessage),
dismissButton: .default(Text("OK")) {
if !viewModel.showToast {
// navigateToSignIn = true // Navigate to SignInView on success
self.navigationPaths.removeAll()
}
}
)
}
.toast(message: viewModel.toastMessage, isShowing: $viewModel.showToast)
.overlay(
Group {
if viewModel.isLoading {
LoadingView()
}
}
)
}
}
//#Preview {
// SetNewPasswordView()
//}
@@ -0,0 +1,164 @@
//
// SigninView.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
import LocalAuthentication
struct SigninView: View {
@State var navigationPaths: [String] = []
@State private var keepLoggedIn = false
@State private var showPassword = false
@EnvironmentObject var viewModel: AuthViewModel
var body: some View {
NavigationStack(path: $navigationPaths) {
ScrollView {
VStack(spacing: 20) {
// Welcome Text
Image("ball")
.resizable()
.frame(width: 80, height: 80)
.foregroundColor(.green)
.padding(.top, 40)
// Email and Password Fields
CustomTextField(placeholder: "Email", text: $viewModel.email, keyboardType: .emailAddress)
VStack(alignment: .leading) {
Text("Password")
.foregroundColor(.black)
.padding(.leading, 10)
ZStack {
if showPassword {
TextField("Password", text: $viewModel.password)
} else {
SecureField("Password", text: $viewModel.password)
}
}
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.cornerRadius(8)
.overlay(
Button(action: {
showPassword.toggle()
}) {
Image(systemName: showPassword ? "eye.slash" : "eye")
.foregroundColor(.gray)
.padding(.trailing, 10)
}, alignment: .trailing
)
}
.padding(0)
// Forgot Password Link
HStack {
Button(action: {
// Push SignUp destination onto the navigation path
navigationPaths.append("ForgotPassword")
}) {
Text("Forgot Password")
.foregroundColor(.black)
.underline()
}
Spacer()
}
// Keep me logged in Checkbox
HStack {
Button(action: {
keepLoggedIn.toggle()
viewModel.isRefresh.toggle()
AppSettings.keepLoginIn.toggle()
}) {
Image(systemName: AppSettings.keepLoginIn ? "checkmark.square" : "square")
.foregroundColor(Color("blue"))
}
Text("Keep me logged in for 30 days")
.foregroundColor(.black)
.onTapGesture {
viewModel.isRefresh.toggle()
}
}
// Log In Button
Button(action: {
viewModel.login {_ in
// Navigate to the Dashboard on successful login
navigationPaths.removeAll()
navigationPaths.append("Dashboard")
}
}) {
Text("Log in")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(viewModel.canLogin ? Color("green") : Color.gray)
.cornerRadius(8)
}
.disabled(!viewModel.canLogin)
.padding(.vertical, 10)
// Sign Up Link
}
.padding(.horizontal, 20)
.onAppear(){
}
}
.navigationTitle("Welcome back")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarBackButtonHidden(true)
.navigationDestination(for: String.self) { value in
if value == "ForgotPassword" {
ForgotPasswordView(navigationPaths: $navigationPaths)
} else if value == "OtpView" {
OtpView(navPaths: $navigationPaths)
} else if value == "SetNewPasswordView" {
SetNewPasswordView(navigationPaths: $navigationPaths)
} else if value == "Dashboard" {
WelcomeView() // Navigate to the Dashboard screen
}
}
.toast(message: viewModel.toastMessage, isShowing: $viewModel.showToast)
.overlay(
Group {
if viewModel.isLoading {
LoadingView()
}
}
)
}
}
}
#Preview {
SigninView()
}
@@ -0,0 +1,32 @@
//
// SideMenu.swift
//
//
import SwiftUI
struct SideMenu: View {
@Binding var isShowing: Bool
var content: AnyView
var edgeTransition: AnyTransition = .move(edge: .leading)
var body: some View {
ZStack(alignment: .bottom) {
if (isShowing) {
Color.black
.opacity(0.3)
.ignoresSafeArea()
.onTapGesture {
isShowing.toggle()
}
content
.transition(edgeTransition)
.background(
Color.clear
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.ignoresSafeArea()
.animation(.easeInOut, value: isShowing)
}
}
@@ -0,0 +1,206 @@
//
// SideMenuView.swift
//
//
import SwiftUI
enum SideMenuRowType: Int, CaseIterable {
case logout
var title: String {
switch self {
case .logout:
return "Logout"
}
}
var iconName: String {
switch self {
case .logout:
return "logout"
}
}
}
struct SideMenuView: View {
@Binding var selectedSideMenuTab: Int?
@Binding var presentSideMenu: Bool
@EnvironmentObject var userAuth: AuthViewModel
@State private var isActive = false // Track active navigation state
@State var nav = [String]()
@State var isUpgrde = false
var body: some View {
HStack {
ZStack {
Rectangle()
.fill(.white)
.frame(width: 320)
.shadow(color: .purple.opacity(0.1), radius: 5, x: 0, y: 3)
VStack(alignment: .leading, spacing: 0) {
HStack {
ProfileHead()
.frame(height: 50)
.padding(.bottom, 30)
}
Spacer()
.frame(height: 40)
ForEach(SideMenuRowType.allCases, id: \.self) { row in
RowView(isSelected: selectedSideMenuTab == row.rawValue, imageName: row.iconName, title: row.title, action: {
if row.title == "Logout" {
// userAuth.logout()
AppSettings.token = ""
presentSideMenu = false
}
})
}
Spacer()
HStack{
Button(action:
{
ConstantData.openLink(urlString: ConstantData.termsLink)
}) {
Text("Terms and Conditions")
.font(.footnote)
.foregroundColor(.gray.opacity(0.7))
.padding(.leading)
}
Spacer()
Button(action: {
ConstantData.openLink(urlString: ConstantData.privacyLink)
}) {
Text("Privacy Policy")
.font(.footnote)
.foregroundColor(.gray.opacity(0.7))
.padding(.trailing)
}
}
Text("v.\(appVersion)")
.font(.footnote)
.padding(.leading)
.foregroundColor(.gray.opacity(0.7))
Spacer()
.frame(height: 50)
}
.padding(.top, 75)
.frame(width: 320)
.background(Color.white)
.background(
NavigationLink(
destination: self.handleMenuSelection(for: selectedSideMenuTab ?? 0),
isActive: $isActive // Bind to the state
) {
EmptyView()
}
)
}
Spacer()
}
.background(.clear)
}
private var appVersion: String {
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "N/A"
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "N/A"
return "\(version) (\(build))"
}
func ProfileHead() -> some View {
VStack(alignment: .leading) {
HStack {
ProfileImageView(imageUrl: AppSettings.photo, size: 50)
.padding(.leading)
Spacer()
Image("menuLeft")
.padding(.trailing)
.onTapGesture {
presentSideMenu.toggle()
}
}
Text("John Smith")
.padding(.leading)
.font(.system(size: 18, weight: .bold))
.foregroundColor(.black)
Text("John@gmail.com")
.padding(.leading)
.font(.system(size: 14, weight: .semibold))
.foregroundColor(.gray)
}
.padding(.leading)
}
func RowView(isSelected: Bool, imageName: String, title: String, action: @escaping (() -> ())) -> some View {
Button {
action()
} label: {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 20) {
Rectangle()
.fill(isSelected ? .gray : .white)
.frame(width: 5)
ZStack {
Image(imageName)
.resizable()
.renderingMode(.template)
.foregroundColor(Colorr.lableColor)
.frame(width: 26, height: 26)
}
.frame(width: 30, height: 30)
Text(title)
.font(.system(size: 14, weight: .regular))
.foregroundColor(.black)
// Spacer()
}
.frame(height: 40)
// Move Divider outside HStack to be a full-width line
if title.lowercased() == "billing" {
Divider()
.background(Colorr.lightColor)
.padding(.horizontal)
.padding(.vertical)
}
}
}
}
private func handleMenuSelection(for tab: Int) -> some View {
switch tab {
case 0:
print("Navigate to ProfileView")
return AnyView(EmptyView())
default:
return AnyView(EmptyView())
}
}
}
@@ -0,0 +1,107 @@
//
// WelcomeView.swift
// Club Portal
//
// Created by Umer Tahir on 11/04/2025.
//
import SwiftUI
struct WelcomeView: View {
@EnvironmentObject var userAuth: AuthViewModel
@State private var isAccountViewPresented = false
@State private var selectedTab = 0 // Track the selected tab
@State private var presentSideMenu = false
@State private var selectedSideMenuTab: Int? = nil // Optional for side menu
@AppStorage("token") var token: String = ""
var body: some View {
// NavigationView {
ZStack(alignment: .topTrailing) {
if token != "" {
TabView(selection: $selectedTab) {
SummaryView(presentSideMenu: $presentSideMenu)
.tabItem {
Image(selectedTab == 0 ? "tab1_" : "tab1")
Text("Home")
}
.tag(0)
//.environmentObject(treeqlViewModel)
ScheduleView(presentSideMenu: $presentSideMenu)
.tabItem {
Image(selectedTab == 1 ? "tab2_" : "tab2")
Text("Daily scheduler")
}
.tag(1)
//.environmentObject(treeqlViewModel)
AvailabilityScreen(presentSideMenu: $presentSideMenu)
.tabItem {
Image(selectedTab == 2 ? "tab3_" : "tab3")
Text("Availability")
}
.tag(2)
// .environmentObject(treeqlViewModel)
}
.accentColor(Color(hex: "#162664"))
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
presentSideMenu.toggle()
}
} label: {
Image("menu")
.resizable()
.frame(width: 32, height: 32)
}
}
}
.background(Color.red)
.ignoresSafeArea(.all)
SideMenu(isShowing: $presentSideMenu, content: AnyView(
SideMenuView(selectedSideMenuTab: $selectedSideMenuTab, presentSideMenu: $presentSideMenu)
))
} else {
SigninView()
.environmentObject(userAuth)
}
}
.navigationBarBackButtonHidden()
.navigationBarBackButtonHidden(true)
.onChange(of: token) { newVal in
print("Token changed: \(newVal)")
}
// }
.onAppear {
// treeqlViewModel.getSportsList()
print(AppSettings.token)
}
.navigationBarBackButtonHidden(true)
.navigationBarBackButtonHidden()
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView()
.environmentObject(AuthViewModel())
}
}
@@ -0,0 +1,346 @@
//
// AppSettings.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import Foundation
import SwiftUI
final class AppSettings {
enum SettingKey: String {
case userID
case email
case fName
case lastName
case role
case token
case photo
case password
case clubId
case ntrp
case keepLoginIn
case clubLogo
case clubName
case clubFee
case serviceFee
case clubPlans
case activePlanID
case plan_name
case price
case allow_clinic
case allow_buddy
case allow_coach
case allow_groups
case allow_court
case lastReservationID
case lastReserveTime
case lastReservationTotal
case isLoggedin
}
static var lastReservationID: Int {
get {
return USERDEFAULTS_GET_INT_KEY(key: SettingKey.lastReservationID.rawValue)
}
set {
USERDEFAULTS_SET_INT_KEY(object: newValue, key: SettingKey.lastReservationID.rawValue)
}
}
static var lastReserveTime: Double {
get {
return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.lastReserveTime.rawValue)
}
set {
USERDEFAULTS_SET_DOUBLE_KEY(object: newValue, key: SettingKey.lastReserveTime.rawValue)
}
}
static var lastReservationTotal: Double {
get {
return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.lastReservationTotal.rawValue)
}
set {
USERDEFAULTS_SET_DOUBLE_KEY(object: newValue, key: SettingKey.lastReservationTotal.rawValue)
}
}
static var plan_name: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.plan_name.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.plan_name.rawValue)
}
}
static var price: Double {
get {
return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.price.rawValue)
}
set {
USERDEFAULTS_SET_DOUBLE_KEY(object: newValue, key: SettingKey.price.rawValue)
}
}
static var allow_clinic: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.allow_clinic.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.allow_clinic.rawValue)
}
}
static var allow_buddy: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.allow_buddy.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.allow_buddy.rawValue)
}
}
static var allow_coach: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.allow_coach.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.allow_coach.rawValue)
}
}
static var allow_groups: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.allow_groups.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.allow_groups.rawValue)
}
}
static var allow_court: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.allow_court.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.allow_court.rawValue)
}
}
static var clubFee: Double {
get {
return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.clubFee.rawValue)
}
set {
USERDEFAULTS_SET_DOUBLE_KEY(object: newValue, key: SettingKey.clubFee.rawValue)
}
}
static var serviceFee: Double {
get {
return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.serviceFee.rawValue)
}
set {
USERDEFAULTS_SET_DOUBLE_KEY(object: newValue, key: SettingKey.serviceFee.rawValue)
}
}
static var keepLoginIn: Bool {
get {
return USERDEFAULTS_GET_BOOL_KEY(key: SettingKey.keepLoginIn.rawValue)
}
set {
USERDEFAULTS_SET_BOOL_KEY(object: newValue, key: SettingKey.keepLoginIn.rawValue)
}
}
static var clubLogo: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.clubLogo.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.clubLogo.rawValue)
}
}
static var clubPlans: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.clubPlans.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.clubPlans.rawValue)
}
}
static var clubName: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.clubName.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.clubName.rawValue)
}
}
static var photo: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.photo.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.photo.rawValue)
}
}
static var password: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.password.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.password.rawValue)
}
}
static var lastName: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.lastName.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.lastName.rawValue)
}
}
static var email: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.email.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.email.rawValue)
}
}
static var fName: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.fName.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.fName.rawValue)
}
}
static var role: String {
get {
return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.role.rawValue)
}
set {
USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.role.rawValue)
}
}
@AppStorage("token") static var token: String = ""
// static var token: String {
// get {
// return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.token.rawValue)
// }
// set {
// USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.token.rawValue)
// }
// }
static var userID: Int {
get {
return USERDEFAULTS_GET_INT_KEY(key: SettingKey.userID.rawValue)
}
set {
USERDEFAULTS_SET_INT_KEY(object: newValue, key: SettingKey.userID.rawValue)
}
}
static var clubId: Int {
get {
return USERDEFAULTS_GET_INT_KEY(key: SettingKey.clubId.rawValue)
}
set {
USERDEFAULTS_SET_INT_KEY(object: newValue, key: SettingKey.clubId.rawValue)
}
}
static var activePlanID: Int {
get {
return USERDEFAULTS_GET_INT_KEY(key: SettingKey.activePlanID.rawValue)
}
set {
USERDEFAULTS_SET_INT_KEY(object: newValue, key: SettingKey.activePlanID.rawValue)
}
}
static var ntrp: Int {
get {
return USERDEFAULTS_GET_INT_KEY(key: SettingKey.ntrp.rawValue)
}
set {
USERDEFAULTS_SET_INT_KEY(object: newValue, key: SettingKey.ntrp.rawValue)
}
}
}
enum USERDEFAULTS_KEYS: String {
case user
}
let USERDEFAULTS = UserDefaults.standard
func USERDEFAULTS_SET_STRING_KEY(object:String, key:String) -> Void {
USERDEFAULTS .set(object, forKey: key)
USERDEFAULTS.synchronize()
}
func USERDEFAULTS_GET_STRING_KEY(key:String) -> String {
return USERDEFAULTS.object(forKey: key) as? String == nil ? "" : USERDEFAULTS.object(forKey: key) as! String
}
func USERDEFAULTS_SET_BOOL_KEY(object:Bool, key:String) -> Void {
USERDEFAULTS .set(object, forKey: key)
USERDEFAULTS.synchronize()
}
func USERDEFAULTS_GET_BOOL_KEY(key:String) -> Bool {
return USERDEFAULTS.object(forKey: key) as? Bool == nil ? false : USERDEFAULTS.object(forKey: key) as! Bool
}
func USERDEFAULTS_SET_INT_KEY(object:Int, key:String) -> Void {
USERDEFAULTS .set(object, forKey: key)
USERDEFAULTS.synchronize()
}
func USERDEFAULTS_GET_INT_KEY(key:String) -> Int {
return USERDEFAULTS.object(forKey: key) as? Int == nil ? 0 : USERDEFAULTS.object(forKey: key) as! Int
}
func USERDEFAULTS_SET_DOUBLE_KEY(object:Double, key:String) -> Void {
USERDEFAULTS .set(object, forKey: key)
USERDEFAULTS.synchronize()
}
func USERDEFAULTS_GET_DOUBLE_KEY(key:String) -> Double {
return USERDEFAULTS.object(forKey: key) as? Double == nil ? 0.0 : USERDEFAULTS.object(forKey: key) as! Double
}
//func USERDEFAULTS_SET_USER_OBJECT(object:User) -> Void {
// USERDEFAULTS .set(try? PropertyListEncoder().encode(object), forKey: USERDEFAULTS_KEYS.user.rawValue)
// USERDEFAULTS.synchronize()
//}
//func USERDEFAULTS_GET_USER_OBJECT() -> User? {
//
// guard let data = USERDEFAULTS.object(forKey: USERDEFAULTS_KEYS.user.rawValue) as? Data else{
// return nil
// }
// let user = try? PropertyListDecoder().decode(User.self, from: data)
// return user
//}
func USERDEFAULTS_REMOVE_OBJECT(key:String){
USERDEFAULTS.removeObject(forKey: key)
USERDEFAULTS.synchronize()
}
@@ -0,0 +1,57 @@
//
// Colorr.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
//
// extension.swift
// courtmatch
//
// Created by Umer Tahir on 13/11/2024.
//
import SwiftUI
struct Colorr {
static let themeBlueColor = Color(hex: "112a8c")
static let reservationBackgroundColor = Color(hex: "C2D6FF").opacity(0.1)
static let requestSentBg = Color( hex: "38C793")
static let requestJoinAlertOrangeBg = Color(hex: "F17B2C")
static let greenColor = Color(hex: "176448")
static let lableColor = Color(hex: "525866")
static let lightColor = Color(hex: "F6F8FA")
static let findBuddyColor = Color(hex: "CBF5E5")
static let redColor = Color(hex: "DF1C41")
static let pieGreen = Color(hex: "2D9F75")
static let pieBlue = Color(hex: "253EA7")
}
struct ConstantData {
static let privacyLink = "https://courtmatchup.manaknightdigital.com/privacy-policy"
static let termsLink = "https://courtmatchup.manaknightdigital.com/terms-and-conditions"
static let stripeTestKey = "pk_test_51Ll5ukBgOlWo0lDUrBhA2W7EX2MwUH9AR5Y3KQoujf7PTQagZAJylWP1UOFbtH4UwxoufZbInwehQppWAq53kmNC00UIKSmebO"
static let appleMarchantId = "merchant.com.courtmatch.app"
static func openLink(urlString: String) {
guard let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) else {
print("Invalid URL")
return
}
UIApplication.shared.open(url)
}
static let NTRPVALUES = ["not set", "8.0","7.5", "7.0", "6.5", "6.0", "5.5", "5.0", "4.5", "4.0", "3.5", "3.0", "2.5", "2.0"]
static let genders = ["Male", "Female", "Other"]
static var isCustomRequest = 0
}
@@ -0,0 +1,32 @@
//
// CustomTextField.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
// Custom TextField Component
struct CustomTextField: View {
let placeholder: String
@Binding var text: String
var keyboardType: UIKeyboardType = .default
var body: some View {
VStack(alignment: .leading) {
Text(placeholder)
.foregroundColor(.black)
.padding(.leading, 10)
TextField( placeholder == "Date of birth" ? "MM/DD/YYY" : placeholder, text: $text)
.keyboardType(keyboardType)
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
)
.cornerRadius(8)
}
}
}
@@ -0,0 +1,18 @@
//
// Extension.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import Foundation
extension String {
var isValidEmail: Bool {
let emailPredicate = NSPredicate(
format: "SELF MATCHES %@",
"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
)
return emailPredicate.evaluate(with: self)
}
}
@@ -0,0 +1,355 @@
import Foundation
import SwiftUI
extension Color {
init(hex: String) {
let scanner = Scanner(string: hex)
var rgbValue: UInt64 = 0
scanner.scanHexInt64(&rgbValue)
let r = Double((rgbValue & 0xff0000) >> 16) / 255.0
let g = Double((rgbValue & 0xff00) >> 8) / 255.0
let b = Double(rgbValue & 0xff) / 255.0
self.init(red: r, green: g, blue: b)
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}
import Foundation
extension String {
/// Converts a date string from one format to another.
/// - Parameters:
/// - inputFormat: The format of the input date string.
/// - outputFormat: The desired format for the output date string.
/// - Returns: A formatted date string, or `nil` if conversion fails.
func convertDateFormat(from inputFormat: String? = "yyyy-MM-dd", to outputFormat: String? = "EEEE, MMM d") -> String? {
let inputFormatter = DateFormatter()
inputFormatter.dateFormat = inputFormat
guard let date = inputFormatter.date(from: self) else {
return nil
}
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = outputFormat
return outputFormatter.string(from: date)
}
func convertDateForReq(from inputFormat: String? = "yyyy-MM-dd", to outputFormat: String? = "EEEE, MMM d") -> String? {
let inputFormatter = DateFormatter()
inputFormatter.dateFormat = inputFormat
inputFormatter.locale = Locale.current
inputFormatter.timeZone = TimeZone.current
guard let date = inputFormatter.date(from: self) else {
return nil
}
let calendar = Calendar.current
let now = Date()
let components = calendar.dateComponents([.day], from: calendar.startOfDay(for: now), to: calendar.startOfDay(for: date))
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "EEEE, MMM d"
let formattedDate = dayFormatter.string(from: date)
switch components.day {
case 0:
return "Today (\(formattedDate))"
case 1:
return "Tomorrow (\(formattedDate))"
default:
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = outputFormat
return outputFormatter.string(from: date)
}
}
func isDateForTmr() ->Bool {
let inputFormatter = DateFormatter()
inputFormatter.dateFormat = "yyyy-MM-dd"
inputFormatter.locale = Locale.current
inputFormatter.timeZone = TimeZone.current
guard let date = inputFormatter.date(from: self) else {
return false
}
let calendar = Calendar.current
let now = Date()
let components = calendar.dateComponents([.day], from: calendar.startOfDay(for: now), to: calendar.startOfDay(for: date))
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "EEEE, MMM d"
if components.day == 1 {
return true
}
return false
}
func getStringArray() -> [String] {
if let data = self.data(using: .utf8) {
do {
let stringArray = try JSONDecoder().decode([String].self, from: data)
print(stringArray) // Output: ["Indoor", "Outdoor"]
return stringArray
} catch {
print("Decoding error: \(error)")
}
}
return []
}
}
extension Double {
/// Rounds the double to 2 decimal places
var roundedTo2Decimals: Double {
var decimalValue = Decimal(self)
var roundedValue = Decimal()
NSDecimalRound(&roundedValue, &decimalValue, 2, .plain) // Rounds to 2 decimal places
return (roundedValue as NSDecimalNumber).doubleValue
}
func formattedString(decimalPlaces: Int) -> String {
return String(format: "%.\(decimalPlaces)f", self)
}
}
extension String {
func fromTimeIntervaldDate(format: String = "MMM dd, yyyy") -> String? {
guard let timestamp = TimeInterval(self) else { return nil }
let date = Date(timeIntervalSince1970: timestamp)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
dateFormatter.timeZone = TimeZone.current // Adjust to device's timezone
let s = dateFormatter.string(from: date)
return s
}
/// Converts a time string from 24-hour format to 12-hour format with AM/PM
func to12HourFormat1() -> String? {
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "HH:mm:ss" // Input format
let outputDateFormatter = DateFormatter()
outputDateFormatter.dateFormat = "hh:mm a" // Output format
// Convert the string to a `Date` object
if let date = inputDateFormatter.date(from: self) {
return outputDateFormatter.string(from: date) // Convert back to string in desired format
} else {
return nil // Return nil if conversion fails
}
}
func to12HourFormat() -> String? {
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "HH:mm:ss" // Input format (24-hour)
let outputDateFormatter = DateFormatter()
outputDateFormatter.dateFormat = "hh:mm a" // Output format (12-hour with AM/PM)
// Split the range into start and end times
let timeComponents = self.components(separatedBy: "-")
if timeComponents.count == 2 {
let startTime = inputDateFormatter.date(from: timeComponents[0])
let endTime = inputDateFormatter.date(from: timeComponents[1])
let start12Hour = outputDateFormatter.string(from: startTime!)
let end12Hour = outputDateFormatter.string(from: endTime!)
return "\(start12Hour) - \(end12Hour)"
}else {
let startTime = inputDateFormatter.date(from: self)
let start12Hour = outputDateFormatter.string(from: startTime!)
return start12Hour
}
// Return nil if conversion fails
}
func convertTo24HourFormat() -> String? {
// Initialize date formatter
let inputFormatter = DateFormatter()
inputFormatter.dateFormat = "hh:mm a" // Input format (12-hour with AM/PM)
// Convert string to date
if let date = inputFormatter.date(from: self) {
// Initialize another date formatter for 24-hour format
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = "HH:mm" // 24-hour format
// Convert date to 24-hour string
return outputFormatter.string(from: date)
}
return nil
}
func timeAgo() -> String? {
// Set up the date formatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.locale = Locale.current
dateFormatter.timeZone = TimeZone.current
// Convert the string into a Date object
guard let date = dateFormatter.date(from: self) else { return nil }
// Get the current date and calculate the time interval
let calendar = Calendar.current
let now = Date()
let components = calendar.dateComponents([.year, .month, .day], from: date, to: now)
// Check the components to return the correct "time ago" string
if let years = components.year, years > 0 {
return "\(years) year\(years > 1 ? "s" : "") ago"
} else if let months = components.month, months > 0 {
return "\(months) month\(months > 1 ? "s" : "") ago"
} else if let days = components.day, days > 0 {
return "\(days) day\(days > 1 ? "s" : "") ago"
} else {
return "Just now"
}
}
}
extension Date {
func dayOfWeek( ) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE" // Full day name
return dateFormatter.string(from: self)
}
func getStringDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd" // Specify the desired format
return dateFormatter.string(from: self)
}
func currentDateOfMonth() ->Int {
let day = Calendar.current.component(.day, from: Date())
return day
}
}
extension UIImage {
func resize(to targetSize: CGSize) -> UIImage? {
let size = self.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(origin: .zero, size: newSize)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
extension String {
func toIntArray() -> [Int] {
// Remove brackets and split the string by commas
let trimmed = self.trimmingCharacters(in: CharacterSet(charactersIn: "[]"))
let stringArray = trimmed.split(separator: ",")
// Convert each substring to an integer
return stringArray.compactMap { Int($0) }
}
func removeFlag() -> String {
let filteredString = self.filter { !$0.isFlag }
return filteredString.trimmingCharacters(in: .whitespaces)
}
}
extension Character {
// Check if a character is a flag emoji
var isFlag: Bool {
return unicodeScalars.allSatisfy { $0.properties.isEmoji && ($0.value >= 0x1F1E6 && $0.value <= 0x1F1FF) }
}
}
extension Double {
func formatNTRP () -> String {
return String(format: "%.1f", self )
}
}
extension String {
func toWeekday() -> String? {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
guard let date = formatter.date(from: self) else {
return nil
}
formatter.dateFormat = "E" // Short weekday like Mon, Tue, etc.
return formatter.string(from: date)
}
}
@@ -0,0 +1,21 @@
//
// LoadingView.swift
// Club Portal
//
// Created by Umer Tahir on 10/04/2025.
//
import SwiftUI
struct LoadingView: View {
var body: some View {
ZStack {
Color.black.opacity(0.3).ignoresSafeArea()
ProgressView("Loading...")
.padding()
.background(Color.white)
.cornerRadius(12)
}
}
}
@@ -0,0 +1,36 @@
//
// ProfileImageView.swift
// Club Portal
//
// Created by Umer Tahir on 15/04/2025.
//
import SwiftUI
struct ProfileImageView: View {
let imageUrl: String?
let size: CGFloat
var body: some View {
if let urlString = imageUrl, let url = URL(string: urlString) {
AsyncImage(url: url) { image in
image
.resizable()
.scaledToFill()
.frame(width: size, height: size)
.clipShape(Circle())
} placeholder: {
Circle()
.fill(Color.gray)
.frame(width: size, height: size)
}
} else {
Circle()
.fill(Color.gray)
.frame(width: size, height: size)
}
}
}
@@ -0,0 +1,40 @@
//
// SignInButton.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
// Social Sign-In Button Component
struct SignInButton: View {
let title: String
let iconName: String
var action: (() -> ())?
var body: some View {
Button(action: {
// Handle sign-in
action?()
}) {
HStack {
Image(iconName)
.font(.headline)
Text(title)
.font(.headline)
}
.foregroundColor(.black)
.frame(maxWidth: .infinity)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
)
.cornerRadius(8)
}
}
}
@@ -0,0 +1,48 @@
//
// ToastModifier.swift
// Club Portal
//
// Created by Umer Tahir on 09/04/2025.
//
import SwiftUI
struct ToastModifier: ViewModifier {
let message: String
let backgroundColor: Color
@Binding var isShowing: Bool
func body(content: Content) -> some View {
ZStack {
content
if isShowing {
VStack {
Spacer()
Text(message)
.padding()
.background(backgroundColor)
.foregroundColor(.white)
.cornerRadius(10)
.transition(.move(edge: .bottom).combined(with: .opacity))
.padding(.bottom, 40)
}
.animation(.easeInOut, value: isShowing)
.onAppear {
// Hide toast automatically after 3 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isShowing = false
}
}
}
}
}
}
extension View {
func toast(message: String, isShowing: Binding<Bool>, backgroundColor: Color = .green) -> some View {
self.modifier(ToastModifier(message: message, backgroundColor: backgroundColor, isShowing: isShowing))
}
}

Some files were not shown because too many files have changed in this diff Show More