[
  {
    "path": ".gitignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,macos\n# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift,macos\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n### Swift ###\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n# Package.resolved\n# *.xcodeproj\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n# .swiftpm\n\n.build/\n\n# CocoaPods\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n# Pods/\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n\n### Xcode ###\n\n## Xcode 8 and earlier\n\n### Xcode Patch ###\n*.xcodeproj/*\n!*.xcodeproj/project.pbxproj\n!*.xcodeproj/xcshareddata/\n!*.xcworkspace/contents.xcworkspacedata\n/*.gcno\n**/xcshareddata/WorkspaceSettings.xcsettings\n\n# End of https://www.toptal.com/developers/gitignore/api/xcode,swift,macos\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/bag.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"bag.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/jacket.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"jacket.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Assets.xcassets/tshirt.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"tshirt.jpg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/Cart/CartCell.swift",
    "content": "//\n//  CartCell.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 22/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct CartCell: View {\n    let store: StoreOf<CartItemDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            VStack {\n                HStack {\n                    AsyncImage(\n                        url: URL(\n                            string: store.cartItem.product.imageString\n                        )\n                    ) {\n                        $0\n                            .resizable()\n                            .aspectRatio(contentMode: .fit)\n                            .frame(width: 100, height: 100)\n                    } placeholder: {\n                        ProgressView()\n                            .frame(width: 100, height: 100)\n                    }\n                    VStack(alignment: .leading) {\n                        Text(store.cartItem.product.title)\n                            .lineLimit(3)\n                            .minimumScaleFactor(0.5)\n                        HStack {\n                            Text(\"$\\(store.cartItem.product.price.description)\")\n                                .font(.custom(\"AmericanTypewriter\", size: 25))\n                                .fontWeight(.bold)\n                        }\n                    }\n                    \n                }\n                ZStack {\n                    Group {\n                        Text(\"Quantity: \")\n                        +\n                        Text(\"\\(store.cartItem.quantity)\")\n                            .fontWeight(.bold)\n                    }\n                    .font(.custom(\"AmericanTypewriter\", size: 25))\n                    HStack {\n                        Spacer()\n                        Button {\n                            store.send(\n                                .deleteCartItem(\n                                    product: store.cartItem.product\n                                )\n                            )\n                        } label: {\n                            Image(systemName: \"trash.fill\")\n                                .foregroundColor(.red)\n                                .padding()\n                        }\n                    }\n                }\n            }\n            .font(.custom(\"AmericanTypewriter\", size: 20))\n            .padding([.bottom, .top], 10)\n        }\n    }\n}\n\nstruct CartCell_Previews: PreviewProvider {\n    static var previews: some View {\n        CartCell(\n            store: Store(\n                initialState: CartItemDomain.State(\n                    id: UUID(),\n                    cartItem: CartItem.sample.first!\n                ),\n                reducer: { CartItemDomain() }\n            )\n        )\n        .previewLayout(.fixed(width: 300, height: 300))\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/Cart/CartItem.swift",
    "content": "//\n//  CartItem.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 18/08/22.\n//\n\nimport Foundation\n\nstruct CartItem: Equatable {\n    let product: Product\n    let quantity: Int\n}\n\nextension CartItem: Encodable {\n    private enum CartItemsKey: String, CodingKey {\n        case productId\n        case quantity\n    }\n    \n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CartItemsKey.self)\n        try container.encode(product.id, forKey: .productId)\n        try container.encode(quantity, forKey: .quantity)\n    }\n}\n\nextension CartItem {\n    static var sample: [CartItem] {\n        [\n            .init(\n                product: Product.sample[0],\n                quantity: 3\n            ),\n            .init(\n                product: Product.sample[1],\n                quantity: 1\n            ),\n            .init(\n                product: Product.sample[2],\n                quantity: 1\n            ),\n        ]\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/Cart/CartItemDomain.swift",
    "content": "//\n//  CartItemDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 22/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct CartItemDomain {\n    @ObservableState\n    struct State: Equatable, Identifiable {\n        let id: UUID\n        let cartItem: CartItem\n    }\n    \n    enum Action: Equatable {\n        case deleteCartItem(product: Product)\n    }\n    \n    func reduce(into state: inout State, action: Action) -> Effect<Action> {\n        switch action {\n            case .deleteCartItem:\n                return .none\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/CartListDomain.swift",
    "content": "//\n//  CartListDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 18/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct CartListDomain {\n    @ObservableState\n    struct State: Equatable {\n        @Presents var alert: AlertState<Action.Alert>?\n        var dataLoadingStatus = DataLoadingStatus.notStarted\n        var cartItems: IdentifiedArrayOf<CartItemDomain.State> = []\n        var totalPrice: Double = 0.0\n        var isPayButtonDisable = false\n        \n        var totalPriceString: String {\n            let roundedValue = round(totalPrice * 100) / 100.0\n            return \"$\\(roundedValue)\"\n        }\n        \n        var isRequestInProcess: Bool {\n            dataLoadingStatus == .loading\n        }\n    }\n    \n    enum Action: Equatable {\n        case alert(PresentationAction<Alert>)\n        case didPressCloseButton\n        case cartItem(IdentifiedActionOf<CartItemDomain>)\n        case getTotalPrice\n        case didPressPayButton\n        case didReceivePurchaseResponse(TaskResult<String>)\n        \n\t\t@CasePathable\n        enum Alert: Equatable {\n            case didConfirmPurchase\n            case didCancelConfirmation\n            case dismissSuccessAlert\n            case dismissErrorAlert\n        }\n    }\n    \n    @Dependency(\\.apiClient.sendOrder) var sendOrder\n    \n    private func verifyPayButtonVisibility(\n        state: inout State\n    ) -> Effect<Action> {\n        state.isPayButtonDisable = state.totalPrice == 0.0\n        return .none\n    }\n    \n    var body: some ReducerOf<Self> {\n        Reduce { state, action in\n            switch action {\n                case let .alert(.presented(alertAction)):\n                    switch alertAction {\n                        case .didConfirmPurchase:\n                            state.dataLoadingStatus = .loading\n                            let items = state.cartItems.map { $0.cartItem }\n                            return .run { send in\n                                await send(\n                                    .didReceivePurchaseResponse(\n                                        TaskResult{\n                                            try await sendOrder(items)\n                                        }\n                                    )\n                                )\n                            }\n                        case .didCancelConfirmation:\n                            state.alert = nil\n                            return .none\n                        case .dismissSuccessAlert:\n                            state.alert = nil\n                            return .none\n                        case .dismissErrorAlert:\n                            state.alert = nil\n                            return .none\n                    }\n\t\t\t\tcase .alert:\n\t\t\t\t\treturn .none\n                case .didPressCloseButton:\n                    return .none\n                case let .cartItem(.element(id: id, action: action)):\n                    switch action {\n                        case .deleteCartItem:\n                            state.cartItems.remove(id: id)\n                            return.send(.getTotalPrice)\n                    }\n                case .getTotalPrice:\n                    let items = state.cartItems.map { $0.cartItem }\n                    state.totalPrice = items.reduce(0.0, {\n                        $0 + ($1.product.price * Double($1.quantity))\n                    })\n                    return verifyPayButtonVisibility(state: &state)\n                case .didPressPayButton:\n                    state.alert = .confirmationAlert(totalPriceString: state.totalPriceString)\n                    return .none\n                case .didReceivePurchaseResponse(.success(let message)):\n                    state.dataLoadingStatus = .success\n                    state.alert = .successAlert\n                    print(\"Success: \", message)\n                    return .none\n                case .didReceivePurchaseResponse(.failure(let error)):\n                    state.dataLoadingStatus = .error\n                    state.alert = .errorAlert\n                    print(\"Error sending your order:\", error.localizedDescription)\n                    return .none\n            }\n        }\n\t\t.ifLet(\\.$alert, action: \\.alert)\n        .forEach(\\.cartItems, action: \\.cartItem) {\n            CartItemDomain()\n        }\n    }\n}\n\nextension AlertState where Action == CartListDomain.Action.Alert {\n    static func confirmationAlert(totalPriceString: String) -> AlertState {\n        AlertState {\n            TextState(\"Confirm your purchase\")\n        } actions: {\n            ButtonState(action: .didConfirmPurchase, label: { TextState(\"Pay \\(totalPriceString)\") })\n            ButtonState(role: .cancel, action: .didCancelConfirmation, label: { TextState(\"Cancel\") })\n        } message: {\n            TextState(\"Do you want to proceed with your purchase of \\(totalPriceString)?\")\n        }\n    }\n    \n    static var successAlert: AlertState {\n        AlertState {\n            TextState(\"Thank you!\")\n        } actions: {\n            ButtonState(action: .dismissSuccessAlert, label: { TextState(\"Done\") })\n        } message: {\n            TextState(\"Your order is in process.\")\n        }\n    }\n    \n    static var errorAlert: AlertState {\n        AlertState {\n            TextState(\"Oops!\")\n        } actions: {\n            ButtonState(action: .dismissErrorAlert, label: { TextState(\"Done\") })\n        } message: {\n            TextState(\"Unable to send order, try again later.\")\n        }\n    }\n}\n\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/CartListView.swift",
    "content": "//\n//  CartListView.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 18/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct CartListView: View {\n    let store: StoreOf<CartListDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            ZStack {\n                NavigationStack {\n                    Group {\n                        if store.cartItems.isEmpty {\n                            Text(\"Oops, your cart is empty! \\n\")\n                                .font(.custom(\"AmericanTypewriter\", size: 25))\n                        } else {\n                            List {\n                                ForEach(\n                                    store.scope(state: \\.cartItems, action: \\.cartItem),\n                                    id: \\.id\n                                ) { store in\n                                    CartCell(store: store)\n                                }\n                            }\n                            .safeAreaInset(edge: .bottom) {\n                                Button {\n                                    store.send(.didPressPayButton)\n                                } label: {\n                                    HStack(alignment: .center) {\n                                        Spacer()\n                                        Text(\"Pay \\(store.totalPriceString)\")\n                                            .font(.custom(\"AmericanTypewriter\", size: 30))\n                                            .foregroundColor(.white)\n                                        \n                                        Spacer()\n                                    }\n                                    \n                                }\n                                .frame(maxWidth: .infinity, minHeight: 60)\n                                .background(\n                                    store.isPayButtonDisable\n                                    ? .gray\n                                    : .blue\n                                )\n                                .cornerRadius(10)\n                                .padding()\n                                .disabled(store.isPayButtonDisable)\n                            }\n\t\t\t\t\t\t\t.alert(\n\t\t\t\t\t\t\t\tstore: store.scope(\n\t\t\t\t\t\t\t\t\tstate: \\.$alert,\n\t\t\t\t\t\t\t\t\taction: \\.alert\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t)\n                        }\n                    }\n                    .navigationTitle(\"Cart\")\n                    .toolbar {\n                        ToolbarItem(placement: .navigationBarLeading) {\n                            Button {\n                                store.send(.didPressCloseButton)\n                            } label: {\n                                Text(\"Close\")\n                            }\n                        }\n                    }\n                    .onAppear {\n                        store.send(.getTotalPrice)\n                    }\n                }\n                if store.isRequestInProcess {\n                    Color.black.opacity(0.2)\n                        .ignoresSafeArea()\n                    ProgressView()\n                }\n            }\n        }\n    }\n}\n\nstruct CartListView_Previews: PreviewProvider {\n    static var previews: some View {\n        CartListView(\n            store: Store(\n                initialState: CartListDomain.State(\n                    cartItems: IdentifiedArrayOf(\n                        uniqueElements: CartItem.sample\n                            .map {\n                                CartItemDomain.State(\n                                    id: UUID(),\n                                    cartItem: $0\n                                )\n                            }\n                    )\n                ),\n                reducer: { CartListDomain() },\n                withDependencies: {\n                    $0.apiClient.sendOrder = { _ in \"OK\" }\n                }\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/CartList/Test/CartListDomainTest.swift",
    "content": "//\n//  CartListDomainTest.swift\n//  OnlineStoreTCATests\n//\n//  Created by Pedro Rojas on 30/08/22.\n//\n\nimport ComposableArchitecture\nimport XCTest\n\n@testable import OnlineStoreTCA\n\n@MainActor\nclass CartListDomainTest: XCTestCase {\n    func testRemoveItemFromCart() async {\n        let cartItemId1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let cartItemId2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        let itemQuantity = 2\n        \n        let cartItems: IdentifiedArrayOf<CartItemDomain.State> = [\n            .init(\n                id: cartItemId1,\n                cartItem: CartItem.init(\n                    product: Product.sample[0],\n                    quantity: itemQuantity\n                )\n            ),\n            .init(\n                id: cartItemId2,\n                cartItem: CartItem.init(\n                    product: Product.sample[1],\n                    quantity: itemQuantity\n                )\n            ),\n        ]\n        \n        let store = TestStore(\n            initialState: CartListDomain.State(cartItems: cartItems),\n            reducer: { CartListDomain() }\n        )\n        \n\t\tawait store.send(\\.cartItem[id: cartItemId1].deleteCartItem, Product.sample[0]) {\n            $0.cartItems = [\n                .init(\n                    id: cartItemId2,\n                    cartItem: CartItem.init(\n                        product: Product.sample[1],\n                        quantity: itemQuantity\n                    )\n                )\n            ]\n        }\n        \n        let expectedPrice = Product.sample[1].price * Double(itemQuantity)\n        await store.receive(\\.getTotalPrice) {\n            $0.totalPrice = expectedPrice\n        }\n    }\n    \n    func testRemoveAllItemsFromCart() async {\n        let cartItemId1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let cartItemId2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        let itemQuantity = 2\n        \n        let cartItems: IdentifiedArrayOf<CartItemDomain.State> = [\n            .init(\n                id: cartItemId1,\n                cartItem: CartItem.init(\n                    product: Product.sample[0],\n                    quantity: itemQuantity\n                )\n            ),\n            .init(\n                id: cartItemId2,\n                cartItem: CartItem.init(\n                    product: Product.sample[1],\n                    quantity: itemQuantity\n                )\n            ),\n        ]\n        \n        let store = TestStore(\n            initialState: CartListDomain.State(cartItems: cartItems),\n            reducer: { CartListDomain() }\n        )\n        \n\t\tawait store.send(\\.cartItem[id: cartItemId1].deleteCartItem, Product.sample[0]) {\n            $0.cartItems = [\n                .init(\n                    id: cartItemId2,\n                    cartItem: CartItem.init(\n                        product: Product.sample[1],\n                        quantity: itemQuantity\n                    )\n                )\n            ]\n        }\n        \n        let expectedPrice = Product.sample[1].price * Double(itemQuantity)\n        await store.receive(\\.getTotalPrice) {\n            $0.totalPrice = expectedPrice\n        }\n        \n\t\tawait store.send(\\.cartItem[id: cartItemId2].deleteCartItem, Product.sample[1]) {\n            $0.cartItems = []\n        }\n        \n        await store.receive(\\.getTotalPrice) {\n            $0.totalPrice = 0\n            $0.isPayButtonDisable = true\n        }\n        \n    }\n    \n    func testSendOrderSuccessfully() async {\n        let cartItemId1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let cartItemId2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        let itemQuantity = 2\n        \n        let cartItems: IdentifiedArrayOf<CartItemDomain.State> = [\n            .init(\n                id: cartItemId1,\n                cartItem: CartItem.init(\n                    product: Product.sample[0],\n                    quantity: itemQuantity\n                )\n            ),\n            .init(\n                id: cartItemId2,\n                cartItem: CartItem.init(\n                    product: Product.sample[1],\n                    quantity: itemQuantity\n                )\n            ),\n        ]\n        \n        let store = TestStore(\n            initialState: CartListDomain.State(cartItems: cartItems),\n            reducer: { CartListDomain() }\n        ) {\n            $0.apiClient.sendOrder = { _ in \"Success\" }\n        }\n        \n\t\tawait store.send(\\.didPressPayButton) {\n\t\t\t$0.alert = .confirmationAlert(totalPriceString: \"$0.0\")\n\t\t}\n\t\t\n\t\tawait store.send(\\.alert.didConfirmPurchase) {\n\t\t\t$0.alert = nil\n            $0.dataLoadingStatus = .loading\n        }\n        \n        \n        await store.receive(\\.didReceivePurchaseResponse, .success(\"Success\")) {\n            $0.dataLoadingStatus = .success\n            $0.alert = .successAlert\n        }\n    }\n    \n    func testSendOrderWithError() async {\n        let cartItemId1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let cartItemId2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        let itemQuantity = 2\n        \n        let cartItems: IdentifiedArrayOf<CartItemDomain.State> = [\n            .init(\n                id: cartItemId1,\n                cartItem: CartItem.init(\n                    product: Product.sample[0],\n                    quantity: itemQuantity\n                )\n            ),\n            .init(\n                id: cartItemId2,\n                cartItem: CartItem.init(\n                    product: Product.sample[1],\n                    quantity: itemQuantity\n                )\n            ),\n        ]\n        \n        let store = TestStore(\n            initialState: CartListDomain.State(cartItems: cartItems),\n            reducer: { CartListDomain() }\n        ) {\n            $0.apiClient.sendOrder = { _ in throw APIClient.Failure() }\n        }\n\t\t\n\t\tawait store.send(\\.didPressPayButton) {\n\t\t\t$0.alert = .confirmationAlert(totalPriceString: \"$0.0\")\n\t\t}\n        \n        await store.send(\\.alert.didConfirmPurchase) {\n            $0.dataLoadingStatus = .loading\n\t\t\t$0.alert = nil\n        }\n        \n        \n        await store.receive(\\.didReceivePurchaseResponse, .failure(APIClient.Failure())) {\n            $0.dataLoadingStatus = .error\n            $0.alert = .errorAlert\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Network/APIClient.swift",
    "content": "//\n//  APIClient.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 23/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\nimport DependenciesMacros\n\nextension DependencyValues {\n    var apiClient: APIClient {\n        get { self[APIClient.self] }\n        set { self[APIClient.self] = newValue }\n    }\n}\n\n\n@DependencyClient\nstruct APIClient {\n    var fetchProducts:  @Sendable () async throws -> [Product]\n    var sendOrder:  @Sendable ([CartItem]) async throws -> String\n    var fetchUserProfile:  @Sendable () async throws -> UserProfile\n    \n    struct Failure: Error, Equatable {}\n}\n\nextension APIClient: TestDependencyKey {\n    static let testValue = Self()\n}\n\n// This is the \"live\" fact dependency that reaches into the outside world to fetch the data from network.\n// Typically this live implementation of the dependency would live in its own module so that the\n// main feature doesn't need to compile it.\nextension APIClient: DependencyKey {\n    static let liveValue = Self(\n        fetchProducts: {\n            let (data, _) = try await URLSession.shared\n                .data(from: URL(string: \"https://fakestoreapi.com/products\")!)\n            let products = try JSONDecoder().decode([Product].self, from: data)\n            return products\n        },\n        sendOrder: { cartItems in\n            let payload = try JSONEncoder().encode(cartItems)\n            var urlRequest = URLRequest(url: URL(string: \"https://fakestoreapi.com/carts\")!)\n            urlRequest.addValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n            urlRequest.httpMethod = \"POST\"\n            \n            let (data, response) = try await URLSession.shared.upload(for: urlRequest, from: payload)\n            \n            guard let httpResponse = (response as? HTTPURLResponse) else {\n                throw Failure()\n            }\n            \n            return \"Status: \\(httpResponse.statusCode)\"\n        },\n        fetchUserProfile: {\n            let (data, _) = try await URLSession.shared\n                .data(from: URL(string: \"https://fakestoreapi.com/users/1\")!)\n            let profile = try JSONDecoder().decode(UserProfile.self, from: data)\n            return profile\n        }\n    )\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Network/DataLoadingStatus.swift",
    "content": "//\n//  DataLoadingStatus.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 28/08/22.\n//\n\nimport Foundation\n\nenum DataLoadingStatus {\n    case notStarted\n    case loading\n    case success\n    case error\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/AddToCart/AddToCartButton.swift",
    "content": "//\n//  AddToCartButton.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 20/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct AddToCartButton: View {\n    let store: StoreOf<AddToCartDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            if store.count > 0 {\n                PlusMinusButton(store: self.store)\n            } else {\n                Button {\n                    store.send(.didTapPlusButton)\n                } label: {\n                    Text(\"Add to Cart\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n    }\n}\n\nstruct AddToCartButton_Previews: PreviewProvider {\n    static var previews: some View {\n        AddToCartButton(\n            store: Store(\n                initialState: AddToCartDomain.State(),\n                reducer: { AddToCartDomain() }\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/AddToCart/AddToCartDomain.swift",
    "content": "//\n//  PlusMinusDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 20/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct AddToCartDomain {\n    @ObservableState\n    struct State: Equatable {\n        var count = 0\n    }\n    \n    enum Action: Equatable {\n        case didTapPlusButton\n        case didTapMinusButton\n    }\n    \n    func reduce(into state: inout State, action: Action) -> Effect<Action> {\n        switch action {\n            case .didTapPlusButton:\n                state.count += 1\n                return .none\n            case .didTapMinusButton:\n                state.count -= 1\n                return .none\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/AddToCart/PlusMinusButton.swift",
    "content": "//\n//  PlusMinusButton.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 20/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct PlusMinusButton: View {\n    let store: StoreOf<AddToCartDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            HStack {\n                Button {\n                    store.send(.didTapMinusButton)\n                } label: {\n                    Text(\"-\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n                \n                Text(store.count.description)\n                    .padding(5)\n                \n                Button {\n                    store.send(.didTapPlusButton)\n                } label: {\n                    Text(\"+\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n    }\n}\n\nstruct PlusMinusButton_Previews: PreviewProvider {\n    static var previews: some View {\n        PlusMinusButton(\n            store: Store(\n                initialState: AddToCartDomain.State(),\n                reducer: { AddToCartDomain() }\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/AddToCart/Test/AddToCartDomainTest.swift",
    "content": "//\n//  AddToCartDomainTest.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 26/08/22.\n//\n\nimport ComposableArchitecture\nimport XCTest\n\n@testable import OnlineStoreTCA\n\n@MainActor\nclass AddToCartDomainTest: XCTestCase {\n    \n    func testIncreaseCounterTappingPlusButtonOnce() async {\n        let store = TestStore(\n            initialState: AddToCartDomain.State(),\n            reducer: { AddToCartDomain() }\n        )\n        \n        \n        await store.send(\\.didTapPlusButton) {\n            $0.count = 1\n        }\n    }\n    \n    func testIncreaseCounterTappingPlusButtonThreeTimes() async {\n        let store = TestStore(\n            initialState: AddToCartDomain.State(),\n            reducer: { AddToCartDomain() }\n        )\n        \n        \n        await store.send(\\.didTapPlusButton) { $0.count = 1 }\n        await store.send(\\.didTapPlusButton) { $0.count = 2 }\n        await store.send(\\.didTapPlusButton) { $0.count = 3 }\n    }\n    \n    func testDecreaseCounterTappingPlusButtonOnce() async {\n        let store = TestStore(\n            initialState: AddToCartDomain.State(),\n            reducer: { AddToCartDomain() }\n        )\n        \n        await store.send(\\.didTapMinusButton) {\n            $0.count = -1\n        }\n    }\n    \n    func testDecreaseCounterTappingPlusButtonThreeTimes() async {\n        let store = TestStore(\n            initialState: AddToCartDomain.State(),\n            reducer: { AddToCartDomain() }\n        )\n        \n        await store.send(\\.didTapMinusButton) { $0.count = -1 }\n        await store.send(\\.didTapMinusButton) { $0.count = -2 }\n        await store.send(\\.didTapMinusButton) { $0.count = -3 }\n    }\n    \n    func testUpdatingCounterTappingPlusAndMinusButtons() async {\n        let store = TestStore(\n            initialState: AddToCartDomain.State(),\n            reducer: { AddToCartDomain() }\n        )\n        \n        await store.send(\\.didTapMinusButton) { $0.count = -1 }\n        await store.send(\\.didTapPlusButton) { $0.count = 0 }\n        await store.send(\\.didTapMinusButton) { $0.count = -1 }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/Product/Product.swift",
    "content": "//\n//  Product.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 17/08/22.\n//\n\nimport Foundation\n\nstruct Product: Equatable, Identifiable {\n    let id: Int\n    let title: String\n    let price: Double // Update to Currency\n    let description: String\n    let category: String // Update to enum\n    let imageString: String\n    \n    // Add rating later...\n}\nextension Product: Decodable {\n    enum ProductKeys: String, CodingKey {\n        case id\n        case title\n        case price\n        case description\n        case category\n        case image\n    }\n    \n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: ProductKeys.self)\n        self.id = try container.decode(Int.self, forKey: .id)\n        self.title = try container.decode(String.self, forKey: .title)\n        self.price = try container.decode(Double.self, forKey: .price)\n        self.description = try container.decode(String.self, forKey: .description)\n        self.category = try container.decode(String.self, forKey: .category)\n        self.imageString = try container.decode(String.self, forKey: .image)\n    }\n}\n\nextension Product {\n    static var sample: [Product] {\n        [\n            .init(\n                id: 1,\n                title: \"Mens Casual Premium Slim Fit T-Shirts\",\n                price: 22.3,\n                description: \"Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.\",\n                category: \"men's clothing\",\n                imageString: \"https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg\"\n            ),\n            .init(\n                id: 2,\n                title: \"Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops\",\n                price: 109.95,\n                description: \"Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday\",\n                category: \"men's clothing\",\n                imageString: \"https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg\"\n            ),\n            .init(\n                id: 3,\n                title: \"Mens Cotton Jacket\",\n                price: 55.99,\n                description: \"Great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling, traveling or other outdoors. Good gift choice for you or your family member. A warm hearted love to Father, husband or son in this thanksgiving or Christmas Day.\",\n                category: \"men's clothing\",\n                imageString: \"https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg\"\n            )\n        ]\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/Product/ProductCell.swift",
    "content": "//\n//  ProductCell.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 20/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct ProductCell: View {\n    let store: StoreOf<ProductDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            VStack {\n                AsyncImage(\n                    url: URL(string: store.product.imageString)\n                ) { image in\n                    image\n                        .resizable()\n                        .aspectRatio(contentMode: .fit)\n                        .frame(height: 300)\n                } placeholder: {\n                    ProgressView()\n                        .frame(height: 300)\n                }\n                \n                VStack(alignment: .leading) {\n                    Text(store.product.title)\n                    HStack {\n                        Text(\"$\\(store.product.price.description)\")\n                            .font(.custom(\"AmericanTypewriter\", size: 25))\n                            .fontWeight(.bold)\n                        Spacer()\n                        AddToCartButton(\n                            store: self.store.scope(\n                                state: \\.addToCartState,\n                                action: \\.addToCart\n                            )\n                        )\n                    }\n                }\n                .font(.custom(\"AmericanTypewriter\", size: 20))\n            }\n            .padding(20)\n        }\n    }\n}\n\nstruct ProductCell_Previews: PreviewProvider {\n    static var previews: some View {\n        ProductCell(\n            store: Store(\n                initialState: ProductDomain.State(\n                    id: UUID(),\n                    product: Product.sample[0]\n                ),\n                reducer: { ProductDomain() }\n            )\n        )\n        .previewLayout(.fixed(width: 300, height: 300))\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/Product/ProductDomain.swift",
    "content": "//\n//  ProductDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 21/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct ProductDomain {\n    @ObservableState\n    struct State: Equatable, Identifiable {\n        let id: UUID\n        let product: Product\n        var addToCartState = AddToCartDomain.State()\n        \n        var count: Int {\n            get { addToCartState.count }\n            set { addToCartState.count = newValue }\n        }\n    }\n    \n    enum Action: Equatable {\n        case addToCart(AddToCartDomain.Action)\n    }\n    \n    var body: some ReducerOf<Self> {\n        Scope(state: \\.addToCartState, action: \\.addToCart) {\n            AddToCartDomain()\n        }\n        Reduce { state, action in\n            switch action {\n                case .addToCart(.didTapPlusButton):\n                    return .none\n                case .addToCart(.didTapMinusButton):\n                    state.addToCartState.count = max(0, state.addToCartState.count)\n                    return .none\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/Product/Test/ProductDomainTest.swift",
    "content": "//\n//  ProductDomainTest.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 27/08/22.\n//\n\nimport ComposableArchitecture\nimport XCTest\n\n@testable import OnlineStoreTCA\n\n@MainActor\nclass ProductDomainTest: XCTestCase {\n    func testIncreaseProductCounterTappingPlusButtonOnce() async {\n        let product = Product(\n            id: 1,\n            title: \"ProductDemo\",\n            price: 10.5,\n            description: \"Hi mom!\",\n            category: \"Category1\",\n            imageString: \"image\"\n        )\n        let store = TestStore(\n            initialState: ProductDomain.State(\n                id: UUID(),\n                product: product\n            ),\n            reducer: { ProductDomain() }\n        )\n        \n        await store.send(\\.addToCart.didTapPlusButton) {\n            $0.addToCartState = AddToCartDomain.State(count: 1)\n        }\n    }\n    \n    func testIncreaseProductCounterTappingPlusButtonThreeTimes() async {\n        let product = Product(\n            id: 1,\n            title: \"ProductDemo\",\n            price: 10.5,\n            description: \"Hi mom!\",\n            category: \"Category1\",\n            imageString: \"image\"\n        )\n        let store = TestStore(\n            initialState: ProductDomain.State(\n                id: UUID(),\n                product: product\n            ),\n            reducer: { ProductDomain() }\n        )\n        \n        await store.send(\\.addToCart.didTapPlusButton) {\n            $0.addToCartState = AddToCartDomain.State(count: 1)\n        }\n        \n        await store.send(\\.addToCart.didTapPlusButton) {\n            $0.addToCartState = AddToCartDomain.State(count: 2)\n        }\n        \n        await store.send(\\.addToCart.didTapPlusButton) {\n            $0.addToCartState = AddToCartDomain.State(count: 3)\n        }\n    }\n    \n    \n    func testIncreaseProductCounterTappingMinusButtonOnce() async {\n        let product = Product(\n            id: 1,\n            title: \"ProductDemo\",\n            price: 10.5,\n            description: \"Hi mom!\",\n            category: \"Category1\",\n            imageString: \"image\"\n        )\n        let store = TestStore(\n            initialState: ProductDomain.State(\n                id: UUID(),\n                product: product\n            ),\n            reducer: { ProductDomain() }\n        )\n        \n        await store.send(\\.addToCart.didTapMinusButton)\n    }\n    \n    func testIncreaseProductCounterTappingMinusButtonThreeTimes() async {\n        let product = Product(\n            id: 1,\n            title: \"ProductDemo\",\n            price: 10.5,\n            description: \"Hi mom!\",\n            category: \"Category1\",\n            imageString: \"image\"\n        )\n        let store = TestStore(\n            initialState: ProductDomain.State(\n                id: UUID(),\n                product: product\n            ),\n            reducer: { ProductDomain() }\n        )\n        \n        // No changes expected!\n        await store.send(\\.addToCart.didTapMinusButton)\n        await store.send(\\.addToCart.didTapMinusButton)\n        await store.send(\\.addToCart.didTapMinusButton)\n    }\n    \n    func testIncreaseProductCounterTappingMinusTwoTimesAndPlusOnce() async {\n        let product = Product(\n            id: 1,\n            title: \"ProductDemo\",\n            price: 10.5,\n            description: \"Hi mom!\",\n            category: \"Category1\",\n            imageString: \"image\"\n        )\n        let store = TestStore(\n            initialState: ProductDomain.State(\n                id: UUID(),\n                product: product\n            ),\n            reducer: { ProductDomain() }\n        )\n        \n        // No changes expected!\n        await store.send(\\.addToCart.didTapMinusButton)\n        await store.send(\\.addToCart.didTapMinusButton)\n        \n        // Change expected!\n        await store.send(\\.addToCart.didTapPlusButton) {\n            $0.addToCartState = AddToCartDomain.State(count: 1)\n        }\n    }\n    \n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/ProductList/ErrorView.swift",
    "content": "//\n//  ErrorView.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 25/08/22.\n//\n\nimport SwiftUI\n\nstruct ErrorView: View {\n    let message: String\n    let retryAction: () -> Void\n    \n    var body: some View {\n        VStack {\n            Text(\":(\")\n                .font(.custom(\"AmericanTypewriter\", size: 50))\n            Text(\"\")\n            Text(message)\n                .font(.custom(\"AmericanTypewriter\", size: 25))\n            Button {\n                retryAction()\n            } label: {\n                Text(\"Retry\")\n                    .font(.custom(\"AmericanTypewriter\", size: 25))\n                    .foregroundColor(.white)\n            }\n            .frame(width: 100, height: 60)\n            .background(.blue)\n            .cornerRadius(10)\n            .padding()\n            \n        }\n    }\n}\n\nstruct ErrorView_Previews: PreviewProvider {\n    static var previews: some View {\n        ErrorView(\n            message: \"Oops, we couldn't fetch product list\",\n            retryAction: {}\n        )\n        \n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/ProductList/ProductListDomain.swift",
    "content": "//\n//  ProductListDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 17/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct ProductListDomain {\n    @ObservableState\n    struct State: Equatable {\n        var dataLoadingStatus = DataLoadingStatus.notStarted\n        @Presents var cartState: CartListDomain.State?\n        var productList: IdentifiedArrayOf<ProductDomain.State> = []\n        \n        var shouldShowError: Bool {\n            dataLoadingStatus == .error\n        }\n        \n        var isLoading: Bool {\n            dataLoadingStatus == .loading\n        }\n    }\n    \n    enum Action: Equatable {\n        case fetchProducts\n        case fetchProductsResponse(TaskResult<[Product]>)\n        case setCartView(isPresented: Bool)\n        case cart(PresentationAction<CartListDomain.Action>)\n        case product(IdentifiedActionOf<ProductDomain>)\n        case resetProduct(product: Product)\n        case closeCart\n    }\n    \n    @Dependency(\\.apiClient.fetchProducts) var fetchProducts\n    @Dependency(\\.uuid) var uuid\n\t\n    var body: some ReducerOf<Self> {\n        Reduce { state, action in\n            switch action {\n                case .fetchProducts:\n                    if state.dataLoadingStatus == .success || state.dataLoadingStatus == .loading {\n                        return .none\n                    }\n                    \n                    state.dataLoadingStatus = .loading\n                    return .run { send in\n                        await send(\n                            .fetchProductsResponse(\n                                TaskResult { try await self.fetchProducts() }\n                            )\n                        )\n                    }\n                case .fetchProductsResponse(.success(let products)):\n                    state.dataLoadingStatus = .success\n                    state.productList = IdentifiedArrayOf(\n                        uniqueElements: products.map {\n                            ProductDomain.State(\n                                id: uuid(),\n                                product: $0\n                            )\n                        }\n                    )\n                    return .none\n                case .fetchProductsResponse(.failure(let error)):\n                    state.dataLoadingStatus = .error\n                    print(error)\n                    print(\"Error getting products, try again later.\")\n                    return .none\n\t\t\t\tcase .cart(.presented(let action)):\n                    switch action {\n                        case .didPressCloseButton:\n                            return closeCart(state: &state)\n                        case .alert(.presented(.dismissSuccessAlert)):\n\t\t\t\t\t\t\tresetProductsToZero(state: &state)\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\treturn .run { send in\n\t\t\t\t\t\t\t\tawait send(.closeCart)\n\t\t\t\t\t\t\t}\n                        case .cartItem(.element(id: _, action: let action)):\n                            switch action {\n                                case .deleteCartItem(let product):\n                                    return .send(.resetProduct(product: product))\n                            }\n                        default:\n                            return .none\n                    }\n\t\t\t\tcase .cart(.dismiss):\n\t\t\t\t\treturn .none\n                case .closeCart:\n                    return closeCart(state: &state)\n                case .resetProduct(let product):\n                    \n                    guard let index = state.productList.firstIndex(\n                        where: { $0.product.id == product.id }\n                    )\n                    else { return .none }\n                    let productStateId = state.productList[index].id\n                    \n                    state.productList[id: productStateId]?.addToCartState.count = 0\n                    return .none\n                case .setCartView(let isPresented):\n                    state.cartState = isPresented\n                    ? CartListDomain.State(\n                        cartItems: IdentifiedArrayOf(\n                            uniqueElements: state\n                                .productList\n                                .compactMap { state in\n                                    state.count > 0\n                                    ? CartItemDomain.State(\n                                        id: uuid(),\n                                        cartItem: CartItem(\n                                            product: state.product,\n                                            quantity: state.count\n                                        )\n                                    )\n                                    : nil\n                                }\n                        )\n                    )\n                    : nil\n                    return .none\n                case .product:\n                    return .none\n            }\n        }\n        .forEach(\\.productList, action: \\.product) {\n            ProductDomain()\n        }\n        .ifLet(\\.$cartState, action: \\.cart) {\n            CartListDomain()\n        }\n    }\n    \n    private func closeCart(\n        state: inout State\n    ) -> Effect<Action> {\n        state.cartState = nil\n        \n        return .none\n    }\n    \n    private func resetProductsToZero(\n        state: inout State\n    ) {\n        for id in state.productList.map(\\.id) {\n            state.productList[id: id]?.count = 0\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/ProductList/ProductListView.swift",
    "content": "//\n//  ProductListView.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 17/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct ProductListView: View {\n    @Perception.Bindable var store: StoreOf<ProductListDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            NavigationView {\n                Group {\n                    if store.isLoading {\n                        ProgressView()\n                            .frame(width: 100, height: 100)\n                    } else if store.shouldShowError {\n                        ErrorView(\n                            message: \"Oops, we couldn't fetch product list\",\n                            retryAction: { store.send(.fetchProducts) }\n                        )\n                        \n                    } else {\n                        List {\n                            ForEach(\n                                store.scope(\n                                    state: \\.productList,\n                                    action: \\.product\n                                ),\n                                id: \\.id\n                            ) { store in\n                                WithPerceptionTracking {\n                                    ProductCell(store: store)\n                                }\n                            }\n                        }\n                    }\n                }\n                .task {\n                    store.send(.fetchProducts)\n                }\n                .navigationTitle(\"Products\")\n                .toolbar {\n                    ToolbarItem(placement: .navigationBarTrailing) {\n                        Button {\n                            store.send(.setCartView(isPresented: true))\n                        } label: {\n                            Text(\"Go to Cart\")\n                        }\n                    }\n                }\n\t\t\t\t.sheet(\n\t\t\t\t\titem: $store.scope(\n\t\t\t\t\t\tstate: \\.cartState,\n\t\t\t\t\t\taction: \\.cart\n\t\t\t\t\t)\n\t\t\t\t) { store in\n\t\t\t\t\tCartListView(store: store)\n\t\t\t\t}\n\t\t\t}\n        }\n    }\n}\n\nstruct ProductListView_Previews: PreviewProvider {\n    static var previews: some View {\n        ProductListView(\n            store: Store(\n                initialState: ProductListDomain.State()\n            ) {\n                ProductListDomain()\n            } withDependencies: {\n                $0.apiClient.fetchProducts = { Product.sample }\n                $0.apiClient.sendOrder = { _ in \"OK\" }\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Products/ProductList/Test/ProductListDomainTest.swift",
    "content": "//\n//  ProductListDomainTest.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 27/08/22.\n//\n\nimport ComposableArchitecture\nimport XCTest\n\n@testable import OnlineStoreTCA\n\n@MainActor\nclass ProductListDomainTest: XCTestCase {\n    \n    override func setUp() async throws {\n        UUID.uuIdTestCounter = 0\n    }\n    \n    func testFetchProductsSuccess() async {\n        let products: [Product] = [\n            .init(\n                id: 1,\n                title: \"ProductDemo\",\n                price: 10.5,\n                description: \"Hi mom!\",\n                category: \"Category1\",\n                imageString: \"image\"\n            ),\n            .init(\n                id: 2,\n                title: \"AnotherProduct\",\n                price: 99.99,\n                description: \"Hi Dad!\",\n                category: \"Category2\",\n                imageString: \"image2\"\n            ),\n        ]\n        \n        let store = TestStore(\n            initialState: ProductListDomain.State(),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.apiClient.fetchProducts = { products }\n            $0.uuid = .incrementing\n        }\n        \n        let productId1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let productId2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        \n        let identifiedArray = IdentifiedArrayOf(\n            uniqueElements: [\n                ProductDomain.State(\n                    id: productId1,\n                    product: products[0]\n                ),\n                ProductDomain.State(\n                    id: productId2,\n                    product: products[1]\n                ),\n            ]\n        )\n        \n        await store.send(\\.fetchProducts) {\n            $0.dataLoadingStatus = .loading\n        }\n        \n        await store.receive(\\.fetchProductsResponse, .success(products)) {\n            $0.productList = identifiedArray\n            $0.dataLoadingStatus = .success\n        }\n    }\n    \n    func testFetchProductsFailure() async {\n        let error = APIClient.Failure()\n        let store = TestStore(\n            initialState: ProductListDomain.State(),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.apiClient.fetchProducts = { throw error }\n            $0.uuid = .incrementing\n        }\n        \n        await store.send(\\.fetchProducts) {\n            $0.dataLoadingStatus = .loading\n        }\n        \n        await store.receive(\\.fetchProductsResponse, .failure(error)) {\n            $0.productList = []\n            $0.dataLoadingStatus = .error\n        }\n    }\n    \n    func testResetProductsToZeroAfterPayingOrder() async {\n        let products: [Product] = [\n            .init(\n                id: 1,\n                title: \"ProductDemo\",\n                price: 10.5,\n                description: \"Hi mom!\",\n                category: \"Category1\",\n                imageString: \"image\"\n            ),\n            .init(\n                id: 2,\n                title: \"AnotherProduct\",\n                price: 99.99,\n                description: \"Hi Dad!\",\n                category: \"Category2\",\n                imageString: \"image2\"\n            ),\n        ]\n        \n        let id1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let id2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        \n        let identifiedProducts = IdentifiedArrayOf(\n            uniqueElements: [\n                ProductDomain.State(\n                    id: id1,\n                    product: products[0]\n                ),\n                ProductDomain.State(\n                    id: id2,\n                    product: products[1]\n                ),\n            ]\n        )\n        \n        let store = TestStore(\n            initialState: ProductListDomain.State(\n                productList: identifiedProducts\n            ),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.uuid = .incrementing\n        }\n        \n\t\tawait store.send(\\.product[id: id1].addToCart.didTapPlusButton) {\n            $0.productList[id: id1]?.addToCartState.count = 1\n        }\n        \n\t\tawait store.send(\\.product[id: id1].addToCart.didTapPlusButton) {\n            $0.productList[id: id1]?.addToCartState.count = 2\n        }\n        \n        let expectedCartState = CartListDomain.State(\n            cartItems: IdentifiedArrayOf(\n                uniqueElements: [\n                    CartItemDomain.State(\n                        id: id1,\n                        cartItem: CartItem(\n                            product: products.first!,\n                            quantity: 2\n                        )\n                    )\n                ]\n            )\n        )\n        \n        await store.send(\\.setCartView, true) {\n            $0.cartState = expectedCartState\n        }\n\t\t\n\t\tawait store.send(\\.cart.didPressPayButton) {\n\t\t\t$0.cartState?.alert = .confirmationAlert(totalPriceString: \"$0.0\")\n\t\t}\n        \n\t\tawait store.send(\\.cart.alert.dismissSuccessAlert) {\n            $0.productList[id: id1]?.addToCartState.count = 0\n\t\t\t$0.cartState?.alert = nil\n        }\n        \n        await store.receive(\\.closeCart) {\n            $0.cartState = nil\n        }\n    }\n    \n    func testItemRemovedFromCart() async {\n        let products: [Product] = [\n            .init(\n                id: 1,\n                title: \"ProductDemo\",\n                price: 10.5,\n                description: \"Hi mom!\",\n                category: \"Category1\",\n                imageString: \"image\"\n            ),\n            .init(\n                id: 2,\n                title: \"AnotherProduct\",\n                price: 99.99,\n                description: \"Hi Dad!\",\n                category: \"Category2\",\n                imageString: \"image2\"\n            ),\n        ]\n        \n        let id1 = UUID(uuidString: \"00000000-0000-0000-0000-000000000000\")!\n        let id2 = UUID(uuidString: \"00000000-0000-0000-0000-000000000001\")!\n        let numberOfItems = 2\n        \n        let identifiedProducts = IdentifiedArrayOf(\n            uniqueElements: [\n                ProductDomain.State(\n                    id: id1,\n                    product: products[0],\n                    addToCartState: AddToCartDomain.State(count: numberOfItems)\n                ),\n                // This item should not be added:\n                ProductDomain.State(\n                    id: id2,\n                    product: products[1],\n                    addToCartState: AddToCartDomain.State(count: 0)\n                ),\n            ]\n        )\n        \n        let store = TestStore(\n            initialState: ProductListDomain.State(\n                productList: identifiedProducts\n            ),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.uuid = .incrementing\n        }\n        \n        let expectedCartState = CartListDomain.State(\n            cartItems: IdentifiedArrayOf(\n                uniqueElements: [\n                    CartItemDomain.State(\n                        id: id1,\n                        cartItem: CartItem(\n                            product: products.first!,\n                            quantity: numberOfItems\n                        )\n                    )\n                ]\n            )\n        )\n        \n        await store.send(\\.setCartView, true) {\n            $0.cartState = expectedCartState\n        }\n\t\tawait store.send(\n\t\t\t.cart(\n\t\t\t\t.presented(\n\t\t\t\t\t.cartItem(\n\t\t\t\t\t\t.element(id: id1, action: .deleteCartItem(product: products[0]))\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t) {\n\t\t\t$0.cartState?.cartItems = []\n\t\t}\n\t\t\n\t\tawait store.receive(\\.cart.getTotalPrice) {\n            $0.cartState?.totalPrice = 0\n            $0.cartState?.isPayButtonDisable = true\n        }\n        await store.receive(\\.resetProduct, products[0]) {\n            $0.productList = identifiedProducts\n            $0.productList[id: id1]?.count = 0\n        }\n    }\n}\n\n\nextension UUID {\n    // uuIdTestCounter needs to be set to 0 on setUp() method\n    static var uuIdTestCounter: UInt = 0\n    \n    static var newUUIDForTest: UUID {\n        defer {\n            uuIdTestCounter += 1\n        }\n        return UUID(uuidString: \"00000000-0000-0000-0000-\\(String(format: \"%012x\", uuIdTestCounter))\")!\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Profile/ProfileDomain.swift",
    "content": "//\n//  ProfileDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 25/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct ProfileDomain {\n    @ObservableState\n    struct State: Equatable {\n        var profile: UserProfile = .default\n        fileprivate var dataState = DataState.notStarted\n        var isLoading: Bool {\n            dataState == .loading\n        }\n    }\n    \n    fileprivate enum DataState {\n        case notStarted\n        case loading\n        case complete\n    }\n    \n    enum Action: Equatable {\n        case fetchUserProfile\n        case fetchUserProfileResponse(TaskResult<UserProfile>)\n    }\n    \n    @Dependency(\\.apiClient.fetchUserProfile) var fetchUserProfile\n    \n    func reduce(into state: inout State, action: Action) -> Effect<Action> {\n        switch action {\n            case .fetchUserProfile:\n                if state.dataState == .complete || state.dataState == .loading {\n                    return .none\n                }\n                \n                state.dataState = .loading\n                return .run { send in\n                    await send(\n                        .fetchUserProfileResponse(\n                            TaskResult { try await self.fetchUserProfile() }\n                        )\n                    )\n                }\n            case .fetchUserProfileResponse(.success(let profile)):\n                state.dataState = .complete\n                state.profile = profile\n                return .none\n            case .fetchUserProfileResponse(.failure(let error)):\n                state.dataState = .complete\n                print(\"Error: \\(error)\")\n                return .none\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Profile/ProfileView.swift",
    "content": "//\n//  ProfileView.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 25/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct ProfileView: View {\n    let store: StoreOf<ProfileDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            NavigationView {\n                ZStack {\n                    Form {\n                        Section {\n                            Text(store.profile.firstName.capitalized)\n                            +\n                            Text(\" \\(store.profile.lastName.capitalized)\")\n                        } header: {\n                            Text(\"Full name\")\n                        }\n                        \n                        Section {\n                            Text(store.profile.email)\n                        } header: {\n                            Text(\"Email\")\n                        }\n                    }\n                    \n                    if store.isLoading {\n                        ProgressView()\n                    }\n                }\n                .task {\n                    store.send(.fetchUserProfile)\n                }\n                .navigationTitle(\"Profile\")\n            }\n        }\n    }\n}\n\nstruct ProfileView_Previews: PreviewProvider {\n    static var previews: some View {\n        ProfileView(\n            store: Store(initialState: ProfileDomain.State()) {\n                ProfileDomain()\n            } withDependencies: {\n                $0.apiClient.fetchUserProfile = { .sample }\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Profile/UserProfile.swift",
    "content": "//\n//  UserProfile.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 25/08/22.\n//\n\nimport Foundation\n\nstruct UserProfile: Equatable {\n    let id: Int\n    let email: String\n    let firstName: String\n    let lastName: String\n}\n\nextension UserProfile: Decodable {\n    private enum ProfileKeys: String, CodingKey {\n        case id\n        case email\n        case name\n        case firstname\n        case lastname\n    }\n    \n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: ProfileKeys.self)\n        self.id = try container.decode(Int.self, forKey: .id)\n        self.email = try container.decode(String.self, forKey: .email)\n        \n        let nameContainer = try container.nestedContainer(keyedBy: ProfileKeys.self, forKey: .name)\n        self.firstName = try nameContainer.decode(String.self, forKey: .firstname)\n        self.lastName = try nameContainer.decode(String.self, forKey: .lastname)\n    }\n}\n\nextension UserProfile {\n    static var sample: UserProfile {\n        .init(\n            id: 1,\n            email: \"hello@demo.com\",\n            firstName: \"Pedro\",\n            lastName: \"Rojas\"\n        )\n    }\n    \n    static var `default`: UserProfile {\n        .init(\n            id: 0,\n            email: \"\",\n            firstName: \"\",\n            lastName: \"\"\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Root/OnlineStoreTCAApp.swift",
    "content": "//\n//  OnlineStoreTCAApp.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 04/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\n@main\nstruct OnlineStoreTCAApp: App {\n    var body: some Scene {\n        WindowGroup {\n            RootView(\n                store: Store(\n                    initialState: RootDomain.State(),\n\t\t\t\t\treducer: { RootDomain() }\n                )\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Root/RootDomain.swift",
    "content": "//\n//  RootDomain.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 24/08/22.\n//\n\nimport Foundation\nimport ComposableArchitecture\n\n@Reducer\nstruct RootDomain {\n    @ObservableState\n    struct State: Equatable {\n        var selectedTab = Tab.products\n        var productListState = ProductListDomain.State()\n        var profileState = ProfileDomain.State()\n    }\n    \n    enum Tab {\n        case products\n        case profile\n    }\n    \n    enum Action: Equatable {\n        case tabSelected(Tab)\n        case productList(ProductListDomain.Action)\n        case profile(ProfileDomain.Action)\n    }\n    \n    var body: some ReducerOf<Self> {\n        Reduce { state, action in\n            switch action {\n                case .productList:\n                    return .none\n                case .tabSelected(let tab):\n                    state.selectedTab = tab\n                    return .none\n                case .profile:\n                    return .none\n            }\n        }\n        Scope(state: \\.productListState, action: \\.productList) {\n            ProductListDomain()\n        }\n        Scope(state:  \\.profileState, action: \\.profile) {\n            ProfileDomain()\n        }\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA/Root/RootView.swift",
    "content": "//\n//  RootView.swift\n//  OnlineStoreTCA\n//\n//  Created by Pedro Rojas on 24/08/22.\n//\n\nimport SwiftUI\nimport ComposableArchitecture\n\nstruct RootView: View {\n    @Perception.Bindable var store: StoreOf<RootDomain>\n    \n    var body: some View {\n        WithPerceptionTracking {\n            TabView(\n                selection: $store.selectedTab.sending(\\.tabSelected)\n            ) {\n                ProductListView(\n                    store: self.store.scope(\n                        state: \\.productListState,\n                        action: \\.productList\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"list.bullet\")\n                    Text(\"Products\")\n                }\n                .tag(RootDomain.Tab.products)\n                ProfileView(\n                    store: self.store.scope(\n                        state: \\.profileState,\n                        action: \\.profile\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"person.fill\")\n                    Text(\"Profile\")\n                }\n                .tag(RootDomain.Tab.profile)\n            }\n        }\n    }\n}\n\nstruct RootView_Previews: PreviewProvider {\n    static var previews: some View {\n        RootView(\n            store: Store(\n                initialState: RootDomain.State()\n            ) {\n                RootDomain()\n            } withDependencies: {\n                $0.apiClient.fetchProducts = { Product.sample }\n                $0.apiClient.sendOrder = { _ in \"OK\" }\n                $0.apiClient.fetchUserProfile = { UserProfile.sample }\n                $0.uuid = .incrementing\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "OnlineStoreTCA.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\tB50F8EFF28B18F020034B039 /* ProductCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8EFE28B18F020034B039 /* ProductCell.swift */; };\n\t\tB50F8F0128B19B880034B039 /* AddToCartButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F0028B19B880034B039 /* AddToCartButton.swift */; };\n\t\tB50F8F0428B19CD30034B039 /* PlusMinusButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F0328B19CD30034B039 /* PlusMinusButton.swift */; };\n\t\tB50F8F0628B19E660034B039 /* AddToCartDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F0528B19E660034B039 /* AddToCartDomain.swift */; };\n\t\tB50F8F0C28B2C1A70034B039 /* ProductDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F0B28B2C1A70034B039 /* ProductDomain.swift */; };\n\t\tB50F8F1028B3D7FB0034B039 /* CartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F0F28B3D7FB0034B039 /* CartCell.swift */; };\n\t\tB50F8F1328B482E80034B039 /* CartItemDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F8F1228B482E80034B039 /* CartItemDomain.swift */; };\n\t\tB51F8DF728BF2FF20047E7D9 /* CartListDomainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51F8DF628BF2FF20047E7D9 /* CartListDomainTest.swift */; };\n\t\tB525334728AD3F6000B29A17 /* ProductListDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525334628AD3F6000B29A17 /* ProductListDomain.swift */; };\n\t\tB525334C28ADCBCE00B29A17 /* Product.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525334B28ADCBCE00B29A17 /* Product.swift */; };\n\t\tB525334E28ADD6DE00B29A17 /* ProductListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525334D28ADD6DE00B29A17 /* ProductListView.swift */; };\n\t\tB525335128AE87C700B29A17 /* CartListDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525335028AE87C700B29A17 /* CartListDomain.swift */; };\n\t\tB525335328AE883B00B29A17 /* CartItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525335228AE883B00B29A17 /* CartItem.swift */; };\n\t\tB525335528AE8C5600B29A17 /* CartListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525335428AE8C5600B29A17 /* CartListView.swift */; };\n\t\tB5389D1528B9AAA4002BF611 /* OnlineStoreTCATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5389D1428B9AAA4002BF611 /* OnlineStoreTCATests.swift */; };\n\t\tB5389D1B28B9AAC6002BF611 /* AddToCartDomainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E18428B919FB00CB51F4 /* AddToCartDomainTest.swift */; };\n\t\tB556E16428B5C44000CB51F4 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E16328B5C44000CB51F4 /* APIClient.swift */; };\n\t\tB556E16B28B5CEA900CB51F4 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = B556E16A28B5CEA900CB51F4 /* ComposableArchitecture */; };\n\t\tB556E17928B671AC00CB51F4 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E17828B671AC00CB51F4 /* RootView.swift */; };\n\t\tB556E17B28B6740A00CB51F4 /* RootDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E17A28B6740A00CB51F4 /* RootDomain.swift */; };\n\t\tB556E17D28B7BE1600CB51F4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E17C28B7BE1600CB51F4 /* ProfileView.swift */; };\n\t\tB556E17F28B7BE2D00CB51F4 /* ProfileDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E17E28B7BE2D00CB51F4 /* ProfileDomain.swift */; };\n\t\tB556E18128B7BEC200CB51F4 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E18028B7BEC200CB51F4 /* UserProfile.swift */; };\n\t\tB556E18328B84F1500CB51F4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B556E18228B84F1500CB51F4 /* ErrorView.swift */; };\n\t\tB5A5CFB5289C257900AC3E5E /* OnlineStoreTCAApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5CFB4289C257900AC3E5E /* OnlineStoreTCAApp.swift */; };\n\t\tB5A5CFB9289C257B00AC3E5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A5CFB8289C257B00AC3E5E /* Assets.xcassets */; };\n\t\tB5A5CFBC289C257B00AC3E5E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A5CFBB289C257B00AC3E5E /* Preview Assets.xcassets */; };\n\t\tB5BE68DB28BA6EA8008FC353 /* ProductDomainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BE68D928BA6EA4008FC353 /* ProductDomainTest.swift */; };\n\t\tB5BE68DF28BB23B1008FC353 /* ProductListDomainTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BE68DD28BB23AC008FC353 /* ProductListDomainTest.swift */; };\n\t\tB5BE68E128BBAE93008FC353 /* DataLoadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BE68E028BBAE93008FC353 /* DataLoadingStatus.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tB5389D1628B9AAA4002BF611 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = B5A5CFA9289C257900AC3E5E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = B5A5CFB0289C257900AC3E5E;\n\t\t\tremoteInfo = OnlineStoreTCA;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\tB50F8EFE28B18F020034B039 /* ProductCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductCell.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F0028B19B880034B039 /* AddToCartButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToCartButton.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F0328B19CD30034B039 /* PlusMinusButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusMinusButton.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F0528B19E660034B039 /* AddToCartDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToCartDomain.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F0B28B2C1A70034B039 /* ProductDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDomain.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F0F28B3D7FB0034B039 /* CartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartCell.swift; sourceTree = \"<group>\"; };\n\t\tB50F8F1228B482E80034B039 /* CartItemDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartItemDomain.swift; sourceTree = \"<group>\"; };\n\t\tB51F8DF628BF2FF20047E7D9 /* CartListDomainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartListDomainTest.swift; sourceTree = \"<group>\"; };\n\t\tB525334628AD3F6000B29A17 /* ProductListDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListDomain.swift; sourceTree = \"<group>\"; };\n\t\tB525334B28ADCBCE00B29A17 /* Product.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Product.swift; sourceTree = \"<group>\"; };\n\t\tB525334D28ADD6DE00B29A17 /* ProductListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListView.swift; sourceTree = \"<group>\"; };\n\t\tB525335028AE87C700B29A17 /* CartListDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartListDomain.swift; sourceTree = \"<group>\"; };\n\t\tB525335228AE883B00B29A17 /* CartItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartItem.swift; sourceTree = \"<group>\"; };\n\t\tB525335428AE8C5600B29A17 /* CartListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartListView.swift; sourceTree = \"<group>\"; };\n\t\tB5389D1228B9AAA3002BF611 /* OnlineStoreTCATests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OnlineStoreTCATests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB5389D1428B9AAA4002BF611 /* OnlineStoreTCATests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineStoreTCATests.swift; sourceTree = \"<group>\"; };\n\t\tB556E16328B5C44000CB51F4 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = \"<group>\"; };\n\t\tB556E17828B671AC00CB51F4 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = \"<group>\"; };\n\t\tB556E17A28B6740A00CB51F4 /* RootDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootDomain.swift; sourceTree = \"<group>\"; };\n\t\tB556E17C28B7BE1600CB51F4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = \"<group>\"; };\n\t\tB556E17E28B7BE2D00CB51F4 /* ProfileDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDomain.swift; sourceTree = \"<group>\"; };\n\t\tB556E18028B7BEC200CB51F4 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = \"<group>\"; };\n\t\tB556E18228B84F1500CB51F4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = \"<group>\"; };\n\t\tB556E18428B919FB00CB51F4 /* AddToCartDomainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToCartDomainTest.swift; sourceTree = \"<group>\"; };\n\t\tB5A5CFB1289C257900AC3E5E /* OnlineStoreTCA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OnlineStoreTCA.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tB5A5CFB4289C257900AC3E5E /* OnlineStoreTCAApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineStoreTCAApp.swift; sourceTree = \"<group>\"; };\n\t\tB5A5CFB8289C257B00AC3E5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tB5A5CFBB289C257B00AC3E5E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\tB5BE68D928BA6EA4008FC353 /* ProductDomainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDomainTest.swift; sourceTree = \"<group>\"; };\n\t\tB5BE68DD28BB23AC008FC353 /* ProductListDomainTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListDomainTest.swift; sourceTree = \"<group>\"; };\n\t\tB5BE68E028BBAE93008FC353 /* DataLoadingStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLoadingStatus.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tB5389D0F28B9AAA3002BF611 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB5A5CFAE289C257900AC3E5E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB556E16B28B5CEA900CB51F4 /* ComposableArchitecture in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tB50F8F0228B19CBD0034B039 /* AddToCart */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5BE68D728BA6E63008FC353 /* Test */,\n\t\t\t\tB50F8F0028B19B880034B039 /* AddToCartButton.swift */,\n\t\t\t\tB50F8F0328B19CD30034B039 /* PlusMinusButton.swift */,\n\t\t\t\tB50F8F0528B19E660034B039 /* AddToCartDomain.swift */,\n\t\t\t);\n\t\t\tpath = AddToCart;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB50F8F0D28B2C1C90034B039 /* ProductList */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5BE68DC28BB2399008FC353 /* Test */,\n\t\t\t\tB525334628AD3F6000B29A17 /* ProductListDomain.swift */,\n\t\t\t\tB525334D28ADD6DE00B29A17 /* ProductListView.swift */,\n\t\t\t\tB556E18228B84F1500CB51F4 /* ErrorView.swift */,\n\t\t\t);\n\t\t\tpath = ProductList;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB50F8F0E28B2C1D40034B039 /* Product */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5BE68D828BA6E94008FC353 /* Test */,\n\t\t\t\tB525334B28ADCBCE00B29A17 /* Product.swift */,\n\t\t\t\tB50F8EFE28B18F020034B039 /* ProductCell.swift */,\n\t\t\t\tB50F8F0B28B2C1A70034B039 /* ProductDomain.swift */,\n\t\t\t);\n\t\t\tpath = Product;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB50F8F1128B4814E0034B039 /* Cart */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB525335228AE883B00B29A17 /* CartItem.swift */,\n\t\t\t\tB50F8F0F28B3D7FB0034B039 /* CartCell.swift */,\n\t\t\t\tB50F8F1228B482E80034B039 /* CartItemDomain.swift */,\n\t\t\t);\n\t\t\tpath = Cart;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB51F8DF528BF2F7C0047E7D9 /* Test */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB51F8DF628BF2FF20047E7D9 /* CartListDomainTest.swift */,\n\t\t\t);\n\t\t\tpath = Test;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB525334328AD3DCE00B29A17 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB50F8F0E28B2C1D40034B039 /* Product */,\n\t\t\t\tB50F8F0D28B2C1C90034B039 /* ProductList */,\n\t\t\t\tB50F8F0228B19CBD0034B039 /* AddToCart */,\n\t\t\t);\n\t\t\tpath = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB525334428AD3DD500B29A17 /* Profile */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB556E17C28B7BE1600CB51F4 /* ProfileView.swift */,\n\t\t\t\tB556E17E28B7BE2D00CB51F4 /* ProfileDomain.swift */,\n\t\t\t\tB556E18028B7BEC200CB51F4 /* UserProfile.swift */,\n\t\t\t);\n\t\t\tpath = Profile;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB525334528AD3DDF00B29A17 /* Root */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5A5CFB4289C257900AC3E5E /* OnlineStoreTCAApp.swift */,\n\t\t\t\tB556E17828B671AC00CB51F4 /* RootView.swift */,\n\t\t\t\tB556E17A28B6740A00CB51F4 /* RootDomain.swift */,\n\t\t\t);\n\t\t\tpath = Root;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB525334F28AE87B500B29A17 /* CartList */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB51F8DF528BF2F7C0047E7D9 /* Test */,\n\t\t\t\tB50F8F1128B4814E0034B039 /* Cart */,\n\t\t\t\tB525335028AE87C700B29A17 /* CartListDomain.swift */,\n\t\t\t\tB525335428AE8C5600B29A17 /* CartListView.swift */,\n\t\t\t);\n\t\t\tpath = CartList;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5389D1328B9AAA4002BF611 /* OnlineStoreTCATests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5389D1428B9AAA4002BF611 /* OnlineStoreTCATests.swift */,\n\t\t\t);\n\t\t\tpath = OnlineStoreTCATests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB556E16228B5BD6000CB51F4 /* Network */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB556E16328B5C44000CB51F4 /* APIClient.swift */,\n\t\t\t\tB5BE68E028BBAE93008FC353 /* DataLoadingStatus.swift */,\n\t\t\t);\n\t\t\tpath = Network;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5A5CFA8289C257900AC3E5E = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5A5CFB3289C257900AC3E5E /* OnlineStoreTCA */,\n\t\t\t\tB5389D1328B9AAA4002BF611 /* OnlineStoreTCATests */,\n\t\t\t\tB5A5CFB2289C257900AC3E5E /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5A5CFB2289C257900AC3E5E /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5A5CFB1289C257900AC3E5E /* OnlineStoreTCA.app */,\n\t\t\t\tB5389D1228B9AAA3002BF611 /* OnlineStoreTCATests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5A5CFB3289C257900AC3E5E /* OnlineStoreTCA */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB556E16228B5BD6000CB51F4 /* Network */,\n\t\t\t\tB525334F28AE87B500B29A17 /* CartList */,\n\t\t\t\tB525334528AD3DDF00B29A17 /* Root */,\n\t\t\t\tB525334428AD3DD500B29A17 /* Profile */,\n\t\t\t\tB525334328AD3DCE00B29A17 /* Products */,\n\t\t\t\tB5A5CFB8289C257B00AC3E5E /* Assets.xcassets */,\n\t\t\t\tB5A5CFBA289C257B00AC3E5E /* Preview Content */,\n\t\t\t);\n\t\t\tpath = OnlineStoreTCA;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5A5CFBA289C257B00AC3E5E /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5A5CFBB289C257B00AC3E5E /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5BE68D728BA6E63008FC353 /* Test */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB556E18428B919FB00CB51F4 /* AddToCartDomainTest.swift */,\n\t\t\t);\n\t\t\tpath = Test;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5BE68D828BA6E94008FC353 /* Test */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5BE68D928BA6EA4008FC353 /* ProductDomainTest.swift */,\n\t\t\t);\n\t\t\tpath = Test;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB5BE68DC28BB2399008FC353 /* Test */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB5BE68DD28BB23AC008FC353 /* ProductListDomainTest.swift */,\n\t\t\t);\n\t\t\tpath = Test;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tB5389D1128B9AAA3002BF611 /* OnlineStoreTCATests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B5389D1A28B9AAA4002BF611 /* Build configuration list for PBXNativeTarget \"OnlineStoreTCATests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB5389D0E28B9AAA3002BF611 /* Sources */,\n\t\t\t\tB5389D0F28B9AAA3002BF611 /* Frameworks */,\n\t\t\t\tB5389D1028B9AAA3002BF611 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tB5389D1728B9AAA4002BF611 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = OnlineStoreTCATests;\n\t\t\tproductName = OnlineStoreTCATests;\n\t\t\tproductReference = B5389D1228B9AAA3002BF611 /* OnlineStoreTCATests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\tB5A5CFB0289C257900AC3E5E /* OnlineStoreTCA */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = B5A5CFBF289C257B00AC3E5E /* Build configuration list for PBXNativeTarget \"OnlineStoreTCA\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tB5A5CFAD289C257900AC3E5E /* Sources */,\n\t\t\t\tB5A5CFAE289C257900AC3E5E /* Frameworks */,\n\t\t\t\tB5A5CFAF289C257900AC3E5E /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = OnlineStoreTCA;\n\t\t\tpackageProductDependencies = (\n\t\t\t\tB556E16A28B5CEA900CB51F4 /* ComposableArchitecture */,\n\t\t\t);\n\t\t\tproductName = OnlineStoreTCA;\n\t\t\tproductReference = B5A5CFB1289C257900AC3E5E /* OnlineStoreTCA.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tB5A5CFA9289C257900AC3E5E /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1400;\n\t\t\t\tLastUpgradeCheck = 1330;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tB5389D1128B9AAA3002BF611 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.0;\n\t\t\t\t\t\tTestTargetID = B5A5CFB0289C257900AC3E5E;\n\t\t\t\t\t};\n\t\t\t\t\tB5A5CFB0289C257900AC3E5E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.3;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = B5A5CFAC289C257900AC3E5E /* Build configuration list for PBXProject \"OnlineStoreTCA\" */;\n\t\t\tcompatibilityVersion = \"Xcode 13.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = B5A5CFA8289C257900AC3E5E;\n\t\t\tpackageReferences = (\n\t\t\t\tB556E16928B5CEA900CB51F4 /* XCRemoteSwiftPackageReference \"swift-composable-architecture\" */,\n\t\t\t);\n\t\t\tproductRefGroup = B5A5CFB2289C257900AC3E5E /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tB5A5CFB0289C257900AC3E5E /* OnlineStoreTCA */,\n\t\t\t\tB5389D1128B9AAA3002BF611 /* OnlineStoreTCATests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tB5389D1028B9AAA3002BF611 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB5A5CFAF289C257900AC3E5E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB5A5CFBC289C257B00AC3E5E /* Preview Assets.xcassets in Resources */,\n\t\t\t\tB5A5CFB9289C257B00AC3E5E /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tB5389D0E28B9AAA3002BF611 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB5389D1B28B9AAC6002BF611 /* AddToCartDomainTest.swift in Sources */,\n\t\t\t\tB5389D1528B9AAA4002BF611 /* OnlineStoreTCATests.swift in Sources */,\n\t\t\t\tB5BE68DB28BA6EA8008FC353 /* ProductDomainTest.swift in Sources */,\n\t\t\t\tB5BE68DF28BB23B1008FC353 /* ProductListDomainTest.swift in Sources */,\n\t\t\t\tB51F8DF728BF2FF20047E7D9 /* CartListDomainTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB5A5CFAD289C257900AC3E5E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tB525335128AE87C700B29A17 /* CartListDomain.swift in Sources */,\n\t\t\t\tB50F8F0128B19B880034B039 /* AddToCartButton.swift in Sources */,\n\t\t\t\tB50F8F1328B482E80034B039 /* CartItemDomain.swift in Sources */,\n\t\t\t\tB525335528AE8C5600B29A17 /* CartListView.swift in Sources */,\n\t\t\t\tB556E18328B84F1500CB51F4 /* ErrorView.swift in Sources */,\n\t\t\t\tB50F8F0628B19E660034B039 /* AddToCartDomain.swift in Sources */,\n\t\t\t\tB5BE68E128BBAE93008FC353 /* DataLoadingStatus.swift in Sources */,\n\t\t\t\tB556E17B28B6740A00CB51F4 /* RootDomain.swift in Sources */,\n\t\t\t\tB525334E28ADD6DE00B29A17 /* ProductListView.swift in Sources */,\n\t\t\t\tB5A5CFB5289C257900AC3E5E /* OnlineStoreTCAApp.swift in Sources */,\n\t\t\t\tB50F8F1028B3D7FB0034B039 /* CartCell.swift in Sources */,\n\t\t\t\tB556E17F28B7BE2D00CB51F4 /* ProfileDomain.swift in Sources */,\n\t\t\t\tB556E17928B671AC00CB51F4 /* RootView.swift in Sources */,\n\t\t\t\tB556E17D28B7BE1600CB51F4 /* ProfileView.swift in Sources */,\n\t\t\t\tB556E18128B7BEC200CB51F4 /* UserProfile.swift in Sources */,\n\t\t\t\tB556E16428B5C44000CB51F4 /* APIClient.swift in Sources */,\n\t\t\t\tB525334728AD3F6000B29A17 /* ProductListDomain.swift in Sources */,\n\t\t\t\tB50F8F0C28B2C1A70034B039 /* ProductDomain.swift in Sources */,\n\t\t\t\tB50F8EFF28B18F020034B039 /* ProductCell.swift in Sources */,\n\t\t\t\tB525335328AE883B00B29A17 /* CartItem.swift in Sources */,\n\t\t\t\tB50F8F0428B19CD30034B039 /* PlusMinusButton.swift in Sources */,\n\t\t\t\tB525334C28ADCBCE00B29A17 /* Product.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tB5389D1728B9AAA4002BF611 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = B5A5CFB0289C257900AC3E5E /* OnlineStoreTCA */;\n\t\t\ttargetProxy = B5389D1628B9AAA4002BF611 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\tB5389D1828B9AAA4002BF611 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = LHJA6J73E9;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.swiftandtips.OnlineStoreTCATests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/OnlineStoreTCA.app/OnlineStoreTCA\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB5389D1928B9AAA4002BF611 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tBUNDLE_LOADER = \"$(TEST_HOST)\";\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = LHJA6J73E9;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.swiftandtips.OnlineStoreTCATests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tTEST_HOST = \"$(BUILT_PRODUCTS_DIR)/OnlineStoreTCA.app/OnlineStoreTCA\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB5A5CFBD289C257B00AC3E5E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB5A5CFBE289C257B00AC3E5E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tB5A5CFC0289C257B00AC3E5E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"OnlineStoreTCA/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = LHJA6J73E9;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.swiftandtips.OnlineStoreTCA;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tB5A5CFC1289C257B00AC3E5E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"OnlineStoreTCA/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = LHJA6J73E9;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.swiftandtips.OnlineStoreTCA;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tB5389D1A28B9AAA4002BF611 /* Build configuration list for PBXNativeTarget \"OnlineStoreTCATests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB5389D1828B9AAA4002BF611 /* Debug */,\n\t\t\t\tB5389D1928B9AAA4002BF611 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB5A5CFAC289C257900AC3E5E /* Build configuration list for PBXProject \"OnlineStoreTCA\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB5A5CFBD289C257B00AC3E5E /* Debug */,\n\t\t\t\tB5A5CFBE289C257B00AC3E5E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tB5A5CFBF289C257B00AC3E5E /* Build configuration list for PBXNativeTarget \"OnlineStoreTCA\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tB5A5CFC0289C257B00AC3E5E /* Debug */,\n\t\t\t\tB5A5CFC1289C257B00AC3E5E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\tB556E16928B5CEA900CB51F4 /* XCRemoteSwiftPackageReference \"swift-composable-architecture\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/pointfreeco/swift-composable-architecture.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = exactVersion;\n\t\t\t\tversion = 1.14.0;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\tB556E16A28B5CEA900CB51F4 /* ComposableArchitecture */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = B556E16928B5CEA900CB51F4 /* XCRemoteSwiftPackageReference \"swift-composable-architecture\" */;\n\t\t\tproductName = ComposableArchitecture;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = B5A5CFA9289C257900AC3E5E /* Project object */;\n}\n"
  },
  {
    "path": "OnlineStoreTCATests/OnlineStoreTCATests.swift",
    "content": "//\n//  OnlineStoreTCATests.swift\n//  OnlineStoreTCATests\n//\n//  Created by Pedro Rojas on 26/08/22.\n//\n\nimport XCTest\n\nfinal class OnlineStoreTCATests: XCTestCase {\n\n    override func setUpWithError() throws {\n        // Put setup code here. This method is called before the invocation of each test method in the class.\n    }\n\n    override func tearDownWithError() throws {\n        // Put teardown code here. This method is called after the invocation of each test method in the class.\n    }\n\n    func testExample() throws {\n        // This is an example of a functional test case.\n        // Use XCTAssert and related functions to verify your tests produce the correct results.\n        // Any test you write for XCTest can be annotated as throws and async.\n        // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.\n        // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.\n    }\n\n    func testPerformanceExample() throws {\n        // This is an example of a performance test case.\n        measure {\n            // Put the code you want to measure the time of here.\n        }\n    }\n\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Before starting\n- This demo was implemented using version [1.15.2](https://pointfreeco.github.io/swift-composable-architecture/1.14.0/documentation/composablearchitecture/) of TCA.\n- The demo runs on iOS 17.6 and above.\n- All credits about TCA go to [Brandon Willams](https://twitter.com/mbrandonw), [Stephen Celis](https://twitter.com/stephencelis) and the incredible team at [pointfree.co](https://www.pointfree.co/) ❤️.\n\n# Online Store made with Composable Architecture (TCA)\nThe purpose of this demo is to provide an introduction to the main concepts of TCA. If you are new to TCA, I **highly** recommend starting with the README from the [main repository](https://github.com/pointfreeco/swift-composable-architecture) and watching the informative [Tour of TCA](https://www.pointfree.co/collections/composable-architecture/a-tour-of-the-composable-architecture). These resources will provide you with a solid foundation and a comprehensive understanding of the TCA framework.\n\n## Content\n* [Motivation](#motivation)\n* [Screenshots of the app](#screenshots)\n* [The basics](#the-basics)\n    * [Archiecture Diagram](#archiecture-diagram)\n    * [Hello World Example](#hello-world-example)\n* [Composition](#composition)\n    * [Body to compose multiple Reducers](#body-to-compose-multiple-reducers)\n    * [Single state operators](#single-state-operators)\n      * [store.scope(state:action:)](#storescopestateaction)\n      * [Scope in Reducers](#scope-in-reducers)\n    * [Collection of states](#collection-of-states)\n      * [forEach in Reducer](#foreach-in-reducer)\n* [Dependencies](#dependencies)\n* [Side Effects](#side-effects)\n    * [Network Calls](#network-calls)\n* [Navigation](#navigation)\n    * [Alerts](#alerts)\n    * [Sheets](#sheets)\n* [Testing](#testing)\n    * [Basics](#testing-basics)\n    * [Side Effects](#testing-side-effects)\n    * [CasePathable](#testing-CasePathable)\n* [Other Topics](#other-topics)\n    * [Optional States](#optional-states)\n    * [Private Actions](#private-actions)\n    * [Making a Root Domain with Tab View](#making-a-root-domain-with-tab-view)\n* [Contact](#contact)\n\n\n## Motivation\n**TL;DR:** This project aims to build an app using TCA, striking a balance between simplicity and complexity. It focuses on exploring the most important use cases of TCA while providing concise and accessible documentation for new learners. The goal is to create a valuable learning resource that offers practical insights into using TCA effectively.\n\nI aimed to showcase the power of the TCA architecture in building robust applications for the Apple ecosystem, including iOS, macOS, and more excitingly, its future expansion beyond the Apple world! 🚀\n\nWhile there are many articles available that demonstrate simple one-screen applications to introduce TCA's core concepts, I noticed a gap between these basic demos and real-world applications like [isoword](https://github.com/pointfreeco/isowords), which can be complex and challenging to understand certain important use cases (like navigation and how reducers are glued).\n\nIn this demo, I have implemented a minimal online store that connects to a real network API (https://fakestoreapi.com). It features a product list, the ability to add items to the cart, and the functionality to place orders. While the requests are not processed in real-time (as it uses a fake API), the network status is simulated, allowing you to experience the interaction and mapping of network calls using TCA.\n\nWhile this demo may not be a full-scale real-world application, it includes enough reducers to illustrate how data can be effectively connected and how domains can be isolated to handle specific components within the app (e.g., Tabs -> Product List -> Product Cell -> Add to Cart button).\n\nFurthermore, I have created tests to demonstrate one of TCA's key features: ensuring that tests fail if the expected state mutations are not captured accurately. This showcases how TCA promotes testability and helps ensure the correctness of your application.\n\nIf you're looking to dive into TCA, this demo provides a valuable middle ground between simple examples and complex projects, offering concise documentation and practical insights into working with TCA in a more realistic application setting.\n\nAny feedback is welcome! 🙌🏻\n\n## Screenshots\n### Tabs\n<img src=\"./Images/demo1.png\"  width=\"25%\" height=\"25%\">|<img src=\"./Images/demo2.png\"  width=\"25%\" height=\"25%\">|<img src=\"./Images/demo6.png\"  width=\"25%\" height=\"25%\">\n\n### Cart\n<img src=\"./Images/demo3.png\"  width=\"25%\" height=\"25%\">|<img src=\"./Images/demo4.png\"  width=\"25%\" height=\"25%\">|<img src=\"./Images/demo5.png\"  width=\"25%\" height=\"25%\">\n\n## The basics\n### Archiecture Diagram\n<img src=\"./Images/TCA_Architecture2.png\">\n\n### Hello World Example\nConsider the following implementation of a simple app using TCA, where you will have two buttons: one to increment a counter displayed on the screen and the other to decrement it.\n\nHere's an example of how this app would be coded with TCA:\n\n1. A struct that will represent the domain of the feature. This struct must conform `ReducerProtocol` protocol and providing `State` struct, `Action` enum and `reduce` method.\n\n```swift\nstruct CounterDomain: ReducerProtocol {\n    struct State {\n        // State of the feature\n    }\n\n    enum Action {\n        // actions that use can do in the app\n    }\n    \n    func reduce(into state: inout State, action: Action) -> EffectTask<Action> {\n        // Method that will mutate the state given an action.\n    }\n}\n```\n\n2. The view that is presented in the screen will display the current state of the app.\n<img src=\"./Images/viewDemo1.png\" width=\"30%\" height=\"30%\">\n\n```swift\nstruct State: Equatable {\n    var counter = 0\n}\n```\n\n3. When the user presses a button (let's say increase button), it will internally send an action to the store.\n<img src=\"./Images/actionDemo1.png\" width=\"30%\" height=\"30%\">\n\n```swift\nenum Action: Equatable {\n    case increaseCounter\n    case decreaseCounter\n}\n```\n\n4. The action will be received by the reducer and proceed to mutate the state. Reducer MUST also return an effect, that represent logic from the \"outside world\" (network calls, notifications, database, etc). If no effect is needed, just return `EffectTask.none` .\n\n```swift\nfunc reduce(into state: inout State, action: Action) -> EffectTask<Action> {\n    switch action {\n    case .increaseCounter:\n        state.counter += 1\n        return .none\n    case .decreaseCounter:\n        state.counter -= 1\n        return .none\n    }\n}\n```\n\n5. Once the mutation is done and the reducer returned the effect, the view will render the update in the screen. \n<img src=\"./Images/viewUpdateDemo1.png\" width=\"30%\" height=\"30%\">\n\n6. To observe state changes in TCA, we need an object called `viewStore`, that in this example is wrapped within WithViewStore view. We can send an action from the view to the store using `viewStore.send()` and an `Action` value.\n\n```swift\nstruct ContentView: View {\n    let store: Store<State, Action>\n\n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            HStack {\n                Button {\n                    viewStore.send(.decreaseCounter)\n                } label: {\n                    Text(\"-\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n\n                Text(viewStore.counter.description)\n                    .padding(5)\n\n                Button {\n                    viewStore.send(.increaseCounter)\n                } label: {\n                    Text(\"+\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n    }\n}\n```\n\n7. View is initialized by a `Store` object.\n\n```swift\nContentView(\n    store: Store(\n        initialState: CounterDomain.State(),\n        reducer: CounterDomain()\n    )\n)\n```\n\nIf you want to learn more about the basics, check out the following [video](https://youtu.be/SfFDj6qT-xg)\n\n> Note: The videos shared here were made using the legacy version of TCA with Environment and without `ReducerProtocol`. If you want to see the legacy version of TCA, check out this [branch](https://github.com/pitt500/OnlineStoreTCA/tree/legacy-tca-with-environment).\n\n## Composition\n\nComposition refers to the process of building complex software systems by combining smaller, reusable software components. Take a look to this image:\n\n<img src=\"./Images/composition2.png\" width=\"80%\" height=\"80%\">\n\nWe started with a simple button counter, then we add an extra state to display text, next we put the whole button in a Product cell, and finally, each product cell will be part of a Product list. That is composition!\n\n### Body to compose multiple Reducers\nIn the previous example, we demonstrated the usage of `reduce(into:action:)` to create our reducer function and define how state will be modified for each action. However, it's important to note that this method is suitable only for leaf components, which refer to the smallest components in your application.\n\nFor larger components, we can leverage the `body` property provided by the `ReducerProtocol`. This property enables you to combine multiple reducers, facilitating the creation of more comprehensive components. By utilizing the `body` property, you can effectively compose and manage the state mutations of these larger components.\n```swift\nvar body: some ReducerProtocol<State, Action> {\n    ChildReducer1()\n    Reduce { state, action in\n        switch action {\n        case .increaseCounter:\n            state.counter += 1\n            return .none\n        case .decreaseCounter:\n            state.counter -= 1\n            return .none\n        }\n    }\n    ChildReducer2()\n}\n```\n\nThe `Reduce` closure will always encapsulate the logic from the parent domain. To understand how to combine additional components, please continue reading below.\n\n> Compared to the previous version of TCA without `ReducerProtocol`, the order of child reducers will not affect the result. Parent Reducer (`Reduce`) will be always executed at the end.\n\n### Single state operators\n\nFor single states (all except collections/lists), TCA provides operators to glue the components and make bigger ones.\n\n#### store.scope(state:action:) \n`store.scope` is an operator used in views to get the child domain's (`AddToCartDomain`) state and action from parent domain (`ProductDomain`) to initialize subviews. \nFor example, the `ProductDomain` below contains two properties as part of its state: `product` and `addToCartState`.\n\n```swift\nstruct ProductDomain: ReducerProtocol {\n    struct State: Equatable, Identifiable {\n        let product: Product\n        var addToCartState = AddToCartDomain.State()\n    }\n    // ...\n```\n\nFurthermore, we utilize an action with an associated value that encapsulates all actions from the child domain, providing a comprehensive and cohesive approach.\n```swift\nstruct ProductDomain: ReducerProtocol {\n    // State ...\n\n    enum Action {\n        case addToCart(AddToCartDomain.Action)\n    }\n    // ...\n```\n\nLet's consider the scenario where we need to configure the `ProductCell` view below. The `ProductCell` is designed to handle the `ProductDomain`, while we need to provide some information to initialize the `AddToCartButton`. However, the `AddToCartButton` is only aware of its own domain, `AddToCartDomain`, and not the `ProductDomain`. To address this, we can use the `scope` method from `store` to get the child's state and action from parent domain. This enables us to narrow down the scope of the button to focus solely on its own functionality.\n\n```swift\nstruct ProductCell: View {\n    let store: Store<ProductDomain.State, ProductDomain.Action>\n    \n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            // More views here ...\n            AddToCartButton(\n                store: self.store.scope(\n                    state: \\.addToCartState,\n                    action: ProductDomain.Action.addToCart\n                )\n            )\n        }\n    }\n```\nBy employing this approach, the `AddToCartDomain` will solely possess knowledge of its own state and remain unaware of any product-related information.\n\n#### Scope in Reducers\n`Scope` is utilized within the `body` to seamlessly transform the child reducer (`AddToCart`) into a compatible form that aligns with the parent reducer (`Product`). This allows for smooth integration and interaction between the two.\n```swift\nvar body: some ReducerProtocol<State, Action> {\n    Scope(state: \\.addToCartState, action: /ProductDomain.Action.addToCart) {\n        AddToCartDomain()\n    }\n    Reduce { state, action in\n        // Parent Reducer logic ...\n    }\n}\n```\nThis transformation becomes highly valuable when combining multiple reducers to construct a more complex component.\n\n> In earlier versions, the `pullback` and `combine` operators were employed to carry out the same operation. You can watch this [video](https://youtu.be/Zf2pFEa3uew).\n\n### Collection of states\n\nAre you looking to manage a collection of states? TCA offers excellent support for that as well!\n\nIn this particular example, instead of using a regular array, TCA requires a list of (`Product`) states, which can be achieved by utilizing `IdentifiedArray`:\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var productList: IdentifiedArrayOf<ProductDomain.State> = []\n        // ...    \n    }\n    // ...\n}\n```\n\n#### forEach in Reducer\n\nThe `forEach` operator functions similarly to the [`Scope`](#scope-in-reducers) operator, with the distinction that it operates on a collection of states. It effectively transforms the child reducers into compatible forms that align with the parent reducer.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and Actions ...\n    \n    var body: some ReducerProtocol<State, Action> {\n        Reduce { state, action in\n            // Parent Reducer...\n        }\n        .forEach(\n            \\.productList, \n            action: /ProductListDomain.Action.product(id:action:)\n        ) {\n            ProductDomain()\n        }\n    }\n}\n```\n\nSubsequently, in the user interface, we employ `ForEachStore` and `store.scope` to iterate through all the (`Product`) states and actions. This enables us to send actions to the corresponding cell and modify its state accordingly.\n```swift\nList {\n    ForEachStore(\n        self.store.scope(\n            state: \\.productListState,\n            action: ProductListDomain.Action\n                .product(id: action:)\n        )\n    ) {\n        ProductCell(store: $0)\n    }\n}\n```\n\n> There's a legacy `forEach` operator, If you want to learn more, check out this [video](https://youtu.be/sid-zfggYhQ)\n\n## Dependencies\nIn previous iterations of TCA, `Environment` played a crucial role in consolidating all the dependencies utilized by a domain.\n\nWith the introduction of the [`ReducerProtocol`](https://www.pointfree.co/blog/posts/81-announcing-the-reducer-protocol), we have eliminated the concept of `Environment`. As a result, dependencies now reside directly within the domain.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State ...\n\n    // Actions...\n\n    var fetchProducts:  () async throws -> [Product]\n    var sendOrder: ([CartItem]) async throws -> String\n    var uuid: () -> UUID\n\n    // Reducer ...\n}\n```\n\nNevertheless, we have the option to leverage the [Dependencies Framework](https://github.com/pointfreeco/swift-dependencies) to achieve a more enhanced approach in managing our dependencies:\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State ...\n\n    // Actions...\n\n    @Dependency(\\.apiClient.fetchProducts) var fetchProducts\n    @Dependency(\\.apiClient.sendOrder) var sendOrder\n    @Dependency(\\.uuid) var uuid\n\n    // Reducer ...\n}\n```\n\n> If you want to learn more about how Environment object works on TCA, take a look to this [video](https://youtu.be/sid-zfggYhQ?list=PLHWvYoDHvsOVo4tklgLW1g7gy4Kmk4kjw&t=103)\n\n## Side Effects\nA side effect refers to an observable change that arises when executing a function or method. This encompasses actions such as modifying state outside the function, performing I/O operations to a file or making network requests. TCA facilitates the encapsulation of such side effects through the use of `EffectTask` objects.\n\n<img src=\"./Images/sideEffects1.png\" width=\"80%\" height=\"80%\">\n\n> If you want to learn more about side effects, check out this [video](https://youtu.be/t3HHam3GYkU)\n\n### Network calls\nNetwork calls are a fundamental aspect of mobile development, and TCA offers robust tools to handle them efficiently. As network calls are considered external interactions or [side effects](#side-effects), TCA utilizes the `EffectTask` object to encapsulate these calls. Specifically, network calls are encapsulated within the `EffectTask.task` construct, allowing for streamlined management of asynchronous operations within the TCA framework.\n\nHowever, it's important to note that the task operator alone is responsible for making the web API call. To obtain the actual response, an additional action needs to be implemented, which will capture and store the result within a `TaskResult` object.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and more ...\n    \n    enum Action: Equatable {\n        case fetchProducts\n        case fetchProductsResponse(TaskResult<[Product]>)\n    }\n   \n    var fetchProducts: () async throws -> [Product]\n    var uuid: () -> UUID\n    \n    var body: some ReducerProtocol<State, Action> {\n        // Other child reducers...\n        Reduce { state, action in\n            switch action {\n            case .fetchProducts:\n                return .task {\n                    // Just making the call \n                    await .fetchProductsResponse(\n                        TaskResult { try await fetchProducts() }\n                    )\n                }\n            case .fetchProductsResponse(.success(let products)):\n                // Getting the success response\n                state.productListState = IdentifiedArrayOf(\n                    uniqueElements: products.map {\n                        ProductDomain.State(\n                            id: uuid(),\n                            product: $0\n                        )\n                    }\n                )\n                return .none\n            case .fetchProductsResponse(.failure(let error)):\n                // Getting an error from the web API\n                print(\"Error getting products, try again later.\", error)\n                return .none\n            }\n        }\n    }\n}\n```\n\n> To learn more about network requests in TCA, I recommend watching this insightful [video](https://youtu.be/sid-zfggYhQ?list=PLHWvYoDHvsOVo4tklgLW1g7gy4Kmk4kjw&t=144) that explains asynchronous requests. Additionally, you can refer to this informative [video](https://youtu.be/j2qymM6i9n4) that demonstrates the configuration of a real web API call, providing practical insights into the process.\n\n## Navigation\n\n Navigation is a huge and complex topic. Navigation are alerts, confirmation dialogs, sheets, popovers and links. Also, you can add a custom navigations if you want. In this project you will see alerts and sheets.\n\n### Alerts\n\nThe TCA library offers support for `AlertView`, enabling the addition of custom state and a consistent UI building approach without deviating from the TCA architecture. To create your own alert using TCA, follow these steps:\n\n1. Create the alert actions inside of the Action enum of the reducer. The recommended way is create a nested enum inside the action.\n\n```swift\nenum Action: Equatable {\n    enum Alert {\n        case alertAction1\n        case alertAction2\n        ....\n    }\n}\n```\n\n2. Next, create a case alert and use `PresentationAction`.\n\n```swift\nenum Action: Equatable {\n    case alert(PresentationAction<Alert>)\n    case alertButtonTapped\n\n    enum Alert {\n        case alertAction1\n        case alertAction2\n        ....\n    }\n}\n```\n\n`PresentationAction` is a generic that represents the presented actions and an special action named dismiss. This is very useful case because with the dismiss action, the reducer can manage if a side effect is running and remove to the system. More information about effect cancelling in navigations [here](https://www.pointfree.co/collections/composable-architecture/navigation/ep225-composable-navigation-behavior).\n\n```swift\npublic enum PresentationAction<Action> {\n  /// An action sent to `nil` out the associated presentation state.\n  case dismiss\n\n  /// An action sent to the associated, non-`nil` presentation state.\n  indirect case presented(Action)\n}\n```\n\n3. Create an alert state inside of the reducer.\n```swift\n@Presents var alert: AlertState<Action.Alert>?\n```\n`@Presents` is a property wrapper that you need to use when creates a navigation state in the reducer. The reason to use `@Presents` is when composing a lots of features together, the root state could overflow the stack. More information [here](https://www.pointfree.co/collections/composable-architecture/navigation/ep230-composable-navigation-stack-vs-heap).\n\n\n4. Extent `AlertState` and create as many alerts as you want. You can create a property wrapper or a function if you need some dynamic information.\n\n```swift\nextension AlertState where Action == CartListDomain.Action.Alert {\n    static var successAlert: AlertState {\n        AlertState {\n            TextState(\"Thank you!\")\n        } actions: {\n            ButtonState(action: .dismissSuccessAlert, label: { TextState(\"Done\") })\n            ButtonState(role: .cancel, action: .didCancelConfirmation, label: { TextState(\"Cancel\") })\n        } message: {\n            TextState(\"Your order is in process.\")\n        }\n    }\n\n    static func confirmationAlert(totalPriceString: String) -> AlertState {\n        AlertState {\n            TextState(\"Confirm your purchase\")\n        } actions: {\n            ButtonState(action: .didConfirmPurchase, label: { TextState(\"Pay \\(totalPriceString)\") })\n            ButtonState(role: .cancel, action: .didCancelConfirmation, label: { TextState(\"Cancel\") })\n        } message: {\n            TextState(\"Do you want to proceed with your purchase of \\(totalPriceString)?\")\n        }\n    }\n}\n```\n\n5. Inside of the body of the reducer you can set the alert. As the state is an optional value, you need to implement `ifLet` in the reducer. This is a particular modifier that not need a reducer like a tipical `ifLet` reducer. \n\nAnother question is when you use a reducer for navigation, you will use the binding operator `$` in the state. This is because navigation modifiers in SwiftUI use a binding for presenting, usually the `isPresented` boolean. In this case, in order to manage when the alert is presented or no, you use a binding state in the reducer. Now, the reducer is fully synchronized with the view.\n\n```swift\nvar body: some ReducerOf<Self> {\n        Reduce { state, action in\n            switch action {\n                case .alert:\n                    return .none\n                case .alertButtonTapped:\n                    state.alert = .successAlert\n                    return .none\n            }\n        }\n        .ifLet(\n            \\.$alert, \n            action: \\.alert\n        )\n}\n```\n\n6. Finally, implements `AlertView` modifier in the view. Remember, you use the binding `$` operator.\n\n```swift\n.alert(\n\tstore: store.scope(\n\t\tstate: \\.$alert,\n\t\taction: \\.alert\n\t)\n)\n```\n\n\n<details>\n<summary>See Alerts in previous versions of TCA</summary>\n\nThe TCA library also offers support for `AlertView`, enabling the addition of custom state and a consistent UI building approach without deviating from the TCA architecture. To create your own alert using TCA, follow these steps:\n\n1. Create an `AlertState` with actions of your own domain.\n2. Create the actions that will trigger events for the alert:\n    - Initialize AlertState (`didPressPayButton`)\n    - Dismiss the alert (`didCancelConfirmation`)\n    - Execute the alert's handler (`didConfirmPurchase`)\n\n```swift\nstruct CartListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var confirmationAlert: AlertState<CartListDomain.Action>?\n        \n        // More properties ...\n    }\n    \n    enum Action: Equatable {\n        case didPressPayButton\n        case didCancelConfirmation\n        case didConfirmPurchase\n        \n        // More actions ...\n    }\n    \n    var body: some ReducerProtocol<State, Action> {\n        Reduce { state, action in\n            switch action {\n            case .didCancelConfirmation:\n                state.confirmationAlert = nil\n                return .none\n            case .didConfirmPurchase:\n                // Sent order and Pay ...\n            case .didPressPayButton:\n                state.confirmationAlert = AlertState(\n                    title: TextState(\"Confirm your purchase\"),\n                    message: TextState(\"Do you want to proceed with your purchase of \\(state.totalPriceString)?\"),\n                    buttons: [\n                        .default(\n                            TextState(\"Pay \\(state.totalPriceString)\"),\n                            action: .send(.didConfirmPurchase)),\n                        .cancel(TextState(\"Cancel\"), action: .send(.didCancelConfirmation))\n                    ]\n                )\n                return .none\n            // More actions ...\n            }\n        }\n        .forEach(\\.cartItems, action: /Action.cartItem(id:action:)) {\n            CartItemDomain()\n        }\n    }\n}              \n```\n</details>\n\n### Sheets\n\nOther type of navigation are sheets. To create your own alert using TCA, follow these steps:\n\n1. As the alerts, create the state. You use `@Presents` to avoid accidentally overflow the stack.\n\n```swift\n@Presents var cartState: CartListDomain.State?\n```\n\n2. Next, create the action. Remember to use PresentationAction inside the case of the sheet.\n\n```swift\ncase cart(PresentationAction<CartListDomain.Action>)\n```\n\n3. Create the `ifLet` in the reducer. Here, you need to define the reducer of the destination.\n\n```swift\n.ifLet(\\.$cartState, action: \\.cart) {\n    CartListDomain()\n}\n```\n\n4. Finally, in the view, you can define the sheet operator like this.\n\n```swift\n.sheet(\n\titem: $store.scope(\n\t\tstate: \\.cartState,\n\t\taction: \\.cart\n\t)\n) { store in\n\tCartListView(store: store)\n}\n```\n\n<details>\n<summary>See Sheets in previous versions of TCA</summary>\n### Opening Modal Views\n\nIf you require to open a view modally in SwiftUI, you will need to use sheet modifier and provide a binding parameter:\n```swift\nfunc sheet<Content>(\n    isPresented: Binding<Bool>,\n    onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content\n) -> some View where Content : View\n```\n\nTo utilize this modifier (or any modifier with binding parameters) in TCA, it is necessary to employ the `binding` operator from `viewStore` and supply two parameters:\n\n1. The state property that will undergo mutation.\n2. The action that will trigger the mutation.\n\n```swift\n// Domain:\nstruct Domain: ReducerProtocol {\n    struct State {\n        var shouldOpenModal = false\n    }\n    enum Action {\n        case setCartView(isPresented: Bool)\n    }\n\n    var body: some ReducerProtocol<State, Action> {\n        Reduce { state, action in\n            switch action {\n                case .setCartView(let isPresented):\n                    state.shouldOpenModal = isPresented\n            }\n        }\n    }\n}\n\n// UI:\nText(\"Parent View\")\n.sheet(\n    isPresented: viewStore.binding(\n        get: \\.shouldOpenModal,\n        send: Action.setModalView(isPresented:)\n    )\n) {\n    Text(\"I'm a Modal View!\")\n}\n```\n\n> If you want to lean more about Binding with TCA and SwiftUI, take a look to this [video](https://youtu.be/Ilr8AsoggIY).\n</details>\n\n## Testing\n\n### Testing Basics\n\nTesting is a crucial part of software development. TCA has its own tools to test reducers in a very simple way.\n\nWhen you test a reducer, you will use a TestStore class passing an initial state and a reducer like the store that you are using in the production code.\n\nNext, you can send an action but, in this case, send receive a closure that you need to expect the result of this action. For example, when you send increseCounter action, you expect that count is equal to 1 if previously, your state counter is 0.\n\nFinally, you send a decreaseCounter and the expectation of this action is count state equal to 0 because previously count was setted to 1.\n\n```swift\n@MainActor\nclass CounterDomainTest: XCTestCase {\n    func testHappyPath() {\n        let store = TestStore(\n            initialState: CounterDomain.State(),\n            reducer: { CounterDomain() }\n        )\n\n        await store.send(.increaseCounter) {\n            $0.count = 1\n        }\n\n        await store.send(.decreaseCounter) {\n            $0.count = 0\n        }\n    }\n}\n```\n\n### Testing Side effects\n\nThe first thing is the ability to mock every side effect of the system. To do that TestStore has a closure for this purpose.\n\nNotice that `fetchProducts` action has a side effect. When it finishes, send an action `fetchProductsResponse` back to the system. When you test this, you will use `store.receive` for response actions.\n\n```swift\n@MainActor\nclass ProductListDomainTest: XCTestCase {\n    func testSideEffects() {\n        let products: [Product] = ...\n        let store = TestStore(\n            initialState: ProductListDomain.State(),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.apiClient.fetchProducts = { products }\n        }\n\n         await store.send(.fetchProducts) {\n            $0.dataLoadingStatus = .loading\n        }\n        \n        await store.receive(.fetchProductsResponse(.success(products))) {\n            $0.products = products\n            $0.dataLoadingStatus = .success\n        }\n    }\n}\n```\n\n### Testing CasePathable\n\nCasePathable is a nice macro that it has a lot of useful tips. One of those is using keypaths for testing actions. For example, if you have this test.\n\n```swift\nawait store.send(\n            .cartItem(\n                .element(\n                    id: cartItemId1,\n                    action: .deleteCartItem(product: Product.sample[0]))\n            )\n        ) {\n            ...\n        }\n```\n\nWe can update this with:\n\n```swift\nawait store.send(\\.cartItem[id: cartItemId1].deleteCartItem, Product.sample[0]) {\n    ...\n}\n```\n\nAnother example:\n\n```swift\nawait store.send(.alert(.presented(.didConfirmPurchase)))\n```\n\n```swift\nawait store.send(\\.alert.didConfirmPurchase)\n```\n\n## Other topics\n\n### Optional States\n\nBy default, TCA keeps a state in memory throughout the entire lifecycle of an app. However, in certain scenarios, maintaining a state can be resource-intensive and unnecessary. One such case is when dealing with modal views that are displayed for a short duration. In these situations, it is more efficient to use optional states.\n\nCreating an optional state in TCA follows the same approach as declaring any optional value in Swift. Simply define the property within the parent state, but instead of assigning a default value, declare it as optional. For instance, in the provided example, the `cartState` property holds an optional state for a Cart List.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var productListState: IdentifiedArrayOf<ProductDomain.State> = []\n        var shouldOpenCart = false\n        var cartState: CartListDomain.State?\n        \n        // More properties...\n    }\n}\n```\n\nNow, in the `Reduce` function, we can utilize the `ifLet` operator to transform the child reducer (`CartListDomain`) into one that is compatible with the parent reducer (`ProductList`). \n\nIn the provided example, the `CartListDomain` will be evaluated only if the `cartState` is non-nil. To assign a new non-optional state, the parent reducer will need to initialize the property (`cartState`) when a specific action (`setCartView`) is triggered. \n\nThis approach ensures that the optional state is properly handled within the TCA framework and allows for seamless state management between the parent and the optional child reducers.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and Actions ...\n    \n    var body: some ReducerProtocol<State, Action> {\n        Reduce { state, action in\n            switch action {\n            //  More cases ...\n            case .setCartView(let isPresented):\n                state.shouldOpenCart = isPresented\n                state.cartState = isPresented\n                ? CartListDomain.State(...)\n                : nil\n                return .none\n            }\n        }\n        .ifLet(\\.cartState, action: /ProductListDomain.Action.cart) {\n            CartListDomain()\n        }\n    }\n}\n```\n\nLastly, in the view, you can employ `IfLetStore` to unwrap a store with optional state. This allows you to conditionally display the corresponding view that operates with that particular state.\n\n\n```swift\nList {\n    ForEachStore(\n        self.store.scope(\n            state: \\.productListState,\n            action: ProductListDomain.Action\n                .product(id: action:)\n        )\n    ) {\n        ProductCell(store: $0)\n    }\n}\n.sheet(\n    isPresented: viewStore.binding(\n        get: \\.shouldOpenCart,\n        send: ProductListDomain.Action.setCartView(isPresented:)\n    )\n) {\n    IfLetStore(\n        self.store.scope(\n            state: \\.cartState,\n            action: ProductListDomain.Action.cart\n        )\n    ) {\n        CartListView(store: $0)\n    }\n}\n```\n\n> If you want to learn more about optional states, check out this [video](https://youtu.be/AV0laQw2OjM).\n\n### Private Actions\n\nBy default, when you declare an action in a TCA domain, it is accessible to other reducers as well. However, there are situations where an action is intended to be specific to a particular reducer and does not need to be exposed outside of it. \n\nIn such cases, you can simply declare private functions to encapsulate those actions within the domain's scope. This approach ensures that the actions remain private and only accessible within the intended context, enhancing the encapsulation and modularity of your TCA implementation:\n\n```swift\nvar body: some ReducerProtocol<State, Action>\n    // More reducers ...\n    Reduce { state, action in\n        switch action {\n        // More actions ...\n        case .cart(let action):\n            switch action {\n            case .didPressCloseButton:\n                return closeCart(state: &state)\n            case .dismissSuccessAlert:\n                resetProductsToZero(state: &state)\n\n                return .task {\n                    .closeCart\n                }\n            }\n        case .closeCart:\n            return closeCart(state: &state)\n        }\n    }\n}\n\nprivate func closeCart(\n        state: inout State\n) -> Effect<Action, Never> {\n    state.shouldOpenCart = false\n    state.cartState = nil\n\n    return .none\n}\n\nprivate func resetProductsToZero(\n    state: inout State\n) {\n    for id in state.productListState.map(\\.id)\n    where state.productListState[id: id]?.count != 0  {\n        state.productListState[id: id]?.addToCartState.count = 0\n    }\n}\n```\n\n> For more about private actions, check out this [video](https://youtu.be/7BkZX_7z-jw).\n\n3. Invoke the UI\n\n<img src=\"./Images/alertView1.png\" width=\"50%\" height=\"50%\">\n\n```swift\nlet store: Store<CartListDomain.State, CartListDomain.Action>\n\nText(\"Parent View\")\n.alert(\n    self.store.scope(state: \\.confirmationAlert, action: { $0 }),\n    dismiss: .didCancelConfirmation\n)\n```\n\n> Explicit action is always needed for `store.scope`. Check out this commit to learn more: https://github.com/pointfreeco/swift-composable-architecture/commit/da205c71ae72081647dfa1442c811a57181fb990\n\nThis [video](https://youtu.be/U3EMduy-DhE) explains more about AlertView in SwiftUI and TCA.\n\n### Making a Root Domain with Tab View\n\nCreating a Root Domain in TCA is similar to creating any other domain. In this case, each property within the state will correspond to a complex substate. To handle tab logic, we can include an enum that represents each tab item, providing a structured approach to managing the different tabs:\n\n```swift\nstruct RootDomain: ReducerProtocol {\n    struct State: Equatable {\n        var selectedTab = Tab.products\n        var productListState = ProductListDomain.State()\n        var profileState = ProfileDomain.State()\n    }\n    \n    enum Tab {\n        case products\n        case profile\n    }\n    \n    enum Action: Equatable {\n        case tabSelected(Tab)\n        case productList(ProductListDomain.Action)\n        case profile(ProfileDomain.Action)\n    }\n    \n    // Dependencies\n    var fetchProducts: @Sendable () async throws -> [Product]\n    var sendOrder:  @Sendable ([CartItem]) async throws -> String\n    var fetchUserProfile:  @Sendable () async throws -> UserProfile\n    var uuid: @Sendable () -> UUID\n    \n    static let live = Self(\n        fetchProducts: APIClient.live.fetchProducts,\n        sendOrder: APIClient.live.sendOrder,\n        fetchUserProfile: APIClient.live.fetchUserProfile,\n        uuid: { UUID() }\n    )\n    \n    var body: some ReducerProtocol<State, Action> {\n        Reduce { state, action in\n            switch action {\n            case .productList:\n                return .none\n            case .tabSelected(let tab):\n                state.selectedTab = tab\n                return .none\n            case .profile:\n                return .none\n            }\n        }\n        Scope(state: \\.productListState, action: /RootDomain.Action.productList) {\n            ProductListDomain(\n                fetchProducts: fetchProducts,\n                sendOrder: sendOrder,\n                uuid: uuid\n            )\n        }\n        Scope(state:  \\.profileState, action: /RootDomain.Action.profile) {\n            ProfileDomain(fetchUserProfile: fetchUserProfile)\n        }\n    }\n}\n```\n\nWhen it comes to the UI implementation, it closely resembles the standard SwiftUI approach, with a small difference. Instead of using a regular property, we hold the `store` property to manage the currently selected tab:\n\n```swift\nstruct RootView: View {\n    let store: Store<RootDomain.State, RootDomain.Action>\n    \n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            TabView(\n                selection: viewStore.binding(\n                    get: \\.selectedTab,\n                    send: RootDomain.Action.tabSelected\n                )\n            ) {\n                ProductListView(\n                    store: self.store.scope(\n                        state: \\.productListState,\n                        action: RootDomain.Action\n                            .productList\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"list.bullet\")\n                    Text(\"Products\")\n                }\n                .tag(RootDomain.Tab.products)\n                ProfileView(\n                    store: self.store.scope(\n                        state: \\.profileState,\n                        action: RootDomain.Action.profile\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"person.fill\")\n                    Text(\"Profile\")\n                }\n                .tag(RootDomain.Tab.profile)\n            }\n        }\n    }\n}\n```\n\nTo call RootView, we provide the initial domain state and the reducer:\nTo instantiate the `RootView`, you need to provide two parameters: the initial domain state and the reducer:\n\n```swift\n@main\nstruct OnlineStoreTCAApp: App {\n    var body: some Scene {\n        WindowGroup {\n            RootView(\n                store: Store(\n                    initialState: RootDomain.State(),\n                    reducer: RootDomain.live\n                )\n            )\n        }\n    }\n}\n```\n\nThese elements enable the proper initialization and functioning of the `RootView` within the TCA architecture.\n\n> For a comprehensive understanding of this implementation, I recommend checking out this [video](https://youtu.be/a_FwMVIhCHY).\n\n## Contact\nIf you have any feedback, I would love to hear from you. Please feel free to reach out to me through any of my social media channels:\n\n* [Youtube](https://youtube.com/@swiftandtips)\n* [Twitter](https://twitter.com/swiftandtips)\n* [LinkedIn](https://www.linkedin.com/in/pedrorojaslo/)\n* [Mastodon](https://iosdev.space/@swiftandtips)\n\nThanks for reading, and have a great day! 😄\n"
  }
]