updated code
This commit is contained in:
@@ -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 */;
|
||||||
|
}
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
+15
@@ -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
|
||||||
|
}
|
||||||
+280
@@ -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>
|
||||||
+11
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Icon (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "clock.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Frame 11.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+21
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 428 B |
BIN
Binary file not shown.
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Compact Button [1.0] (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Icon-right.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Compact Button [1.0].pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Frame 4 (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Frame 595 (2).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Frame 595.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "calendar-clock, date-time.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "calendar-clock, date-time (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Frame 595 (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
+21
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
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
|
||||||
|
}
|
||||||
+26
@@ -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 }
|
||||||
|
}
|
||||||
+73
@@ -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 }
|
||||||
|
}
|
||||||
+29
@@ -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 }
|
||||||
|
}
|
||||||
+38
@@ -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 }
|
||||||
|
}
|
||||||
|
|
||||||
+31
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+68
@@ -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?
|
||||||
|
}
|
||||||
+12
@@ -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
|
||||||
|
|
||||||
+146
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
+6
@@ -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("We’ve 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("Didn’t 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
Reference in New Issue
Block a user