├── .gitignore ├── README.md ├── capacitor.config.ts ├── ionic.config.json ├── ios ├── .gitignore └── App │ ├── App.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── App.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── App │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon-20x20@1x.png │ │ │ ├── AppIcon-20x20@2x-1.png │ │ │ ├── AppIcon-20x20@2x.png │ │ │ ├── AppIcon-20x20@3x.png │ │ │ ├── AppIcon-29x29@1x.png │ │ │ ├── AppIcon-29x29@2x-1.png │ │ │ ├── AppIcon-29x29@2x.png │ │ │ ├── AppIcon-29x29@3x.png │ │ │ ├── AppIcon-40x40@1x.png │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ ├── AppIcon-40x40@2x.png │ │ │ ├── AppIcon-40x40@3x.png │ │ │ ├── AppIcon-512@2x.png │ │ │ ├── AppIcon-60x60@2x.png │ │ │ ├── AppIcon-60x60@3x.png │ │ │ ├── AppIcon-76x76@1x.png │ │ │ ├── AppIcon-76x76@2x.png │ │ │ ├── AppIcon-83.5x83.5@2x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Splash.imageset │ │ │ ├── Contents.json │ │ │ ├── splash-2732x2732-1.png │ │ │ ├── splash-2732x2732-2.png │ │ │ └── splash-2732x2732.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── capacitor.config.json │ └── config.xml │ └── Podfile ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── assets │ ├── avatar.jpeg │ ├── icon │ │ ├── favicon.png │ │ └── icon.png │ ├── mapplaceholder.png │ ├── nonefound.png │ └── shapes.svg ├── data.json ├── index.html └── manifest.json ├── src ├── App.jsx ├── components │ ├── ExploreContainer.css │ ├── ExploreContainer.jsx │ ├── LongPlaceCard.jsx │ ├── PlaceCard.jsx │ ├── SubPages.jsx │ └── Tabs.jsx ├── data │ └── fetcher.js ├── index.jsx ├── nav │ ├── AllRoutes.jsx │ └── NavRoutes.jsx ├── pages │ ├── Place.jsx │ ├── Tab1.css │ ├── Tab1.jsx │ ├── Tab2.css │ ├── Tab2.jsx │ ├── Tab3.css │ └── Tab3.jsx ├── store │ ├── PlacesStore.js │ ├── Selectors.js │ └── index.js ├── styles │ ├── Favourites.module.scss │ ├── Home.module.scss │ ├── Place.module.scss │ └── Places.module.scss └── theme │ ├── floating-tab-bar.scss │ ├── main.scss │ └── variables.css └── typings └── cordova-typings.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | .idea 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Optional eslint cache 28 | .eslintcache 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ionic-react-travel-app 2 | An example of a travel style app built with Ionic React 3 | 4 | If you'd like to support, you can buy me a coffee ☕️ 5 | 6 |  7 | 8 | ### Included in this Ionic React Template/UI 9 | * Animations (Ionic & custom) 10 | * Local API Fetching 11 | * Custom Floating Tabs 12 | * State management with Pullstate 13 | * SASS Modules & Ionic CSS utilities 14 | 15 | ### To run 16 | 17 | ```javascript 18 | npm install 19 | ionic serve 20 | ``` 21 | 22 | Alternatively, you can add the iOS, Android platform and run natively. 23 | 24 | # Are you on Twitter? Lets connect [@93alan](https://twitter.com/93alan) 25 | # Have you checked out Ionic React Hub yet? [Ionic React Hub](https://ionicreacthub.com) 26 | -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'com.example.app', 5 | appName: 'ionic-react-travel-app', 6 | webDir: 'build', 7 | bundledWebRuntime: false 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-react-travel-app", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react" 7 | } 8 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/Podfile.lock 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | -------------------------------------------------------------------------------- /ios/App/App.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; 11 | 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; 12 | 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; 13 | 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; 14 | 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 15 | 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 16 | 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; 17 | A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; 22 | 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; 23 | 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; 30 | AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; 32 | FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 504EC3011FED79650016851F /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */, 51 | ); 52 | name = Frameworks; 53 | sourceTree = ""; 54 | }; 55 | 504EC2FB1FED79650016851F = { 56 | isa = PBXGroup; 57 | children = ( 58 | 504EC3061FED79650016851F /* App */, 59 | 504EC3051FED79650016851F /* Products */, 60 | 7F8756D8B27F46E3366F6CEA /* Pods */, 61 | 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | 504EC3051FED79650016851F /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 504EC3041FED79650016851F /* App.app */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | 504EC3061FED79650016851F /* App */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 50379B222058CBB4000EE86E /* capacitor.config.json */, 77 | 504EC3071FED79650016851F /* AppDelegate.swift */, 78 | 504EC30B1FED79650016851F /* Main.storyboard */, 79 | 504EC30E1FED79650016851F /* Assets.xcassets */, 80 | 504EC3101FED79650016851F /* LaunchScreen.storyboard */, 81 | 504EC3131FED79650016851F /* Info.plist */, 82 | 2FAD9762203C412B000D30F8 /* config.xml */, 83 | 50B271D01FEDC1A000F3C39B /* public */, 84 | ); 85 | path = App; 86 | sourceTree = ""; 87 | }; 88 | 7F8756D8B27F46E3366F6CEA /* Pods */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */, 92 | AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */, 93 | ); 94 | name = Pods; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXNativeTarget section */ 100 | 504EC3031FED79650016851F /* App */ = { 101 | isa = PBXNativeTarget; 102 | buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */; 103 | buildPhases = ( 104 | 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */, 105 | 504EC3001FED79650016851F /* Sources */, 106 | 504EC3011FED79650016851F /* Frameworks */, 107 | 504EC3021FED79650016851F /* Resources */, 108 | 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = App; 115 | productName = App; 116 | productReference = 504EC3041FED79650016851F /* App.app */; 117 | productType = "com.apple.product-type.application"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | 504EC2FC1FED79650016851F /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | LastSwiftUpdateCheck = 0920; 126 | LastUpgradeCheck = 0920; 127 | TargetAttributes = { 128 | 504EC3031FED79650016851F = { 129 | CreatedOnToolsVersion = 9.2; 130 | LastSwiftMigration = 1100; 131 | ProvisioningStyle = Automatic; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */; 136 | compatibilityVersion = "Xcode 8.0"; 137 | developmentRegion = en; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = 504EC2FB1FED79650016851F; 144 | productRefGroup = 504EC3051FED79650016851F /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | 504EC3031FED79650016851F /* App */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | 504EC3021FED79650016851F /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */, 159 | 50B271D11FEDC1A000F3C39B /* public in Resources */, 160 | 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, 161 | 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, 162 | 504EC30D1FED79650016851F /* Main.storyboard in Resources */, 163 | 2FAD9763203C412B000D30F8 /* config.xml in Resources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXResourcesBuildPhase section */ 168 | 169 | /* Begin PBXShellScriptBuildPhase section */ 170 | 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = { 171 | isa = PBXShellScriptBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | ); 175 | inputPaths = ( 176 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 177 | "${PODS_ROOT}/Manifest.lock", 178 | ); 179 | name = "[CP] Check Pods Manifest.lock"; 180 | outputPaths = ( 181 | "$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt", 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | shellPath = /bin/sh; 185 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 186 | showEnvVarsInLog = 0; 187 | }; 188 | 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = { 189 | isa = PBXShellScriptBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | ); 193 | inputPaths = ( 194 | ); 195 | name = "[CP] Embed Pods Frameworks"; 196 | outputPaths = ( 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | shellPath = /bin/sh; 200 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n"; 201 | showEnvVarsInLog = 0; 202 | }; 203 | /* End PBXShellScriptBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 504EC3001FED79650016851F /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | 504EC30B1FED79650016851F /* Main.storyboard */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | 504EC30C1FED79650016851F /* Base */, 221 | ); 222 | name = Main.storyboard; 223 | sourceTree = ""; 224 | }; 225 | 504EC3101FED79650016851F /* LaunchScreen.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 504EC3111FED79650016851F /* Base */, 229 | ); 230 | name = LaunchScreen.storyboard; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | 504EC3141FED79650016851F /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | CODE_SIGN_IDENTITY = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = dwarf; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | ENABLE_TESTABILITY = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu11; 271 | GCC_DYNAMIC_NO_PIC = NO; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_OPTIMIZATION_LEVEL = 0; 274 | GCC_PREPROCESSOR_DEFINITIONS = ( 275 | "DEBUG=1", 276 | "$(inherited)", 277 | ); 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 285 | MTL_ENABLE_DEBUG_INFO = YES; 286 | ONLY_ACTIVE_ARCH = YES; 287 | SDKROOT = iphoneos; 288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | }; 291 | name = Debug; 292 | }; 293 | 504EC3151FED79650016851F /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_ANALYZER_NONNULL = YES; 298 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_COMMA = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 316 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 317 | CLANG_WARN_STRICT_PROTOTYPES = YES; 318 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 319 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | CODE_SIGN_IDENTITY = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu11; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 339 | VALIDATE_PRODUCT = YES; 340 | }; 341 | name = Release; 342 | }; 343 | 504EC3171FED79650016851F /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */; 346 | buildSettings = { 347 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 348 | CODE_SIGN_STYLE = Automatic; 349 | INFOPLIST_FILE = App/Info.plist; 350 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 351 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 352 | OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; 353 | PRODUCT_BUNDLE_IDENTIFIER = com.example.app; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 356 | SWIFT_VERSION = 5.0; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Debug; 360 | }; 361 | 504EC3181FED79650016851F /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */; 364 | buildSettings = { 365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 366 | CODE_SIGN_STYLE = Automatic; 367 | INFOPLIST_FILE = App/Info.plist; 368 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = com.example.app; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; 373 | SWIFT_VERSION = 5.0; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | }; 376 | name = Release; 377 | }; 378 | /* End XCBuildConfiguration section */ 379 | 380 | /* Begin XCConfigurationList section */ 381 | 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | 504EC3141FED79650016851F /* Debug */, 385 | 504EC3151FED79650016851F /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 504EC3171FED79650016851F /* Debug */, 394 | 504EC3181FED79650016851F /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | /* End XCConfigurationList section */ 400 | }; 401 | rootObject = 504EC2FC1FED79650016851F /* Project object */; 402 | } 403 | -------------------------------------------------------------------------------- /ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/App/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 10 | // Override point for customization after application launch. 11 | return true 12 | } 13 | 14 | func applicationWillResignActive(_ application: UIApplication) { 15 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 16 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 17 | } 18 | 19 | func applicationDidEnterBackground(_ application: UIApplication) { 20 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 21 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 22 | } 23 | 24 | func applicationWillEnterForeground(_ application: UIApplication) { 25 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 26 | } 27 | 28 | func applicationDidBecomeActive(_ application: UIApplication) { 29 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 30 | } 31 | 32 | func applicationWillTerminate(_ application: UIApplication) { 33 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 34 | } 35 | 36 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { 37 | // Called when the app was launched with a url. Feel free to add additional processing here, 38 | // but if you want the App API to support tracking app url opens, make sure to keep this call 39 | return ApplicationDelegateProxy.shared.application(app, open: url, options: options) 40 | } 41 | 42 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 43 | // Called when the app was launched with an activity, including Universal Links. 44 | // Feel free to add additional processing here, but if you want the App API to support 45 | // tracking app url opens, make sure to keep this call 46 | return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) 47 | } 48 | 49 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 50 | super.touchesBegan(touches, with: event) 51 | 52 | let statusBarRect = UIApplication.shared.statusBarFrame 53 | guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return } 54 | 55 | if statusBarRect.contains(touchPoint) { 56 | NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil) 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon-29x29@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AppIcon-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AppIcon-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AppIcon-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon-20x20@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "AppIcon-20x20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "AppIcon-29x29@1x.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "AppIcon-29x29@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "AppIcon-40x40@1x.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "AppIcon-40x40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "AppIcon-76x76@1x.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "AppIcon-76x76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "AppIcon-83.5x83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "AppIcon-512@2x.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ionic-react-travel-app 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UIViewControllerBasedStatusBarAppearance 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/App/App/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.example.app", 3 | "appName": "ionic-react-travel-app", 4 | "webDir": "build", 5 | "bundledWebRuntime": false 6 | } 7 | -------------------------------------------------------------------------------- /ios/App/App/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/App/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | use_frameworks! 3 | 4 | # workaround to avoid Xcode caching of Pods that requires 5 | # Product -> Clean Build Folder after new Cordova plugins installed 6 | # Requires CocoaPods 1.6 or newer 7 | install! 'cocoapods', :disable_input_output_paths => true 8 | 9 | def capacitor_pods 10 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 11 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 12 | pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' 13 | pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' 14 | pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' 15 | pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' 16 | end 17 | 18 | target 'App' do 19 | capacitor_pods 20 | # Add your Pods here 21 | end 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-react-travel-app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@capacitor/app": "1.0.2", 7 | "@capacitor/core": "3.1.2", 8 | "@capacitor/haptics": "1.0.2", 9 | "@capacitor/ios": "^3.1.2", 10 | "@capacitor/keyboard": "1.0.2", 11 | "@capacitor/status-bar": "1.0.2", 12 | "@ionic/react": "^5.5.0", 13 | "@ionic/react-router": "^5.5.0", 14 | "@testing-library/jest-dom": "^5.11.9", 15 | "@testing-library/react": "^11.2.5", 16 | "@testing-library/user-event": "^12.6.3", 17 | "@types/jest": "^26.0.20", 18 | "@types/node": "^12.19.15", 19 | "@types/react": "^16.14.3", 20 | "@types/react-dom": "^16.9.10", 21 | "@types/react-router": "^5.1.11", 22 | "@types/react-router-dom": "^5.1.7", 23 | "ionicons": "^5.4.0", 24 | "node-sass": "^4.14.1", 25 | "pullstate": "^1.22.1", 26 | "react": "^17.0.1", 27 | "react-dom": "^17.0.1", 28 | "react-iconly": "^2.2.2", 29 | "react-router": "^5.2.0", 30 | "react-router-dom": "^5.2.0", 31 | "react-scripts": "4.0.2", 32 | "reselect": "^4.0.0", 33 | "swiper": "^6.8.0", 34 | "typescript": "^4.1.3", 35 | "web-vitals": "^0.2.4", 36 | "workbox-background-sync": "^5.1.4", 37 | "workbox-broadcast-update": "^5.1.4", 38 | "workbox-cacheable-response": "^5.1.4", 39 | "workbox-core": "^5.1.4", 40 | "workbox-expiration": "^5.1.4", 41 | "workbox-google-analytics": "^5.1.4", 42 | "workbox-navigation-preload": "^5.1.4", 43 | "workbox-precaching": "^5.1.4", 44 | "workbox-range-requests": "^5.1.4", 45 | "workbox-routing": "^5.1.4", 46 | "workbox-strategies": "^5.1.4", 47 | "workbox-streams": "^5.1.4" 48 | }, 49 | "scripts": { 50 | "start": "react-scripts start", 51 | "build": "react-scripts build", 52 | "test": "react-scripts test", 53 | "eject": "react-scripts eject" 54 | }, 55 | "eslintConfig": { 56 | "extends": [ 57 | "react-app", 58 | "react-app/jest" 59 | ] 60 | }, 61 | "browserslist": { 62 | "production": [ 63 | ">0.2%", 64 | "not dead", 65 | "not op_mini all" 66 | ], 67 | "development": [ 68 | "last 1 chrome version", 69 | "last 1 firefox version", 70 | "last 1 safari version" 71 | ] 72 | }, 73 | "devDependencies": { 74 | "@capacitor/cli": "3.1.2" 75 | }, 76 | "description": "An Ionic project" 77 | } 78 | -------------------------------------------------------------------------------- /public/assets/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/public/assets/avatar.jpeg -------------------------------------------------------------------------------- /public/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/public/assets/icon/favicon.png -------------------------------------------------------------------------------- /public/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/public/assets/icon/icon.png -------------------------------------------------------------------------------- /public/assets/mapplaceholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/public/assets/mapplaceholder.png -------------------------------------------------------------------------------- /public/assets/nonefound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/public/assets/nonefound.png -------------------------------------------------------------------------------- /public/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/data.json: -------------------------------------------------------------------------------- 1 | [{"name":"Eiffel Tower, Paris","desc":"1. Eiffel Tower, Paris","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-france-eiffel-tower-lower-night.jpg", "rating": 4.5, "distance": 327, "temp": 23},{"name":"The Colosseum, Rome","desc":"The Colosseum, Rome","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-italy-rome-colosseum.jpg", "rating": 5, "distance": 827, "temp": 37},{"name":"Statue of Liberty, New York City","desc":"Statue of Liberty, New York City","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-new-york-statue-of-liberty.jpg", "rating": 4, "distance": "1,207", "temp": 17},{"name":"Machu Picchu, Peru","desc":"Machu Picchu, Peru","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-peru-machu-picchu-overview.jpg", "rating": 4.5, "distance": "990", "temp": 39},{"name":"The Acropolis, Athens","desc":"The Acropolis, Athens","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-greece-athens-acropolis-moon.jpg", "rating": 4, "distance": 683, "temp": 28},{"name":"The Taj Mahal, India","desc":"The Taj Mahal, India","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-india-taj-mahal.jpg", "rating": 5, "distance": "1,407", "temp": 42},{"name":"Pyramids of Giza, Egypt","desc":"7. Pyramids of Giza, Egypt","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-egypt-pyramids-giza.jpg", "rating": 3.5, "distance": "1,119", "temp": 44},{"name":"Great Wall of China, China","desc":"8. Great Wall of China","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-china-great-wall.jpg", "rating": 5, "distance": "1,576", "temp": 38},{"name":"Angkor Wat, Cambodia","desc":"9. Angkor Wat, Cambodia","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-cambodia-angkor-wat-stone-faces.jpg", "rating": 4.5, "distance": "990", "temp": 41},{"name":"Petra, Jordan","desc":"10. Petra, Jordan","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-jordan-petra.jpg"},{"name":"Grand Canyon, USA","desc":"11. Grand Canyon, USA","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-arizona-grand-canyon.jpg", "rating": 5, "distance": "982", "temp": 29},{"name":"Stonehenge, England","desc":"12. Stonehenge, England","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-england-stonehenge.jpg", "rating": 4.5, "distance": 132, "temp": 12},{"name":"Borobudur, Indonesia","desc":"13. Borobudur, Indonesia","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-indonesia-borobudur.jpg", "rating": 3, "distance": "1,303", "temp": 34},{"name":"Niagara Falls, Canada & USA","desc":"14. Niagara Falls, Canada & USA","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-canada-united-states-niagara-falls.jpg", "rating": 4.5, "distance": 293, "temp": 25},{"name":"Bagan, Myanmar","desc":"15. Bagan, Myanmar","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-myanmar-bagan.jpg", "rating": 4.5, "distance": "971", "temp": 34},{"name":"Sydney Opera House, Australia","desc":"16. Sydney Opera House","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-sydney-opera-house.jpg", "rating": 5, "distance": "1,874", "temp": 37},{"name":"Mount Kilimanjaro, Tanzania","desc":"17. Mount Kilimanjaro","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-tanzania-mt-kilimanjaro.jpg", "rating": 2.5, "distance": "639", "temp": 39},{"name":"The Louvre, Paris","desc":"18. The Louvre, Paris","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-france-paris-louvre.jpg", "rating": 5, "distance": "549", "temp": 25},{"name":"Forbidden City, China","desc":"19. Forbidden City, China","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-china-forbidden-city.jpg", "rating": 4, "distance": "1,439", "temp": 36},{"name":"Prague Castle, Czech Republic","desc":"20. Prague Castle, Czech Republic","image":"https://www.planetware.com/wpimages/2020/08/top-attractions-in-the-world-czech-republic-prague-castle.jpg", "rating": 4, "distance": "478", "temp": 22}] -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic Travel App 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ionic App", 3 | "name": "My Ionic App", 4 | "icons": [ 5 | { 6 | "src": "assets/icon/favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "assets/icon/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": ".", 18 | "display": "standalone", 19 | "theme_color": "#ffffff", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { IonApp } from '@ionic/react'; 2 | 3 | /* Core CSS required for Ionic components to work properly */ 4 | import '@ionic/react/css/core.css'; 5 | 6 | /* Basic CSS for apps built with Ionic */ 7 | import '@ionic/react/css/normalize.css'; 8 | import '@ionic/react/css/structure.css'; 9 | import '@ionic/react/css/typography.css'; 10 | 11 | /* Optional CSS utils that can be commented out */ 12 | import '@ionic/react/css/padding.css'; 13 | import '@ionic/react/css/float-elements.css'; 14 | import '@ionic/react/css/text-alignment.css'; 15 | import '@ionic/react/css/text-transformation.css'; 16 | import '@ionic/react/css/flex-utils.css'; 17 | import '@ionic/react/css/display.css'; 18 | 19 | /* Theme variables */ 20 | import './theme/variables.css'; 21 | 22 | // Floating tabs 23 | import "./theme/floating-tab-bar.scss"; 24 | import "./theme/main.scss"; 25 | 26 | import { useEffect } from 'react'; 27 | import { fetchData } from './data/fetcher'; 28 | import NavRoutes from './nav/NavRoutes'; 29 | 30 | const App = () => { 31 | 32 | useEffect(() => { 33 | 34 | fetchData(); 35 | }, []); 36 | 37 | return ( 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export default App; 45 | -------------------------------------------------------------------------------- /src/components/ExploreContainer.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | position: absolute; 4 | left: 0; 5 | right: 0; 6 | top: 50%; 7 | transform: translateY(-50%); 8 | } 9 | 10 | .container strong { 11 | font-size: 20px; 12 | line-height: 26px; 13 | } 14 | 15 | .container p { 16 | font-size: 16px; 17 | line-height: 22px; 18 | color: #8c8c8c; 19 | margin: 0; 20 | } 21 | 22 | .container a { 23 | text-decoration: none; 24 | } -------------------------------------------------------------------------------- /src/components/ExploreContainer.jsx: -------------------------------------------------------------------------------- 1 | import './ExploreContainer.css'; 2 | 3 | const ExploreContainer = ({ name }) => { 4 | return ( 5 | 6 | {name} 7 | Explore UI Components 8 | 9 | ); 10 | }; 11 | 12 | export default ExploreContainer; 13 | -------------------------------------------------------------------------------- /src/components/LongPlaceCard.jsx: -------------------------------------------------------------------------------- 1 | import { IonCard, IonCardTitle, IonNote } from "@ionic/react"; 2 | import styles from "../styles/Home.module.scss"; 3 | 4 | export const LongPlaceCard = ({ place = false }) => ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | { place.name } 13 | { place.destination } 14 | 15 | 16 | ); -------------------------------------------------------------------------------- /src/components/PlaceCard.jsx: -------------------------------------------------------------------------------- 1 | import { IonCard, IonCardHeader, IonCardTitle, IonNote, useIonToast, CreateAnimation, IonIcon } from "@ionic/react"; 2 | import { useState } from "react"; 3 | import { useRef } from "react"; 4 | import { Iconly } from "react-iconly"; 5 | import { heart, trashBin } from "ionicons/icons"; 6 | import { addFavourite } from "../store/PlacesStore"; 7 | 8 | import styles from "../styles/Home.module.scss"; 9 | 10 | const PlaceCard = ({ place = false, fromFavourites = false }) => { 11 | 12 | const animationRef = useRef(); 13 | const cardRef = useRef(); 14 | const [ presentToast ] = useIonToast(); 15 | const [ hideAnimatedIcon, setHideAnimatedIcon ] = useState(true); 16 | 17 | const floatStyle = { 18 | 19 | display: hideAnimatedIcon ? "none" : "", 20 | position: "absolute", 21 | zIndex: "10" 22 | }; 23 | 24 | const floatGrowAnimation = { 25 | 26 | property: "transform", 27 | fromValue: "translateY(0) scale(1)", 28 | toValue: "translateY(-20px) scale(2)" 29 | }; 30 | 31 | const mainAnimation = { 32 | 33 | duration: 600, 34 | iterations: "1", 35 | fromTo: [ floatGrowAnimation ], 36 | easing: "cubic-bezier(0.25, 0.7, 0.25, 0.7)" 37 | }; 38 | 39 | const handleAddFavourite = async (e, place) => { 40 | 41 | e.stopPropagation(); 42 | e.preventDefault(); 43 | 44 | if (fromFavourites) { 45 | 46 | // Add a fadeOut animation before removing 47 | cardRef.current.classList.add("animate__fadeOut"); 48 | 49 | setTimeout(() => { 50 | addFavourite(place, fromFavourites); 51 | }, 500); 52 | } else { 53 | 54 | addFavourite(place, fromFavourites); 55 | } 56 | 57 | presentToast({ 58 | 59 | header: `Favourite ${ fromFavourites ? "removed" : "added" }!`, 60 | buttons: [ 61 | { 62 | text: "♡", 63 | } 64 | ], 65 | message: `${ place.name } has been ${ fromFavourites ? "removed from" : "added to" } your favourites.`, 66 | duration: 1500, 67 | color: "success" 68 | }); 69 | 70 | setHideAnimatedIcon(false); 71 | await animationRef.current.animation.play(); 72 | setHideAnimatedIcon(true); 73 | } 74 | 75 | return ( 76 | 77 | 78 | 79 | { place && 80 | handleAddFavourite(e, place) }> 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | } 89 | 90 | 91 | 92 | { place ? place.name : "Sorry" } 93 | { place ? place.destination : "No results found" } 94 | 95 | 96 | ); 97 | } 98 | 99 | export default PlaceCard; -------------------------------------------------------------------------------- /src/components/SubPages.jsx: -------------------------------------------------------------------------------- 1 | import { Route } from "react-router-dom"; 2 | 3 | const SubPages = (props) => { 4 | 5 | return ( 6 | <> 7 | { props.routes.map((route, i) => { 8 | 9 | const RouteComponent = route.component; 10 | 11 | return } exact={ false } />; 12 | })} 13 | > 14 | ); 15 | } 16 | 17 | export default SubPages; -------------------------------------------------------------------------------- /src/components/Tabs.jsx: -------------------------------------------------------------------------------- 1 | import { IonTabBar, IonTabButton, IonTabs, IonRouterOutlet } from "@ionic/react"; 2 | import { useState } from "react"; 3 | import { Iconly } from "react-iconly"; 4 | import { Route } from "react-router-dom"; 5 | 6 | const Tabs = (props) => { 7 | 8 | const [ selected, setSelected ] = useState("tab_1"); 9 | 10 | return ( 11 | setSelected(e.detail.tab) }> 12 | 13 | 14 | { props.tabs.map((tab, i) => { 15 | 16 | const TabComponent = tab.component; 17 | 18 | if (tab.isTab) { 19 | return } exact={ true }/>; 20 | } else { 21 | 22 | return } exact={ false } />; 23 | } 24 | })} 25 | 26 | 27 | 28 | 29 | { props.tabs.map((tab, i) => { 30 | 31 | const isSelected = selected === `tab_${ i + 1 }`; 32 | 33 | if (tab.isTab) { 34 | 35 | return ( 36 | 37 | 38 | { isSelected && } 39 | 40 | ); 41 | } 42 | })} 43 | 44 | 45 | ); 46 | } 47 | 48 | export default Tabs; -------------------------------------------------------------------------------- /src/data/fetcher.js: -------------------------------------------------------------------------------- 1 | import { PlacesStore } from "../store"; 2 | 3 | export const fetchData = async () => { 4 | 5 | const response = await fetch("/data.json"); 6 | const data = await response.json(); 7 | 8 | await data.forEach((place, i) => { 9 | 10 | delete place.desc; 11 | 12 | const placeName = place.name; 13 | const placeNameParts = placeName.split(","); 14 | 15 | place.id = i + 1; 16 | place.name = placeNameParts[0].trim(); 17 | place.destination = placeNameParts[1].trim(); 18 | }); 19 | 20 | PlacesStore.update(s => { s.places = data; }); 21 | } -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); -------------------------------------------------------------------------------- /src/nav/AllRoutes.jsx: -------------------------------------------------------------------------------- 1 | // Main Tabs 2 | import Tab1 from "../pages/Tab1"; 3 | import Tab2 from "../pages/Tab2"; 4 | import Tab3 from "../pages/Tab3"; 5 | 6 | // Main tab children 7 | import Place from "../pages/Place"; 8 | 9 | // Sub pages 10 | // import InboxItem from "../../pages/InboxItem"; 11 | 12 | // Tab icons 13 | // If using ionicons, import here and pass as ref to tabRoutes 14 | 15 | // Import custom tab menu 16 | import Tabs from "../components/Tabs"; 17 | import SubPages from "../components/SubPages"; 18 | 19 | // Array of objects representing tab pages 20 | // These will be the main tabs across the app 21 | 22 | // * PARAMS per tab object * 23 | // isTab = true will make the tab appear 24 | // default = the default tab page to open and be redirected to at "/" 25 | // NOTE: there should only be one default tab (default: true) 26 | // label = the label to show with the tab 27 | // component = the component related to this tab page 28 | // icon = icon to show on the tab bar menu 29 | // path = the path which the tab is accessible 30 | export const tabRoutes = [ 31 | 32 | { label: "Home", component: Tab1, icon: "Home", path: "/tabs/home", default: true, isTab: true }, 33 | { label: "Places", component: Tab2, icon: "Location", path: "/tabs/places", default: false, isTab: true }, 34 | { label: "Favourites", component: Tab3, icon: "Heart", path: "/tabs/favourites", default: false, isTab: true }, 35 | // { label: "Profile", component: Tab3, icon: "User", path: "/tabs/profile", default: false, isTab: true }, 36 | ]; 37 | 38 | // Array of objects representing children pages of tabs 39 | 40 | // * PARAMS per tab object * 41 | // isTab = should always be set to false for these 42 | // component = the component related to this tab page 43 | // path = the path which the tab is accessible 44 | 45 | // These pages should be related to tab pages and be held within the same path 46 | // E.g. /tabs/tab1/child 47 | const tabChildrenRoutes = [ 48 | 49 | // { component: InboxItem, path: "/tabs/tab2/:id", isTab: false }, 50 | ]; 51 | 52 | // Array of objects representing sub pages 53 | 54 | // * PARAMS per tab object * 55 | // component = the component related to this sub page 56 | // path = the path which the sub page is accessible 57 | 58 | // This array should be sub pages which are not directly related to a tab page 59 | // E.g. /child 60 | const subPageRoutes = [ 61 | 62 | { component: Place, path: "/view-place/:place_id" } 63 | ]; 64 | 65 | // Let's combine these together as they need to be controlled within the same IonRouterOutlet 66 | const tabsAndChildrenRoutes = [ ...tabRoutes, ...tabChildrenRoutes ]; 67 | 68 | // Render sub routes 69 | export const AllSubPages = () => ( ); 70 | 71 | // Render tab menu 72 | export const AllTabs = () => ( ); -------------------------------------------------------------------------------- /src/nav/NavRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { IonRouterOutlet } from "@ionic/react"; 2 | import { IonReactRouter } from "@ionic/react-router"; 3 | import { Redirect, Route } from "react-router-dom"; 4 | import { AllSubPages, AllTabs, tabRoutes } from "./AllRoutes"; 5 | 6 | const NavRoutes = () => { 7 | 8 | return ( 9 | 10 | 11 | 12 | } /> 13 | 14 | 15 | t.default)[0].component } exact={ true } /> 16 | t.default)[0].path.toString() }/> 17 | 18 | 19 | ); 20 | } 21 | 22 | export default NavRoutes; -------------------------------------------------------------------------------- /src/pages/Place.jsx: -------------------------------------------------------------------------------- 1 | import { IonBackButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCol, IonContent, IonFooter, IonGrid, IonHeader, IonIcon, IonNote, IonPage, IonRow, IonText, IonTitle, IonToolbar, useIonRouter, useIonToast, useIonViewDidEnter, useIonViewWillEnter } from '@ionic/react'; 2 | import './Tab2.css'; 3 | 4 | import styles from "../styles/Place.module.scss"; 5 | import { useParams } from 'react-router'; 6 | import { useStoreState } from 'pullstate'; 7 | import { PlacesStore } from '../store'; 8 | import { checkIsFavourite, getPlace } from '../store/Selectors'; 9 | import { Iconly } from 'react-iconly'; 10 | import { useRef } from 'react'; 11 | import { thermometerOutline } from 'ionicons/icons'; 12 | import { addFavourite } from '../store/PlacesStore'; 13 | 14 | const Place = () => { 15 | 16 | const headingRef = useRef(); 17 | const router = useIonRouter(); 18 | const { place_id } = useParams(); 19 | 20 | const place = useStoreState(PlacesStore, getPlace(place_id)); 21 | const isFavourite = useStoreState(PlacesStore, checkIsFavourite(place_id)); 22 | 23 | const [ presentToast ] = useIonToast(); 24 | 25 | useIonViewWillEnter(() => { 26 | 27 | headingRef.current.classList.add("animate__slideInDown"); 28 | headingRef.current.style.display = ""; 29 | }); 30 | 31 | const handleAddFavourite = async (e, place) => { 32 | 33 | e.stopPropagation(); 34 | e.preventDefault(); 35 | 36 | const favourited = isFavourite.length > 0; 37 | 38 | addFavourite(place, favourited); 39 | 40 | presentToast({ 41 | 42 | header: `Favourite ${ favourited ? "removed" : "added" }!`, 43 | buttons: [ 44 | { 45 | text: "♡", 46 | } 47 | ], 48 | message: `${ place.name } has been ${ favourited ? "removed from" : "added to" } your favourites.`, 49 | duration: 1500, 50 | color: "success" 51 | }); 52 | } 53 | 54 | return ( 55 | 56 | 57 | 58 | 59 | 60 | 61 | router.goBack() }> 62 | 63 | 64 | 65 | handleAddFavourite(e, place) }> 66 | 0 ? "bold" : "light" } name="Heart" /> 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | { place.name } 78 | { place.destination } 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | { place.rating } stars 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | { place.distance } miles 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | { place.temp }°C 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | $120/person 126 | This includes flights, luggage and transfers 127 | 128 | 129 | 130 | 131 | 132 | About 133 | 134 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce accumsan sem eget purus lacinia, tristique aliquet ipsum consequat. 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | Book now 143 | 144 | 145 | 146 | 147 | 148 | ); 149 | }; 150 | 151 | export default Place; 152 | -------------------------------------------------------------------------------- /src/pages/Tab1.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/src/pages/Tab1.css -------------------------------------------------------------------------------- /src/pages/Tab1.jsx: -------------------------------------------------------------------------------- 1 | import { IonBadge, IonCard, IonCardHeader, IonCardTitle, IonCol, IonContent, IonGrid, IonHeader, IonNote, IonPage, IonRouterLink, IonRow, IonSearchbar, IonTitle, IonToolbar, useIonViewDidEnter, useIonViewWillEnter } from '@ionic/react'; 2 | import { useStoreState } from 'pullstate'; 3 | import { Iconly } from 'react-iconly'; 4 | import { PlacesStore } from '../store'; 5 | import { getPlaces } from '../store/Selectors'; 6 | 7 | import styles from "../styles/Home.module.scss"; 8 | 9 | import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from 'swiper'; 10 | 11 | import { Swiper, SwiperSlide } from 'swiper/react'; 12 | 13 | // Import Swiper styles 14 | import 'swiper/swiper.scss'; 15 | import 'swiper/components/navigation/navigation.scss'; 16 | import 'swiper/components/pagination/pagination.scss'; 17 | import 'swiper/components/scrollbar/scrollbar.scss'; 18 | import { useState } from 'react'; 19 | import { useEffect } from 'react'; 20 | import PlaceCard from '../components/PlaceCard'; 21 | import { LongPlaceCard } from '../components/LongPlaceCard'; 22 | 23 | // install Swiper modules 24 | SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]); 25 | 26 | const Tab1 = () => { 27 | 28 | const places = useStoreState(PlacesStore, getPlaces); 29 | 30 | const [ search, setSearch ] = useState("Search"); 31 | const [ searchResults, setSearchResults ] = useState([ ...places ]); 32 | 33 | const [ slideSpace, setSlideSpace ] = useState(0); 34 | const [ longSlideSpace, setLongSlideSpace ] = useState(5); 35 | 36 | useIonViewDidEnter(() => { 37 | 38 | setSlideSpace(-40); 39 | setLongSlideSpace(1); 40 | setSearch(""); 41 | }); 42 | 43 | const performSearch = async () => { 44 | 45 | const searchTerm = search.toLowerCase(); 46 | let tempResults = []; 47 | 48 | if (searchTerm !== "") { 49 | places.forEach(place => { 50 | 51 | if (place.name.toLowerCase().includes(searchTerm)) { 52 | 53 | tempResults.push(place); 54 | } 55 | }); 56 | } else { 57 | 58 | tempResults = [ ...places ]; 59 | } 60 | 61 | setSearchResults(tempResults); 62 | } 63 | 64 | useEffect(() => { 65 | 66 | performSearch(); 67 | }, [ search ]); 68 | 69 | return ( 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | Welcome 82 | Joe Bloggs 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | Where do you want to go? 100 | 101 | 102 | 103 | 104 | 105 | setSearch(e.target.value) } className={ styles.search } animated={ true } placeholder="Try 'Statue of Liberty'" /> 106 | 107 | 108 | 109 | 110 | Popular Trips 111 | 112 | View all → 113 | 114 | 115 | 116 | 117 | 118 | { places && places.length > 0 && 119 | 120 | 121 | { searchResults.map((place, index) => { 122 | 123 | if (index < 4) { 124 | return ( 125 | 126 | 127 | 128 | 129 | ); 130 | } 131 | })} 132 | 133 | { searchResults.length === 0 && 134 | 135 | 136 | 137 | 138 | } 139 | 140 | } 141 | 142 | 143 | 144 | 145 | Top Destinations 146 | 147 | 148 | View all → 149 | 150 | 151 | 152 | 153 | 154 | { places && places.length > 0 && 155 | 156 | { places.map((place, index) => { 157 | 158 | if (index > 4 && index < 9) { 159 | return ( 160 | 161 | 162 | 163 | 164 | ); 165 | } 166 | })} 167 | 168 | 169 | } 170 | 171 | 172 | 173 | 174 | ); 175 | }; 176 | 177 | export default Tab1; -------------------------------------------------------------------------------- /src/pages/Tab2.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/src/pages/Tab2.css -------------------------------------------------------------------------------- /src/pages/Tab2.jsx: -------------------------------------------------------------------------------- 1 | import { IonContent, IonHeader, IonItem, IonLabel, IonList, IonNote, IonPage, IonSearchbar, IonTitle, IonToolbar, useIonViewDidEnter } from '@ionic/react'; 2 | import { useStoreState } from 'pullstate'; 3 | import { useEffect, useState } from 'react'; 4 | import { Iconly } from 'react-iconly'; 5 | import ExploreContainer from '../components/ExploreContainer'; 6 | import { PlacesStore } from '../store'; 7 | import { getPlaces } from '../store/Selectors'; 8 | 9 | import styles from "../styles/Places.module.scss"; 10 | 11 | const Tab2 = () => { 12 | 13 | const places = useStoreState(PlacesStore, getPlaces); 14 | const [ search, setSearch ] = useState("Search"); 15 | const [ searchResults, setSearchResults ] = useState([ ...places ]); 16 | 17 | useIonViewDidEnter(() => { 18 | 19 | setSearch(""); 20 | }); 21 | 22 | const performSearch = async () => { 23 | 24 | const searchTerm = search.toLowerCase(); 25 | let tempResults = []; 26 | 27 | if (searchTerm !== "") { 28 | places.forEach(place => { 29 | 30 | if (place.name.toLowerCase().includes(searchTerm)) { 31 | 32 | tempResults.push(place); 33 | } 34 | }); 35 | } else { 36 | 37 | tempResults = [ ...places ]; 38 | } 39 | 40 | setSearchResults(tempResults); 41 | } 42 | 43 | useEffect(() => { 44 | 45 | performSearch(); 46 | }, [ search ]); 47 | 48 | return ( 49 | 50 | 51 | 52 | Places 53 | 54 | 55 | 56 | 57 | 58 | Places 59 | 60 | 61 | 62 | setSearch(e.target.value) } animated={ true } placeholder="Try 'Grand Canyon'" className="animate__animated animate__slideInDown animate__faster" /> 63 | 64 | 65 | { searchResults.map((place, index) => { 66 | 67 | // We could do some cool things with this 68 | const animateDirection = index % 2 == 0 ? "animate__slideInLeft" : "animate__slideInRight"; 69 | 70 | return ( 71 | 72 | 73 | 74 | 75 | 76 | { place.name } 77 | { place.destination } 78 | 79 | 80 | 81 | 82 | 83 | { (Math.trunc(place.rating*100)/100).toFixed(1) } 84 | 85 | 86 | 87 | ); 88 | })} 89 | 90 | 91 | 92 | ); 93 | }; 94 | 95 | export default Tab2; 96 | -------------------------------------------------------------------------------- /src/pages/Tab3.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alanmontgomery/ionic-react-travel-app/6b7f0d192bb06731d7dce5eb8c43ca2fda53a540/src/pages/Tab3.css -------------------------------------------------------------------------------- /src/pages/Tab3.jsx: -------------------------------------------------------------------------------- 1 | import { IonCard, IonCardContent, IonCardSubtitle, IonCol, IonContent, IonGrid, IonHeader, IonNote, IonPage, IonRow, IonText, IonTitle, IonToolbar } from '@ionic/react'; 2 | import { useStoreState } from 'pullstate'; 3 | import { Iconly } from 'react-iconly'; 4 | import PlaceCard from '../components/PlaceCard'; 5 | import { PlacesStore } from '../store'; 6 | import { getFavourites } from '../store/Selectors'; 7 | 8 | import styles from "../styles/Favourites.module.scss"; 9 | 10 | const Tab3 = () => { 11 | 12 | const favourites = useStoreState(PlacesStore, getFavourites); 13 | return ( 14 | 15 | 16 | 17 | Favourites 18 | 19 | 20 | 21 | 22 | 23 | Favourites 24 | 25 | 26 | 27 | 28 | { favourites.length > 0 && 29 | 30 | { favourites.map((favourite, index) => { 31 | 32 | return ( 33 | 34 | 35 | 36 | ) 37 | })} 38 | 39 | } 40 | 41 | { favourites.length === 0 && 42 | 43 | 44 | 45 | 46 | You don't have any favourites yet! 47 | You can add a favourite by pressing the heart button anywhere within the app. 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | } 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default Tab3; 63 | -------------------------------------------------------------------------------- /src/store/PlacesStore.js: -------------------------------------------------------------------------------- 1 | import { Store } from 'pullstate'; 2 | 3 | const PlacesStore = new Store({ 4 | 5 | places: [], 6 | favourites: [] 7 | }); 8 | 9 | export default PlacesStore; 10 | 11 | export const addFavourite = (place, remove) => { 12 | 13 | if (remove) { 14 | 15 | PlacesStore.update(s => { 16 | 17 | const newFavourites = s.favourites.filter(f => f.id !== place.id); 18 | s.favourites = newFavourites; 19 | }); 20 | } else { 21 | 22 | PlacesStore.update(s => { s.favourites = [ ...s.favourites, place ]}); 23 | } 24 | } -------------------------------------------------------------------------------- /src/store/Selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const getState = state => state; 4 | 5 | // General getters 6 | export const getPlaces = createSelector(getState, state => state.places); 7 | export const getFavourites = createSelector(getState, state => state.favourites); 8 | 9 | // More specific getters 10 | export const getPlace = placeId => createSelector(getState, state => state.places.filter(p => parseInt(p.id) === parseInt(placeId))[0]); 11 | export const checkIsFavourite = placeId => createSelector(getState, state => state.favourites.filter(f => parseInt(f.id) === parseInt(placeId))); 12 | 13 | // export const getChatNotificationCount = contactId => createSelector(getState, state => (state.chats.filter(c => parseInt(c.contact_id) === parseInt(contactId))[0].chats).filter(chat => chat.read === false)); 14 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | export { default as PlacesStore } from "./PlacesStore"; -------------------------------------------------------------------------------- /src/styles/Favourites.module.scss: -------------------------------------------------------------------------------- 1 | .favouriteButton { 2 | 3 | background-color: rgb(241, 241, 241); 4 | width: fit-content; 5 | display: flex; 6 | margin: 0 auto; 7 | border-radius: 500px; 8 | padding: 1rem; 9 | margin-top: 2rem; 10 | margin-bottom: 2rem; 11 | 12 | svg { 13 | 14 | margin-left: -0.05rem; 15 | height: 4.2rem; 16 | width: 4.2rem; 17 | color: rgb(243, 102, 102); 18 | } 19 | } 20 | 21 | .message { 22 | 23 | h2 { 24 | 25 | color: black; 26 | font-size: 1.2rem; 27 | } 28 | 29 | p { 30 | color: rgb(165, 165, 165); 31 | font-size: 0.9rem; 32 | margin-top: 0.5rem; 33 | } 34 | } -------------------------------------------------------------------------------- /src/styles/Home.module.scss: -------------------------------------------------------------------------------- 1 | .avatar { 2 | 3 | img { 4 | 5 | border-radius: 15px; 6 | height: 4rem; 7 | width: 4rem; 8 | border: 2px solid rgb(235, 235, 235); 9 | padding: 0.1rem; 10 | } 11 | } 12 | 13 | .notifications { 14 | 15 | border-radius: 500px; 16 | border: 2px solid rgb(235, 235, 235); 17 | padding: 0.5rem; 18 | color: rgb(202, 202, 202); 19 | } 20 | 21 | .notificationIndicator { 22 | 23 | position: absolute; 24 | height: 12px; 25 | width: 12px; 26 | border: 2px solid white; 27 | background-color: red; 28 | border-radius: 500px; 29 | margin-top: -2.6rem; 30 | margin-left: 1.6rem; 31 | } 32 | 33 | .welcome { 34 | 35 | margin-left: -1.5rem; 36 | 37 | h3 { 38 | 39 | padding: 0; 40 | margin-top: 0.4rem; 41 | font-size: 1rem; 42 | font-weight: 600; 43 | } 44 | 45 | ion-note { 46 | 47 | font-size: 0.9rem; 48 | } 49 | } 50 | 51 | .slidesContainer, 52 | .longSlidesContainer { 53 | 54 | margin-top: -1.25rem; 55 | } 56 | 57 | .search { 58 | 59 | --border-radius: 10px; 60 | --background: rgb(246, 246, 246); 61 | --color: black; 62 | padding-left: 0.5rem; 63 | padding-right: 0.5rem; 64 | // padding-top: 0; 65 | } 66 | 67 | .slide { 68 | 69 | width: 70%; 70 | padding: 0.5rem; 71 | padding-bottom: 0; 72 | border-radius: 20px; 73 | 74 | img { 75 | 76 | border-radius: 20px; 77 | height: 10rem; 78 | width: 100%; 79 | padding: 0; 80 | } 81 | 82 | ion-card-title { 83 | 84 | margin-top: -0.5rem; 85 | font-size: 0.8rem; 86 | } 87 | 88 | ion-note { 89 | 90 | font-size: 0.6rem; 91 | } 92 | 93 | .imageHeader { 94 | 95 | position: relative; 96 | } 97 | } 98 | 99 | .longSlide { 100 | 101 | width: 80%; 102 | padding: 0.5rem; 103 | padding-bottom: 0.3rem; 104 | border-radius: 20px; 105 | 106 | img { 107 | 108 | border-radius: 20px; 109 | height: 5rem; 110 | width: 100%; 111 | padding: 0; 112 | } 113 | 114 | .details { 115 | 116 | margin-top: -3.75rem; 117 | position: absolute; 118 | z-index: 999; 119 | margin-left: 0.5rem; 120 | background-color: rgba(0, 0, 0, 0.5); 121 | padding: 0.4rem; 122 | border-radius: 10px; 123 | width: 80%; 124 | } 125 | 126 | ion-card-title { 127 | 128 | font-size: 0.8rem; 129 | --color: white; 130 | margin: 0; 131 | padding: 0; 132 | } 133 | 134 | ion-note { 135 | 136 | font-size: 0.6rem; 137 | --color: white; 138 | } 139 | 140 | .imageHeader { 141 | 142 | position: relative; 143 | } 144 | } -------------------------------------------------------------------------------- /src/styles/Place.module.scss: -------------------------------------------------------------------------------- 1 | .customHeader { 2 | 3 | img { 4 | 5 | border-bottom-left-radius: 150px; 6 | // border-bottom-right-radius: 50px; 7 | } 8 | } 9 | 10 | .customBackButton { 11 | 12 | position: absolute; 13 | top: 3rem; 14 | left: 1.4rem; 15 | border-radius: 500px; 16 | padding: 0.5rem; 17 | background-color: rgba(0, 0, 0, 0.5); 18 | border: 2px solid white; 19 | display: flex; 20 | flex-direction: row; 21 | align-content: center; 22 | align-items: center; 23 | justify-content: center; 24 | 25 | svg { 26 | 27 | color: rgb(255, 255, 255); 28 | height: 1.75rem; 29 | width: 1.75rem; 30 | } 31 | } 32 | 33 | .customFavouriteButton { 34 | 35 | position: absolute; 36 | top: 3rem; 37 | right: 1.4rem; 38 | border-radius: 500px; 39 | padding: 0.5rem; 40 | background-color: rgba(0, 0, 0, 0.5); 41 | border: 2px solid white; 42 | display: flex; 43 | flex-direction: row; 44 | align-content: center; 45 | align-items: center; 46 | justify-content: center; 47 | 48 | svg { 49 | 50 | color: rgb(255, 255, 255); 51 | height: 1.75rem; 52 | width: 1.75rem; 53 | } 54 | } 55 | 56 | .mainContent { 57 | 58 | // position: relative; 59 | margin-top: -5rem !important; 60 | z-index: 99; 61 | } 62 | 63 | .placeHeading { 64 | 65 | $grad_color: #f8f8f8; 66 | $main_color: white; 67 | 68 | background: 69 | linear-gradient(135deg, $grad_color 25%, transparent 25%) -50px 0, 70 | linear-gradient(225deg, $grad_color 25%, transparent 25%) -50px 0, 71 | linear-gradient(315deg, $grad_color 25%, transparent 25%), 72 | linear-gradient(45deg, $grad_color 25%, transparent 25%); 73 | background-size: 100px 100px; 74 | background-color: $main_color; 75 | } 76 | 77 | .footer { 78 | 79 | position: absolute; 80 | bottom: 2rem; 81 | } 82 | 83 | .footerContent { 84 | 85 | width: 87%; 86 | display: flex; 87 | align-content: center; 88 | align-items: center; 89 | justify-content: space-evenly; 90 | margin: 0 auto; 91 | padding: 1.5rem; 92 | background-color: var(--ion-color-dark); 93 | border-radius: 30px; 94 | 95 | ion-card-title { 96 | 97 | font-size: 1.2rem; 98 | --color: white; 99 | } 100 | 101 | svg { 102 | 103 | height: 2rem; 104 | width: 2rem; 105 | color: white; 106 | } 107 | } 108 | 109 | .statContainer { 110 | 111 | border-right: 2px solid rgb(230, 230, 230); 112 | display: flex; 113 | flex-direction: column; 114 | align-items: center; 115 | align-content: center; 116 | justify-content: center; 117 | 118 | ion-note { 119 | 120 | margin-top: 0.5rem; 121 | } 122 | } 123 | 124 | .temp { 125 | 126 | border: none; 127 | } 128 | 129 | .placeholderMapCard { 130 | 131 | padding-bottom: 0; 132 | border-radius: 30px; 133 | border: 2px solid rgb(221, 221, 221); 134 | height: 10rem; 135 | 136 | img { 137 | 138 | height: 10rem; 139 | width: 100%; 140 | } 141 | padding-bottom: 0; 142 | } 143 | 144 | .aboutContainer { 145 | 146 | margin-top: -1rem; 147 | padding: 1rem; 148 | 149 | ion-card-title { 150 | 151 | font-size: 1.2rem; 152 | margin-bottom: 0.5rem; 153 | } 154 | } 155 | 156 | .price { 157 | 158 | background-color: rgba(0, 0, 0, 0.075); 159 | padding: 0.5rem; 160 | margin-right: 1rem; 161 | font-size: 1.4rem; 162 | text-align: center; 163 | border-radius: 15px; 164 | } 165 | 166 | .priceSub { 167 | 168 | font-size: 0.7rem; 169 | margin-right: 1rem; 170 | margin-left: -1rem; 171 | } -------------------------------------------------------------------------------- /src/styles/Places.module.scss: -------------------------------------------------------------------------------- 1 | .placeItem { 2 | 3 | --padding-top: 1.5rem; 4 | --padding-bottom: 1.5rem; 5 | 6 | img { 7 | 8 | height: 4.5rem; 9 | width: 4.5rem; 10 | border-radius: 20px; 11 | padding: 0.2rem; 12 | border: 2px solid rgb(226, 226, 226); 13 | margin-right: 1rem; 14 | } 15 | 16 | .placeItemRating { 17 | 18 | display: flex; 19 | align-content: center; 20 | align-items: center; 21 | 22 | svg { 23 | 24 | margin-right: 0.2rem; 25 | color: rgba(255, 201, 23, 0.404); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/theme/floating-tab-bar.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | 3 | --ion-background-color: white; 4 | --ion-tab-bar-background: var(--ion-color-dark); 5 | --ion-tab-bar-color: rgb(112, 112, 112); 6 | --ion-tab-bar-color-selected: rgb(255, 255, 255); 7 | } 8 | 9 | ion-tab-bar { 10 | 11 | box-shadow: 0px 1px 12px rgba(0, 0, 0, 0.4); 12 | border-radius: 30px !important; 13 | 14 | height: 70px; 15 | width: 85%; 16 | padding-top: 5px; 17 | padding-bottom: 5px; 18 | 19 | bottom: 30px; 20 | position: relative; 21 | margin: 0 auto !important; 22 | border-top: none; 23 | } 24 | 25 | ion-tab-button { 26 | 27 | border-radius: 30px !important; 28 | } -------------------------------------------------------------------------------- /src/theme/main.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 2 | 3 | * { 4 | 5 | // font-family: 'Poppins', sans-serif; 6 | } 7 | 8 | .bold { 9 | 10 | font-weight: 700; 11 | } 12 | 13 | .favouriteButton { 14 | 15 | position: absolute; 16 | top: 10px; 17 | right: 15px; 18 | background-color: white; 19 | 20 | display: flex; 21 | flex-direction: row; 22 | align-content: center; 23 | justify-content: center; 24 | align-items: center; 25 | text-align: center; 26 | border-radius: 500px; 27 | padding: 0.4rem; 28 | 29 | svg { 30 | 31 | margin-left: -0.05rem; 32 | height: 1.2rem; 33 | width: 1.2rem; 34 | color: rgb(243, 102, 102); 35 | } 36 | } -------------------------------------------------------------------------------- /src/theme/variables.css: -------------------------------------------------------------------------------- 1 | /* Ionic Variables and Theming. For more info, please see: 2 | http://ionicframework.com/docs/theming/ */ 3 | 4 | /** Ionic CSS Variables **/ 5 | :root { 6 | /** primary **/ 7 | --ion-color-primary: #3880ff; 8 | --ion-color-primary-rgb: 56, 128, 255; 9 | --ion-color-primary-contrast: #ffffff; 10 | --ion-color-primary-contrast-rgb: 255, 255, 255; 11 | --ion-color-primary-shade: #3171e0; 12 | --ion-color-primary-tint: #4c8dff; 13 | 14 | /** secondary **/ 15 | --ion-color-secondary: #3dc2ff; 16 | --ion-color-secondary-rgb: 61, 194, 255; 17 | --ion-color-secondary-contrast: #ffffff; 18 | --ion-color-secondary-contrast-rgb: 255, 255, 255; 19 | --ion-color-secondary-shade: #36abe0; 20 | --ion-color-secondary-tint: #50c8ff; 21 | 22 | /** tertiary **/ 23 | --ion-color-tertiary: #5260ff; 24 | --ion-color-tertiary-rgb: 82, 96, 255; 25 | --ion-color-tertiary-contrast: #ffffff; 26 | --ion-color-tertiary-contrast-rgb: 255, 255, 255; 27 | --ion-color-tertiary-shade: #4854e0; 28 | --ion-color-tertiary-tint: #6370ff; 29 | 30 | /** success **/ 31 | --ion-color-success: #2dd36f; 32 | --ion-color-success-rgb: 45, 211, 111; 33 | --ion-color-success-contrast: #ffffff; 34 | --ion-color-success-contrast-rgb: 255, 255, 255; 35 | --ion-color-success-shade: #28ba62; 36 | --ion-color-success-tint: #42d77d; 37 | 38 | /** warning **/ 39 | --ion-color-warning: #ffc409; 40 | --ion-color-warning-rgb: 255, 196, 9; 41 | --ion-color-warning-contrast: #000000; 42 | --ion-color-warning-contrast-rgb: 0, 0, 0; 43 | --ion-color-warning-shade: #e0ac08; 44 | --ion-color-warning-tint: #ffca22; 45 | 46 | /** danger **/ 47 | --ion-color-danger: #eb445a; 48 | --ion-color-danger-rgb: 235, 68, 90; 49 | --ion-color-danger-contrast: #ffffff; 50 | --ion-color-danger-contrast-rgb: 255, 255, 255; 51 | --ion-color-danger-shade: #cf3c4f; 52 | --ion-color-danger-tint: #ed576b; 53 | 54 | /** dark **/ 55 | --ion-color-dark: #222428; 56 | --ion-color-dark-rgb: 34, 36, 40; 57 | --ion-color-dark-contrast: #ffffff; 58 | --ion-color-dark-contrast-rgb: 255, 255, 255; 59 | --ion-color-dark-shade: #1e2023; 60 | --ion-color-dark-tint: #383a3e; 61 | 62 | /** medium **/ 63 | --ion-color-medium: #92949c; 64 | --ion-color-medium-rgb: 146, 148, 156; 65 | --ion-color-medium-contrast: #ffffff; 66 | --ion-color-medium-contrast-rgb: 255, 255, 255; 67 | --ion-color-medium-shade: #808289; 68 | --ion-color-medium-tint: #9d9fa6; 69 | 70 | /** light **/ 71 | --ion-color-light: #f4f5f8; 72 | --ion-color-light-rgb: 244, 245, 248; 73 | --ion-color-light-contrast: #000000; 74 | --ion-color-light-contrast-rgb: 0, 0, 0; 75 | --ion-color-light-shade: #d7d8da; 76 | --ion-color-light-tint: #f5f6f9; 77 | } 78 | 79 | .tab-dot { 80 | 81 | border-radius: 500px; 82 | background-color: white; 83 | height: 4px; 84 | width: 4px; 85 | margin-top: 1.2rem; 86 | position: absolute; 87 | z-index: 999; 88 | } 89 | 90 | input.searchbar-input { 91 | 92 | padding-top: 1.5rem !important; 93 | padding-bottom: 1.5rem !important; 94 | } 95 | 96 | ion-searchbar ion-icon { 97 | 98 | margin-top: 0.4rem; 99 | } -------------------------------------------------------------------------------- /typings/cordova-typings.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /// --------------------------------------------------------------------------------
Explore UI Components
{ place.destination }
You can add a favourite by pressing the heart button anywhere within the app.