[
  {
    "path": ".azurepipelines/aks-cost-optimization.yml",
    "content": "name: aks-cost-optimization\n\ntrigger: none\n\npr: none\n\nvariables:\n  - group: contosotraders-cloudtesting-variable-group\n  - name: ACR_NAME\n    value: contosotradersacr\n  - name: AKS_CLUSTER_NAME\n    value: contoso-traders-aks\n  - name: AKS_CPU_LIMIT\n    value: 250m\n  - name: AKS_MEMORY_LIMIT\n    value: 256Mi\n  - name: AKS_REPLICAS\n    value: \"1\"\n  - name: AKS_SECRET_NAME_ACR_PASSWORD\n    value: contoso-traders-acr-password\n  - name: KV_NAME\n    value: contosotraderskv\n  - name: LOAD_TEST_SERVICE_NAME\n    value: contoso-traders-loadtest\n  - name: PRODUCTS_ACR_REPOSITORY_NAME\n    value: contosotradersapiproducts\n  - name: RESOURCE_GROUP_NAME\n    value: contoso-traders-rg\n\npool:\n  vmImage: ubuntu-latest\n\nstages:\n  - stage: default\n    jobs:\n      - job: aks_cost_optimization\n        strategy:\n          matrix:\n            Cpu250m_Mem256Mi:\n              AKS_REPLICAS: \"1\"\n              AKS_CPU_LIMIT: \"250m\"\n              AKS_MEMORY_LIMIT: \"256Mi\"\n            Cpu250m_Mem128Mi:\n              AKS_REPLICAS: \"1\"\n              AKS_CPU_LIMIT: \"250m\"\n              AKS_MEMORY_LIMIT: \"128Mi\"\n            Cpu100m_Mem256Mi:\n              AKS_REPLICAS: \"1\"\n              AKS_CPU_LIMIT: \"100m\"\n              AKS_MEMORY_LIMIT: \"256Mi\"\n            Cpu100m_Mem128Mi:\n              AKS_REPLICAS: \"1\"\n              AKS_CPU_LIMIT: \"100m\"\n              AKS_MEMORY_LIMIT: \"128Mi\"\n          maxParallel: 1\n        steps:\n          - task: AzureCLI@1\n            displayName: get products api endpoint\n            name: getProductsApiEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)\"\n          - task: replacetokens@5\n            displayName: substitute tokens in deployment manifest\n            inputs:\n              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              tokenPattern: custom\n              tokenPrefix: \"{\"\n              tokenSuffix: \"}\"\n              inlineVariables: |\n                SUFFIX: \"$(SUFFIX)\"\n                AKS_REPLICAS: \"$(AKS_REPLICAS)\"\n                AKS_CPU_LIMIT: \"$(AKS_CPU_LIMIT)\"\n                AKS_MEMORY_LIMIT: \"$(AKS_MEMORY_LIMIT)\"\n          - task: KubernetesManifest@1\n            displayName: apply deployment manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest\n              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)\n          - task: AzureLoadTest@1\n            displayName: load test (products API)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n              loadtestConfigFile: ./loadtests/contoso-traders-products.yaml\n              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)\n              env: |\n                [\n                  {\n                    \"name\": \"domain\",\n                    \"value\": \"$(getProductsApiEndpoint.productsApiEndpoint)\"\n                  },\n                  {\n                    \"name\": \"protocol\",\n                    \"value\": \"https\"\n                  },\n                  {\n                    \"name\": \"path\",\n                    \"value\": \"v1/Products/1\"\n                  },\n                  {\n                    \"name\": \"threads_per_engine\",\n                    \"value\": \"25\"\n                  },\n                  {\n                    \"name\": \"ramp_up_time\",\n                    \"value\": \"0\"\n                  },\n                  {\n                    \"name\": \"duration_in_sec\",\n                    \"value\": \"45\"\n                  }\n                ]\n\n      - job: reset_aks\n        dependsOn: [aks_cost_optimization]\n        condition: always()\n        steps:\n          - task: AzureCLI@1\n            displayName: get products api endpoint\n            name: getProductsApiEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)\"\n          - task: replacetokens@5\n            displayName: substitute tokens in deployment manifest\n            inputs:\n              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              tokenPattern: custom\n              tokenPrefix: \"{\"\n              tokenSuffix: \"}\"\n              inlineVariables: |\n                SUFFIX: \"$(SUFFIX)\"\n                AKS_REPLICAS: \"$(AKS_REPLICAS)\"\n                AKS_CPU_LIMIT: \"$(AKS_CPU_LIMIT)\"\n                AKS_MEMORY_LIMIT: \"$(AKS_MEMORY_LIMIT)\"\n          - task: KubernetesManifest@1\n            displayName: apply deployment manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest\n              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)\n"
  },
  {
    "path": ".azurepipelines/contoso-traders-cloud-testing.yml",
    "content": "name: contoso-traders-cloud-testing\n\ntrigger:\n  - main\n\npr:\n  branches:\n    include:\n      - main\n  paths:\n    exclude:\n      - \"docs/**\"\n      - \"demo-scripts/**\"\n\nvariables:\n  - group: contosotraders-cloudtesting-variable-group\n  - name: ACR_NAME\n    value: contosotradersacr\n  - name: AKS_CLUSTER_NAME\n    value: contoso-traders-aks\n  - name: AKS_CPU_LIMIT\n    value: 250m\n  - name: AKS_DNS_LABEL\n    value: contoso-traders-products\n  - name: AKS_MEMORY_LIMIT\n    value: 256Mi\n  - name: AKS_NODES_RESOURCE_GROUP_NAME\n    value: contoso-traders-aks-nodes-rg\n  - name: AKS_REPLICAS\n    value: \"1\"\n  - name: AKS_SECRET_NAME_ACR_PASSWORD\n    value: contoso-traders-acr-password\n  - name: AKS_SECRET_NAME_KV_ENDPOINT\n    value: contoso-traders-kv-endpoint\n  - name: AKS_SECRET_NAME_MI_CLIENTID\n    value: contoso-traders-mi-clientid\n  - name: AZURE_AD_APP_NAME\n    value: contoso-traders-cloud-testing-app\n  - name: CARTS_ACA_NAME\n    value: contoso-traders-carts\n  - name: CARTS_ACR_REPOSITORY_NAME\n    value: contosotradersapicarts\n  - name: CARTS_INTERNAL_ACA_NAME\n    value: contoso-traders-intcarts\n  - name: CDN_PROFILE_NAME\n    value: contoso-traders-cdn\n  - name: CHAOS_AKS_EXPERIMENT_NAME\n    value: contoso-traders-chaos-aks-experiment\n  - name: KV_NAME\n    value: contosotraderskv\n  - name: LOAD_TEST_SERVICE_NAME\n    value: contoso-traders-loadtest\n  - name: MSGRAPH_API_ID\n    value: 00000003-0000-0000-c000-000000000000\n  - name: MSGRAPH_API_PERMISSION_EMAIL\n    value: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0=Scope\n  - name: MSGRAPH_API_PERMISSION_USER_READ\n    value: e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope\n  - name: PRODUCTS_ACR_REPOSITORY_NAME\n    value: contosotradersapiproducts\n  - name: PRODUCTS_DB_NAME\n    value: productsdb\n  - name: PRODUCTS_DB_SERVER_NAME\n    value: contoso-traders-products\n  - name: PRODUCTS_DB_USER_NAME\n    value: localadmin\n  - name: PRODUCT_DETAILS_CONTAINER_NAME\n    value: product-details\n  - name: PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME\n    value: contosotradersimg\n  - name: PRODUCT_LIST_CONTAINER_NAME\n    value: product-list\n  - name: PRODUCTS_CDN_ENDPOINT_NAME\n    value: contoso-traders-images\n  - name: RESOURCE_GROUP_NAME\n    value: contoso-traders-rg\n  - name: STORAGE_ACCOUNT_NAME\n    value: contosotradersimg\n  - name: UI_CDN_ENDPOINT_NAME\n    value: contoso-traders-ui2\n  - name: UI_STORAGE_ACCOUNT_NAME\n    value: contosotradersui2\n  - name: USER_ASSIGNED_MANAGED_IDENTITY_NAME\n    value: contoso-traders-mi-kv-access\n\npool:\n  vmImage: ubuntu-latest\n\nstages:\n  - stage: default\n    jobs:\n      - job: provision\n        steps:\n          # section #0: optional configuration of the Azure AD app.\n          # create the Azure AD application (and update it if it already exists).\n          # note: This is an idempotent operation.\n          - task: AzureCLI@1\n            displayName: create/update azure active directory app\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az ad app create --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --sign-in-audience AzureADandPersonalMicrosoftAccount\n          - task: AzureCLI@1\n            displayName: get azure ad app's object id\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            name: getAzureAdAppObjId\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=azureAdAppObjId;isOutput=true]$(az ad app list --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --query [].id -o tsv)\"\n          - task: AzureCLI@1\n            displayName: get azure ad app's client id\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            name: getAzureAdAppClientId\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=azureAdAppClientId;isOutput=true]$(az ad app list --display-name $(AZURE_AD_APP_NAME)$(SUFFIX) --query [].appId -o tsv)\"\n          - task: AzureCLI@1\n            displayName: register app as a spa\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az rest \\\n                  --method PATCH \\\n                  --uri https://graph.microsoft.com/v1.0/applications/$(getAzureAdAppObjId.azureAdAppObjId) \\\n                  --headers 'Content-Type=application/json' \\\n                  --body '{\"spa\":{\"redirectUris\":[\"https://localhost:3000/authcallback\",\"http://localhost:3000/authcallback\",\"https://production.contosotraders.com/authcallback\",\"https://cloudtesting.contosotraders.com/authcallback\"]}}'\n          - task: AzureCLI@1\n            displayName: enable issuance of id, access tokens\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az ad app update --id $(getAzureAdAppObjId.azureAdAppObjId) --enable-access-token-issuance true --enable-id-token-issuance true\n          - task: AzureCLI@1\n            displayName: enable email claim in access token\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az ad app update --id $(getAzureAdAppObjId.azureAdAppObjId) --optional-claims \"{\\\"accessToken\\\":[{\\\"name\\\":\\\"email\\\",\\\"essential\\\":false}]}\"\n          # note: requesting MS Graph permissions in Azure AD app unfortunately isn't idempotent.\n          # Even, if you have already requested the permissions, it'll keep adding to the list of requested permissions until you hit limit on max permissions requested.\n          # Details: https://github.com/Azure/azure-cli/issues/24512\n          - task: AzureCLI@1\n            displayName: delete any requested Microsoft Graph permissions\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az ad app permission delete \\\n                  --id $(getAzureAdAppObjId.azureAdAppObjId) \\\n                  --api $(MSGRAPH_API_ID)\n          - task: AzureCLI@1\n            displayName: request Microsoft Graph permissions\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az ad app permission add \\\n                  --id $(getAzureAdAppObjId.azureAdAppObjId) \\\n                  --api $(MSGRAPH_API_ID) \\\n                  --api-permissions $(MSGRAPH_API_PERMISSION_USER_READ) $(MSGRAPH_API_PERMISSION_EMAIL)\n\n          #\n          # section #1: provisioning the resources on Azure using bicep templates\n          #\n          # The first step is to create the resource group: `contoso-traders-rg`.\n          # The below step can also be manually executed as follows:\n          # az deployment sub create --location {LOCATION} --template-file .\\createResourceGroup.bicep\n          # Note: You can specify any location for `{LOCATION}`. It's the region where the deployment metadata will be stored, and not\n          # where the resource groups will be deployed.\n          - task: AzureResourceManagerTemplateDeployment@3\n            displayName: create resource group\n            inputs:\n              deploymentScope: Subscription\n              azureResourceManagerConnection: SERVICEPRINCIPAL\n              location: $(DEPLOYMENTREGION)\n              csmFile: ./iac/createResourceGroup.bicep\n              overrideParameters: -rgName $(RESOURCE_GROUP_NAME) -suffix $(SUFFIX) -rgLocation $(DEPLOYMENTREGION)\n          # Next step is to deploy the Azure resources to the resource group `contoso-traders-rg` created above. The deployed resources\n          # include storage accounts, function apps, app services cosmos db, and service bus etc.\n          # The below step can also be manually executed as follows:\n          # az deployment group create -g contoso-traders-rg --template-file .\\createResources.bicep --parameters .\\createResources.parameters.json\n          # Note: The `createResources.parameters.json` file contains the parameters for the deployment; specifically the environment name.\n          # You can modify the parameters to customize the deployment.\n          # Note: The bicep template outputs are not shown in the logs. You can extract the outputs as shown here:\n          # https://github.com/Azure/arm-deploy#another-example-on-how-to-use-this-action-to-get-the-output-of-arm-template\n          - task: AzureResourceManagerTemplateDeployment@3\n            displayName: create resources\n            inputs:\n              deploymentScope: Resource Group\n              azureResourceManagerConnection: SERVICEPRINCIPAL\n              location: $(DEPLOYMENTREGION)\n              resourceGroupName: \"$(RESOURCE_GROUP_NAME)$(SUFFIX)\"\n              csmFile: ./iac/createResources.bicep\n              csmParametersFile: ./iac/createResources.parameters.json\n              overrideParameters: -suffix $(SUFFIX) -sqlPassword $(SQLPASSWORD) -deployPrivateEndpoints $(DEPLOYPRIVATEENDPOINTS)\n\n          # Add the logged-in service principal to the key vault access policy\n          - task: AzureCLI@1\n            displayName: add service principal to kv access policy\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az keyvault set-policy -n $(KV_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --secret-permissions get list set --object-id $(az ad sp show --id $(az account show --query \"user.name\" -o tsv) --query \"id\" -o tsv)\n          # The AKS agent pool needs to be assigned the user-assigned managed identity created (which has kv access)\n          - task: AzureCLI@1\n            displayName: assign user-assigned managed-identity to aks agentpool\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az vmss identity assign \\\n                  --identities $(az identity show -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(USER_ASSIGNED_MANAGED_IDENTITY_NAME)$(SUFFIX) --query \"id\" -o tsv) \\\n                  --ids $(az vmss list -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX) --query \"[0].id\" -o tsv) \\\n          # Seed the DBs and storage accounts\n          - script: sqlcmd -S $(PRODUCTS_DB_SERVER_NAME)$(SUFFIX).database.windows.net -U $(PRODUCTS_DB_USER_NAME) -P $(SQLPASSWORD) -d $(PRODUCTS_DB_NAME) -i ./src/ContosoTraders.Api.Products/Migration/productsdb.sql\n            displayName: seed products db\n          - task: AzureCLI@1\n            displayName: seed product image (product details)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az storage blob sync --account-name '$(PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$(PRODUCT_DETAILS_CONTAINER_NAME)' -s 'src/ContosoTraders.Api.Images/product-details'\n          - task: AzureCLI@1\n            displayName: seed product image (product list)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az storage blob sync --account-name '$(PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$(PRODUCT_LIST_CONTAINER_NAME)' -s 'src/ContosoTraders.Api.Images/product-list'\n          - task: AzureCLI@1\n            displayName: purge product images cdn endpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '$(PRODUCTS_CDN_ENDPOINT_NAME)$(SUFFIX)' -g '$(RESOURCE_GROUP_NAME)$(SUFFIX)' --profile-name '$(CDN_PROFILE_NAME)$(SUFFIX)'\n          - task: AzureCLI@1\n            displayName: extract acr password\n            name: extractAcrPassword\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=acrPassword;isOutput=true;issecret=true]$(az acr credential show -n $(ACR_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --query \"passwords[0].value\" --output tsv)\"\n          - script: docker login $(ACR_NAME)$(SUFFIX).azurecr.io --username $(ACR_NAME)$(SUFFIX) --password $(extractAcrPassword.acrPassword)\n            displayName: azure container registry login\n          - task: AzureCLI@1\n            displayName: set aks context\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)\n\n          #\n          # section #2: deploy the carts api\n          #\n          - script: docker build src -f ./src/ContosoTraders.Api.Carts/Dockerfile -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):latest -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)\n            displayName: docker build\n          - script: docker push --all-tags $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME)\n            displayName: docker push (to acr)\n          - task: AzureCLI@1\n            displayName: deploy to aca\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az config set extension.use_dynamic_install=yes_without_prompt\n                az containerapp update -n $(CARTS_ACA_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --image $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)\n          - task: AzureCLI@1\n            displayName: deploy to aca (internal)\n            condition: eq(variables['DEPLOYPRIVATEENDPOINTS'], 'true')\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az config set extension.use_dynamic_install=yes_without_prompt\n                az containerapp update -n $(CARTS_INTERNAL_ACA_NAME)$(SUFFIX) -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --image $(ACR_NAME)$(SUFFIX).azurecr.io/$(CARTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)\n          - task: AzureCLI@1\n            displayName: get carts api endpoint\n            name: getCartsApiEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=cartsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name cartsApiEndpoint --query value -o tsv)\"\n\n          #\n          # section #3: deploy the products api\n          #\n          - task: HelmInstaller@1\n            displayName: install helm\n            name: installHelm\n            inputs:\n              helmVersionToInstall: 3.9.0\n          - script: docker build src -f ./src/ContosoTraders.Api.Products/Dockerfile -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):latest -t $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)\n            displayName: docker build\n          - script: docker push --all-tags $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME)\n            displayName: docker push (to acr)\n          # more details: https://stackoverflow.com/a/45881324\n          - script: kubectl delete secret $(AKS_SECRET_NAME_ACR_PASSWORD) --ignore-not-found\n            displayName: delete any existing kubernetes secret (acr password)\n          - script: |\n              kubectl create secret docker-registry $(AKS_SECRET_NAME_ACR_PASSWORD) \\\n                --docker-server=$(ACR_NAME)$(SUFFIX).azurecr.io \\\n                --docker-username=$(ACR_NAME)$(SUFFIX) \\\n                --docker-password=$(extractAcrPassword.acrPassword) \\\n                --save-config \\\n                -o yaml | kubectl apply -f -\n            displayName: create kubernetes secret (acr password)\n          - task: AzureCLI@1\n            displayName: get managedIdentityClientId\n            name: getManagedIdentityClientId\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=managedIdentityClientId;isOutput=true]$(az identity show -g $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(USER_ASSIGNED_MANAGED_IDENTITY_NAME)$(SUFFIX) --query \"clientId\" -o tsv)\"\n          - task: KubernetesManifest@1\n            displayName: create kubernetes secret (kv endpoint)\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: createSecret\n              secretName: $(AKS_SECRET_NAME_KV_ENDPOINT)\n              secretType: generic\n              secretArguments: --from-literal=$(AKS_SECRET_NAME_KV_ENDPOINT)=\"https://$(KV_NAME)$(SUFFIX).vault.azure.net/\"\n          - task: KubernetesManifest@1\n            displayName: create kubernetes secret (managed identity client id)\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: createSecret\n              secretName: $(AKS_SECRET_NAME_MI_CLIENTID)\n              secretType: generic\n              secretArguments: --from-literal=$(AKS_SECRET_NAME_MI_CLIENTID)=$(getManagedIdentityClientId.managedIdentityClientId)\n          - task: replacetokens@5\n            displayName: substitute tokens in deployment manifest\n            inputs:\n              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              tokenPattern: custom\n              tokenPrefix: \"{\"\n              tokenSuffix: \"}\"\n              inlineVariables: |\n                SUFFIX: \"$(SUFFIX)\"\n                AKS_REPLICAS: \"$(AKS_REPLICAS)\"\n                AKS_CPU_LIMIT: \"$(AKS_CPU_LIMIT)\"\n                AKS_MEMORY_LIMIT: \"$(AKS_MEMORY_LIMIT)\"\n          - task: KubernetesManifest@1\n            displayName: apply deployment manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n              containers: $(ACR_NAME)$(SUFFIX).azurecr.io/$(PRODUCTS_ACR_REPOSITORY_NAME):$(Build.SourceVersion)\n              imagePullSecrets: $(AKS_SECRET_NAME_ACR_PASSWORD)\n          - task: KubernetesManifest@1\n            displayName: apply service manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Service.yaml\n          # setup chaos mesh\n          - task: KubernetesManifest@1\n            displayName: apply namespace manifest (chaos-testing)\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml\n          - script: |\n              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)\n              helm repo add chaos-mesh https://charts.chaos-mesh.org\n              helm repo update\n              helm upgrade --install chaos-mesh chaos-mesh/chaos-mesh --namespace=chaos-testing --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath=/run/containerd/containerd.sock\n            displayName: setup chaos mesh\n          # create the ingress controller\n          - script: |\n              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)\n              helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\n              helm repo update\n              helm upgrade --install --wait --timeout=1h nginx-ingress ingress-nginx/ingress-nginx \\\n                --set controller.replicaCount=1 \\\n                --set controller.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n                --set defaultBackend.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n                --set controller.admissionWebhooks.patch.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n                --set controller.service.externalTrafficPolicy=Local\n            displayName: create ingress controller\n          - task: AzureCLI@1\n            displayName: set dns label on public ip\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az network public-ip update --dns-name $(AKS_DNS_LABEL)$(SUFFIX) -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX) -n $(az network public-ip list --query \"[?starts_with(name,'kubernetes-') ].name\" -o tsv -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX))\n          # hack: extract the full fqdn / dns label of the aks app's public IP address\n          - task: AzureCLI@1\n            displayName: get aks-fqdn\n            name: getAksFqdn\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              # note: There should be a whitespace between ')' and ']'. More details: https://stackoverflow.com/a/59154958\n              inlineScript: echo \"##vso[task.setvariable variable=aksFqdn;isOutput=true]$(az network public-ip list --query \"[?starts_with(name,'kubernetes-') ].dnsSettings.fqdn\" -o tsv -g $(AKS_NODES_RESOURCE_GROUP_NAME)$(SUFFIX))\"\n          # install cert-manager\n          - task: KubernetesManifest@1\n            displayName: apply namespace manifest (cert-manager)\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml\n          - script: |\n              az aks get-credentials --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --name $(AKS_CLUSTER_NAME)$(SUFFIX)\n              kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml\n            displayName: install cert-manager\n          - bash: sleep 30s\n            displayName: sleep for 30 seconds\n          - task: KubernetesManifest@1\n            displayName: apply clusterIssuer manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml\n          - task: replacetokens@5\n            displayName: substitute tokens in certificate manifest\n            inputs:\n              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml\n              tokenPattern: custom\n              tokenPrefix: \"{\"\n              tokenSuffix: \"}\"\n              inlineVariables: \"AKS_FQDN: $(getAksFqdn.aksFqdn)\"\n          - task: KubernetesManifest@1\n            displayName: apply certificate manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml\n          - task: replacetokens@5\n            displayName: substitute tokens in ingress manifest\n            inputs:\n              targetFiles: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml\n              tokenPattern: custom\n              tokenPrefix: \"{\"\n              tokenSuffix: \"}\"\n              inlineVariables: \"AKS_FQDN: $(getAksFqdn.aksFqdn)\"\n          - task: KubernetesManifest@1\n            displayName: apply ingress manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml\n          - task: KubernetesManifest@1\n            displayName: apply clusterRole manifest\n            inputs:\n              connectionType: azureResourceManager\n              azureSubscriptionConnection: SERVICEPRINCIPAL\n              azureResourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              kubernetesCluster: $(AKS_CLUSTER_NAME)$(SUFFIX)\n              action: deploy\n              manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml\n          - task: AzureCLI@1\n            displayName: set productsApiEndpoint in kv\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az keyvault secret set --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --value $(getAksFqdn.aksFqdn) --description \"endpoint url (fqdn) of the products api\"\n          - task: AzureCLI@1\n            displayName: get products api endpoint\n            name: getProductsApiEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=productsApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name productsApiEndpoint --query value -o tsv)\"\n\n          #\n          # section #4: deploy the ui\n          #\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_APIURLSHOPPINGCART]https://$(getCartsApiEndpoint.cartsApiEndpoint)/v1\"\n            displayName: set REACT_APP_APIURLSHOPPINGCART\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_APIURL]https://$(getProductsApiEndpoint.productsApiEndpoint)/v1\"\n            displayName: set REACT_APP_APIURL\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_B2CCLIENTID]$(getAzureAdAppClientId.azureAdAppClientId)\"\n            displayName: set REACT_APP_B2CCLIENTID\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n          - task: NodeTool@0\n            displayName: install nodejs\n            inputs:\n              versionSpec: 18.x\n          - script: npm ci\n            displayName: npm ci\n            workingDirectory: src/ContosoTraders.Ui.Website\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_BINGMAPSKEY]$(BINGMAPSKEY)\"\n            displayName: set REACT_APP_BINGMAPSKEY            \n          - script: npm run build\n            displayName: npm run build\n            workingDirectory: src/ContosoTraders.Ui.Website\n          - task: AzureCLI@1\n            displayName: deploy ui to storage\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az storage blob sync --account-name '$(UI_STORAGE_ACCOUNT_NAME)$(SUFFIX)' -c '$web' -s 'src/ContosoTraders.Ui.Website/build'\n          - task: AzureCLI@1\n            displayName: purge ui cdn endpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '$(UI_CDN_ENDPOINT_NAME)$(SUFFIX)' -g '$(RESOURCE_GROUP_NAME)$(SUFFIX)' --profile-name '$(CDN_PROFILE_NAME)$(SUFFIX)'\n          - task: AzureCLI@1\n            displayName: get ui cdn endpoint\n            name: getUiCdnEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=uiCdnEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name uiCdnEndpoint --query value -o tsv)\"\n          - task: AzureCLI@1\n            displayName: register auth callback (UI CDN)\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: |\n                az rest \\\n                  --method PATCH \\\n                  --uri https://graph.microsoft.com/v1.0/applications/$(getAzureAdAppObjId.azureAdAppObjId) \\\n                  --headers 'Content-Type=application/json' \\\n                  --body '{\"spa\":{\"redirectUris\":[\"https://localhost:3000/authcallback\",\"http://localhost:3000/authcallback\",\"https://staging.contosotraders.com/authcallback\",\"https://production.contosotraders.com/authcallback\",\"https://cloudtesting.contosotraders.com/authcallback\",\"https://$(getUiCdnEndpoint.uiCdnEndpoint)/authcallback\"]}}'\n          - task: AzureCLI@1\n            displayName: display ui cdn endpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo UI CDN endpoint accessible at https://$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name uiCdnEndpoint --query value -o tsv)\n\n      - job: load_tests_with_chaos_products_api\n        dependsOn: [provision, playwright_tests_ui]\n        variables:\n          productsApiEndpoint: $[ dependencies.provision.outputs['getProductsApiEndpoint.productsApiEndpoint'] ]\n        steps:\n          - task: AzureCLI@1\n            displayName: get chaos experiment resource id\n            name: getChaosAksExperimentResourceId\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=chaosAksExperimentResourceId;isOutput=true]$(az resource show --resource-group $(RESOURCE_GROUP_NAME)$(SUFFIX) --namespace Microsoft.Chaos --resource-type Experiments --name $(CHAOS_AKS_EXPERIMENT_NAME)$(SUFFIX) --query \"id\" -o tsv)\"\n          - task: AzureCLI@1\n            displayName: start chaos experiment (pod failure)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: az rest --method post --uri https://management.azure.com$(getChaosAksExperimentResourceId.chaosAksExperimentResourceId)/start?api-version=2021-09-15-preview\n          - bash: sleep 30s\n            displayName: sleep for 30 seconds\n          - task: AzureLoadTest@1\n            displayName: load test (products API)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n              loadtestConfigFile: ./loadtests/contoso-traders-products.yaml\n              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)\n              env: |\n                [\n                  {\n                    \"name\": \"domain\",\n                    \"value\": \"$(productsApiEndpoint)\"\n                  },\n                  {\n                    \"name\": \"protocol\",\n                    \"value\": \"https\"\n                  },\n                  {\n                    \"name\": \"path\",\n                    \"value\": \"v1/Products/1\"\n                  },\n                  {\n                    \"name\": \"threads_per_engine\",\n                    \"value\": \"5\"\n                  },\n                  {\n                    \"name\": \"ramp_up_time\",\n                    \"value\": \"0\"\n                  },\n                  {\n                    \"name\": \"duration_in_sec\",\n                    \"value\": \"120\"\n                  }\n                ]\n\n      - job: load_tests_carts_internal_api\n        condition: eq(variables['DEPLOYPRIVATEENDPOINTS'], 'true')\n        dependsOn: [provision, playwright_tests_ui]\n        steps:\n          - task: AzureCLI@1\n            displayName: get carts api endpoint (internal)\n            name: getCartsInternalApiEndpoint\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=cartsInternalApiEndpoint;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) --name cartsInternalApiEndpoint --query value -o tsv)\"\n          - task: AzureCLI@1\n            displayName: get vnetAcaSubnetId\n            name: getVnetAcaSubnetId\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              scriptLocation: inlineScript\n              inlineScript: echo \"##vso[task.setvariable variable=vnetAcaSubnetId;isOutput=true]$(az keyvault secret show --vault-name $(KV_NAME)$(SUFFIX) -n vnetAcaSubnetId --query \"value\" -o tsv)\"\n          - task: replacetokens@5\n            displayName: substitute tokens in load test config file\n            inputs:\n              targetFiles: ./loadtests/contoso-traders-carts-internal.yaml\n              tokenPattern: doublebraces\n              inlineVariables: \"LOAD_TEST_SUBNET_ID: $(getVnetAcaSubnetId.vnetAcaSubnetId)\"\n          - task: AzureLoadTest@1\n            displayName: load test (carts API)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n              loadtestConfigFile: ./loadtests/contoso-traders-carts-internal.yaml\n              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)\n              env: |\n                [\n                  {\n                    \"name\": \"domain\",\n                    \"value\": \"$(getCartsInternalApiEndpoint.cartsInternalApiEndpoint)\"\n                  },\n                  {\n                    \"name\": \"protocol\",\n                    \"value\": \"https\"\n                  },\n                  {\n                    \"name\": \"path\",\n                    \"value\": \"v1/ShoppingCart/loadtest\"\n                  },\n                  {\n                    \"name\": \"threads_per_engine\",\n                    \"value\": \"5\"\n                  },\n                  {\n                    \"name\": \"ramp_up_time\",\n                    \"value\": \"0\"\n                  },\n                  {\n                    \"name\": \"duration_in_sec\",\n                    \"value\": \"120\"\n                  }\n                ]\n\n      - job: load_tests_carts_api\n        dependsOn: [provision, playwright_tests_ui]\n        variables:\n          cartsApiEndpoint: $[ dependencies.provision.outputs['getCartsApiEndpoint.cartsApiEndpoint'] ]\n        steps:\n          - task: AzureLoadTest@1\n            displayName: load test (carts API)\n            inputs:\n              azureSubscription: SERVICEPRINCIPAL\n              # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n              loadtestConfigFile: ./loadtests/contoso-traders-carts.yaml\n              resourceGroup: $(RESOURCE_GROUP_NAME)$(SUFFIX)\n              loadtestResource: $(LOAD_TEST_SERVICE_NAME)$(SUFFIX)\n              env: |\n                [\n                  {\n                    \"name\": \"domain\",\n                    \"value\": \"$(cartsApiEndpoint)\"\n                  },\n                  {\n                    \"name\": \"protocol\",\n                    \"value\": \"https\"\n                  },\n                  {\n                    \"name\": \"path\",\n                    \"value\": \"v1/ShoppingCart/loadtest\"\n                  },\n                  {\n                    \"name\": \"threads_per_engine\",\n                    \"value\": \"5\"\n                  },\n                  {\n                    \"name\": \"ramp_up_time\",\n                    \"value\": \"0\"\n                  },\n                  {\n                    \"name\": \"duration_in_sec\",\n                    \"value\": \"120\"\n                  }\n                ]\n\n      - job: playwright_tests_ui\n        dependsOn: [provision]\n        container: mcr.microsoft.com/playwright:v1.43.1-jammy\n        variables:\n          APIURLSHOPPINGCART: $[ dependencies.provision.outputs['getCartsApiEndpoint.cartsApiEndpoint'] ]\n          APIURL: $[ dependencies.provision.outputs['getProductsApiEndpoint.productsApiEndpoint'] ]\n          BASEURLFORPLAYWRIGHTTESTING: $[ dependencies.provision.outputs['getUiCdnEndpoint.uiCdnEndpoint'] ]\n          B2CCLIENTID: $[ dependencies.provision.outputs['getAzureAdAppClientId.azureAdAppClientId'] ]\n        steps:\n          - task: NodeTool@0\n            displayName: install nodejs\n            inputs:\n              versionSpec: 18.x\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_APIURLSHOPPINGCART]https://$(APIURLSHOPPINGCART)/v1\"\n            displayName: set REACT_APP_APIURLSHOPPINGCART\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_APIURL]https://$(APIURL)/v1\"\n            displayName: set REACT_APP_APIURL\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_BASEURLFORPLAYWRIGHTTESTING]https://$(BASEURLFORPLAYWRIGHTTESTING)\"\n            displayName: set REACT_APP_BASEURLFORPLAYWRIGHTTESTING\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_B2CCLIENTID]$(B2CCLIENTID)\"\n            displayName: set REACT_APP_B2CCLIENTID\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_AADUSERNAME]$(AADUSERNAME)\"\n            displayName: set REACT_APP_AADUSERNAME\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n          - script: echo \"##vso[task.setvariable variable=REACT_APP_AADPASSWORD]$(AADPASSWORD)\"\n            displayName: set REACT_APP_AADPASSWORD\n            condition: and(ne(variables['AADUSERNAME'], ''), ne(variables['AADPASSWORD'], ''))\n          - script: npm ci\n            displayName: install dependencies\n            workingDirectory: src/ContosoTraders.Ui.Website\n          - script: |\n              echo \"##vso[task.setvariable variable=CI;]1\"\n              npx playwright test\n            displayName: run playwright tests\n            workingDirectory: src/ContosoTraders.Ui.Website\n            timeoutInMinutes: 20\n          - task: PublishTestResults@2\n            displayName: publish test results\n            condition: succeededOrFailed()\n            inputs:\n              searchFolder: src/ContosoTraders.Ui.Website/playwright-report-junit\n              testResultsFormat: JUnit\n              testResultsFiles: e2e-junit-results.xml\n              mergeTestResults: true\n              failTaskOnFailedTests: true\n              testRunTitle: Playwright Tests\n          - task: PublishBuildArtifacts@1\n            displayName: upload playwright report\n            condition: failed()\n            inputs:\n              PathtoPublish: 'src/ContosoTraders.Ui.Website/playwright-report'\n              ArtifactName: 'playwright-report'\n              publishLocation: 'Container'              \n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"Playwright\",\n\t\"image\": \"mcr.microsoft.com/playwright:v1.43.1-jammy\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/node:1\": {\n\t\t\t\"nodeGypDependencies\": true,\n\t\t\t\"version\": \"18\"\n\t\t},\n\t\t\"ghcr.io/devcontainers-contrib/features/npm-package:1\": {\n\t\t\t\"package\": \"typescript\",\n\t\t\t\"version\": \"latest\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.sh eol=lf\nmvnw eol=lf"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# Change Description\n\n> Please include a summary of the changes.\n\n## Linked GitHub Issue\n\n> Link to any related github issue number (e.g. #56).\n\nFixes # (issue)\n\n## Checklist\n\nPlease check all options that are relevant.\n\n- [ ] I have made all necessary updates to the documentation.\n- [ ] I have made all necessary updates to the provisioning scripts (bicep templates, github workflows).\n- [ ] This is not a breaking change.\n"
  },
  {
    "path": ".github/workflows/aks-cost-optimization.yml",
    "content": "name: aks-cost-optimization\n\non:\n  workflow_dispatch:\n  \nenv:\n  ACR_NAME: contosotradersacr\n  AKS_CLUSTER_NAME: contoso-traders-aks\n  AKS_CPU_LIMIT: 250m\n  AKS_MEMORY_LIMIT: 256Mi\n  AKS_REPLICAS: \"1\"\n  AKS_SECRET_NAME_ACR_PASSWORD: contoso-traders-acr-password\n  KV_NAME: contosotraderskv\n  LOAD_TEST_SERVICE_NAME: contoso-traders-loadtest\n  PRODUCTS_ACR_REPOSITORY_NAME: contosotradersapiproducts\n  RESOURCE_GROUP_NAME: contoso-traders-rg\n\njobs:\n  aks-cost-optimization:\n    strategy:\n      fail-fast: false\n      matrix:\n        AKS_CPU_LIMIT: [\"250m\", \"100m\"]\n        AKS_MEMORY_LIMIT: [\"256Mi\", \"128Mi\"]\n      max-parallel: 1\n    runs-on: ubuntu-latest\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      - name: extract acr password\n        uses: azure/CLI@v1\n        id: extract-acr-password\n        with:\n          inlineScript: |\n            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query \"passwords[0].value\" --output tsv)\n            echo \"::add-mask::$acrPassword\"\n            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT\n      - name: azure container registry login\n        uses: azure/docker-login@v1\n        with:\n          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io\n          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}\n          password: ${{ steps.extract-acr-password.outputs.acrPassword }}\n      - name: set aks context\n        uses: azure/aks-set-context@v3\n        with:\n          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n      - name: get products api endpoint\n        uses: azure/CLI@v1\n        id: get-productsApiEndpoint\n        with:\n          inlineScript: echo \"productsApiEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n      - name: substitute tokens in deployment manifest\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{\"\n          tokenSuffix: \"}\"\n          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n        env:\n          SUFFIX: ${{ vars.SUFFIX }}\n          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}\n          AKS_CPU_LIMIT: ${{ matrix.AKS_CPU_LIMIT }}\n          AKS_MEMORY_LIMIT: ${{ matrix.AKS_MEMORY_LIMIT }}\n      - name: apply deployment manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest\n          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}\n          force: true\n      - name: sleep for 30 seconds\n        run: sleep 30s\n        shell: bash\n      - name: load test (products API)\n        uses: Azure/load-testing@v1.1.19\n        with:\n          # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n          loadtestConfigFile: ./loadtests/contoso-traders-products.yaml\n          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}\n          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          env: |\n            [\n              {\n                \"name\": \"domain\",\n                \"value\": \"${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}\"\n              },\n              {\n                \"name\": \"protocol\",\n                \"value\": \"https\"\n              },\n              {\n                \"name\": \"path\",\n                \"value\": \"v1/Products/1\"\n              },\n              {\n                \"name\": \"threads_per_engine\",\n                \"value\": \"25\"\n              },\n              {\n                \"name\": \"ramp_up_time\",\n                \"value\": \"0\"\n              },\n              {\n                \"name\": \"duration_in_sec\",\n                \"value\": \"45\"\n              }\n            ]\n\n  reset-aks:\n    runs-on: ubuntu-latest\n    needs: [aks-cost-optimization]\n    if: always()\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      - name: extract acr password\n        uses: azure/CLI@v1\n        id: extract-acr-password\n        with:\n          inlineScript: |\n            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query \"passwords[0].value\" --output tsv)\n            echo \"::add-mask::$acrPassword\"\n            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT\n      - name: azure container registry login\n        uses: azure/docker-login@v1\n        with:\n          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io\n          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}\n          password: ${{ steps.extract-acr-password.outputs.acrPassword }}\n      - name: set aks context\n        uses: azure/aks-set-context@v3\n        with:\n          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n      - name: get products api endpoint\n        uses: azure/CLI@v1\n        id: get-productsApiEndpoint\n        with:\n          inlineScript: echo \"productsApiEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n      - name: substitute tokens in deployment manifest\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{\"\n          tokenSuffix: \"}\"\n          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n        env:\n          SUFFIX: ${{ vars.SUFFIX }}\n          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}\n          AKS_CPU_LIMIT: ${{ env.AKS_CPU_LIMIT }}\n          AKS_MEMORY_LIMIT: ${{ env.AKS_MEMORY_LIMIT }}\n      - name: apply deployment manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest\n          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}\n          force: true\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL\"\n\non:\n    workflow_dispatch:\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}\n    permissions:\n      security-events: write\n      packages: read\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        - language: csharp\n          build-mode: autobuild\n        - language: javascript-typescript\n          build-mode: none\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v3\n      with:\n        languages: ${{ matrix.language }}\n        build-mode: ${{ matrix.build-mode }}\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v3\n      with:\n        category: \"/language:${{matrix.language}}\"\n"
  },
  {
    "path": ".github/workflows/contoso-traders-cloud-testing.yml",
    "content": "name: contoso-traders-cloud-testing\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n    paths-ignore: [\"docs/**\", \"demo-scripts/**\"]\n\nenv:\n  ACR_NAME: contosotradersacr\n  AKS_CLUSTER_NAME: contoso-traders-aks\n  AKS_CPU_LIMIT: 250m\n  AKS_DNS_LABEL: contoso-traders-products\n  AKS_MEMORY_LIMIT: 256Mi\n  AKS_NODES_RESOURCE_GROUP_NAME: contoso-traders-aks-nodes-rg\n  AKS_REPLICAS: \"1\"\n  AKS_SECRET_NAME_ACR_PASSWORD: contoso-traders-acr-password\n  AKS_SECRET_NAME_KV_ENDPOINT: contoso-traders-kv-endpoint\n  AKS_SECRET_NAME_MI_CLIENTID: contoso-traders-mi-clientid\n  AZURE_AD_APP_NAME: contoso-traders-cloud-testing-app\n  CARTS_ACA_NAME: contoso-traders-carts\n  CARTS_ACR_REPOSITORY_NAME: contosotradersapicarts\n  CARTS_INTERNAL_ACA_NAME: contoso-traders-intcarts\n  CDN_PROFILE_NAME: contoso-traders-cdn\n  CHAOS_AKS_EXPERIMENT_NAME: contoso-traders-chaos-aks-experiment\n  KV_NAME: contosotraderskv\n  LOAD_TEST_SERVICE_NAME: contoso-traders-loadtest\n  MSGRAPH_API_ID: 00000003-0000-0000-c000-000000000000\n  MSGRAPH_API_PERMISSION_EMAIL: 64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0=Scope\n  MSGRAPH_API_PERMISSION_USER_READ: e1fe6dd8-ba31-4d61-89e7-88639da4683d=Scope\n  PRODUCTS_ACR_REPOSITORY_NAME: contosotradersapiproducts\n  PRODUCTS_DB_NAME: productsdb\n  PRODUCTS_DB_SERVER_NAME: contoso-traders-products\n  PRODUCTS_DB_USER_NAME: localadmin\n  PRODUCT_DETAILS_CONTAINER_NAME: product-details\n  PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME: contosotradersimg\n  PRODUCT_LIST_CONTAINER_NAME: product-list\n  PRODUCTS_CDN_ENDPOINT_NAME: contoso-traders-images\n  RESOURCE_GROUP_NAME: contoso-traders-rg\n  STORAGE_ACCOUNT_NAME: contosotradersimg\n  UI_CDN_ENDPOINT_NAME: contoso-traders-ui2\n  UI_STORAGE_ACCOUNT_NAME: contosotradersui2\n  USER_ASSIGNED_MANAGED_IDENTITY_NAME: contoso-traders-mi-kv-access\n\njobs:\n  provision:\n    runs-on: ubuntu-22.04\n    env:\n      AADUSERNAME: ${{ secrets.AADUSERNAME }}\n      AADPASSWORD: ${{ secrets.AADPASSWORD }}\n    outputs:\n      azureAdAppClientId: ${{ steps.get-azureAdAppClientId.outputs.azureAdAppClientId }}\n      azureAdAppObjId: ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }}\n      cartsApiEndpoint: ${{ steps.get-cartsApiEndpoint.outputs.cartsApiEndpoint }}\n      productsApiEndpoint: ${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}\n      uiCdnEndpoint: ${{ steps.get-uiCdnEndpoint.outputs.uiCdnEndpoint }}\n    concurrency:\n      group: provision\n      cancel-in-progress: true\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      # section #0: optional configuration of the Azure AD app.\n      # create the Azure AD application (and update it if it already exists).\n      # note: This is an idempotent operation.\n      - name: create/update azure active directory app\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: az ad app create --display-name ${{ env.AZURE_AD_APP_NAME}}${{ vars.SUFFIX }} --sign-in-audience AzureADandPersonalMicrosoftAccount\n      - name: get azure ad app's object id\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        id: get-azureAdAppObjId\n        with:\n          inlineScript: echo \"azureAdAppObjId\"=\"$(az ad app list --display-name ${{ env.AZURE_AD_APP_NAME }}${{ vars.SUFFIX }} --query [].id -o tsv)\" >> $GITHUB_OUTPUT\n      - name: get azure ad app's client id\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        id: get-azureAdAppClientId\n        with:\n          inlineScript: echo \"azureAdAppClientId\"=\"$(az ad app list --display-name ${{ env.AZURE_AD_APP_NAME }}${{ vars.SUFFIX }} --query [].appId -o tsv)\" >> $GITHUB_OUTPUT\n      - name: register app as a spa\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: |\n            az rest \\\n              --method PATCH \\\n              --uri https://graph.microsoft.com/v1.0/applications/${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \\\n              --headers 'Content-Type=application/json' \\\n              --body '{\"spa\":{\"redirectUris\":[\"https://localhost:3000/authcallback\",\"http://localhost:3000/authcallback\",\"https://production.contosotraders.com/authcallback\",\"https://cloudtesting.contosotraders.com/authcallback\"]}}'\n      - name: enable issuance of id, access tokens\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: az ad app update --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} --enable-access-token-issuance true --enable-id-token-issuance true\n      - name: enable email claim in access token\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: az ad app update --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} --optional-claims \"{\\\"accessToken\\\":[{\\\"name\\\":\\\"email\\\",\\\"essential\\\":false}]}\"\n      # note: requesting MS Graph permissions in Azure AD app unfortunately isn't idempotent.\n      # Even, if you have already requested the permissions, it'll keep adding to the list of requested permissions until you hit limit on max permissions requested.\n      # Details: https://github.com/Azure/azure-cli/issues/24512\n      - name: delete any requested Microsoft Graph permissions\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: |\n            az ad app permission delete \\\n              --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \\\n              --api ${{ env.MSGRAPH_API_ID }}\n      - name: request Microsoft Graph permissions\n        uses: azure/CLI@v1\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        with:\n          inlineScript: |\n            az ad app permission add \\\n              --id ${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \\\n              --api ${{ env.MSGRAPH_API_ID }} \\\n              --api-permissions ${{ env.MSGRAPH_API_PERMISSION_USER_READ }} ${{ env.MSGRAPH_API_PERMISSION_EMAIL }}\n      #\n      # section #1: provisioning the resources on Azure using bicep templates\n      #\n      # The first step is to create the resource group: `contoso-traders-rg`.\n      # The below step can also be manually executed as follows:\n      # az deployment sub create --location {LOCATION} --template-file .\\createResourceGroup.bicep\n      # Note: You can specify any location for `{LOCATION}`. It's the region where the deployment metadata will be stored, and not\n      # where the resource groups will be deployed.\n      - name: create resource group\n        uses: Azure/arm-deploy@v1\n        with:\n          scope: subscription\n          region: ${{ vars.DEPLOYMENTREGION }}\n          template: ./iac/createResourceGroup.bicep\n          parameters: rgName=${{ env.RESOURCE_GROUP_NAME }} suffix=${{ vars.SUFFIX }} rgLocation=${{ vars.DEPLOYMENTREGION }}\n      # Next step is to deploy the Azure resources to the resource group `contoso-traders-rg` created above. The deployed resources\n      # include storage accounts, function apps, app services cosmos db, and service bus etc.\n      # The below step can also be manually executed as follows:\n      # az deployment group create -g contoso-traders-rg --template-file .\\createResources.bicep --parameters .\\createResources.parameters.json\n      # Note: The `createResources.parameters.json` file contains the parameters for the deployment; specifically the environment name.\n      # You can modify the parameters to customize the deployment.\n      # Note: The bicep template outputs are not shown in the logs. You can extract the outputs as shown here:\n      # https://github.com/Azure/arm-deploy#another-example-on-how-to-use-this-action-to-get-the-output-of-arm-template\n      - name: create resources\n        uses: Azure/arm-deploy@v1\n        with:\n          scope: resourcegroup\n          region: ${{ vars.DEPLOYMENTREGION }}\n          resourceGroupName: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          template: ./iac/createResources.bicep\n          parameters: ./iac/createResources.parameters.json suffix=${{ vars.SUFFIX }} sqlPassword=${{ secrets.SQLPASSWORD }} deployPrivateEndpoints=${{ vars.DEPLOYPRIVATEENDPOINTS }}\n      # Add the logged-in service principal to the key vault access policy\n      - name: add service principal to kv access policy\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az keyvault set-policy -n ${{ env.KV_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --secret-permissions get list set --object-id $(az ad sp show --id $(az account show --query \"user.name\" -o tsv) --query \"id\" -o tsv)\n      # The AKS agent pool needs to be assigned the user-assigned managed identity created (which has kv access)\n      - name: assign user-assigned managed-identity to aks agentpool\n        uses: azure/CLI@v1\n        with:\n          inlineScript: |\n            az vmss identity assign \\\n              --identities $(az identity show -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.USER_ASSIGNED_MANAGED_IDENTITY_NAME }}${{ vars.SUFFIX }} --query \"id\" -o tsv) \\\n              --ids $(az vmss list -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query \"[0].id\" -o tsv) \\\n      # Seed the DBs and storage accounts\n      - name: seed products db\n        uses: azure/sql-action@v2.2\n        with:\n          connection-string: Server=tcp:${{ env.PRODUCTS_DB_SERVER_NAME }}${{ vars.SUFFIX }}.database.windows.net,1433;Initial Catalog=${{ env.PRODUCTS_DB_NAME }};Persist Security Info=False;User ID=${{ env.PRODUCTS_DB_USER_NAME }};Password=${{ secrets.SQLPASSWORD }};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;\n          path: ./src/ContosoTraders.Api.Products/Migration/productsdb.sql\n      - name: seed product image (product details)\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az storage blob sync --account-name '${{ env.PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '${{ env.PRODUCT_DETAILS_CONTAINER_NAME }}' -s 'src/ContosoTraders.Api.Images/product-details'\n      - name: seed product image (product list)\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az storage blob sync --account-name '${{ env.PRODUCT_IMAGES_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '${{ env.PRODUCT_LIST_CONTAINER_NAME }}' -s 'src/ContosoTraders.Api.Images/product-list'\n      - name: purge product images cdn endpoint\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '${{ env.PRODUCTS_CDN_ENDPOINT_NAME }}${{ vars.SUFFIX }}' -g '${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}' --profile-name '${{ env.CDN_PROFILE_NAME }}${{ vars.SUFFIX }}'\n      - name: extract acr password\n        uses: azure/CLI@v1\n        id: extract-acr-password\n        with:\n          inlineScript: |\n            acrPassword=$(az acr credential show -n ${{ env.ACR_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --query \"passwords[0].value\" --output tsv)\n            echo \"::add-mask::$acrPassword\"\n            echo acrPassword=$acrPassword >> $GITHUB_OUTPUT\n      - name: azure container registry login\n        uses: azure/docker-login@v1\n        with:\n          login-server: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io\n          username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}\n          password: ${{ steps.extract-acr-password.outputs.acrPassword }}\n      - name: set aks context\n        uses: azure/aks-set-context@v3\n        with:\n          resource-group: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          cluster-name: ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n      #\n      # section #2: deploy the carts api\n      #\n      - name: docker build\n        run: docker build src -f ./src/ContosoTraders.Api.Carts/Dockerfile -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:latest -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}\n      - name: docker push (to acr)\n        run: docker push --all-tags ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}\n      - name: deploy to aca\n        uses: azure/CLI@v1\n        with:\n          inlineScript: |\n            az config set extension.use_dynamic_install=yes_without_prompt\n            az containerapp update -n ${{ env.CARTS_ACA_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --image ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}\n      - name: deploy to aca (internal)\n        if: ${{ vars.DEPLOYPRIVATEENDPOINTS == 'true' }}\n        uses: azure/CLI@v1\n        with:\n          inlineScript: |\n            az config set extension.use_dynamic_install=yes_without_prompt\n            az containerapp update -n ${{ env.CARTS_INTERNAL_ACA_NAME }}${{ vars.SUFFIX }} -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --image ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.CARTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}\n      - name: get carts api endpoint\n        uses: azure/CLI@v1\n        id: get-cartsApiEndpoint\n        with:\n          inlineScript: echo \"cartsApiEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name cartsApiEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n\n      #\n      # section #3: deploy the products api\n      #\n      - name: install helm\n        uses: Azure/setup-helm@v3\n        id: install-helm\n        with:\n          version: v3.9.0\n      - name: docker build\n        run: docker build src -f ./src/ContosoTraders.Api.Products/Dockerfile -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:latest -t ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}\n      - name: docker push (to acr)\n        run: docker push --all-tags ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}\n      - name: setup kubectl\n        uses: azure/setup-kubectl@v3\n      - name: create kubernetes secret (acr password)\n        uses: Azure/k8s-create-secret@v4\n        with:\n          secret-name: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}\n          container-registry-url: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io\n          container-registry-username: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}\n          container-registry-password: ${{ steps.extract-acr-password.outputs.acrPassword }}\n      - name: get managedIdentityClientId\n        uses: azure/CLI@v1\n        id: get-managedIdentityClientId\n        with:\n          inlineScript: echo \"managedIdentityClientId\"=\"$(az identity show -g ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.USER_ASSIGNED_MANAGED_IDENTITY_NAME }}${{ vars.SUFFIX }} --query \"clientId\" -o tsv)\" >> $GITHUB_OUTPUT\n      - name: create kubernetes secret (kv endpoint)\n        uses: Azure/k8s-create-secret@v4\n        with:\n          secret-type: \"generic\"\n          secret-name: ${{ env.AKS_SECRET_NAME_KV_ENDPOINT }}\n          string-data: '{ \"${{ env.AKS_SECRET_NAME_KV_ENDPOINT }}\" : \"https://${{ env.KV_NAME }}${{ vars.SUFFIX }}.vault.azure.net/\" }'\n      - name: create kubernetes secret (managed identity client id)\n        uses: Azure/k8s-create-secret@v4\n        with:\n          secret-type: \"generic\"\n          secret-name: ${{ env.AKS_SECRET_NAME_MI_CLIENTID }}\n          string-data: '{ \"${{ env.AKS_SECRET_NAME_MI_CLIENTID }}\" : \"${{ steps.get-managedIdentityClientId.outputs.managedIdentityClientId }}\" }'\n      - name: substitute tokens in deployment manifest\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{\"\n          tokenSuffix: \"}\"\n          files: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n        env:\n          SUFFIX: ${{ vars.SUFFIX }}\n          AKS_REPLICAS: ${{ env.AKS_REPLICAS }}\n          AKS_CPU_LIMIT: ${{ env.AKS_CPU_LIMIT }}\n          AKS_MEMORY_LIMIT: ${{ env.AKS_MEMORY_LIMIT }}\n      - name: lint deployment manifest\n        uses: azure/k8s-lint@v2.0\n        with:\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n      - name: apply deployment manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Deployment.yaml\n          images: ${{ env.ACR_NAME }}${{ vars.SUFFIX }}.azurecr.io/${{ env.PRODUCTS_ACR_REPOSITORY_NAME }}:${{ github.sha }}\n          imagepullsecrets: ${{ env.AKS_SECRET_NAME_ACR_PASSWORD }}\n          force: true\n      - name: apply service manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Service.yaml\n          force: true\n      # setup chaos mesh\n      - name: apply namespace manifest (chaos-testing)\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml\n          force: true\n      - name: setup chaos mesh\n        run: |\n          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n          ${{ steps.install-helm.outputs.helm-path }} repo add chaos-mesh https://charts.chaos-mesh.org\n          ${{ steps.install-helm.outputs.helm-path }} repo update\n          ${{ steps.install-helm.outputs.helm-path }} upgrade --install chaos-mesh chaos-mesh/chaos-mesh --namespace=chaos-testing --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath=/run/containerd/containerd.sock\n      # create the ingress controller\n      - name: create ingress controller\n        run: |\n          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n          ${{ steps.install-helm.outputs.helm-path }} repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\n          ${{ steps.install-helm.outputs.helm-path }} repo update\n          ${{ steps.install-helm.outputs.helm-path }} upgrade --install --wait --timeout=1h nginx-ingress ingress-nginx/ingress-nginx \\\n            --set controller.replicaCount=1 \\\n            --set controller.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n            --set defaultBackend.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n            --set controller.admissionWebhooks.patch.nodeSelector.\"kubernetes\\.io/os\"=linux \\\n            --set controller.service.externalTrafficPolicy=Local\n      - name: set dns label on public ip\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az network public-ip update --dns-name ${{ env.AKS_DNS_LABEL }}${{ vars.SUFFIX }} -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} -n $(az network public-ip list --query \"[?starts_with(name,'kubernetes-') ].name\" -o tsv -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }})\n      # hack: extract the full fqdn / dns label of the aks app's public IP address\n      - name: get aks-fqdn\n        uses: azure/CLI@v1\n        id: get-aks-fqdn\n        with:\n          # note: There should be a whitespace between ')' and ']'. More details: https://stackoverflow.com/a/59154958\n          inlineScript: echo \"aksFqdn\"=\"$(az network public-ip list --query \"[?starts_with(name,'kubernetes-') ].dnsSettings.fqdn\" -o tsv -g ${{ env.AKS_NODES_RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }})\" >> $GITHUB_OUTPUT\n      # install cert-manager\n      - name: apply namespace manifest (cert-manager)\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml\n          force: true\n      - name: install cert-manager\n        run: |\n          az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --name ${{ env.AKS_CLUSTER_NAME }}${{ vars.SUFFIX }}\n          kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml\n      - name: sleep for 30 seconds\n        run: sleep 30s\n        shell: bash\n      - name: apply clusterIssuer manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml\n          force: true\n      - name: substitute tokens in certificate manifest\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{\"\n          tokenSuffix: \"}\"\n          files: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml\n        env:\n          AKS_FQDN: ${{ steps.get-aks-fqdn.outputs.aksFqdn }}\n      - name: apply certificate manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Certificate.yaml\n          force: true\n      - name: substitute tokens in ingress manifest\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{\"\n          tokenSuffix: \"}\"\n          files: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml\n        env:\n          AKS_FQDN: ${{ steps.get-aks-fqdn.outputs.aksFqdn }}\n      - name: apply ingress manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/Ingress.yaml\n          force: true\n      - name: apply clusterRole manifest\n        uses: Azure/k8s-deploy@v4\n        with:\n          pull-images: false\n          manifests: ./src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml\n          force: true\n      - name: set productsApiEndpoint in kv\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az keyvault secret set --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --value ${{ steps.get-aks-fqdn.outputs.aksFqdn }} --description \"endpoint url (fqdn) of the products api\"\n      - name: get products api endpoint\n        uses: azure/CLI@v1\n        id: get-productsApiEndpoint\n        with:\n          inlineScript: echo \"productsApiEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name productsApiEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n\n      #\n      # section #4: deploy the ui\n      #\n      - name: set REACT_APP_APIURLSHOPPINGCART\n        run: echo \"REACT_APP_APIURLSHOPPINGCART\"=\"https://${{ steps.get-cartsApiEndpoint.outputs.cartsApiEndpoint }}/v1\" >> $GITHUB_ENV\n      - name: set REACT_APP_APIURL\n        run: echo \"REACT_APP_APIURL\"=\"https://${{ steps.get-productsApiEndpoint.outputs.productsApiEndpoint }}/v1\" >> $GITHUB_ENV\n      - name: set REACT_APP_B2CCLIENTID\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        run: echo \"REACT_APP_B2CCLIENTID\"=\"${{ steps.get-azureAdAppClientId.outputs.azureAdAppClientId }}\" >> $GITHUB_ENV\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: npm\n          cache-dependency-path: src/ContosoTraders.Ui.Website/package-lock.json\n      - name: npm ci\n        run: npm ci\n        working-directory: src/ContosoTraders.Ui.Website\n      - name: npm run build\n        run: npm run build\n        env:\n          REACT_APP_BINGMAPSKEY: ${{ secrets.BINGMAPSKEY }}\n        working-directory: src/ContosoTraders.Ui.Website\n      - name: deploy ui to storage\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az storage blob sync --account-name '${{ env.UI_STORAGE_ACCOUNT_NAME }}${{ vars.SUFFIX }}' -c '$web' -s 'src/ContosoTraders.Ui.Website/build'\n      - name: purge ui cdn endpoint\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az cdn endpoint purge --no-wait --content-paths '/*' -n '${{ env.UI_CDN_ENDPOINT_NAME }}${{ vars.SUFFIX }}' -g '${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}' --profile-name '${{ env.CDN_PROFILE_NAME }}${{ vars.SUFFIX }}'\n      - name: get ui cdn endpoint\n        uses: azure/CLI@v1\n        id: get-uiCdnEndpoint\n        with:\n          inlineScript: echo \"uiCdnEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name uiCdnEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n      - name: register auth callback (UI CDN)\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        uses: azure/CLI@v1\n        with:\n          inlineScript: |\n            az rest \\\n              --method PATCH \\\n              --uri https://graph.microsoft.com/v1.0/applications/${{ steps.get-azureAdAppObjId.outputs.azureAdAppObjId }} \\\n              --headers 'Content-Type=application/json' \\\n              --body '{\"spa\":{\"redirectUris\":[\"https://localhost:3000/authcallback\",\"http://localhost:3000/authcallback\",\"https://staging.contosotraders.com/authcallback\",\"https://production.contosotraders.com/authcallback\",\"https://cloudtesting.contosotraders.com/authcallback\",\"https://${{ steps.get-uiCdnEndpoint.outputs.uiCdnEndpoint }}/authcallback\"]}}'\n      - name: display ui cdn endpoint\n        uses: azure/CLI@v1\n        with:\n          inlineScript: echo UI CDN endpoint accessible at https://$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name uiCdnEndpoint --query value -o tsv)\n\n  load-tests-with-chaos-products-api:\n    needs: [provision, playwright-tests-ui]\n    runs-on: ubuntu-22.04\n    concurrency:\n      group: load-tests-with-chaos-products-api\n      cancel-in-progress: true\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      - name: get chaos experiment resource id\n        uses: azure/CLI@v1\n        id: get-chaosAksExperimentResourceId\n        with:\n          inlineScript: echo \"chaosAksExperimentResourceId\"=\"$(az resource show --resource-group ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }} --namespace Microsoft.Chaos --resource-type Experiments --name ${{ env.CHAOS_AKS_EXPERIMENT_NAME }}${{ vars.SUFFIX }} --query \"id\" -o tsv)\" >> $GITHUB_OUTPUT\n      - name: start chaos experiment (pod failure)\n        uses: azure/CLI@v1\n        with:\n          inlineScript: az rest --method post --uri https://management.azure.com${{ steps.get-chaosAksExperimentResourceId.outputs.chaosAksExperimentResourceId }}/start?api-version=2021-09-15-preview\n      - name: sleep for 30 seconds\n        run: sleep 30s\n        shell: bash\n      - name: load test (products API)\n        uses: Azure/load-testing@v1.1.19\n        with:\n          # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n          loadtestConfigFile: ./loadtests/contoso-traders-products.yaml\n          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}\n          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          env: |\n            [\n              {\n                \"name\": \"domain\",\n                \"value\": \"${{ needs.provision.outputs.productsApiEndpoint }}\"\n              },\n              {\n                \"name\": \"protocol\",\n                \"value\": \"https\"\n              },\n              {\n                \"name\": \"path\",\n                \"value\": \"v1/Products/1\"\n              },\n              {\n                \"name\": \"threads_per_engine\",\n                \"value\": \"5\"\n              },\n              {\n                \"name\": \"ramp_up_time\",\n                \"value\": \"0\"\n              },\n              {\n                \"name\": \"duration_in_sec\",\n                \"value\": \"120\"\n              }\n            ]\n\n  load-tests-carts-internal-api:\n    if: ${{ vars.DEPLOYPRIVATEENDPOINTS == 'true' }}\n    needs: [provision, playwright-tests-ui]\n    runs-on: ubuntu-22.04\n    concurrency:\n      group: load-tests-carts-internal-api\n      cancel-in-progress: true\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      - name: get carts api endpoint (internal)\n        uses: azure/CLI@v1\n        id: get-cartsInternalApiEndpoint\n        with:\n          inlineScript: echo \"cartsInternalApiEndpoint\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} --name cartsInternalApiEndpoint --query value -o tsv)\" >> $GITHUB_OUTPUT\n      - name: get vnetAcaSubnetId\n        uses: azure/CLI@v1\n        id: get-vnetAcaSubnetId\n        with:\n          inlineScript: echo \"vnetAcaSubnetId\"=\"$(az keyvault secret show --vault-name ${{ env.KV_NAME }}${{ vars.SUFFIX }} -n vnetAcaSubnetId --query \"value\" -o tsv)\" >> $GITHUB_OUTPUT\n      - name: substitute tokens in load test config file\n        uses: cschleiden/replace-tokens@v1.2\n        with:\n          tokenPrefix: \"{{\"\n          tokenSuffix: \"}}\"\n          files: ./loadtests/contoso-traders-carts-internal.yaml\n        env:\n          LOAD_TEST_SUBNET_ID: ${{ steps.get-vnetAcaSubnetId.outputs.vnetAcaSubnetId }}\n      - name: load test (carts internal API)\n        uses: Azure/load-testing@v1.1.19\n        with:\n          # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n          loadtestConfigFile: ./loadtests/contoso-traders-carts-internal.yaml\n          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}\n          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          env: |\n            [\n              {\n                \"name\": \"domain\",\n                \"value\": \"${{ steps.get-cartsInternalApiEndpoint.outputs.cartsInternalApiEndpoint }}\"\n              },\n              {\n                \"name\": \"protocol\",\n                \"value\": \"https\"\n              },\n              {\n                \"name\": \"path\",\n                \"value\": \"v1/ShoppingCart/loadtest\"\n              },\n              {\n                \"name\": \"threads_per_engine\",\n                \"value\": \"5\"\n              },\n              {\n                \"name\": \"ramp_up_time\",\n                \"value\": \"0\"\n              },\n              {\n                \"name\": \"duration_in_sec\",\n                \"value\": \"120\"\n              }\n            ]\n\n  load-tests-carts-api:\n    needs: [provision, playwright-tests-ui]\n    runs-on: ubuntu-22.04\n    concurrency:\n      group: load-tests-carts-api\n      cancel-in-progress: true\n    steps:\n      - name: checkout code\n        uses: actions/checkout@v4\n      - name: azure login\n        uses: azure/login@v1\n        with:\n          creds: ${{ secrets.SERVICEPRINCIPAL }}\n      - name: load test (carts API)\n        uses: Azure/load-testing@v1.1.19\n        with:\n          # Path of the YAML file. Should be fully qualified path or relative to the default working directory\n          loadtestConfigFile: ./loadtests/contoso-traders-carts.yaml\n          loadtestResource: ${{ env.LOAD_TEST_SERVICE_NAME }}${{ vars.SUFFIX }}\n          resourceGroup: ${{ env.RESOURCE_GROUP_NAME }}${{ vars.SUFFIX }}\n          env: |\n            [\n              {\n                \"name\": \"domain\",\n                \"value\": \"${{ needs.provision.outputs.cartsApiEndpoint }}\"\n              },\n              {\n                \"name\": \"protocol\",\n                \"value\": \"https\"\n              },\n              {\n                \"name\": \"path\",\n                \"value\": \"v1/ShoppingCart/loadtest\"\n              },\n              {\n                \"name\": \"threads_per_engine\",\n                \"value\": \"5\"\n              },\n              {\n                \"name\": \"ramp_up_time\",\n                \"value\": \"0\"\n              },\n              {\n                \"name\": \"duration_in_sec\",\n                \"value\": \"120\"\n              }\n            ]\n\n  playwright-tests-ui:\n    needs: [provision]\n    timeout-minutes: 20\n    runs-on: ubuntu-22.04\n    container:\n      image: mcr.microsoft.com/playwright:v1.43.1-jammy\n    defaults:\n      run:\n        working-directory: src/ContosoTraders.Ui.Website\n    env:\n      AADUSERNAME: ${{ secrets.AADUSERNAME }}\n      AADPASSWORD: ${{ secrets.AADPASSWORD }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v3\n        with:\n          node-version: 18\n          cache: npm\n          cache-dependency-path: src/ContosoTraders.Ui.Website/package-lock.json\n      - name: Set env variables for testing endpoints\n        run: |\n          echo \"REACT_APP_APIURLSHOPPINGCART\"=\"https://${{ needs.provision.outputs.cartsApiEndpoint }}/v1\" >> $GITHUB_ENV\n          echo \"REACT_APP_APIURL\"=\"https://${{ needs.provision.outputs.productsApiEndpoint }}/v1\" >> $GITHUB_ENV\n          echo \"REACT_APP_BASEURLFORPLAYWRIGHTTESTING\"=\"https://${{ needs.provision.outputs.uiCdnEndpoint }}\" >> $GITHUB_ENV\n      - name: Set env variables for testing login\n        if: ${{ env.AADUSERNAME != '' && env.AADPASSWORD != '' }}\n        run: |\n          echo \"REACT_APP_B2CCLIENTID\"=\"${{ needs.provision.outputs.azureAdAppClientId }}\" >> $GITHUB_ENV\n          echo \"REACT_APP_AADUSERNAME\"=\"${{ env.AADUSERNAME }}\" >> $GITHUB_ENV\n          echo \"REACT_APP_AADPASSWORD\"=\"${{ env.AADPASSWORD }}\" >> $GITHUB_ENV\n      - name: install dependencies\n        run: npm ci\n      - name: run playwright tests\n        id: test\n        run: HOME=/root npx playwright test\n      - name: upload playwright report\n        uses: actions/upload-artifact@v3\n        if: always()\n        with:\n          name: playwright-report\n          path: src/ContosoTraders.Ui.Website/playwright-report/\n          retention-days: 30\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\nauth.json\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n.vscode/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk \n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output \nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder \n.mfractor/\n\nDeploy/helm/__values/*\n!Deploy/helm/__values/readme.txt\n\nSource\\Services\\Contoso.Traders.Rewards.Registration.Api\\Properties\\PublishProfiles\\*.pubxml\nSource/Services/Contoso.Traders.Cart.Api/.env\n\n/Source/.env\n_gvalues.dev.yaml\n\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Microsoft Open Source Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\n\nResources:\n\n- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\n- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\n- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute to ContosoTraders\n\nThis repo is a reference and learning resource and everyone is invited to contribute, however not all PRs will be accepted into the main branch (**`main`**).\n\nThere's a general development strategy that's driven by @marcusfelling, who chooses, or defines criteria for choosing, the issues to include in the codebase, given a bunch of constraints and other guidelines.\n\n## Coding Standards\n\nThere are no explicit coding standards so pay attention to the general coding style, that's (mostly) used everywhere.\n\n## Forks and Branches\n\nAll contributions must be submitted as a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) so you need to [fork this repo](https://help.github.com/articles/fork-a-repo/) on your GitHub account.\n\nThe main branche is **`main`**:\n\n- **`main`**: Contains the latest code **and it is the branch actively developed**.\n**All PRs must be against `main` branch to be considered**.\n\n- Any other branch is considered temporary and could be deleted at any time. Do not submit any PR to them!\n\n## DISCLAIMER - This is not a PRODUCTION-READY TEMPLATE for microservices\n\nContosoTraders is a reference application to **showcase architectural patterns** for developing microservices applications using various Azure Services. **IT IS NOT A PRODUCTION-READY TEMPLATE** to start real-world application. In fact, the application is in a **permanent beta state**, as it’s also used to test new potentially interesting technologies as they show up.\n\nSince this is a learning resource, some design decisions have favored simplicity to convey a pattern, over production-grade robustness.\n\n## Suggestions\n\nWe hope this helps us all to work better and avoid some of the problems/frustrations of working in such a large community.\n\nWe'd also appreciate any comments or ideas to improve this.\n"
  },
  {
    "path": "LICENSE",
    "content": "    MIT License\n\n    Copyright (c) Microsoft Corporation.\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n"
  },
  {
    "path": "README.md",
    "content": "# Contoso Traders - Cloud testing tools demo app\n\nThe Contoso Traders app is a sample application showcasing [Playwright](https://playwright.dev), [Azure Load Testing](https://aka.ms/malt-docs), [Azure Chaos Studio](https://aka.ms/CHAOS-docs) and more.\n\nThis repo contains the source code, deployment templates, and demo scripts for exploring these cloud testing tools.\n\n## Overview Video\n[![Watch the overview video](https://img.youtube.com/vi/7JletmiT3io/hq1.jpg)](https://youtu.be/7JletmiT3io)\n\n## Documentation and Resources\n\n* Application Links: [UI](https://cloudtesting.contosotraders.com/) | [Carts API](https://contoso-traders-cartsctprd.bluestone-748d2276.eastus.azurecontainerapps.io/swagger/index.html) | [Products API](https://contoso-traders-productsctprd.eastus.cloudapp.azure.com/swagger/index.html)\n* [Deployment Instructions](./docs/deployment-instructions.md) | [Running Locally](./docs/running-locally.md)\n\n## Continuous Integration\n\n| Pipeline                                                                     | Status                                                                                                                                                                                                                                                                               | Details                                                        |\n| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |\n| [GitHub Workflow](./.github/workflows/contoso-traders-cloud-testing.yml)     | [![contoso-traders-cloud-testing](https://github.com/microsoft/contosotraders-cloudtesting/actions/workflows/contoso-traders-cloud-testing.yml/badge.svg?branch=main)](https://github.com/microsoft/contosotraders-cloudtesting/actions/workflows/contoso-traders-cloud-testing.yml) | Deploys to [production](https://production.contosotraders.com) |\n| [Azure DevOps Pipeline](./.azurepipelines/contoso-traders-cloud-testing.yml) | [![Build Status](https://dev.azure.com/MicrosoftTestDemos/ContosoTraders_Testing/_apis/build/status%2Fmicrosoft.contosotraders-cloudtesting?branchName=main)](https://dev.azure.com/MicrosoftTestDemos/ContosoTraders_Testing/_build/latest?definitionId=1&branchName=main)          | Deploys to [staging](https://staging.contosotraders.com)       |\n\n## Demo Scripts\n\n* [Developer Workflow](./demo-scripts/dev-workflow/walkthrough.md)\n\n* Azure Load Testing - Generate high-scale load and identify performance bottlenecks.\n  * [Create a load test for the shopping cart API.](./demo-scripts/azure-load-testing/walkthrough.md)\n  * [Use GitHub Actions for regression testing.](./demo-scripts/azure-load-testing/walkthrough.md#walkthrough-regression-testing-with-github-workflows)\n  * [Create a load test for a private endpoint that’s behind a VNet.](./demo-scripts/azure-load-testing/private-endpoints.md)\n  * [Right-size your AKS cluster using load tests.](./demo-scripts/azure-load-testing/aks-cost-optimization.md)\n\n* Azure Chaos Studio - Improve application resilience by introducing faults and simulating outages.\n  * [Create an experiment using Key Vault Deny Access fault to test the products API (AKS).](./demo-scripts/azure-chaos-studio/walkthrough.md)\n  * [Run experiment in GitHub Actions to inject faults (pod failures) into the AKS cluster.](./demo-scripts/azure-chaos-studio/walkthrough.md#walkthrough-running-chaos-experiments-via-github-workflows)\n\n* Playwright - Reliable end-to-end testing for modern web apps.\n  * [Use the VS Code extension to explore and run web tests](./demo-scripts/testing-with-playwright/walkthrough.md) for [API testing](src/ContosoTraders.Ui.Website/tests/api), [Authentication](src/ContosoTraders.Ui.Website/tests/auth.setup.ts), [Shopping cart](src/ContosoTraders.Ui.Website/tests/cart.spec.ts), [Uploading files](src/ContosoTraders.Ui.Website/tests/fileupload.spec.ts), [Visual Comparisons](src/ContosoTraders.Ui.Website/tests/pages.spec.ts#L63), [Emulation](src/ContosoTraders.Ui.Website/tests/map.spec.ts), [Mocking](src/ContosoTraders.Ui.Website/tests/mocks.spec.ts), and [using a CSV for data](src/ContosoTraders.Ui.Website/tests/account.ts)\n\n## Architecture\n\n![Architecture](./docs/architecture/contoso-traders-enhancements.drawio.png)\n\n## Contributing\n\nThis project welcomes contributions and suggestions.  Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.\n\nWhen you submit a pull request, a CLA bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\ncontact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n\n## Trademarks\n\nThis project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft\ntrademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).\nUse of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.\nAny use of third-party trademarks or logos are subject to those third-party's policies.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->\n\n# Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).\n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n* Full paths of source file(s) related to the manifestation of the issue\n* The location of the affected source code (tag/branch/commit or direct URL)\n* Any special configuration required to reproduce the issue\n* Step-by-step instructions to reproduce the issue\n* Proof-of-concept or exploit code (if possible)\n* Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "SUPPORT.md",
    "content": "\r\n# Support\r\n\r\n## How to file issues and get help  \r\n\r\nThis project uses GitHub Issues to track bugs and feature requests. Please search the existing\r\nissues before filing new issues to avoid duplicates.  For new issues, file your bug or\r\nfeature request as a new Issue.\r\n\r\n## Microsoft Support Policy  \r\n\r\nSupport for this project is limited to the resources listed above.\r\n"
  },
  {
    "path": "demo-scripts/azure-chaos-studio/walkthrough.md",
    "content": "# Azure Chaos Studio: Overview\n\nAzure Chaos Studio is an Azure service that allows you to create & run chaos experiments on your application's infrastructure. By deliberately introducing faults that simulate real-world outages, you can test your application's resiliency and identify potential issues before they impact your customers.\n\n## Key Takeaways\n\nIn this demo, you'll get an overview of Azure's Chaos Studio service; a managed service that can be used to simulate faults on your application's infrastructure.\n\n- Running Chaos Experiments to introduce faults in Azure Key Vault (deny access) and seeing its impact on the application.\n- Running Chaos Experiments via GitHub Workflows.\n\n## Before You Begin\n\nPlease execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n## Walkthrough: Identify the Chaos Studio target (Key Vault)\n\n1. In the Azure portal, you can navigate to the Azure Chaos Studio service from the search bar as follows.\n\n   ![chaos studio](./media/chaos1.png)\n\n2. Next, click on the `Target` tab and filter down to the `contoso-traders-rg{SUFFIX}` resource group.\n\n   ![chaos studio](./media/chaos2.png)\n\n3. Next, go to the `contosotraderskv${SUFFIX}` key vault resource, and click on the `Manage actions` button.\n\n   ![chaos studio](./media/chaos3.png)\n\n4. You'll notice that the \"Key Vault Deny Access\" fault will be being injected in the key vault when the chaos experiment is run. This fault will prevent the application from accessing the key vault, leading the application to fail.\n\n   ![chaos studio](./media/chaos4.png)\n\n## Walkthrough: Review the Chaos Experiment\n\n1. In the Chaos Studio, click on the `Experiment` tab and click on the `contoso-traders-chaos-kv-experiment{SUFFIX}` experiment.\n\n   ![chaos studio](./media/chaos5.png)\n\n2. Click on the experiment's `Edit` button to review the experiment's configuration.\n\n   ![chaos studio](./media/chaos6.png)\n\n3. Click on the action's `Edit` button to review the action's configuration.\n\n   ![chaos studio](./media/chaos7.png)\n\n4. You'll notice that the experiment is configured to run on the the `contosotraderskv${SUFFIX}` key vault resource for 5 minutes. For the duration of the experiment, the `Key Vault Deny Access` fault will be injected into the key vault (i.e. the key vault will not be accessible even to principals mentioned in its access policies).\n\n   ![chaos studio](./media/chaos8.png)\n\n   ![chaos studio](./media/chaos9.png)\n\n## Walkthrough: Run the Chaos Experiment\n\n1. Before starting the experiment, you can verify that the application is working as expected by navigating to the application's URL and clicking on any product category (e.g. `laptops`). The application should load the product category page successfully by fetching data from the API.\n\n   ![chaos studio](./media/chaos10.png)\n\n2. Next, navigate to the Chaos Studio and click on the `Experiment` tab. Click on the `contoso-traders-chaos-kv-experiment{SUFFIX}` experiment and click on the `Start` button.\n\n   ![chaos studio](./media/chaos11.png)\n\n   ![chaos studio](./media/chaos12.png)\n\n3. The experiment is now underway and during the course of the experiment, the key vault will not be accessible.\n\n   ![chaos studio](./media/chaos13.png)\n\n   ![chaos studio](./media/chaos14.png)\n\n## Walkthrough: Exposing resiliency issues in application\n\n1. The application's Products API follows the externalized configuration pattern, wherein upon startup, the API fetches DB connection strings, passwords etc from Azure key vault. The API then uses the connection string to connect to its product catalog db. If the key vault is not accessible, the API will fail to fetch the connection string and will fail to start.\n\n   ![chaos studio](./media/kv-config-provider.png)\n\n2. Let us force the API to restart (note: upon restarting, it'll attempt to connect to the key vault to fetch the connection string). We can do by simply deleting the API's pod. The AKS deployment will then recreate the pod and the API will restart.\n\n   ![chaos studio](./media/chaos15.png)\n\n   ![chaos studio](./media/chaos16.png)\n\n3. As soon as the new pod is created, the API will attempt to connect to the key vault to fetch the connection string. Since the key vault is not accessible, the API will fail to start.\n\n   ![chaos studio](./media/chaos17.png)\n\n4. You can verify that the application is not working as expected by navigating to the application's URL and clicking on any product category (e.g. `laptops`). The application should fail to load the product category page.\n\n   ![chaos studio](./media/chaos18.png)\n\n## Walkthrough: After the Chaos Experiment ends\n\n1. After the 5 minutes are up, the experiment will end and the key vault will be accessible again.\n\n   ![chaos studio](./media/chaos19.png)\n\n2. AKS's deployment ensures that the API will automatically restarted on crashes (with exponential back-off applied). Once the chaos experiment ends, the key vault will be accessible again. When AKS restarts the pod after this, the API will be able to connect to the key vault and will start successfully.\n\n   ![chaos studio](./media/chaos20.png)\n\n   ![chaos studio](./media/chaos21.png)\n\n## Walkthrough: Running Chaos Experiments via GitHub Workflows\n\n1. We have a Chaos Experiment `contoso-traders-chaos-aks-experiment{SUFFIX}` that injects faults (pod failures) into the AKS cluster: `contoso-traders-aks{SUFFIX}` for a duration of 5 mins.\n\n   ![chaos studio](./media/chaos22.png)\n\n2. Internally, this experiment leverages [Chaos Mesh](https://chaos-mesh.org/), a CNCF project that orchestrates fault injection on Kubernetes environments (e.g. network latency, pod failures, and even node failures).\n\n   ![chaos studio](./media/chaos23.png)\n\n3. The github workflow `contoso-traders-cloud-testing.yml` triggers the AKS chaos experiment (pod failures), while simultaneously running a load test against the same AKS cluster.\n\n   ![chaos studio](./media/chaos24.png)\n\n## More Information\n\n- [Azure Chaos Studio](https://learn.microsoft.com/azure/chaos-studio/)\n- [Pricing](https://azure.microsoft.com/pricing/details/chaos-studio/)\n"
  },
  {
    "path": "demo-scripts/azure-load-testing/aks-cost-optimization.md",
    "content": "# Azure Load Testing: AKS Cost Optimization\n\n## Key Takeaways\n\nIn this demo, we'll see how Azure Load Testing can be used to help \"right-size\" an AKS cluster. The demo will start with a representative load test against a large AKS cluster. We'll iteratively scale down the cluster until we find the smallest cluster size that can handle the load. We'll also use the load test's integrated server-side metrics to analyze the cluster's resource utilization.\n\n## Before you begin\n\nPlease execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n## Walkthrough\n\n1. We have a GitHub workflow (and a Azure DevOps pipeline too) that executes load tests on a AKS cluster using a matrix of cluster sizes. The workflow is defined in the [aks-cost-optimization.yml](../../.github/workflows/aks-cost-optimization.yml) file. The workflow can be triggered manually on-demand.\n\n   ![aks cost optimization](./media/aks-cost-optimization-0.png)\n\n\n2. The load test, defined in the [load-test-aks.yml](../../.github/workflows/load-test-aks.yml) file, targets the `Products API`.\n\n3. [Optional Step] From the Azure Portal, we'll tweak the existing load test `contoso-traders-products` to incorporate server side AKS metrics using steps below:\n\n   ![aks cost optimization](./media/aks-cost-optimization-1.png)\n\n   ![aks cost optimization](./media/aks-cost-optimization-2.png)\n\n   ![aks cost optimization](./media/aks-cost-optimization-3.png)\n\n   ![aks cost optimization](./media/aks-cost-optimization-4.png)\n\n4. We manually invoke the GitHub workflow. In the beginning, the AKS cluster's size is automatically increased (CPU Limit: 250m, Mem Limit: 256 Mi) and a load test is run against it with 25 virtual concurrent users (threads) for a short duration of 30 seconds.\n\n5. In the next matrix step, the cluster is automatically rescaled a smaller size (CPU Limit: 250m, Mem Limit: 128 Mi) and the load test is run again.\n\n6. In next iterations, the scale down continues until the cluster is unable handle the load any longer. The last passing iteration is the smallest cluster size that can handle the load. In below example, the \"right-sized\" cluster will have CPU Limit: 250m, Mem Limit: 128 Mi.\n\n   ![aks cost optimization](./media/aks-cost-optimization-5.png)\n\n## Cost Considerations\n\nA quick note on costs considerations when you run the GitHub workflow (or Azure DevOps pipeline):\n\n1. Github Actions ([pricing details](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#included-storage-and-minutes)): Each run will consume approximately 25 billable minutes on linux runners.\n\n2. Azure Load Testing ([pricing details](https://azure.microsoft.com/pricing/details/load-testing/)): The number of virtual users and duration of the test are the key factors that determine the cost of the test. In this demo, the load tests are configured to use 25 virtual users and the test matrix sets up 4 test runs, each of 30 seconds duration.\n\n## Summary\n\nIn this demo, we saw how Azure Load Testing was used to help \"right-size\" an AKS cluster to optimize costs. The demo started with a representative load test against a large AKS cluster. We iteratively scaled down the cluster until we found the smallest cluster size that can handle the load.\n"
  },
  {
    "path": "demo-scripts/azure-load-testing/private-endpoints.md",
    "content": "# Azure Load Testing: Private Endpoints\n\n## Key Takeaways\n\nIn this demo, we'll attempt to load test an private API endpoint using the Azure Load Testing Preview. The private API endpoint in question is only accessible from within an Azure virtual network (VNET).\nWe'll demonstrate Azure Load Testing service's capability to generate load from within a virtual network (using VNET resource injection).\n\n## Before You Begin\n\nPlease execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n> **Warning**\nTo deploy the additional resources for this walkthrough ensure you have set the GitHub Action's variable `DEPLOYPRIVATEENDPOINTS` to `true`. If you're using Azure DevOps Pipeline, then please ensure that the variable `DEPLOYPRIVATEENDPOINTS` is set to `true` in the `contosotraders-cloudtesting-variable-group` variable group.\n \n\nSpecifically, here's what happens behind the scenes:\n\n* An Azure virtual network (VNET) is created with three subnets:\n  * A subnet for Azure Container Apps to deploy its infrastructure as well as the application's API private endpoints.\n  * A subnet for Azure Load Testing to inject its resources.\n  * A subnet for Azure VMs (jumpboxes) to access the application's private endpoints (for visual verification purposes).\n\n* An Azure Container Apps instance is deployed to host the application's API endpoints. The API endpoints are configured to be private endpoints, and the ingress controller only allows internal VNET traffic (i.e. endpoint is only accessible from within the VNET).\n\n* A private DNS zone (e.g. `eastus.azurecontainerapps.io`) is created to resolve the private endpoints' DNS names. This private DNS zone is associated with the VNET's subnet ACA. We add the necessary A records to link the endpoint FQDN to the ACA's private IP address.\n\n* A jumpbox VM is deployed to the VNET's VM subnet. This VM will be used in this demo to access the application's private endpoints.\n\n  ![VNET configuration](./media/vnet-configuration.png)\n\n## Walkthrough: Identify the Load Test Target\n\n1. In the Azure portal, you can navigate to the `contoso-traders-intcarts{SUFFIX}` Azure Container App in the `contoso-traders-rg` resource group. This is the application that hosts the `Carts API`. Note that this application is only accessible from within the VNET.\n\n   ![ACA](./media/private-endpoint-1.png)\n\n2. You can get the URL of the `Carts API` by as shown below.\n\n   ![ACA](./media/private-endpoint-2.png)\n\n3. In a separate browser tab, enter the following url in the address bar to load the API's swagger page: `<ACA url>/swagger/index.html`. You'll notice that the API's endpoints are not reachable via the public internet.\n\n   ![ACA](./media/private-endpoint-3.png)\n\n4. To access the API's endpoints, you'll need to access the API from within the VNET. You can RDP into the `jumpboxvm` VM. This is a jumpbox VM located in the same resource group `contoso-traders-rg`. From this RDP session, you can access the API's swagger page: `<ACA url>/swagger/index.html`\n\n   ![ACA](./media/private-endpoint-4.png)\n\n5. The specific API that we'll be using for is the `Carts API`'s `GET <ACA url>/v1/ShoppingCart/loadtest` endpoint. Please note down this endpoint for later use.\n\n   ![ACA](./media/private-endpoint-5.png)\n\n## Walkthrough: Modify the previously created Load Test\n\n1. In the Azure portal, navigate to the Azure Load Testing instance (in the `contoso-traders-rg` resource group) that we created in the [previous demo](./walkthrough.md).\n\n2. Now click on `Configure` > `Tests`.\n\n   ![Load Test Private Endpoint](./media/load-test-private-endpoint-1.png)\n\n3. Navigate to the `Parameters` tab in the Edit Test blade. Modify the `domain` value to point to the `Carts API`'s private endpoint. You can use the `<ACA url>` you noted down earlier (note: please remove the `https://` prefix).\n\n   ![Load Test Private Endpoint](./media/load-test-private-endpoint-2.png)\n\n4. Navigate to the `Load` tab. Change `Configure test traffic mode` to `private`. Also, specify the VNET and subnet details: `contoso-traders-vnet{SUFFIX}` and `subnet-loadtest` respectively.\n\n   ![Load Test Private Endpoint](./media/load-test-private-endpoint-3.png)\n\n5. Click apply to save the changes.\n\n6. Now, click `Run` to start the load test against the private endpoint.\n\n   ![Load Test Private Endpoint](./media/load-test-private-endpoint-4.png)\n\n7. Behind the scenes, the Azure load testing service will inject the testing infrastructure into the specified VNET. The load test will successfully run to completion after that.\n\n   ![Load Test Private Endpoint](./media/load-test-private-endpoint-5.png)\n\n## Summary\n\nIn this demo, we saw how Azure Load Testing can be used to generate load from within a virtual network to test private/restricted API endpoints.\n\n## More Information\n\n* [Troubleshooting private endpoints](https://docs.microsoft.com/azure/container-apps/troubleshoot-private-endpoints)\n* [Test private endpoints by deploying Azure Load Testing in an Azure virtual network](https://learn.microsoft.com/azure/load-testing/how-to-test-private-endpoint)\n* [Scenarios for deploying Azure Load Testing in a virtual network](https://learn.microsoft.com/azure/load-testing/concept-azure-load-testing-vnet-injection)\n* [Blog Post: Load test endpoints with access restrictions using Azure Load Testing](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/load-test-endpoints-with-access-restrictions-using-azure-load/ba-p/3610412)\n* [Blog Post: Load test private endpoints deployed in another Azure region or subscription](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/load-test-private-endpoints-deployed-in-another-azure-region-or/ba-p/3693277)\n"
  },
  {
    "path": "demo-scripts/azure-load-testing/walkthrough.md",
    "content": "# Azure Load Testing: Overview\n\n## Key Takeaways\n\nIn this demo, you'll get an overview of Azure's Load Testing service; a managed service that can be used to simulate load on your application's UI and APIs endpoints.\n\nYou'll also get an insight into:\n\n- how to identify an application's breaking point under incrementally increasing load.\n- how to leverage server-side metrics and Azure AppInsights to identify the performance bottleneck.\n- how to guard your application against performance regressions leveraging load testing in CI/CD pipelines.\n\nAll these are especially crucial for an e-commerce application like Contoso Traders, which is expected to instantly handle a large, sudden spike in number of users, with low latency and no downtime.\n\n## Before You Begin\n\nPlease execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n## Walkthrough: Identify the Load Test Target\n\n1. In the Azure portal, you can navigate to the Azure Container App in the `contoso-traders-rg{SUFFIX}` resource group. This is the application that hosts the `Carts API`.\n\n   ![ACA](./media/aca-2.png)\n\n2. You can get the URL of the `Carts API` by as shown below.\n\n   ![ACA](./media/aca-endpoint.png)\n\n3. In a separate browser tab, enter the following url in the address bar to load the API's swagger page: `<ACA url>/swagger/index.html`\n\n   ![ACA](./media/aca-swagger.png)\n\n4. You can now identify the API that you want to load test. In this case, we'll be load testing the `Carts API`'s `GET <ACA url>/v1/ShoppingCart/loadtest` endpoint. Please note down this endpoint for later use.\n\n   ![ACA](./media/aca-swagger-2.png)\n\n## Walkthrough: Creating a Load Test\n\n1. In the Azure portal, you can navigate to the Azure Load Testing service in the `contoso-traders-rg{SUFFIX}` resource group.\n\n   ![load testing](./media/load-test-browse.png)\n\n2. You can create a new load test as follows: Navigate to the `Tests` section, and then click on `Create` > `Create a URL-based Test` button.\n\n   ![load testing](./media/load-test-create-1.png)\n\n3. In the `Basic` blade, you can specify the target URL. You can also specify the number of concurrent users, and the duration of the test. See example below:\n\n   ![load testing](./media/load-test-create-2.png)\n\n> **Note**: The target URL is the URL from the `Carts API` that you identified in the previous section.\n\n## Walkthrough: Running the Load Test\n\n1. Once you've entered the load test specifications above, you can run it by clicking on the `Run` button.\n\n   ![load testing](./media/load-test-run.png)\n\n2. The load test will take about 2 minutes to complete. Once done, it'll display the summary and client-side metrics.\n\n   ![load testing](./media/load-test-in-progress.png)\n\n   ![load testing](./media/load-test-completed.png)\n\n## Walkthrough: Incorporate Server Side Metrics\n\n1. Click on the `App Components` button. Then from the flyout, select the `contoso-traders-carts{SUFFIX}` CosmosDB component. This will add relevant metrics from the CosmosDB to the load test dashboard.\n\n   ![load testing](./media/load-test-server-side-metrics.png)\n\n2. Re-run the load test, and you'll see the impact of the synthetic load on the DB (in real-time).\n\n   ![load testing](./media/load-test-run-2.png)\n\n> **Note**: Unfortunately, ACA metrics are not yet supported in Azure Load Testing's server side metrics. This feature will be coming soon.\n\n## Walkthrough: Review ACA Metrics & Dashboards\n\n1. In the Azure portal, you can navigate to the Azure Container App in the `contoso-traders-rg{SUFFIX}` resource group.\n\n   ![ACA](./media/aca.png)\n\n2. For demo purposes, we have configured a `HTTP Scaling` rule that horizontally scales out additional replicas when the number of concurrent requests exceeds a threshold (`3` in this case). ACA also supports automatic scale-in to zero when traffic dips below threshold.\n\n   ![ACA Scaling Rules](./media/aca-scaling-rules.png)\n\n3. In the metrics tab, you can see the various metrics measured & published by the ACA infrastructure. You can create a metric chart that combines two metrics: `replica count` vs `requests`. It'll now have updated with the latest data after the load test. Of particular interest is the replica count chart of `Carts API`, which shows the instances auto-scaled out under increasing load. After load subsided, the instances auto-scaled back in to zero.\n\n   ![load testing ACA](./media/aca-metrics2.png)\n\n## Walkthrough: Export the JMX File, Results\n\n1. Navigate back to the Load Testing service, and click on the recently concluded test run. From there you can click on `Download` > `Input File`. This will download the JMX file in a zip archive.\n\n   ![load testing](./media/load-test-export-jmx.png)\n\n2. You can review the JMX file by simply loading it up in notepad or VSCode.\n\n   ![load testing](./media/load-test-jmx-view.png)\n\n3. The load test results can also be downloaded via in the `Download` > `Results` button. This will download a CSV file (inside a zip archive).\n\n   ![load testing](./media/load-test-export-results.png)\n\n   ![load testing](./media/load-test-results-view.png)\n\n## Walkthrough: Identify application's breakpoints\n\n1. Let us now modify the existing load test. We'll use it to put the application under increasing load, ultimately leading to failure. The goal is to identify the application's breakpoints (performance bottlenecks).\n\n   ![app breakpoint](./media/app-breakpoint-1.png)\n\n2. Modify the existing test configuration as follows:\n\n   1. Increase the number of concurrent users to `250` (from original `5`).\n   2. Change the test duration to `300` seconds (from original `120` seconds)\n   3. Change the ramp-up time to `300` seconds (from original `120` seconds).\n\n   ![app breakpoint](./media/app-breakpoint-2.png)\n\n3. Increase the number of engine instances to `2` (from original `1`).\n\n   ![app breakpoint](./media/app-breakpoint-3.png)\n\n4. Run the modified load test. You'll notice that the application starts to eventually fail under the increased load.\n\n   ![app breakpoint](./media/app-breakpoint-4.png)\n\n5. If you add the server-side metrics for the `contoso-traders-cartsct{SUFFIX}` CosmosDB, you'll notice that the DB's normalized RU consumption eventually starts to peg at 100% under load.\n\n   ![app breakpoint](./media/app-breakpoint-4-2.png)\n\n6. App Insights can help us narrow down the root cause of the error. Navigate to the `contoso-traders-rg{SUFFIX}` resource group, and click on the `contoso-traders-aict{SUFFIX}` resource.\n\n   ![app breakpoint](./media/app-breakpoint-5.png)\n\n7. In the App Insights blade, click on the `Failures` tab. Narrow down the time range to (say) the last 30 minutes. You'll see the listed failures (sampled by App Insights) that occurred during the load test.\n\n   ![app breakpoint](./media/app-breakpoint-6.png)\n\n8. Clicking on any one sample will give you a detailed view of the error (including stack trace in case of an exception). In this case, the error is a `500` error, caused by a `TaskCanceledException` (due to a gateway timeout in CosmosDB). This is a good indication that the application is failing due to a performance bottleneck.\n\n   ![app breakpoint](./media/app-breakpoint-7.png)\n\n## Walkthrough: Regression Testing with Github Workflows\n\n1. We have a GitHub workflow that executes load tests on the application's APIs. This workflow is automatically triggered on every checkin to the `main` branch. Specifically the load tests are run on the `Product API` and `Carts API` immediately after they're deployed to the AKS cluster and ACA respectively. This will help identify if any code (or infra) change causes the application performance to degrade under (simulated) load.\n\n   ![github workflow](./media/github-workflow-2.png)\n\n2. The workflow uses a github action to invoke the [Azure Load Testing](https://learn.microsoft.com/en-us/azure/load-testing/) service and simulate load on the application's `Product API` and `Carts API`, which are hosted on AKS and ACA respectively.\n\n   ![github action for load testing](./media/github-action.png)\n\n3. The workflow file references a load test configuration file (yml), which specifies the following:\n   1. The load test parameters.\n   2. The JMX/JMeter script to be used.\n   3. The pass/fail criteria for the test.\n\n   See an example of a load test configuration file below.\n\n   ```yaml\n   testName: contoso-traders-carts\n   testPlan: contoso-traders-carts.jmx\n   engineInstances: 1\n   failureCriteria:\n      - avg(response_time_ms) > 5000\n      - percentage(error) > 20\n   ```\n\n4. The load test takes about 3 minutes to execute. In this specific example, you can see that the load test failed since the average response time exceeded the specified threshold of 5000ms.\n\n   ![workflow for load testing](./media/github-workflow-3.png)\n\n5. Once done, you can navigate to the Azure Portal to get more in-depth details about the test.\n\n   ![load testing portal](./media/portal-load-test.png)\n\n## Summary\n\nIn this demo, you got an overview of Azure's Load Testing service; including how to create a load test, run it, and review the results. You also saw how to incorporate server-side metrics from Azure Services, and how to export the JMX file and results. Finally, you saw how to create a new load test from the JMX file, and how to use a GitHub workflow to execute load tests on the application's APIs.\n\n## More Information\n\n- [Azure Load Testing](https://learn.microsoft.com/azure/load-testing/)\n- [Blogs on Azure Load Testing](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/bg-p/AppsonAzureBlog/label-name/Azure%20Load%20Testing)\n- [Pricing](https://azure.microsoft.com/pricing/details/load-testing/)\n"
  },
  {
    "path": "demo-scripts/dev-workflow/walkthrough.md",
    "content": "\n# DevOps with GitHub & Azure: Technical Walkthrough  \n\n## Key Takeaways\n\nThe key takeaways from this demo are:\n\n- GitHub Actions is used to automate the deployment of the application and infrastructure. Using GitHub Action workflows, the application and infrastructure can be deployed to Azure cloud with a single click, allowing you to implement continuous integration and continuous deployment process.  \n- GitHub Actions integrates with Azure services to enable you to build, test, and deploy to Azure directly from your GitHub repository, along with tons of other integrations.\n- Using GitHub Actions, you will be able to incorporate your application testing, performance testing to validate your application for production readiness as part of your DevOps process itself.  \n- As part of this demo script, you will be adding a new feature to enable **dark mode** capability on Contoso Traders Website, along with adding playwright based testing in your DevOps cycle.\n  \n## Before you Begin\n\nYou must have Contoso Traders deployed in your environment and setup with GitHub Actions.  Please refer to the deployment instructions [here](../../docs/deployment-instructions.md)\n\n## Walkthrough – GitHub Actions for CI/CD\n\nGitHub Actions is a way to automate processes and workflows in your GitHub repository. Some of the benefits of using GitHub Actions include the ability to automate your software development lifecycle, integrate with other tools and services, including Azure services.\n\nLet us take a look at the GitHub Actions used by Contoso Traders for CI/CD.\n\n## Review Workflows used in Contoso Traders\n\n1. Navigate to [ContosoTraders CloudTesting repository.](https://github.com/microsoft/ContosoTraders-CloudTesting)\n\n2. Go to the **github/workflows** folder; inside, you'll find the workflow **YAML file** that are used to deploy and set up the resources.  \n\n    ![image](media/actionlist.png)\n\n3. Here is a quick overview of the workflow. If you are interested, you can review the workflow code to get into more details.  \n\n    a. **contoso-traders-cloud-testing:**  This workflow provisions Azure resources used for hosting the application, deploys the application to the provisioned resources. It also runs load testing & playwright tests as part of CI/CD cycle ensure the app is production ready with each change.\n\n     It includes everything needed to get the application up and running in an Azure Environment.\n\n      ![image](media/provision.png)\n\n## Monitor GitHub Actions Workflow\n\nGitHub Actions workflows can be monitored from the Actions tab on a repository. This tab shows a list of all the active and past workflows, along with their status and any associated logs. Users can see at a glance whether their workflows are running successfully and can troubleshoot any issues that may arise. Additionally, users can set up notifications to be alerted when a workflow starts or completes, or if it encounters an error. This can help users stay on top of their workflows and ensure that their projects are running smoothly.\n\nLet us take a look at the workflows status for Contoso Traders in this public repository.\n\n1. Navigate to [ContosoTraders/Actions](https://github.com/microsoft/ContosoTraders-CloudTesting/actions). Select the workflow **contoso-traders-cloud-testing**. This will the history of workflows execution.  \n\n    ![image](media/actions1.png)\n\n2. Select the latest run from the list. In Summary, you will see 5 jobs listed.\n\n    - `provision`: Used for provisioning Azure resources, configure access policies and permissions, seeding initial database.\n    - `playwright-tests-ui`: Used to execute playwright tests to validate UI functionalities before the UI service is deployed.\n    - `load-tests-carts-api`: Used to execute Azure Load testing to validate performance for Carts API.\n    - `load-tests-carts-internal-api`: Same as above, but executes Load testing against an internal/private API endpoint.\n    - `load-tests-with-chaos-products-api`: Used to execute Azure Load testing to validate performance for Products API with simultaneous chaos experiments (fault injection) enabled.\n\n    ![image](media/actionmonitor.png)\n\n3. Click on **provision** job. You can now see the detailed task of this job and expand to see the logs and steps.\n\n   ![image](media/actions3.png)\n\n  Similarly, you can review other jobs and workflows. Workflow are set to run on push to the main branch so that any new code change to the main branch is automatically built and deployed.\n  \n## Demo – Experience GitHub Actions in Action  \n\nNow that we have reviewed the GitHub Actions workflows, let us take a step-by-step approach to test the end-to-end CI/CD process.  As part of enhancement to the Contoso Traders website, you are asked to add **dark mode** functionality to the website.\n\nLet us add the dark mode functionality to the application by modifying the source code of our website.\n\n  ![image](media/L300-1.png)\n\nYour marketing team requires changing this to following\n\nContoso Traders is one stop shop for electronics items including smartphones, laptops, and other popular gadgets. Contoso Traders delivery premium quality electronics at affordable rates to resellers across the globe.  \n\nLet us make the changes and experience magic of GitHub Actions.  \n\n1. Login to your fork of Contoso Traders repository and navigate to Contoso Traders repository `https://github.com/**YOURGITHUBUSERNAME**/ContosoTraders-CloudTesting`.\n\n2. Create a new branch **add-dark-mode**.\n\n    ![image](media/addbranch.png)\n\n3. Navigate to appbar.js file, located in  **src/ContosoTraders.Ui.Website/src/shared/header/appbar.js**\n\n4. To make it easier for you to complete this change, source code already include code blocks for adding dark mode functionality. You will need to uncomment the code block, let's start by clicking on edit **symbol (2)**.\n\n5. Uncomment line number 8, 414,415 & 416.\n\n    ![image](media/uncommentcode1.png)\n\n    ![image](media/uncommentcode2.png)\n\n6. Commit the change to add-dark-mode branch. Click on Commit changes after updating commit message.  \n\n     ![image](media/commit1.png)\n\n7. Now, let's update playwright tests to include testing for dark mode functionality. Edit **darkmode.spec.ts** located in **src/ContosoTraders.Ui.Website/tests** and remove the `skip` annotation from line 7. \n\n8. Now, let us raise a pull request to merge this change to **main branch**. Click on **Pull Requests** and notice that changes are detected already. Click on **Review and raise Pull Request**.\n\n    ![image](media/L300-5.png)\n\n9. Change the base from microsoft/contosotraders to **YOURUSERNAME/contosotraders** and click **Create Pull Request**.  \n\n    ![image](media/addpr.png)\n\n10. Merge the pull request to main branch.  \n\n    ![image](media/L300-7.png)\n\n11. Merging the pull request should trigger your GitHub Action workflow, it will take few minutes for workflow to complete.  You can navigate inside the workflow to review progress, as documented in previous step.\n\n    ![image](media/workflowrunning.png)\n\n12. Once the workflow successfully completes, you can navigate to your Contoso Traders URL and see that the dark mode functionality is now added to the website.\n\n    ![image](media/darkmode.png)\n\n## Summary\n\nIn this scenario, we looked at how GitHub Actions can incorporate entire lifecycle of application deployment, performance testing & testing with playwright as part of CI/CD cycle.\n\n## Additional Reading  \n\nReference Links  \n\n- [DevSecops in GitHub](https://learn.microsoft.com/azure/architecture/solution-ideas/articles/devsecops-in-github)\n- [GitHub features and Actions](https://github.com/features/actions)\n- [GitHub Advnced Security](https://docs.github.com/get-started/learning-about-github/about-github-advanced-security)\n- [Microsoft Defender for DevOps - the benefits and features | Microsoft Learn](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-devops-introduction)  \n"
  },
  {
    "path": "demo-scripts/testing-with-playwright/walkthrough.md",
    "content": "# Testing with Playwright: Overview\n\n## Key Takeaways\n\n## Before You Begin\n\n1. Please execute the steps outlined in the [deployment instructions](../../docs/deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n2. Once done, please execute the steps mentioned in the [running locally](../../docs/running-locally.md) document. Basically this will ensure that Playwright is installed on your machine.\n\n## Installing VSCode Extension\n\nYou can install the Playwright extension for VSCode from the marketplace ([LINK](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)). This extension will help you get started with Playwright quickly. It will also help you run and debug your tests from within VSCode.\n\n  ![Playwright](./media/playwright-1.png)\n\nThe Playwright docs have more details here: [Using the Playwright VSCode Extension](https://playwright.dev/docs/getting-started-vscode)\n\n## Running tests in VSCode\n\n1. Restore project dependencies:\n   * Open a cmd window and navigate to the `src/ContosoTraders.Ui.Website` folder.\n   * Run `npm install`.\n\n1. Set environment variables for the remote testing endpoints. The values can be grabbed from the GitHub Action logs that set them in the pipeline.\n    1. Navigate to [ContosoTraders/Actions](https://github.com/microsoft/ContosoTraders-CloudTesting/actions)\n\n    1. Drill into the latest run by clicking on the `playwright-tests-ui` job, then clicking on the step `Set env variables for testing endpoints`.\n    ![image](media/actions1.png)\n    1. This project uses a `.env.playwright.local` file to easily manage these variables. Open `src\\ContosoTraders.Ui.Website\\.env.playwright.local` and set the following environment variables using the values from the logs above (these will be unique to your environment). NOTE: Do NOT check in these changes to source control!:\n\n    ```bash\n    REACT_APP_APIURLSHOPPINGCART = ''\n    REACT_APP_APIURL = ''\n    REACT_APP_BASEURLFORPLAYWRIGHTTESTING = ''\n    ```\n\n1. Click on the `Testing` tab in VSCode's activity bar. This will show you all the tests in your project (in a tree structure). There are tests that demonstrate [API testing](../../src/ContosoTraders.Ui.Website/tests/api.cart.spec.ts), [Authentication](../../src/ContosoTraders.Ui.Website/tests/auth.setup.ts), [Visual Comparisons](../../src/ContosoTraders.Ui.Website/tests/pages.spec.ts:63), [Emulation](../../src/ContosoTraders.Ui.Website/tests/map.spec.ts), [Mocking](../../src/ContosoTraders.Ui.Website/tests/mocks.spec.ts), and [using a CSV for data](../../src/ContosoTraders.Ui.Website/tests/account.ts).\n\n   ![Playwright](./media/playwright-2.png)\n\n1. You can run a single test (or a group of tests) by clicking the triangle symbol next to it. When Playwright finishes executing the test(s), you will see a green tick next to your test block as well as the time it took to run the test.\n\n   ![Playwright](./media/playwright-3.png)\n\n1. You can navigate to the test code by right-clicking on the test name in the tree structure, selecting `Go To Test`.\n\n   ![Playwright](./media/playwright-4.png)\n\n## Debugging the tests\n\n1. You can set breakpoints in your test code by clicking on the left \"gutter\" (the left-most column in the code editor). Right-clicking in the gutter will show you more options (like setting a conditional breakpoint).\n\n   ![Playwright](./media/playwright-5.png)\n\n1. Then you can run a test in debug mode by clicking on the `Debug` button next to the test name in the tree structure.\n\n   ![Playwright](./media/playwright-6.png)\n\n1. Once the breakpoint is hit, you can use the single-step through the code and inspect variables (Note: These debugging features are already built into VSCode, and aren't playwright specific. [More details](https://code.visualstudio.com/docs/editor/debugging)).\n\n   ![Playwright](./media/playwright-7.png)\n\n## Testing with Azure AD\n\nIn order to test authentication, we can configure AAD, then run tests to log in to a Contoso Traders account. The specific steps are:\n\n1. Identify the Service Principal details created in the [deployment instructions](../../docs/deployment-instructions.md).\n\n1. Add the above Service Principal into the the [Application Administrator](https://learn.microsoft.com/azure/active-directory/roles/permissions-reference#application-administrator) active directory role.\n\n    1. Go to the Azure portal, and navigate to the Azure Active Directory blade. Then click on the `Roles and Administrators` tab on the left.\n    1. Select the `Application Administrator` role, and click on the `Add assignments` button.\n    1. Select the service principal that you created in the previous step. Click on the `Add` button.\n\n    ![Application Administrator](../../docs/images/ad-application-administrator.png)\n\n    >\n    > Notes:\n    >\n    > * Unfortunately, there is no AZ CLI, AZ PowerShell or Bicep template support to add a service principal to the `Application Administrator` role. You'll have to do this manually through the Azure portal.\n    >\n    > * Note: In order for you to add the service principal to the `Application Administrator` role, you must yourself be a member of the `Global Administrator` role in Azure Active Directory.\n    >\n\n1. Create a test account (MFA disabled).\n\n1. To run the [example test](../../src/ContosoTraders.Ui.Website/tests/account.ts) in GitHub Actions, add the test account credentials as github environment-level variables.\n\n   | Variable Name | Variable Value               |\n   | ------------- | ---------------------------- |\n   | `AADUSERNAME` | username of the test account |\n   | `AADPASSWORD` | password of the test account |\n\n   > If you're using Azure Pipelines instead of GitHub Actions, you can set the above variables in the `contosotraders-cloudtesting-variable-group` variable group. See [this document](../../docs/deployment-instructions-azure-pipelines.md#prepare-your-azure-pipeline-for-deployment) for details.\n   >\n   > If you wish to run the [example test](../../src/ContosoTraders.Ui.Website/tests/account.ts) locally, set the credentials as 2 environment variables: REACT_APP_AADUSERNAME and REACT_APP_AADPASSWORD\n\n1. Re-run the github workflow `contoso-traders-cloud-testing`. This will configure the Azure AD to enable login functionality in the app.\n\n   This test has a beforeAll hook that will log in to the app, then the test case uses the logged in state to fill out the personal info form.\n\n1. Read the [Playwright Authentication Documentation](https://playwright.dev/docs/auth)\n\n> Tests written with Playwright execute in isolated clean-slate environments called browser contexts. This isolation model improves reproducibility and prevents cascading test failures. New browser contexts can load existing authentication state. This eliminates the need to login in every context and speeds up test execution.\n\n## More Information\n\n* [Playwright Documentation](https://playwright.dev/)\n\n* [Using the Playwright VSCode Extension](https://playwright.dev/docs/getting-started-vscode)\n\n* [VSCode Debugging](https://code.visualstudio.com/docs/editor/debugging)\n"
  },
  {
    "path": "docs/architecture/.$contoso-traders-enhancements.drawio.bkp",
    "content": "<mxfile host=\"Electron\" modified=\"2022-11-24T11:44:25.430Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36\" etag=\"0ivnrrHtWxYojl2-M1km\" version=\"20.3.0\" type=\"device\"><diagram id=\"cPhL8d0vtsXr2UynWkRd\" name=\"Page-1\">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</diagram></mxfile>"
  },
  {
    "path": "docs/architecture/.$contoso-traders-enhancements.drawio.dtmp",
    "content": "<mxfile host=\"Electron\" modified=\"2023-03-19T09:38:10.917Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36\" etag=\"jRXm6rDv2sSY2WuEMFRG\" version=\"20.8.16\" type=\"device\"><diagram id=\"cPhL8d0vtsXr2UynWkRd\" name=\"Page-1\">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</diagram></mxfile>"
  },
  {
    "path": "docs/architecture/contoso-traders-enhancements.drawio",
    "content": "<mxfile host=\"Electron\" modified=\"2023-03-19T09:38:10.021Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36\" etag=\"gBZ3QQc4x7UdVyWMEKUC\" version=\"20.8.16\" type=\"device\"><diagram id=\"cPhL8d0vtsXr2UynWkRd\" name=\"Page-1\">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</diagram></mxfile>"
  },
  {
    "path": "docs/deployment-instructions-azure-pipelines.md",
    "content": "# Contoso Traders - Deployment instructions using Azure Pipelines\n\nThis document will help you deploy the Contoso Traders application in your Azure environment using Azure Pipelines.\n\n## Prerequisites\n\n1. You'll have to follow the steps in the [Deployment Instructions](./deployment-instructions.md) document to set up your Azure subscription and github repository fork.\n2. You'll need to have an Azure DevOps organization and project set up. If you don't have one, you can follow the instructions [here](https://docs.microsoft.com/en-us/azure/devops/organizations/projects/create-project?view=azure-devops&tabs=preview-page).\n3. Please ensure that the following Azure DevOps marketplace extensions are installed for your organization:\n\n   - [Azure Load Testing](https://marketplace.visualstudio.com/items?itemName=AzloadTest.AzloadTesting)\n   - [Replace Tokens](https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens)\n\n## Prepare your Azure Pipeline for Deployment\n\n1. Ensure that you [provide access](https://github.com/settings/connections/applications/0d4949be3b947c3ce4a5) to the Azure Devops application in your GitHub account. Specifically ensure that the application has access to your forked GitHub repository.\n\n2. Create a new Azure Pipeline: In your Azure DevOps project, click on `Pipelines` > `New pipeline` > `GitHub` > Select your GitHub fork > `Existing Azure Pipelines YAML file` > `contoso-traders-cloud-testing/azure-pipelines.yml` (ensure branch is `main`, path is `/.azurepipelines/azure-pipelines.yml`) > `Continue`.\n\n3. Set up a service connection to Azure: In your Azure DevOps project, click on `Project settings` (bottom left of page) > `Service Connections` > `New service connection` > `Azure Resource Manager` > `Service principal (manual)` > `Next`.\n\n    At this point, you'll need the JSON output from the `az ad sp create-for-rbac` command [executed earlier](./deployment-instructions.md#prepare-your-azure-subscription).\n\n   ```json\n   {\n     \"clientId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\",\n     \"clientSecret\": \"your-client-secret\",\n     \"tenantId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\",\n     \"subscriptionId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\"\n   }\n   ```\n\n   On the `New Azure Service Connection` page, enter the following values:\n\n   - **Environment**: `Azure Cloud`\n   - **Scope Level**: `Subscription`\n   - **Subscription Id**: `subscriptionId` property from the JSON output\n   - **Subscription Name**: The name of your Azure subscription\n   - **Service Principal Id**: `clientId` property from the JSON output\n   - **Credential**: Choose the `Service principal key` option\n   - **Service Principal Key**: `clientSecret` property from the JSON output\n   - **Tenant ID**: `tenantId` property from the JSON output\n   - **Service Connection Name**: `SERVICEPRINCIPAL` (please use this exact name)\n   - **Description**: `Service connection to Azure subscription using service principal`\n   - **Grant access permission to all pipelines**: Ensure this option is checked\n\n   Click the `Verify and save` button.\n\n4. Set up the variables for your Azure Pipeline. Click on `Pipelines` > `Library` > `+ Variable Group`. Create a variable group called `contosotraders-cloudtesting-variable-group` with the following variables:\n\n    | Variable Name      | Variable Value                                                                                                                                                                              | Is Secret? |\n    | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |\n    | `SQLPASSWORD`      | 8 to 15 characters long, must contain uppercase, lowercase, and numeric characters                                                                                                          | YES        |\n    | `SUFFIX`           | A unique environment suffix (max 6 characters, alphanumeric, lower case only, no whitespace, no special chars). E.g. 'test51' or '1stg'                                                     | NO         |\n    | `DEPLOYMENTREGION` | The Azure region to deploy the application in. Must be one of: `australiaeast`,`centralus`,`eastus`,`eastus2`,`japaneast`,`northcentralus`,`uksouth`,`westcentralus`,`westeurope` | NO         |\n\n   After saving the variable group, click on `Pipeline permissions` and ensure that the `azure-pipelines.yml` pipeline has access to the variable group.\n\n### Deploy the Application\n\n1. Go to your Azure Project's `Pipelines` tab, select the `contoso-traders-cloud-testing` pipeline, and click on the `Run Pipeline` button.\n\n2. The Azure pipeline will provision the necessary infrastructure to your Azure subscription as well as deploy the applications (APIs, UI) to the infrastructure. Note that the pipeline might take about 15 mins to complete.\n\n  ![workflow-logs](./images/github-workflow.png)\n\n>You may get an error if you do not have free parallel jobs available in your Azure DevOps organization. If you get this error, you'll have to [request for more parallel jobs](https://docs.microsoft.com/en-us/azure/devops/pipelines/licensing/concurrent-jobs?view=azure-devops&tabs=yaml) in your Azure DevOps organization.\n>\n>For any other deployment errors, please check the [Troubleshooting Deployment Errors section](./deployment-instructions.md#troubleshooting-deployment-errors) in the Deployment Instruction document.\n\n## Next Steps\n\nYou can now proceed to [exploring demo scenarios](./deployment-instructions.md#explore-demo-scenarios).\n"
  },
  {
    "path": "docs/deployment-instructions.md",
    "content": "# Contoso Traders - Deployment instructions\n\nThis document will help you deploy the Contoso Traders application in your Azure environment. You'll be using both GitHub Actions and Azure CLI for this.\n\nOnce deployed, you'll be able to walk through various demo scenarios for Microsoft Playwright, Azure Load Testing, and Azure Chaos Studio.\n\n## Prerequisites\n\nYou will need following to get started:\n\n1. **GitHub account**: Create a free account [here](https://github.com/).\n2. **Azure subscription**: Create a free account [here](https://azure.microsoft.com/free/).\n3. **Azure CLI**: Instructions to download and install [here](https://learn.microsoft.com/cli/azure/install-azure-cli).\n4. **VS Code**: Download and install [here](https://code.visualstudio.com/download).\n\n## Prepare your Azure Subscription\n\n1. Log into Azure CLI with your Azure credentials: `az login`\n\n2. Ensure that the correct Azure subscription is selected: `az account show`\n   * If not, select the correct subscription: `az account set -s <AZURE-SUBSCRIPTION-ID>`. Replace `<AZURE-SUBSCRIPTION-ID>` with your Azure subscription ID.\n\n3. Register some required resource providers in your Azure subscription:\n   * `az provider register -n Microsoft.OperationsManagement -c`\n   * `az provider register -n Microsoft.Cdn -c`\n   * `az provider register -n Microsoft.Chaos -c`\n\n4. Create an Azure Service Principal and add it to the `Owner` role in your Azure subscription:\n   * `az ad sp create-for-rbac -n contosotraders-sp --role Owner --scopes /subscriptions/<AZURE-SUBSCRIPTION-ID> --sdk-auth`. Replace `<AZURE-SUBSCRIPTION-ID>` with your Azure subscription ID.\n   * Make a note of the JSON output from above step (especially the `clientId`, `clientSecret`, `subscriptionId` and `tenantId` properties). These will be required later.\n   * You'll notice a warning in the output: `Option '--sdk-auth' has been deprecated and will be removed in a future release`. This is [a known issue, without workarounds, but can be safely ignored](https://github.com/Azure/azure-cli/issues/20743).\n\n5. If for some reason, you do not have permissions to add the service principal in the `Owner` role on the subscription, then you can create a custom role and assign it to the service principal as follows (remember to replace `<AZURE-SUBSCRIPTION-ID>` in snippets below with your Azure subscription ID).\n\n   1. If using bash:\n\n      ```bash\n      az role definition create --role-definition '{\n          \"Name\": \"ContosoTraders Write Role Assignments\",\n          \"Description\": \"Perform Role Assignments\",\n          \"Actions\": [\"Microsoft.Authorization/roleAssignments/write\"],\n          \"AssignableScopes\": [\"/subscriptions/<AZURE-SUBSCRIPTION-ID>\"]\n      }'\n      ```\n\n   2. If using PowerShell or cmd shell, you can run `az role definition create --role-definition ./custom-role.json`. Note that you need to first create a file called `custom-role.json` containing the following snippet.\n\n      ```json\n      {\n          \"Name\": \"ContosoTraders Write Role Assignments\",\n          \"Description\": \"Perform Role Assignments\",\n          \"Actions\": [\"Microsoft.Authorization/roleAssignments/write\"],\n          \"AssignableScopes\": [\"/subscriptions/<AZURE-SUBSCRIPTION-ID>\"]\n      }\n      ```\n\n   3. Finally create the service principal and assign it to the custom role:\n\n      ```bash\n      `az ad sp create-for-rbac -n contosotraders-sp --role \"ContosoTraders Write Role Assignments\" --scopes /subscriptions/<AZURE-SUBSCRIPTION-ID> --sdk-auth`\n      ```\n\n6. If you haven't used Azure Cognitive Services with your subscription, you'll need to accept the responsible AI terms.\nManually create an Azure Cognitive Service resource in your subscription temporarily, and accept the Responsible AI terms. You can then delete the resource.\n\n   * The Responsible AI terms are shown only once per subscription (during first Cognitive Service resource creation in subscription), and once accepted, they are not shown again.\n   * Currently, there exists no mechanism to accept the Responsible AI terms programmatically. It can only be done manually through the Azure portal.\n   * You can read more about Responsible AI [here](https://learn.microsoft.com/azure/machine-learning/concept-responsible-ai).\n\n   ![agree-cognitive-service-screenshot](./images/agree-cognitive-service-screenshot.png)\n\n## Prepare your GitHub Repository\n\n1. Fork the [contosotraders-cloudtesting repo](https://github.com/microsoft/contosotraders-cloudtesting) in your account.\n\n## Prepare your GitHub Workflow for Deployment\n\n>\n>If you wish to deploy using Azure Pipelines instead of GitHub Workflows, you can follow the instructions [here](./deployment-instructions-azure-pipelines.md) and skip this section entirely.\n>\n\n1. Set up the repository secrets in your forked repo. On your fork of the github repository, go to the `Settings` tab > `Secrets and variables` > `Actions` > `Secrets` tab and create these necessary repository secrets:\n\n    | Secret Name        | Secret Value                                                                       |\n    | ------------------ | ---------------------------------------------------------------------------------- |\n    | `SQLPASSWORD`      | 8 to 15 characters long, must contain uppercase, lowercase, and numeric characters |\n    | `SERVICEPRINCIPAL` | See details below                                                                  |\n\n    The value of the `SERVICEPRINCIPAL` secret above needs to have the below format.\n\n   ```json\n   {\n     \"clientId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\",\n     \"clientSecret\": \"your-client-secret\",\n     \"tenantId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\",\n     \"subscriptionId\": \"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz\"\n   }\n   ```\n\n    The values of the properties needed can be found in the JSON output of the `az ad sp create-for-rbac` command in the previous section.\n\n2. Set up the repository variables in your forked repo. On your fork of the github repository, go to the `Settings` tab > `Secrets and variables` > `Actions` > `Variables` tab and create these necessary repository variables:\n\n    | Variable Name      | Variable Value                                                                                                                                                                              |\n    | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n    | `SUFFIX`           | A unique environment suffix (max 6 characters, alphanumeric, lower case only, no whitespace, no special chars). E.g. 'test51' or '1stg'                                                     |\n    | `DEPLOYMENTREGION` | The Azure region to deploy the application in. Must be one of: `australiaeast`,`centralus`,`eastus`,`eastus2`,`japaneast`,`northcentralus`,`uksouth`,`westcentralus`,`westeurope` |\n\n3. (optional) if you would like to deploy the additional resources to test private endpoints, set the following variable:'\n\n    | Variable Name      | Variable Value                                                                                                                                                                              |\n    | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n    | `DEPLOYPRIVATEENDPOINTS`           | `true`\n\n### Deploy the Application\n\n1. Go to your forked repo's `Actions` tab, selecting the `contoso-traders-cloud-testing` workflow, and click on the `Run workflow` button.\n\n2. This github workflow will provision the necessary infrastructure to your Azure subscription as well as deploy the applications (APIs, UI) to the infrastructure. Note that the workflow might take about 15 mins to complete.\n\n  ![workflow-logs](./images/github-workflow.png)\n\n### Verify the Deployment\n\n1. Once the workflow completes, the UI's accessible CDN endpoint URL will be displayed in the github workflow run.\n\n    ![Endpoints in workflow logs](./images/ui-endpoint-github-workflow.png)\n\n2. Clicking on the URL above, will load the application in a new browser tab. You can then verify that the application is indeed up and running.\n\n### Troubleshooting Deployment Errors\n\nHere are some common problems that you may encounter during deployment:\n\n1. Intermittent errors: Should you encounter any of [these intermittent errors](https://github.com/microsoft/ContosoTraders/issues?q=is%3Aissue+is%3Aopen+label%3Adevops) in the github workflow, please re-run the failed jobs (it'll will pass on retry). We're working to fix these soon.\n\n2. There is a [known issue](https://github.com/Azure/login/issues/249) where the Azure login github action fails if the service principal's `clientSecret` begins with `-` (hyphen). If you encounter this, please regenerate a new secret, update the repository secret in your github fork, and restart the workflow.\n\n## Explore Demo Scenarios\n\nFor further learning, you can run through some of the demo scripts listed below:\n\n* [Developer workflow](../demo-scripts/dev-workflow/walkthrough.md)\n* [Azure Load Testing](../demo-scripts/azure-load-testing/walkthrough.md)\n* [Azure Chaos Studio](../demo-scripts/azure-chaos-studio/walkthrough.md)\n* [UI Testing with Playwright](../demo-scripts/testing-with-playwright/walkthrough.md)\n\n## Cleanup\n\nOnce you are done deploying, testing, exploring, you should delete the `contoso-traders-rg{SUFFIX}` resource group to prevent incurring additional costs.\n\n  ![resource group deletion](./images/resource-group-deletion.png)\n\nThe `contoso-traders-aks-nodes-rg{SUFFIX}` will be automatically deleted as part of the AKS cluster deletion.\n\n## Cost Considerations\n\nA quick note on costs considerations when you deploy the application to your Azure subscription:\n\n1. Azure Load Testing ([pricing details](https://azure.microsoft.com/pricing/details/load-testing/)): The number of virtual users and duration of the test are the key factors that determine the cost of the test. In this demo, the load tests are configured to use 5 virtual users and the test is set to run for 3 mins.\n2. Azure Kubernetes Service ([pricing details](https://azure.microsoft.com/pricing/details/kubernetes-service/)): The number of nodes and the number of hours that the cluster is running are the key factors that determine the cost of the cluster. In this demo, the cluster is configured to use 1 node (powered by vm scale sets) and the cluster is set to run 24x7 (you can manually stop the cluster when not in use). Because of a [limitation in the AKS bicep schema](https://github.com/Azure/bicep/issues/6974), the AKS cluster has to use premium SSD storage disks.\n3. Azure Container Apps ([pricing details](https://azure.microsoft.com/pricing/details/container-apps/)): Each instance has 0.5 vCPU and 1.0 GiB of memory. In this demo, the container app is configured to use 1 instance, but can autoscale out to max 3 instances under load.\n4. Azure Virtual Machines ([pricing details](https://azure.microsoft.com/pricing/details/virtual-machines/windows/)): The jumpbox VM uses the `Standard_D2s_v3` VM size, which has 2 vCPU and 8 GiB of memory. The jumpbox VMs are schedule to auto-shutdown at 1900 UTC daily. You can also manually stop & deallocate the VM when not in use.\n5. Github Actions / storage quota ([pricing details](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#included-storage-and-minutes)): We've set the playwright test to enable recordings only on failures/retries. This brings the playwright report to ~55 MB when tests fail.\n\n>\n> The above costs are based on the default configuration of the demo. You can modify the configuration to reduce the costs. For example, you can reduce the number of instances in the container app, reduce the number of virtual users in the load test, etc.\n>\n"
  },
  {
    "path": "docs/running-locally.md",
    "content": "# ContosoTraders: Running Locally\n\n1. Follow the [deployment instructions](./deployment-instructions.md) to provision the infrastructure in your own Azure subscription.\n\n2. Ensure that you have the following installed on your local machine:\n   * [Git](https://git-scm.com/downloads)\n   * [Node LTS v18.15.0](https://nodejs.org/dist/v18.15.0/)\n   * [DOTNET 7 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)\n   * [AZ CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)\n\n3. Login to AZ CLI using the details of the [service principal previously created](./deployment-instructions.md):\n   * `az login --service-principal -u <clientId> -p <clientSecret> --tenant <tenantId>`\n\n4. Git clone your forked repository to your local machine.\n   * `git clone <FORKED-REPO-URL> <PATH-TO-A-LOCAL-FOLDER>`\n\n5. Run the Products API locally:\n   * Open a cmd window and navigate to the `src/ContosoTraders.Api.Products` folder.\n   * Run `dotnet user-secrets set \"KeyVaultEndpoint\" \"https://contosotraderskv<SUFFIX>.vault.azure.net/\"`. Replace `<SUFFIX>` with the [value used in the github repository variable](./deployment-instructions.md#prepare-your-github-account).\n   * Run `dotnet build && dotnet run`. This will start the web API at `https://localhost:62300/swagger`.\n   * Note that your browser may show you a warning about insecure connection which you can safely ignore.\n      ![self signed cert warning](./images/self-signed-cert.png)\n\n6. Run the Carts API locally:\n   * Open a cmd window and navigate to the `src/ContosoTraders.Api.Carts` folder.\n   * Run `dotnet user-secrets set \"KeyVaultEndpoint\" \"https://contosotraderskv<SUFFIX>.vault.azure.net/\"`. Replace `<SUFFIX>` with the [value used in the github repository variable](./deployment-instructions.md#prepare-your-github-account).\n   * Run `dotnet build && dotnet run`. This will start the web API at `https://localhost:62400/swagger`.\n   * Note that your browser may show you a warning about insecure connection which you can safely ignore.\n\n7. Run the UI locally:\n   * Open a cmd window and navigate to the `src/ContosoTraders.Ui.Website` folder.\n   * Run `npm install`.\n   * Set the following environment variables:\n     * `set REACT_APP_APIURL=https://localhost:62300/v1`\n     * `set REACT_APP_APIURLSHOPPINGCART=https://localhost:62400/v1`\n     * `set REACT_APP_BASEURLFORPLAYWRIGHTTESTING=http://localhost:3000` (note: This endpoint is `http://` and NOT `https://`)\n     * `set REACT_APP_B2CCLIENTID=<B2C-CLIENT-ID>` (note: This step is optional. Replace `<B2C-CLIENT-ID>` with the output of the command: `az ad app list --display-name contoso-traders-cloud-testing-app<SUFFIX> --query \"[].appId\" -o tsv`)\n   * Run `npm run start`. This will start the UI on `http://localhost:3000`.\n   * Note that your browser may show you a warning about insecure connection which you can safely ignore.\n\n8. Run the Playwright UI tests locally:\n   * Ensure that you have executed step #7 above and launched the UI locally.\n   * Open a cmd window and navigate to the `src/ContosoTraders.Ui.Website` folder.\n   * Run `npx playwright install --with-deps`.\n   * Run `npx playwright test`.\n   * The Playwright UI tests will run for a few minutes and a HTML report will be displayed at the end.\n"
  },
  {
    "path": "iac/createPrivateDnsZone.bicep",
    "content": "// Note: There is a very specific reason the creation of the private DNS zone happens in this separate bicep module (as opposed \n// to the main bicep file). A few of resource 'names' are derived from the FQDN on the ACA app; which only becomes known\n// \"AFTER\" the ACA app is created.\n// If you attempt to use the FQDN in the 'name' properties (e.g. name of A record, name of private dns zone, etc.) then you'll \n// encounter an error like this:\n//\n// This expression is being used in an assignment to the \"name\" property of the \"Microsoft.XXXX/YYYY\" type, which requires a value \n// that can be calculated at the start of the deployment. Properties of XXXYYY which can be calculated at the start include \"name\"\n//\n// More details: https://stackoverflow.com/q/73232751\n\nparam privateDnsZoneName string\nparam privateDnsZoneVnetId string\nparam privateDnsZoneVnetLinkName string\nparam privateDnsZoneARecordName string\nparam privateDnsZoneARecordIp string\nparam resourceTags object\n\n// private dns zone\nresource privdnszone 'Microsoft.Network/privateDnsZones@2020-06-01' = {\n  name: privateDnsZoneName\n  location: 'global'\n  tags: resourceTags\n}\n\n// vnet link\nresource privdnszone_vnetlink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {\n  name: privateDnsZoneVnetLinkName\n  location: 'global'\n  tags: resourceTags\n  parent: privdnszone\n  properties: {\n    registrationEnabled: true\n    virtualNetwork: {\n      id: privateDnsZoneVnetId\n    }\n  }\n}\n\n// private dns zone: 'A' record\nresource symbolicname 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {\n  name: privateDnsZoneARecordName\n  parent: privdnszone\n  properties: {\n    aRecords: [\n      {\n        ipv4Address: privateDnsZoneARecordIp\n      }\n    ]\n    ttl: 3600\n  }\n}\n"
  },
  {
    "path": "iac/createResourceGroup.bicep",
    "content": "// common\ntargetScope = 'subscription'\n\n// parameters\n////////////////////////////////////////////////////////////////////////////////\n\n// common\n@description('Rg for storage account, service bus, cosmos db & function app. Value is passed from GHA variable.')\nparam rgName string\n\n@minLength(3)\n@maxLength(6)\n@description('A unique environment suffix (max 6 characters, alphanumeric only).')\nparam suffix string\n\n@description('Set rg location')\n@allowed([\n  'australiaeast'\n  'centralus'\n  'eastus'\n  'eastus2'\n  'japaneast'\n  'northcentralus'\n  'uksouth'\n  'westcentralus'\n  'westeurope'\n])\nparam rgLocation string\n\n// variables\n////////////////////////////////////////////////////////////////////////////////\n\n// tags\nvar rgTags = {\n  Product: '${rgName}${suffix}'\n  Environment: suffix\n}\n\n// resource groups\n////////////////////////////////////////////////////////////////////////////////\n\nresource rg 'Microsoft.Resources/resourceGroups@2022-09-01' = {\n  name: '${rgName}${suffix}'\n  location: rgLocation\n  tags: rgTags\n}\n\n// outputs\n////////////////////////////////////////////////////////////////////////////////\n\noutput outputRgName string = rg.name\n"
  },
  {
    "path": "iac/createResources.bicep",
    "content": "// common\ntargetScope = 'resourceGroup'\n\n// parameters\n////////////////////////////////////////////////////////////////////////////////\n\n// common\n@minLength(3)\n@maxLength(6)\n@description('A unique environment suffix (max 6 characters, alphanumeric only).')\nparam suffix string\n\n@secure()\n@description('A password which will be set on all SQL Azure DBs.')\nparam sqlPassword string // @TODO: Obviously, we need to fix this!\n\nparam resourceLocation string = resourceGroup().location\n\n// tenant\nparam tenantId string = subscription().tenantId\n\n// aks\nparam aksLinuxAdminUsername string // value supplied via parameters file\n\nparam prefix string = 'contosotraders'\n\nparam prefixHyphenated string = 'contoso-traders'\n\n// sql\nparam sqlServerHostName string = environment().suffixes.sqlServerHostname\n\n// use param to conditionally deploy private endpoint resources\nparam deployPrivateEndpoints bool = false\n\n// variables\n////////////////////////////////////////////////////////////////////////////////\n\n// key vault\nvar kvName = '${prefix}kv${suffix}'\nvar kvSecretNameProductsApiEndpoint = 'productsApiEndpoint'\nvar kvSecretNameProductsDbConnStr = 'productsDbConnectionString'\nvar kvSecretNameProfilesDbConnStr = 'profilesDbConnectionString'\nvar kvSecretNameStocksDbConnStr = 'stocksDbConnectionString'\nvar kvSecretNameCartsApiEndpoint = 'cartsApiEndpoint'\nvar kvSecretNameCartsInternalApiEndpoint = 'cartsInternalApiEndpoint'\nvar kvSecretNameCartsDbConnStr = 'cartsDbConnectionString'\nvar kvSecretNameImagesEndpoint = 'imagesEndpoint'\nvar kvSecretNameAppInsightsConnStr = 'appInsightsConnectionString'\nvar kvSecretNameUiCdnEndpoint = 'uiCdnEndpoint'\nvar kvSecretNameVnetAcaSubnetId = 'vnetAcaSubnetId'\n\n// user-assigned managed identity (for key vault access)\nvar userAssignedMIForKVAccessName = '${prefixHyphenated}-mi-kv-access${suffix}'\n\n// cosmos db (stocks db)\nvar stocksDbAcctName = '${prefixHyphenated}-stocks${suffix}'\nvar stocksDbName = 'stocksdb'\nvar stocksDbStocksContainerName = 'stocks'\n\n// cosmos db (carts db)\nvar cartsDbAcctName = '${prefixHyphenated}-carts${suffix}'\nvar cartsDbName = 'cartsdb'\nvar cartsDbStocksContainerName = 'carts'\n\n// app service plan (products api)\nvar productsApiAppSvcPlanName = '${prefixHyphenated}-products${suffix}'\nvar productsApiAppSvcName = '${prefixHyphenated}-products${suffix}'\nvar productsApiSettingNameKeyVaultEndpoint = 'KeyVaultEndpoint'\nvar productsApiSettingNameManagedIdentityClientId = 'ManagedIdentityClientId'\n\n// sql azure (products db)\nvar productsDbServerName = '${prefixHyphenated}-products${suffix}'\nvar productsDbName = 'productsdb'\nvar productsDbServerAdminLogin = 'localadmin'\nvar productsDbServerAdminPassword = sqlPassword\n\n// sql azure (profiles db)\nvar profilesDbServerName = '${prefixHyphenated}-profiles${suffix}'\nvar profilesDbName = 'profilesdb'\nvar profilesDbServerAdminLogin = 'localadmin'\nvar profilesDbServerAdminPassword = sqlPassword\n\n// azure container app (carts api)\nvar cartsApiAcaName = '${prefixHyphenated}-carts${suffix}'\nvar cartsApiAcaEnvName = '${prefix}acaenv${suffix}'\nvar cartsApiAcaSecretAcrPassword = 'acr-password'\nvar cartsApiAcaContainerDetailsName = '${prefixHyphenated}-carts${suffix}'\nvar cartsApiSettingNameKeyVaultEndpoint = 'KeyVaultEndpoint'\nvar cartsApiSettingNameManagedIdentityClientId = 'ManagedIdentityClientId'\n\n// azure container app (carts api - internal only)\nvar cartsInternalApiAcaName = '${prefixHyphenated}-intcarts${suffix}'\nvar cartsInternalApiAcaEnvName = '${prefix}intacaenv${suffix}'\nvar cartsInternalApiAcaSecretAcrPassword = 'acr-password'\nvar cartsInternalApiAcaContainerDetailsName = '${prefixHyphenated}-intcarts${suffix}'\nvar cartsInternalApiSettingNameKeyVaultEndpoint = 'KeyVaultEndpoint'\nvar cartsInternalApiSettingNameManagedIdentityClientId = 'ManagedIdentityClientId'\n\n// storage account (product images)\nvar productImagesStgAccName = '${prefix}img${suffix}'\nvar productImagesProductDetailsContainerName = 'product-details'\nvar productImagesProductListContainerName = 'product-list'\n\n// storage account (old website)\nvar uiStgAccName = '${prefix}ui${suffix}'\n\n// storage account (new website)\nvar ui2StgAccName = '${prefix}ui2${suffix}'\n\n// storage account (image classifier)\nvar imageClassifierStgAccName = '${prefix}ic${suffix}'\nvar imageClassifierWebsiteUploadsContainerName = 'website-uploads'\n\n// cdn\nvar cdnProfileName = '${prefixHyphenated}-cdn${suffix}'\nvar cdnImagesEndpointName = '${prefixHyphenated}-images${suffix}'\nvar cdnUiEndpointName = '${prefixHyphenated}-ui${suffix}'\nvar cdnUi2EndpointName = '${prefixHyphenated}-ui2${suffix}'\n\n// azure container registry\nvar acrName = '${prefix}acr${suffix}'\n\n// load testing service\nvar loadTestSvcName = '${prefixHyphenated}-loadtest${suffix}'\n\n// application insights\nvar logAnalyticsWorkspaceName = '${prefixHyphenated}-loganalytics${suffix}'\nvar appInsightsName = '${prefixHyphenated}-ai${suffix}'\n\n// portal dashboard\nvar portalDashboardName = '${prefixHyphenated}-dashboard${suffix}'\n\n// aks cluster\nvar aksClusterName = '${prefixHyphenated}-aks${suffix}'\nvar aksClusterDnsPrefix = '${prefixHyphenated}-aks${suffix}'\nvar aksClusterNodeResourceGroup = '${prefixHyphenated}-aks-nodes-rg${suffix}'\n\n// virtual network\nvar vnetName = '${prefixHyphenated}-vnet${suffix}'\nvar vnetAddressSpace = '10.0.0.0/16'\nvar vnetAcaSubnetName = 'subnet-aca'\nvar vnetAcaSubnetAddressPrefix = '10.0.0.0/23'\nvar vnetVmSubnetName = 'subnet-vm'\nvar vnetVmSubnetAddressPrefix = '10.0.2.0/23'\nvar vnetLoadTestSubnetName = 'subnet-loadtest'\nvar vnetLoadTestSubnetAddressPrefix = '10.0.4.0/23'\n\n// jumpbox vm\nvar jumpboxPublicIpName = '${prefixHyphenated}-jumpbox${suffix}'\nvar jumpboxNsgName = '${prefixHyphenated}-jumpbox${suffix}'\nvar jumpboxNicName = '${prefixHyphenated}-jumpbox${suffix}'\nvar jumpboxVmName = 'jumpboxvm'\nvar jumpboxVmAdminLogin = 'localadmin'\nvar jumpboxVmAdminPassword = sqlPassword\nvar jumpboxVmShutdownSchduleName = 'shutdown-computevm-jumpboxvm'\nvar jumpboxVmShutdownScheduleTimezoneId = 'UTC'\n\n// private dns zone\nvar privateDnsZoneVnetLinkName = '${prefixHyphenated}-privatednszone-vnet-link${suffix}'\n\n// chaos studio\nvar chaosKvExperimentName = '${prefixHyphenated}-chaos-kv-experiment${suffix}'\nvar chaosKvSelectorId = guid('${prefixHyphenated}-chaos-kv-selector-id${suffix}')\nvar chaosAksExperimentName = '${prefixHyphenated}-chaos-aks-experiment${suffix}'\nvar chaosAksSelectorId = guid('${prefixHyphenated}-chaos-aks-selector-id${suffix}')\n\n// tags\nvar resourceTags = {\n  Product: prefixHyphenated\n  Environment: suffix\n}\n\n// resources\n////////////////////////////////////////////////////////////////////////////////\n\n//\n// key vault\n//\n\nresource kv 'Microsoft.KeyVault/vaults@2022-07-01' = {\n  name: kvName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    // @TODO: Hack to enable temporary access to devs during local development/debugging.\n    accessPolicies: [\n      {\n        objectId: '31de563b-fc1a-43a2-9031-c47630038328'\n        tenantId: tenantId\n        permissions: {\n          secrets: [\n            'get'\n            'list'\n            'delete'\n            'set'\n            'recover'\n            'backup'\n            'restore'\n          ]\n        }\n      }\n    ]\n    sku: {\n      family: 'A'\n      name: 'standard'\n    }\n    softDeleteRetentionInDays: 7\n    tenantId: tenantId\n  }\n\n  // secret\n  resource kv_secretProductsApiEndpoint 'secrets' = {\n    name: kvSecretNameProductsApiEndpoint\n    tags: resourceTags\n    properties: {\n      contentType: 'endpoint url (fqdn) of the products api'\n      value: 'placeholder' // Note: This will be set via github worfklow\n    }\n  }\n\n  // secret \n  resource kv_secretProductsDbConnStr 'secrets' = {\n    name: kvSecretNameProductsDbConnStr\n    tags: resourceTags\n    properties: {\n      contentType: 'connection string to the products db'\n      value: 'Server=tcp:${productsDbServerName}${sqlServerHostName},1433;Initial Catalog=${productsDbName};Persist Security Info=False;User ID=${productsDbServerAdminLogin};Password=${productsDbServerAdminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'\n    }\n  }\n\n  // secret \n  resource kv_secretProfilesDbConnStr 'secrets' = {\n    name: kvSecretNameProfilesDbConnStr\n    tags: resourceTags\n    properties: {\n      contentType: 'connection string to the profiles db'\n      value: 'Server=tcp:${profilesDbServerName}${sqlServerHostName},1433;Initial Catalog=${profilesDbName};Persist Security Info=False;User ID=${profilesDbServerAdminLogin};Password=${profilesDbServerAdminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'\n    }\n  }\n\n  // secret \n  resource kv_secretStocksDbConnStr 'secrets' = {\n    name: kvSecretNameStocksDbConnStr\n    tags: resourceTags\n    properties: {\n      contentType: 'connection string to the stocks db'\n      value: stocksdba.listConnectionStrings().connectionStrings[0].connectionString\n    }\n  }\n\n  // secret\n  resource kv_secretCartsApiEndpoint 'secrets' = {\n    name: kvSecretNameCartsApiEndpoint\n    tags: resourceTags\n    properties: {\n      contentType: 'endpoint url (fqdn) of the carts api'\n      value: cartsapiaca.properties.configuration.ingress.fqdn\n    }\n  }\n\n  // secret\n  resource kv_secretCartsInternalApiEndpoint 'secrets' =\n    if (deployPrivateEndpoints) {\n      name: kvSecretNameCartsInternalApiEndpoint\n      tags: resourceTags\n      properties: {\n        contentType: 'endpoint url (fqdn) of the (internal) carts api'\n        value: deployPrivateEndpoints ? cartsinternalapiaca.properties.configuration.ingress.fqdn : ''\n      }\n    }\n\n  // secret\n  resource kv_secretCartsDbConnStr 'secrets' = {\n    name: kvSecretNameCartsDbConnStr\n    tags: resourceTags\n    properties: {\n      contentType: 'connection string to the carts db'\n      value: cartsdba.listConnectionStrings().connectionStrings[0].connectionString\n    }\n  }\n\n  // secret\n  resource kv_secretImagesEndpoint 'secrets' = {\n    name: kvSecretNameImagesEndpoint\n    tags: resourceTags\n    properties: {\n      contentType: 'endpoint url of the images cdn'\n      value: 'https://${cdnprofile_imagesendpoint.properties.hostName}'\n    }\n  }\n\n  // secret\n  resource kv_secretAppInsightsConnStr 'secrets' = {\n    name: kvSecretNameAppInsightsConnStr\n    tags: resourceTags\n    properties: {\n      contentType: 'connection string to the app insights instance'\n      value: appinsights.properties.ConnectionString\n    }\n  }\n\n  // secret\n  resource kv_secretUiCdnEndpoint 'secrets' = {\n    name: kvSecretNameUiCdnEndpoint\n    tags: resourceTags\n    properties: {\n      contentType: 'endpoint url (cdn endpoint) of the ui'\n      value: cdnprofile_ui2endpoint.properties.hostName\n    }\n  }\n\n  // secret\n  resource kv_secretVnetAcaSubnetId 'secrets' =\n    if (deployPrivateEndpoints) {\n      name: kvSecretNameVnetAcaSubnetId\n      tags: resourceTags\n      properties: {\n        contentType: 'subnet id of the aca subnet'\n        value: deployPrivateEndpoints ? vnet.properties.subnets[0].id : ''\n      }\n    }\n\n  // access policies\n  resource kv_accesspolicies 'accessPolicies' = {\n    name: 'replace'\n    properties: {\n      // @TODO: I was unable to figure out how to assign an access policy to the AKS cluster's agent pool's managed identity.\n      // Hence, that specific access policy will be assigned from a github workflow (using AZ CLI).\n      accessPolicies: [\n        {\n          tenantId: tenantId\n          objectId: userassignedmiforkvaccess.properties.principalId\n          permissions: {\n            secrets: ['get', 'list']\n          }\n        }\n        {\n          tenantId: tenantId\n          objectId: aks.properties.identityProfile.kubeletidentity.objectId\n          permissions: {\n            secrets: ['get', 'list']\n          }\n        }\n      ]\n    }\n  }\n}\n\nresource kv_roledefinitionforchaosexp 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {\n  scope: kv\n  // This is the Key Vault Contributor role\n  // See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#key-vault-contributor\n  name: 'f25e0fa2-a7c8-4377-a976-54943a77a395'\n}\n\nresource kv_roleassignmentforchaosexp 'Microsoft.Authorization/roleAssignments@2022-04-01' = {\n  scope: kv\n  name: guid(kv.id, chaoskvexperiment.id, kv_roledefinitionforchaosexp.id)\n  properties: {\n    roleDefinitionId: kv_roledefinitionforchaosexp.id\n    principalId: chaoskvexperiment.identity.principalId\n    principalType: 'ServicePrincipal'\n  }\n}\n\nresource userassignedmiforkvaccess 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {\n  name: userAssignedMIForKVAccessName\n  location: resourceLocation\n  tags: resourceTags\n}\n\n//\n// stocks db\n//\n\n// cosmos db account\nresource stocksdba 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = {\n  name: stocksDbAcctName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    databaseAccountOfferType: 'Standard'\n    enableFreeTier: false\n    capabilities: [\n      {\n        name: 'EnableServerless'\n      }\n    ]\n    locations: [\n      {\n        locationName: resourceLocation\n      }\n    ]\n  }\n\n  // cosmos db database\n  resource stocksdba_db 'sqlDatabases' = {\n    name: stocksDbName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      resource: {\n        id: stocksDbName\n      }\n    }\n\n    // cosmos db collection\n    resource stocksdba_db_c1 'containers' = {\n      name: stocksDbStocksContainerName\n      location: resourceLocation\n      tags: resourceTags\n      properties: {\n        resource: {\n          id: stocksDbStocksContainerName\n          partitionKey: {\n            paths: [\n              '/id'\n            ]\n          }\n        }\n      }\n    }\n  }\n}\n\n//\n// carts db\n//\n\n// cosmos db account\nresource cartsdba 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = {\n  name: cartsDbAcctName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    databaseAccountOfferType: 'Standard'\n    enableFreeTier: false\n    capabilities: [\n      {\n        name: 'EnableServerless'\n      }\n    ]\n    locations: [\n      {\n        locationName: resourceLocation\n      }\n    ]\n  }\n\n  // cosmos db database\n  resource cartsdba_db 'sqlDatabases' = {\n    name: cartsDbName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      resource: {\n        id: cartsDbName\n      }\n    }\n\n    // cosmos db collection\n    resource cartsdba_db_c1 'containers' = {\n      name: cartsDbStocksContainerName\n      location: resourceLocation\n      tags: resourceTags\n      properties: {\n        resource: {\n          id: cartsDbStocksContainerName\n          partitionKey: {\n            paths: [\n              '/Email'\n            ]\n          }\n        }\n      }\n    }\n  }\n}\n\n//\n// products api\n//\n\n// app service plan (linux)\nresource productsapiappsvcplan 'Microsoft.Web/serverfarms@2022-03-01' = {\n  name: productsApiAppSvcPlanName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'B1'\n  }\n  properties: {\n    reserved: true\n  }\n  kind: 'linux'\n}\n\n// app service\nresource productsapiappsvc 'Microsoft.Web/sites@2022-03-01' = {\n  name: productsApiAppSvcName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'UserAssigned'\n    userAssignedIdentities: {\n      '${userassignedmiforkvaccess.id}': {}\n    }\n  }\n  properties: {\n    clientAffinityEnabled: false\n    httpsOnly: true\n    serverFarmId: productsapiappsvcplan.id\n    siteConfig: {\n      linuxFxVersion: 'DOTNETCORE|7.0'\n      alwaysOn: true\n      appSettings: [\n        {\n          name: productsApiSettingNameKeyVaultEndpoint\n          value: kv.properties.vaultUri\n        }\n        {\n          name: productsApiSettingNameManagedIdentityClientId\n          value: userassignedmiforkvaccess.properties.clientId\n        }\n      ]\n    }\n  }\n}\n\n//\n// products db\n//\n\n// sql azure server\nresource productsdbsrv 'Microsoft.Sql/servers@2022-05-01-preview' = {\n  name: productsDbServerName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    administratorLogin: productsDbServerAdminLogin\n    administratorLoginPassword: productsDbServerAdminPassword\n    publicNetworkAccess: 'Enabled'\n  }\n\n  // sql azure database\n  resource productsdbsrv_db 'databases' = {\n    name: productsDbName\n    location: resourceLocation\n    tags: resourceTags\n    sku: {\n      capacity: 5\n      tier: 'Basic'\n      name: 'Basic'\n    }\n  }\n\n  // sql azure firewall rule (allow access from all azure resources/services)\n  resource productsdbsrv_db_fwlallowazureresources 'firewallRules' = {\n    name: 'AllowAllWindowsAzureIps'\n    properties: {\n      endIpAddress: '0.0.0.0'\n      startIpAddress: '0.0.0.0'\n    }\n  }\n\n  // @TODO: Hack to enable temporary access to devs during local development/debugging.\n  resource productsdbsrv_db_fwllocaldev 'firewallRules' = {\n    name: 'AllowLocalDevelopment'\n    properties: {\n      endIpAddress: '255.255.255.255'\n      startIpAddress: '0.0.0.0'\n    }\n  }\n}\n\n//\n// profiles db\n//\n\n// sql azure server\nresource profilesdbsrv 'Microsoft.Sql/servers@2022-05-01-preview' = {\n  name: profilesDbServerName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    administratorLogin: profilesDbServerAdminLogin\n    administratorLoginPassword: profilesDbServerAdminPassword\n    publicNetworkAccess: 'Enabled'\n  }\n\n  // sql azure database\n  resource profilesdbsrv_db 'databases' = {\n    name: profilesDbName\n    location: resourceLocation\n    tags: resourceTags\n    sku: {\n      capacity: 5\n      tier: 'Basic'\n      name: 'Basic'\n    }\n  }\n\n  // sql azure firewall rule (allow access from all azure resources/services)\n  resource profilesdbsrv_db_fwl 'firewallRules' = {\n    name: 'AllowAllWindowsAzureIps'\n    properties: {\n      endIpAddress: '0.0.0.0'\n      startIpAddress: '0.0.0.0'\n    }\n  }\n}\n\n//\n// carts api\n//\n\n// aca environment\nresource cartsapiacaenv 'Microsoft.App/managedEnvironments@2022-06-01-preview' = {\n  name: cartsApiAcaEnvName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Consumption'\n  }\n  properties: {\n    zoneRedundant: false\n  }\n}\n\n// aca\nresource cartsapiaca 'Microsoft.App/containerApps@2022-06-01-preview' = {\n  name: cartsApiAcaName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'UserAssigned'\n    userAssignedIdentities: {\n      '${userassignedmiforkvaccess.id}': {}\n    }\n  }\n  properties: {\n    configuration: {\n      activeRevisionsMode: 'Single'\n      ingress: {\n        external: true\n        allowInsecure: false\n        targetPort: 80\n        traffic: [\n          {\n            latestRevision: true\n            weight: 100\n          }\n        ]\n      }\n      registries: [\n        {\n          passwordSecretRef: cartsApiAcaSecretAcrPassword\n          server: acr.properties.loginServer\n          username: acr.name\n        }\n      ]\n      secrets: [\n        {\n          name: cartsApiAcaSecretAcrPassword\n          value: acr.listCredentials().passwords[0].value\n        }\n      ]\n    }\n    environmentId: cartsapiacaenv.id\n    template: {\n      scale: {\n        minReplicas: 1\n        maxReplicas: 10\n        rules: [\n          {\n            name: 'http-scaling-rule'\n            http: {\n              metadata: {\n                concurrentRequests: '3'\n              }\n            }\n          }\n        ]\n      }\n      containers: [\n        {\n          env: [\n            {\n              name: cartsApiSettingNameKeyVaultEndpoint\n              value: kv.properties.vaultUri\n            }\n            {\n              name: cartsApiSettingNameManagedIdentityClientId\n              value: userassignedmiforkvaccess.properties.clientId\n            }\n          ]\n          // using a public image initially because no images have been pushed to our private ACR yet\n          // at this point. At a later point, our github workflow will update the ACA app to use the \n          // images from our private ACR.\n          image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'\n          name: cartsApiAcaContainerDetailsName\n          resources: {\n            cpu: json('0.5')\n            memory: '1.0Gi'\n          }\n        }\n      ]\n    }\n  }\n}\n\n//\n// product images\n//\n\n// storage account (product images)\nresource productimagesstgacc 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n  name: productImagesStgAccName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Standard_LRS'\n  }\n  kind: 'StorageV2'\n  properties: {\n    allowBlobPublicAccess: true\n  }\n  // blob service\n  resource productimagesstgacc_blobsvc 'blobServices' = {\n    name: 'default'\n\n    // container\n    resource productimagesstgacc_blobsvc_productdetailscontainer 'containers' = {\n      name: productImagesProductDetailsContainerName\n      properties: {\n        publicAccess: 'Container'\n      }\n    }\n\n    // container\n    resource productimagesstgacc_blobsvc_productlistcontainer 'containers' = {\n      name: productImagesProductListContainerName\n      properties: {\n        publicAccess: 'Container'\n      }\n    }\n  }\n}\n\n//\n// main website / ui\n// new website / ui\n//\n\n// storage account (main website)\nresource uistgacc 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n  name: uiStgAccName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Standard_LRS'\n  }\n  kind: 'StorageV2'\n  properties: {\n    allowBlobPublicAccess: true\n  }\n  // blob service\n  resource uistgacc_blobsvc 'blobServices' = {\n    name: 'default'\n  }\n}\n\nresource uistgacc_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {\n  name: 'DeploymentScript'\n  location: resourceLocation\n  tags: resourceTags\n}\n\nresource uistgacc_roledefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {\n  scope: subscription()\n  // This is the Storage Account Contributor role, which is the minimum role permission we can give. \n  // See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#:~:text=17d1049b-9a84-46fb-8f53-869881c3d3ab\n  name: '17d1049b-9a84-46fb-8f53-869881c3d3ab'\n}\n\n// This requires the service principal to be in 'owner' role or a custom role with 'Microsoft.Authorization/roleAssignments/write' permissions.\n// Details: https://learn.microsoft.com/en-us/answers/questions/287573/authorization-failed-when-when-writing-a-roleassig.html\nresource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {\n  scope: uistgacc\n  name: guid(resourceGroup().id, uistgacc_mi.id, uistgacc_roledefinition.id)\n  properties: {\n    roleDefinitionId: uistgacc_roledefinition.id\n    principalId: uistgacc_mi.properties.principalId\n    principalType: 'ServicePrincipal'\n  }\n}\n\nresource deploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {\n  name: 'DeploymentScript'\n  location: resourceLocation\n  kind: 'AzurePowerShell'\n  identity: {\n    type: 'UserAssigned'\n    userAssignedIdentities: {\n      '${uistgacc_mi.id}': {}\n    }\n  }\n  dependsOn: [\n    // we need to ensure we wait for the role assignment to be deployed before trying to access the storage account\n    roleAssignment\n  ]\n  properties: {\n    azPowerShellVersion: '3.0'\n    scriptContent: loadTextContent('./scripts/enable-static-website.ps1')\n    retentionInterval: 'PT4H'\n    environmentVariables: [\n      {\n        name: 'ResourceGroupName'\n        value: resourceGroup().name\n      }\n      {\n        name: 'StorageAccountName'\n        value: uistgacc.name\n      }\n    ]\n  }\n}\n\n// storage account (new website)\nresource ui2stgacc 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n  name: ui2StgAccName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Standard_LRS'\n  }\n  kind: 'StorageV2'\n  properties: {\n    allowBlobPublicAccess: true\n  }\n\n  // blob service\n  resource ui2stgacc_blobsvc 'blobServices' = {\n    name: 'default'\n  }\n}\n\nresource ui2stgacc_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {\n  name: 'DeploymentScript2'\n  location: resourceLocation\n  tags: resourceTags\n}\n\nresource ui2stgacc_roledefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {\n  scope: subscription()\n  // This is the Storage Account Contributor role, which is the minimum role permission we can give. \n  // See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#:~:text=17d1049b-9a84-46fb-8f53-869881c3d3ab\n  name: '17d1049b-9a84-46fb-8f53-869881c3d3ab'\n}\n\n// This requires the service principal to be in 'owner' role or a custom role with 'Microsoft.Authorization/roleAssignments/write' permissions.\n// Details: https://learn.microsoft.com/en-us/answers/questions/287573/authorization-failed-when-when-writing-a-roleassig.html\nresource roleAssignment2 'Microsoft.Authorization/roleAssignments@2022-04-01' = {\n  scope: ui2stgacc\n  name: guid(resourceGroup().id, ui2stgacc_mi.id, ui2stgacc_roledefinition.id)\n  properties: {\n    roleDefinitionId: ui2stgacc_roledefinition.id\n    principalId: ui2stgacc_mi.properties.principalId\n    principalType: 'ServicePrincipal'\n  }\n}\n\nresource deploymentScript2 'Microsoft.Resources/deploymentScripts@2020-10-01' = {\n  name: 'DeploymentScript2'\n  location: resourceLocation\n  kind: 'AzurePowerShell'\n  identity: {\n    type: 'UserAssigned'\n    userAssignedIdentities: {\n      '${ui2stgacc_mi.id}': {}\n    }\n  }\n  dependsOn: [\n    // we need to ensure we wait for the role assignment to be deployed before trying to access the storage account\n    roleAssignment\n  ]\n  properties: {\n    azPowerShellVersion: '3.0'\n    scriptContent: loadTextContent('./scripts/enable-static-website.ps1')\n    retentionInterval: 'PT4H'\n    environmentVariables: [\n      {\n        name: 'ResourceGroupName'\n        value: resourceGroup().name\n      }\n      {\n        name: 'StorageAccountName'\n        value: ui2stgacc.name\n      }\n    ]\n  }\n}\n\n//\n// image classifier\n//\n\n// storage account (main website)\nresource imageclassifierstgacc 'Microsoft.Storage/storageAccounts@2023-01-01' = {\n  name: imageClassifierStgAccName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Standard_LRS'\n  }\n  kind: 'StorageV2'\n  properties: {\n    allowBlobPublicAccess: true\n  }\n\n  // blob service\n  resource imageclassifierstgacc_blobsvc 'blobServices' = {\n    name: 'default'\n\n    // container\n    resource uistgacc_blobsvc_websiteuploadscontainer 'containers' = {\n      name: imageClassifierWebsiteUploadsContainerName\n      properties: {\n        publicAccess: 'Container'\n      }\n    }\n  }\n}\n\n//\n// cdn\n//\n\nresource cdnprofile 'Microsoft.Cdn/profiles@2022-11-01-preview' = {\n  name: cdnProfileName\n  location: 'global'\n  tags: resourceTags\n  sku: {\n    name: 'Standard_Microsoft'\n  }\n}\n\n// endpoint (product images)\nresource cdnprofile_imagesendpoint 'Microsoft.Cdn/profiles/endpoints@2022-11-01-preview' = {\n  name: cdnImagesEndpointName\n  location: 'global'\n  tags: resourceTags\n  parent: cdnprofile\n  properties: {\n    isCompressionEnabled: true\n    contentTypesToCompress: [\n      'image/svg+xml'\n    ]\n    deliveryPolicy: {\n      rules: [\n        {\n          name: 'Global'\n          order: 0\n          actions: [\n            {\n              name: 'CacheExpiration'\n              parameters: {\n                typeName: 'DeliveryRuleCacheExpirationActionParameters'\n                cacheBehavior: 'SetIfMissing'\n                cacheType: 'All'\n                cacheDuration: '10:00:00'\n              }\n            }\n          ]\n        }\n      ]\n    }\n    originHostHeader: replace(replace(productimagesstgacc.properties.primaryEndpoints.blob, 'https://', ''), '/', '')\n    origins: [\n      {\n        name: replace(\n          replace(replace(productimagesstgacc.properties.primaryEndpoints.blob, 'https://', ''), '/', ''),\n          '.',\n          '-'\n        )\n        properties: {\n          hostName: replace(replace(productimagesstgacc.properties.primaryEndpoints.blob, 'https://', ''), '/', '')\n          originHostHeader: replace(\n            replace(productimagesstgacc.properties.primaryEndpoints.blob, 'https://', ''),\n            '/',\n            ''\n          )\n        }\n      }\n    ]\n  }\n}\n\n// endpoint (ui / old website)\nresource cdnprofile_uiendpoint 'Microsoft.Cdn/profiles/endpoints@2022-11-01-preview' = {\n  name: cdnUiEndpointName\n  location: 'global'\n  tags: resourceTags\n  parent: cdnprofile\n  properties: {\n    isCompressionEnabled: true\n    contentTypesToCompress: [\n      'application/eot'\n      'application/font'\n      'application/font-sfnt'\n      'application/javascript'\n      'application/json'\n      'application/opentype'\n      'application/otf'\n      'application/pkcs7-mime'\n      'application/truetype'\n      'application/ttf'\n      'application/vnd.ms-fontobject'\n      'application/xhtml+xml'\n      'application/xml'\n      'application/xml+rss'\n      'application/x-font-opentype'\n      'application/x-font-truetype'\n      'application/x-font-ttf'\n      'application/x-httpd-cgi'\n      'application/x-javascript'\n      'application/x-mpegurl'\n      'application/x-opentype'\n      'application/x-otf'\n      'application/x-perl'\n      'application/x-ttf'\n      'font/eot'\n      'font/ttf'\n      'font/otf'\n      'font/opentype'\n      'image/svg+xml'\n      'text/css'\n      'text/csv'\n      'text/html'\n      'text/javascript'\n      'text/js'\n      'text/plain'\n      'text/richtext'\n      'text/tab-separated-values'\n      'text/xml'\n      'text/x-script'\n      'text/x-component'\n      'text/x-java-source'\n    ]\n    deliveryPolicy: {\n      rules: [\n        {\n          name: 'Global'\n          order: 0\n          actions: [\n            {\n              name: 'CacheExpiration'\n              parameters: {\n                typeName: 'DeliveryRuleCacheExpirationActionParameters'\n                cacheBehavior: 'SetIfMissing'\n                cacheType: 'All'\n                cacheDuration: '10:00:00'\n              }\n            }\n          ]\n        }\n      ]\n    }\n    originHostHeader: replace(replace(uistgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n    origins: [\n      {\n        name: replace(replace(replace(uistgacc.properties.primaryEndpoints.web, 'https://', ''), '/', ''), '.', '-')\n        properties: {\n          hostName: replace(replace(uistgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n          originHostHeader: replace(replace(uistgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n        }\n      }\n    ]\n  }\n}\n\n// endpoint (ui / new website)\nresource cdnprofile_ui2endpoint 'Microsoft.Cdn/profiles/endpoints@2022-11-01-preview' = {\n  name: cdnUi2EndpointName\n  location: 'global'\n  tags: resourceTags\n  parent: cdnprofile\n  properties: {\n    isCompressionEnabled: true\n    contentTypesToCompress: [\n      'application/eot'\n      'application/font'\n      'application/font-sfnt'\n      'application/javascript'\n      'application/json'\n      'application/opentype'\n      'application/otf'\n      'application/pkcs7-mime'\n      'application/truetype'\n      'application/ttf'\n      'application/vnd.ms-fontobject'\n      'application/xhtml+xml'\n      'application/xml'\n      'application/xml+rss'\n      'application/x-font-opentype'\n      'application/x-font-truetype'\n      'application/x-font-ttf'\n      'application/x-httpd-cgi'\n      'application/x-javascript'\n      'application/x-mpegurl'\n      'application/x-opentype'\n      'application/x-otf'\n      'application/x-perl'\n      'application/x-ttf'\n      'font/eot'\n      'font/ttf'\n      'font/otf'\n      'font/opentype'\n      'image/svg+xml'\n      'text/css'\n      'text/csv'\n      'text/html'\n      'text/javascript'\n      'text/js'\n      'text/plain'\n      'text/richtext'\n      'text/tab-separated-values'\n      'text/xml'\n      'text/x-script'\n      'text/x-component'\n      'text/x-java-source'\n    ]\n    deliveryPolicy: {\n      rules: [\n        {\n          name: 'Global'\n          order: 0\n          actions: [\n            {\n              name: 'CacheExpiration'\n              parameters: {\n                typeName: 'DeliveryRuleCacheExpirationActionParameters'\n                cacheBehavior: 'SetIfMissing'\n                cacheType: 'All'\n                cacheDuration: '02:00:00'\n              }\n            }\n          ]\n        }\n        {\n          name: 'EnforceHttps'\n          order: 1\n          conditions: [\n            {\n              name: 'RequestScheme'\n              parameters: {\n                typeName: 'DeliveryRuleRequestSchemeConditionParameters'\n                matchValues: [\n                  'HTTP'\n                ]\n                operator: 'Equal'\n                negateCondition: false\n                transforms: []\n              }\n            }\n          ]\n          actions: [\n            {\n              name: 'UrlRedirect'\n              parameters: {\n                typeName: 'DeliveryRuleUrlRedirectActionParameters'\n                redirectType: 'Found'\n                destinationProtocol: 'Https'\n              }\n            }\n          ]\n        }\n      ]\n    }\n    originHostHeader: replace(replace(ui2stgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n    origins: [\n      {\n        name: replace(replace(replace(ui2stgacc.properties.primaryEndpoints.web, 'https://', ''), '/', ''), '.', '-')\n        properties: {\n          hostName: replace(replace(ui2stgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n          originHostHeader: replace(replace(ui2stgacc.properties.primaryEndpoints.web, 'https://', ''), '/', '')\n        }\n      }\n    ]\n  }\n}\n\n//\n// container registry\n//\n\nresource acr 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = {\n  name: acrName\n  location: resourceLocation\n  tags: resourceTags\n  sku: {\n    name: 'Basic'\n  }\n  properties: {\n    adminUserEnabled: true\n    publicNetworkAccess: 'Enabled'\n  }\n}\n\n//\n// load testing service\n//\n\nresource loadtestsvc 'Microsoft.LoadTestService/loadTests@2022-12-01' = {\n  name: loadTestSvcName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'UserAssigned'\n    userAssignedIdentities: {\n      '${userassignedmiforkvaccess.id}': {}\n    }\n  }\n}\n\n//\n// application insights\n//\n\n// log analytics workspace\nresource loganalyticsworkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {\n  name: logAnalyticsWorkspaceName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    publicNetworkAccessForIngestion: 'Enabled'\n    publicNetworkAccessForQuery: 'Enabled'\n    sku: {\n      name: 'PerGB2018' // pay-as-you-go\n    }\n  }\n}\n\n// app insights instance\nresource appinsights 'Microsoft.Insights/components@2020-02-02' = {\n  name: appInsightsName\n  location: resourceLocation\n  tags: resourceTags\n  kind: 'web'\n  properties: {\n    Application_Type: 'web'\n    WorkspaceResourceId: loganalyticsworkspace.id\n  }\n}\n\n//\n// portal dashboard\n//\n\nresource dashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = {\n  name: portalDashboardName\n  location: resourceLocation\n  tags: resourceTags\n  properties: {\n    lenses: [\n      {\n        order: 0\n        parts: [\n          {\n            position: {\n              x: 0\n              y: 0\n              rowSpan: 4\n              colSpan: 2\n            }\n          }\n        ]\n      }\n    ]\n  }\n}\n\n//\n// aks cluster\n//\n\nresource aks 'Microsoft.ContainerService/managedClusters@2022-10-02-preview' = {\n  name: aksClusterName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'SystemAssigned'\n  }\n  properties: {\n    dnsPrefix: aksClusterDnsPrefix\n    nodeResourceGroup: aksClusterNodeResourceGroup\n    agentPoolProfiles: [\n      {\n        name: 'agentpool'\n        osDiskSizeGB: 0 // Specifying 0 will apply the default disk size for that agentVMSize.\n        count: 1\n        vmSize: 'standard_b2s'\n        osType: 'Linux'\n        mode: 'System'\n      }\n    ]\n    linuxProfile: {\n      adminUsername: aksLinuxAdminUsername\n      ssh: {\n        publicKeys: [\n          {\n            keyData: loadTextContent('rsa.pub') // @TODO: temporary hack, until we autogen the keys\n          }\n        ]\n      }\n    }\n  }\n}\n\nresource aks_roledefinitionforchaosexp 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {\n  scope: aks\n  // This is the Azure Kubernetes Service Cluster Admin Role\n  // See https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#azure-kubernetes-service-cluster-admin-role\n  name: '0ab0b1a8-8aac-4efd-b8c2-3ee1fb270be8'\n}\n\nresource aks_roleassignmentforchaosexp 'Microsoft.Authorization/roleAssignments@2022-04-01' = {\n  scope: aks\n  name: guid(aks.id, chaosaksexperiment.id, aks_roledefinitionforchaosexp.id)\n  properties: {\n    roleDefinitionId: aks_roledefinitionforchaosexp.id\n    principalId: chaosaksexperiment.identity.principalId\n    principalType: 'ServicePrincipal'\n  }\n}\n\n//\n// virtual network\n//\n\nresource vnet 'Microsoft.Network/virtualNetworks@2022-07-01' =\n  if (deployPrivateEndpoints) {\n    name: vnetName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      addressSpace: {\n        addressPrefixes: [\n          vnetAddressSpace\n        ]\n      }\n      subnets: [\n        {\n          name: vnetAcaSubnetName\n          properties: {\n            addressPrefix: vnetAcaSubnetAddressPrefix\n          }\n        }\n        {\n          name: vnetVmSubnetName\n          properties: {\n            addressPrefix: vnetVmSubnetAddressPrefix\n          }\n        }\n        {\n          name: vnetLoadTestSubnetName\n          properties: {\n            addressPrefix: vnetLoadTestSubnetAddressPrefix\n          }\n        }\n      ]\n    }\n  }\n\n//\n// jumpbox vm\n// \n\n// public ip address\nresource jumpboxpublicip 'Microsoft.Network/publicIPAddresses@2022-07-01' =\n  if (deployPrivateEndpoints) {\n    name: jumpboxPublicIpName\n    location: resourceLocation\n    tags: resourceTags\n    sku: {\n      name: 'Standard'\n      tier: 'Regional'\n    }\n    properties: {\n      deleteOption: 'Delete'\n      publicIPAllocationMethod: 'Static'\n    }\n  }\n\n// network security group\nresource jumpboxnsg 'Microsoft.Network/networkSecurityGroups@2022-07-01' =\n  if (deployPrivateEndpoints) {\n    name: jumpboxNsgName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      securityRules: [\n        {\n          name: 'allow-rdp-port-3389'\n          properties: {\n            access: 'Allow'\n            destinationAddressPrefix: 'VirtualNetwork'\n            destinationPortRange: '3389'\n            direction: 'Inbound'\n            priority: 300\n            protocol: '*'\n            sourceAddressPrefix: '*'\n            sourcePortRange: '*'\n          }\n        }\n      ]\n    }\n  }\n\n// network interface controller\nresource jumpboxnic 'Microsoft.Network/networkInterfaces@2022-07-01' =\n  if (deployPrivateEndpoints) {\n    name: jumpboxNicName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      ipConfigurations: [\n        {\n          name: 'nic-ip-config'\n          properties: {\n            primary: true\n            privateIPAllocationMethod: 'Dynamic'\n            subnet: {\n              id: deployPrivateEndpoints ? vnet.properties.subnets[1].id : ''\n            }\n            publicIPAddress: {\n              id: deployPrivateEndpoints ? jumpboxpublicip.id : ''\n            }\n          }\n        }\n      ]\n      networkSecurityGroup: {\n        id: deployPrivateEndpoints ? jumpboxnsg.id : ''\n      }\n      nicType: 'Standard'\n    }\n  }\n\n// virtual machine\nresource jumpboxvm 'Microsoft.Compute/virtualMachines@2022-08-01' =\n  if (deployPrivateEndpoints) {\n    name: jumpboxVmName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      hardwareProfile: {\n        vmSize: 'standard_b2s'\n      }\n      storageProfile: {\n        osDisk: {\n          createOption: 'FromImage'\n          managedDisk: {\n            storageAccountType: 'StandardSSD_LRS'\n          }\n        }\n        imageReference: {\n          offer: 'WindowsServer'\n          publisher: 'MicrosoftWindowsServer'\n          sku: '2019-datacenter-gensecond'\n          version: 'latest'\n        }\n      }\n      networkProfile: {\n        networkInterfaces: [\n          {\n            id: deployPrivateEndpoints ? jumpboxnic.id : ''\n            properties: {\n              deleteOption: 'Delete'\n            }\n          }\n        ]\n      }\n      osProfile: {\n        adminPassword: jumpboxVmAdminPassword\n        #disable-next-line adminusername-should-not-be-literal // @TODO: This is a temporary hack, until we can generate the password\n        adminUsername: jumpboxVmAdminLogin\n        computerName: jumpboxVmName\n      }\n    }\n  }\n\n// auto-shutdown schedule\nresource jumpboxvmschedule 'Microsoft.DevTestLab/schedules@2018-09-15' =\n  if (deployPrivateEndpoints) {\n    name: jumpboxVmShutdownSchduleName\n    location: resourceLocation\n    tags: resourceTags\n    properties: {\n      targetResourceId: deployPrivateEndpoints ? jumpboxvm.id : ''\n      dailyRecurrence: {\n        time: '2100'\n      }\n      notificationSettings: {\n        status: 'Disabled'\n      }\n      status: 'Enabled'\n      taskType: 'ComputeVmShutdownTask'\n      timeZoneId: jumpboxVmShutdownScheduleTimezoneId\n    }\n  }\n\n//\n// private dns zone\n//\n\nmodule privateDnsZone './createPrivateDnsZone.bicep' =\n  if (deployPrivateEndpoints) {\n    name: 'createPrivateDnsZone'\n    params: {\n      privateDnsZoneName: deployPrivateEndpoints\n        ? join(skip(split(cartsinternalapiaca.properties.configuration.ingress.fqdn, '.'), 2), '.')\n        : ''\n      privateDnsZoneVnetId: deployPrivateEndpoints ? vnet.id : ''\n      privateDnsZoneVnetLinkName: privateDnsZoneVnetLinkName\n      privateDnsZoneARecordName: deployPrivateEndpoints\n        ? join(take(split(cartsinternalapiaca.properties.configuration.ingress.fqdn, '.'), 2), '.')\n        : ''\n      privateDnsZoneARecordIp: deployPrivateEndpoints ? cartsinternalapiacaenv.properties.staticIp : ''\n      resourceTags: resourceTags\n    }\n  }\n\n// aca environment (internal)\nresource cartsinternalapiacaenv 'Microsoft.App/managedEnvironments@2022-06-01-preview' =\n  if (deployPrivateEndpoints) {\n    name: cartsInternalApiAcaEnvName\n    location: resourceLocation\n    tags: resourceTags\n    sku: {\n      name: 'Consumption'\n    }\n    properties: {\n      zoneRedundant: false\n      vnetConfiguration: {\n        infrastructureSubnetId: deployPrivateEndpoints ? vnet.properties.subnets[0].id : ''\n        internal: true\n      }\n    }\n  }\n\n// aca (internal)\nresource cartsinternalapiaca 'Microsoft.App/containerApps@2022-06-01-preview' =\n  if (deployPrivateEndpoints) {\n    name: cartsInternalApiAcaName\n    location: resourceLocation\n    tags: resourceTags\n    identity: {\n      type: 'UserAssigned'\n      userAssignedIdentities: {\n        '${userassignedmiforkvaccess.id}': {}\n      }\n    }\n    properties: {\n      configuration: {\n        activeRevisionsMode: 'Single'\n        ingress: {\n          external: true\n          allowInsecure: false\n          targetPort: 80\n          traffic: [\n            {\n              latestRevision: true\n              weight: 100\n            }\n          ]\n        }\n        registries: [\n          {\n            passwordSecretRef: cartsInternalApiAcaSecretAcrPassword\n            server: acr.properties.loginServer\n            username: acr.name\n          }\n        ]\n        secrets: [\n          {\n            name: cartsInternalApiAcaSecretAcrPassword\n            value: acr.listCredentials().passwords[0].value\n          }\n        ]\n      }\n      environmentId: cartsinternalapiacaenv.id\n      template: {\n        scale: {\n          minReplicas: 1\n          maxReplicas: 3\n          rules: [\n            {\n              name: 'http-scaling-rule'\n              http: {\n                metadata: {\n                  concurrentRequests: '3'\n                }\n              }\n            }\n          ]\n        }\n        containers: [\n          {\n            env: [\n              {\n                name: cartsInternalApiSettingNameKeyVaultEndpoint\n                value: kv.properties.vaultUri\n              }\n              {\n                name: cartsInternalApiSettingNameManagedIdentityClientId\n                value: userassignedmiforkvaccess.properties.clientId\n              }\n            ]\n            // using a public image initially because no images have been pushed to our private ACR yet\n            // at this point. At a later point, our github workflow will update the ACA app to use the \n            // images from our private ACR.\n            image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'\n            name: cartsInternalApiAcaContainerDetailsName\n            resources: {\n              cpu: json('0.5')\n              memory: '1.0Gi'\n            }\n          }\n        ]\n      }\n    }\n  }\n\n//\n// chaos studio\n//\n\n// target: kv\nresource chaoskvtarget 'Microsoft.Chaos/targets@2022-10-01-preview' = {\n  name: 'Microsoft-KeyVault'\n  location: resourceLocation\n  scope: kv\n  properties: {}\n\n  // capability: kv (deny access)\n  resource chaoskvcapability 'capabilities' = {\n    name: 'DenyAccess-1.0'\n  }\n}\n\n// chaos experiment: kv\nresource chaoskvexperiment 'Microsoft.Chaos/experiments@2022-10-01-preview' = {\n  name: chaosKvExperimentName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'SystemAssigned'\n  }\n  properties: {\n    selectors: [\n      {\n        type: 'List'\n        id: chaosKvSelectorId\n        targets: [\n          {\n            id: chaoskvtarget.id\n            type: 'ChaosTarget'\n          }\n        ]\n      }\n    ]\n    startOnCreation: false\n    steps: [\n      {\n        name: 'step1'\n        branches: [\n          {\n            name: 'branch1'\n            actions: [\n              {\n                name: 'urn:csci:microsoft:keyVault:denyAccess/1.0'\n                type: 'continuous'\n                selectorId: chaosKvSelectorId\n                duration: 'PT5M'\n                parameters: []\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n}\n\n// target: aks\nresource chaosakstarget 'Microsoft.Chaos/targets@2022-10-01-preview' = {\n  name: 'Microsoft-AzureKubernetesServiceChaosMesh'\n  location: resourceLocation\n  scope: aks\n  properties: {}\n\n  // capability: aks (pod failures)\n  resource chaosakscapability 'capabilities' = {\n    name: 'PodChaos-2.1'\n  }\n}\n\n// chaos experiment: aks (chaos mesh)\nresource chaosaksexperiment 'Microsoft.Chaos/experiments@2022-10-01-preview' = {\n  name: chaosAksExperimentName\n  location: resourceLocation\n  tags: resourceTags\n  identity: {\n    type: 'SystemAssigned'\n  }\n  properties: {\n    selectors: [\n      {\n        type: 'List'\n        id: chaosAksSelectorId\n        targets: [\n          {\n            id: chaosakstarget.id\n            type: 'ChaosTarget'\n          }\n        ]\n      }\n    ]\n    startOnCreation: false\n    steps: [\n      {\n        name: 'step1'\n        branches: [\n          {\n            name: 'branch1'\n            actions: [\n              {\n                name: 'urn:csci:microsoft:azureKubernetesServiceChaosMesh:podChaos/2.1'\n                type: 'continuous'\n                selectorId: chaosAksSelectorId\n                duration: 'PT5M'\n                parameters: [\n                  {\n                    key: 'jsonSpec'\n                    value: '{\\'action\\':\\'pod-failure\\',\\'mode\\':\\'all\\',\\'duration\\':\\'3s\\',\\'selector\\':{\\'namespaces\\':[\\'default\\'],\\'labelSelectors\\':{\\'app\\':\\'contoso-traders-products\\'}}}'\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n}\n\n// outputs\n////////////////////////////////////////////////////////////////////////////////\n\noutput cartsApiEndpoint string = 'https://${cartsapiaca.properties.configuration.ingress.fqdn}'\noutput uiCdnEndpoint string = 'https://${cdnprofile_ui2endpoint.properties.hostName}'\n"
  },
  {
    "path": "iac/createResources.parameters.json",
    "content": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"parameters\": {\n    \"aksLinuxAdminUsername\": {\n      \"value\": \"localadmin\"\n    }\n  }\n}\n"
  },
  {
    "path": "iac/dashboard.json",
    "content": "{\n  \"properties\": {\n    \"lenses\": {\n      \"0\": {\n        \"order\": 0,\n        \"parts\": {\n          \"0\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 0,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/serverFarms/contoso-traders-productstest\"\n                          },\n                          \"name\": \"CpuPercentage\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/serverfarms\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"CPU Percentage\"\n                          }\n                        },\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/serverFarms/contoso-traders-productstest\"\n                          },\n                          \"name\": \"MemoryPercentage\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/serverfarms\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Memory Percentage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvcPlan : Max CPU% and Max Memory%\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/serverFarms/contoso-traders-productstest\"\n                          },\n                          \"name\": \"CpuPercentage\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/serverfarms\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"CPU Percentage\"\n                          }\n                        },\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/serverFarms/contoso-traders-productstest\"\n                          },\n                          \"name\": \"MemoryPercentage\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/serverfarms\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Memory Percentage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvcPlan : Max CPU%, Max Mem%\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"1\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 2,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"Http5xx\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Http Server Errors\",\n                            \"resourceDisplayName\": \"contoso-traders-productstest\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Http 5xx\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 2\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"Http5xx\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Http Server Errors\",\n                            \"resourceDisplayName\": \"contoso-traders-productstest\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvc : Http 5xx Errors\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"2\": {\n            \"position\": {\n              \"x\": 4,\n              \"y\": 2,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"Requests\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Requests\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvc : Sum Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"Requests\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Requests\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvc : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"3\": {\n            \"position\": {\n              \"x\": 8,\n              \"y\": 2,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"HttpResponseTime\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Response Time\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvc : Response Time\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Web/sites/contoso-traders-productstest\"\n                          },\n                          \"name\": \"HttpResponseTime\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.web/sites\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Response Time\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products API : AppSvc : Response Time\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"4\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 4,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_limit\",\n                          \"aggregationType\": 4,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU Limit\"\n                          }\n                        },\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_used\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU used\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products DB : Sql : DTU Utilization\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_limit\",\n                          \"aggregationType\": 4,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU Limit\"\n                          }\n                        },\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_used\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU used\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products DB : Sql : DTU Utilization\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"5\": {\n            \"position\": {\n              \"x\": 4,\n              \"y\": 4,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_consumption_percent\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU percentage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products DB : Sql : DTU% \",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.Sql/servers/contoso-traders-productstest/databases/productsdb\"\n                          },\n                          \"name\": \"dtu_consumption_percent\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.sql/servers/databases\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"DTU percentage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Products DB : Sql : DTU% \",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"6\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 6,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"7\": {\n            \"position\": {\n              \"x\": 4,\n              \"y\": 6,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequestUnits\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Request Units\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : RUs\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequestUnits\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Request Units\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : RUs\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"8\": {\n            \"position\": {\n              \"x\": 8,\n              \"y\": 6,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Throttled Requests (429s)\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : Throttled Requests (429s)\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"filterCollection\": {\n                        \"filters\": [\n                          {\n                            \"key\": \"StatusCode\",\n                            \"operator\": 0,\n                            \"values\": [\n                              \"429\"\n                            ]\n                          }\n                        ]\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Throttled Requests (429s)\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Stocks DB : CosmosDB : Throttled Requests (429s)\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"StatusCode\": {\n                  \"model\": {\n                    \"operator\": \"equals\",\n                    \"values\": [\n                      \"429\"\n                    ]\n                  }\n                },\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"9\": {\n            \"position\": {\n              \"x\": 12,\n              \"y\": 6,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"NormalizedRUConsumption\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Normalized RU Consumption\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"StocksDB : CosmosDB : Normalized RU Consumption\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"NormalizedRUConsumption\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Normalized RU Consumption\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"StocksDB : CosmosDB : Normalized RU Consumption\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"10\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 8,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"UsageNanoCores\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"CPU Usage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : CPU Usage\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"UsageNanoCores\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"CPU Usage\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : CPU Usage\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"11\": {\n            \"position\": {\n              \"x\": 4,\n              \"y\": 8,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"Requests\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Requests\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"Requests\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Requests\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"12\": {\n            \"position\": {\n              \"x\": 8,\n              \"y\": 8,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"Replicas\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Replica Count\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : Replica Count\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.App/containerApps/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"Replicas\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.app/containerapps\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Replica Count\"\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts API : ACA : Replica Count\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"13\": {\n            \"position\": {\n              \"x\": 0,\n              \"y\": 10,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Requests\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"14\": {\n            \"position\": {\n              \"x\": 4,\n              \"y\": 10,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequestUnits\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Request Units\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : RUs\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequestUnits\",\n                          \"aggregationType\": 1,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Request Units\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : RUs\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"15\": {\n            \"position\": {\n              \"x\": 8,\n              \"y\": 10,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Throttled Requests (429s)\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"filterCollection\": {\n                        \"filters\": [\n                          {\n                            \"key\": \"StatusCode\",\n                            \"operator\": 0,\n                            \"values\": [\n                              \"429\"\n                            ]\n                          }\n                        ]\n                      },\n                      \"grouping\": {\n                        \"dimension\": \"StatusCode\",\n                        \"top\": 50\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"TotalRequests\",\n                          \"aggregationType\": 7,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Total Requests\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Throttled Requests (429s)\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      },\n                      \"grouping\": {\n                        \"dimension\": \"StatusCode\",\n                        \"top\": 50\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"StatusCode\": {\n                  \"model\": {\n                    \"operator\": \"equals\",\n                    \"values\": [\n                      \"429\"\n                    ]\n                  }\n                },\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          },\n          \"16\": {\n            \"position\": {\n              \"x\": 12,\n              \"y\": 10,\n              \"colSpan\": 4,\n              \"rowSpan\": 2\n            },\n            \"metadata\": {\n              \"inputs\": [\n                {\n                  \"name\": \"options\",\n                  \"value\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"NormalizedRUConsumption\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Normalized RU Consumption\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Normalized RU Consumption\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        }\n                      },\n                      \"timespan\": {\n                        \"relative\": {\n                          \"duration\": 1800000\n                        },\n                        \"showUTCTime\": false,\n                        \"grain\": 1\n                      }\n                    }\n                  },\n                  \"isOptional\": true\n                },\n                {\n                  \"name\": \"sharedTimeRange\",\n                  \"isOptional\": true\n                }\n              ],\n              \"type\": \"Extension/HubsExtension/PartType/MonitorChartPart\",\n              \"settings\": {\n                \"content\": {\n                  \"options\": {\n                    \"chart\": {\n                      \"metrics\": [\n                        {\n                          \"resourceMetadata\": {\n                            \"id\": \"/subscriptions/d0c33f09-f408-4bb7-935e-c53c50358a38/resourceGroups/contoso-traders-rg/providers/Microsoft.DocumentDB/databaseAccounts/contoso-traders-cartstest\"\n                          },\n                          \"name\": \"NormalizedRUConsumption\",\n                          \"aggregationType\": 3,\n                          \"namespace\": \"microsoft.documentdb/databaseaccounts\",\n                          \"metricVisualization\": {\n                            \"displayName\": \"Normalized RU Consumption\",\n                            \"color\": null\n                          }\n                        }\n                      ],\n                      \"title\": \"Carts DB : CosmosDB : Normalized RU Consumption\",\n                      \"titleKind\": 2,\n                      \"visualization\": {\n                        \"chartType\": 2,\n                        \"legendVisualization\": {\n                          \"isVisible\": true,\n                          \"position\": 2,\n                          \"hideSubtitle\": false\n                        },\n                        \"axisVisualization\": {\n                          \"x\": {\n                            \"isVisible\": true,\n                            \"axisType\": 2\n                          },\n                          \"y\": {\n                            \"isVisible\": true,\n                            \"axisType\": 1\n                          }\n                        },\n                        \"disablePinning\": true\n                      }\n                    }\n                  }\n                }\n              },\n              \"filters\": {\n                \"MsPortalFx_TimeRange\": {\n                  \"model\": {\n                    \"format\": \"local\",\n                    \"granularity\": \"auto\",\n                    \"relative\": \"30m\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"metadata\": {\n      \"model\": {\n        \"timeRange\": {\n          \"value\": {\n            \"relative\": {\n              \"duration\": 24,\n              \"timeUnit\": 1\n            }\n          },\n          \"type\": \"MsPortalFx.Composition.Configuration.ValueTypes.TimeRange\"\n        },\n        \"filterLocale\": {\n          \"value\": \"en-us\"\n        },\n        \"filters\": {\n          \"value\": {\n            \"MsPortalFx_TimeRange\": {\n              \"model\": {\n                \"format\": \"utc\",\n                \"granularity\": \"auto\",\n                \"relative\": \"24h\"\n              },\n              \"displayCache\": {\n                \"name\": \"UTC Time\",\n                \"value\": \"Past 24 hours\"\n              },\n              \"filteredPartIds\": [\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d3e\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d40\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d42\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d44\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d46\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d48\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d4a\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d4c\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d4e\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d50\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d52\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d54\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d56\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d58\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d5a\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d5c\",\n                \"StartboardPart-MonitorChartPart-0d25c87b-86d2-4f4c-b66c-ada9ac7b3d5e\"\n              ]\n            }\n          }\n        }\n      }\n    }\n  },\n  \"name\": \"contoso-traders-dashboardtest\",\n  \"type\": \"Microsoft.Portal/dashboards\",\n  \"location\": \"INSERT LOCATION\",\n  \"tags\": {\n    \"hidden-title\": \"contoso-traders-dashboardtest\"\n  },\n  \"apiVersion\": \"2015-08-01-preview\"\n}"
  },
  {
    "path": "iac/rsa",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\n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\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "iac/rsa.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDmGzcTpLPWs7LSA8xCVDXgAa/9dLR+7OWAkcxjHcfZ++vpBhww2MbN3WErdTv/3ItheqRIxwhs1sZ2D2NKHdg69NAU1gZrVDUTQosRyt7qa2xQxm5vO3vsPr9rHWKscwbev/KtUt2rQMWE4yDoozn9yUqYtWvW9Nj+/PMCyWJ48WNweEMh4MMbvfZozoSV1qFIVT+8R4xHDP1FtjQOD+P88cpB81xgUtQGXkKQgRGPxhMwS/S8CcEIPGJufh7mVjUrcXZNnv+Uf5NSSBdVvUKhwUiW7JqgVI38vqL6OOGnEO34GfCMTLQoPPFt3cTCyJFqvYY2w7XGYMNV3xYxortiaxKfdflrd4BOqejm/dxFMuRt96WIHiUN7u8G7lTZ2DoAJviOXxodDrgm33nFHQjhO0XndEkZ44DbGd/aOLk36jyMOW2Z25wVILGggB6lMsPcE6VrTQsOT0RtzdA59v3gdq7Phs/H0Oqhi9uA1rsgsd7sqzj4vpdaq9rrAK1gQ9ODQi8kN5pvwD5wWgnzT78/3kMQq2oLtrZrbPFNQS5TZJ9Hu/kMnCxRpf5rAIvG6u5wP4Up64Goa8y7ZOBHs3I0WGw55HL0NRkJRqd9PIvPo/C7cYoy5U1tskzzJkZoTPsU+4AQtPojuRX0OQlSH+VvXbEzyVJTnrL9FsWbfWm6KQ== localadmin@highwaystar\n"
  },
  {
    "path": "iac/scripts/enable-static-website.ps1",
    "content": "$ErrorActionPreference = 'Stop'\n$storageAccount = Get-AzStorageAccount -ResourceGroupName $env:ResourceGroupName -AccountName $env:StorageAccountName\nEnable-AzStorageStaticWebsite -Context $storageAccount.Context -IndexDocument 'index.html' -ErrorDocument404Path 'index.html'\n"
  },
  {
    "path": "loadtests/contoso-traders-carts-internal.yaml",
    "content": "testName: contoso-traders-carts-internal\ntestPlan: contoso-traders-carts.jmx\ndescription:\nengineInstances: 1\nsubnetId: {{LOAD_TEST_SUBNET_ID}}\nfailureCriteria:\n  - avg(response_time_ms) > 5000\n  - percentage(error) > 20"
  },
  {
    "path": "loadtests/contoso-traders-carts.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"5.0\" jmeter=\"5.4.1\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Azure Load Testing Quickstart\" enabled=\"true\">\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <boolProp name=\"TestPlan.tearDown_on_shutdown\">true</boolProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"threads_per_engine\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads_per_engine</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"threads_per_engine\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"ramp_up_time\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">ramp_up_time</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"ramp_up_time\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"duration_in_sec\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">duration_in_sec</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"duration_in_sec\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"domain\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">domain</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"domain\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"protocol\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"path\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">path</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"path\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Thread Group\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">continue</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <intProp name=\"LoopController.loops\">-1</intProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads_per_engine}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${ramp_up_time}</stringProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${duration_in_sec}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">5</stringProp>\n        <boolProp name=\"ThreadGroup.same_user_on_next_iteration\">true</boolProp>\n      </ThreadGroup>\n      <hashTree>\n        <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Homepage\" enabled=\"true\">\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n            <collectionProp name=\"Arguments.arguments\"/>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\">${domain}</stringProp>\n          <stringProp name=\"HTTPSampler.port\"></stringProp>\n          <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\">${path}</stringProp>\n          <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n          <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n          <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n        </HTTPSamplerProxy>\n        <hashTree/>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "loadtests/contoso-traders-carts.yaml",
    "content": "testName: contoso-traders-carts\ntestPlan: contoso-traders-carts.jmx\ndescription:\nengineInstances: 1\nfailureCriteria:\n  - avg(response_time_ms) > 5000\n  - percentage(error) > 20"
  },
  {
    "path": "loadtests/contoso-traders-products.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"5.0\" jmeter=\"5.4.1\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Azure Load Testing Quickstart\" enabled=\"true\">\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <boolProp name=\"TestPlan.tearDown_on_shutdown\">true</boolProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"threads_per_engine\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads_per_engine</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"threads_per_engine\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"ramp_up_time\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">ramp_up_time</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"ramp_up_time\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"duration_in_sec\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">duration_in_sec</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"duration_in_sec\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"domain\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">domain</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"domain\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"protocol\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"path\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">path</stringProp>\n            <stringProp name=\"Argument.value\">${__BeanShell( System.getenv(\"path\") )}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Thread Group\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">continue</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <intProp name=\"LoopController.loops\">-1</intProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads_per_engine}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${ramp_up_time}</stringProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${duration_in_sec}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">5</stringProp>\n        <boolProp name=\"ThreadGroup.same_user_on_next_iteration\">true</boolProp>\n      </ThreadGroup>\n      <hashTree>\n        <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Homepage\" enabled=\"true\">\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n            <collectionProp name=\"Arguments.arguments\"/>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\">${domain}</stringProp>\n          <stringProp name=\"HTTPSampler.port\"></stringProp>\n          <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\">${path}</stringProp>\n          <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n          <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n          <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n        </HTTPSamplerProxy>\n        <hashTree/>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "loadtests/contoso-traders-products.yaml",
    "content": "testName: contoso-traders-products\ntestPlan: contoso-traders-products.jmx\ndescription:\nengineInstances: 1\nfailureCriteria:\n  - avg(response_time_ms) > 5000\n  - percentage(error) > 20"
  },
  {
    "path": "src/.dockerignore",
    "content": "**/.classpath\n**/.dockerignore\n**/.env\n**/.git\n**/.gitignore\n**/.project\n**/.settings\n**/.toolstarget\n**/.vs\n**/.vscode\n**/*.*proj.user\n**/*.dbmdl\n**/*.jfm\n**/azds.yaml\n**/bin\n**/charts\n**/docker-compose*\n**/Dockerfile*\n**/node_modules\n**/npm-debug.log\n**/obj\n**/secrets.dev.yaml\n**/values.dev.yaml\nLICENSE\nREADME.md"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/ContosoTraders.Api.Carts.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<Nullable>enable</Nullable>\n\t\t<ImplicitUsings>enable</ImplicitUsings>\n\t\t<UserSecretsId>5da7ea86-4b97-4e79-aec6-1fed11e373de</UserSecretsId>\n\t\t<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" Version=\"1.17.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\ContosoTraders.Api.Core\\ContosoTraders.Api.Core.csproj\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Controllers/ProfilesController.cs",
    "content": "﻿namespace ContosoTraders.Api.Carts.Controllers;\n\n[Route(\"v1/[controller]\")]\npublic class ProfilesController : ContosoTradersControllerBase\n{\n    public ProfilesController(IMediator mediator) : base(mediator)\n    {\n    }\n\n    [HttpGet]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetProfiles()\n    {\n        var request = new GetProfilesRequest();\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpGet(\"me\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetProfile([FromHeader(Name = RequestHeaderConstants.HeaderNameUserEmail)] string userEmail)\n    {\n        var request = new GetProfileRequest\n        {\n            Email = userEmail\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Controllers/ShoppingCartController.cs",
    "content": "﻿namespace ContosoTraders.Api.Carts.Controllers;\n\n[Route(\"v1/[controller]\")]\npublic class ShoppingCartController : ContosoTradersControllerBase\n{\n    public ShoppingCartController(IMediator mediator) : base(mediator)\n    {\n    }\n\n    /// <summary>\n    /// </summary>\n    /// <returns></returns>\n    [HttpGet]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetCart([FromHeader(Name = RequestHeaderConstants.HeaderNameUserEmail)] string userEmail)\n    {\n        var request = new GetCartRequest\n        {\n            Email = userEmail?.ToLowerInvariant()\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpPost]\n    [ProducesResponseType(StatusCodes.Status201Created)]\n    public async Task<IActionResult> AddItemToCart([FromBody] CartDto cartDto)\n    {\n        var request = new AddItemToCartRequest\n        {\n            CartItem = cartDto\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpPut(\"product\")]\n    [ProducesResponseType(StatusCodes.Status201Created)] // 201 to preserve compatibility with the original API.\n    public async Task<IActionResult> UpdateCartItemQuantity([FromBody] CartDto cartDto)\n    {\n        var request = new UpdateCartItemQuantityRequest\n        {\n            CartItem = cartDto\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpDelete(\"product\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> RemoveItemFromCart([FromBody] CartDto cartDto)\n    {\n        var request = new RemoveItemFromCartRequest\n        {\n            CartItem = cartDto\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    #region Load testing // @TODO: Remove this later and replace with JMeter/JMX tests\n\n    [HttpGet(\"loadtest\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> LoadTest()\n    {\n        var request = new LoadTestRequest();\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    #endregion\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Dockerfile",
    "content": "FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:7.0 AS build\nWORKDIR /src\nCOPY [\"ContosoTraders.Api.Carts/ContosoTraders.Api.Carts.csproj\", \"ContosoTraders.Api.Carts/\"]\nCOPY [\"ContosoTraders.Api.Core/ContosoTraders.Api.Core.csproj\", \"ContosoTraders.Api.Core/\"]\nRUN dotnet restore \"ContosoTraders.Api.Carts/ContosoTraders.Api.Carts.csproj\"\nCOPY . .\nWORKDIR \"/src/ContosoTraders.Api.Carts\"\nRUN dotnet build \"ContosoTraders.Api.Carts.csproj\" -c Release -o /app/build\n\nFROM build AS publish\nRUN dotnet publish \"ContosoTraders.Api.Carts.csproj\" -c Release -o /app/publish\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nENTRYPOINT [\"dotnet\", \"ContosoTraders.Api.Carts.dll\"]"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Program.cs",
    "content": "DependencyInjection.ConfigureApp();"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Properties/ServiceDependencies/tailwind-traders-cart - Zip Deploy/profile.arm.json",
    "content": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"metadata\": {\n    \"_dependencyType\": \"compute.function.linux.appService\"\n  },\n  \"parameters\": {\n    \"resourceGroupName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-sandbox-rg\",\n      \"metadata\": {\n        \"description\":\n          \"Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking.\"\n      }\n    },\n    \"resourceGroupLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"eastus\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support.\"\n      }\n    },\n    \"resourceName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-cart\",\n      \"metadata\": {\n        \"description\": \"Name of the main resource to be created by this template.\"\n      }\n    },\n    \"resourceLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"[parameters('resourceGroupLocation')]\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource. By default use resource group's location, unless the resource provider is not supported there.\"\n      }\n    }\n  },\n  \"resources\": [\n    {\n      \"type\": \"Microsoft.Resources/resourceGroups\",\n      \"name\": \"[parameters('resourceGroupName')]\",\n      \"location\": \"[parameters('resourceGroupLocation')]\",\n      \"apiVersion\": \"2019-10-01\"\n    },\n    {\n      \"type\": \"Microsoft.Resources/deployments\",\n      \"name\":\n        \"[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n      \"resourceGroup\": \"[parameters('resourceGroupName')]\",\n      \"apiVersion\": \"2019-10-01\",\n      \"dependsOn\": [\n        \"[parameters('resourceGroupName')]\"\n      ],\n      \"properties\": {\n        \"mode\": \"Incremental\",\n        \"expressionEvaluationOptions\": {\n          \"scope\": \"inner\"\n        },\n        \"parameters\": {\n          \"resourceGroupName\": {\n            \"value\": \"[parameters('resourceGroupName')]\"\n          },\n          \"resourceGroupLocation\": {\n            \"value\": \"[parameters('resourceGroupLocation')]\"\n          },\n          \"resourceName\": {\n            \"value\": \"[parameters('resourceName')]\"\n          },\n          \"resourceLocation\": {\n            \"value\": \"[parameters('resourceLocation')]\"\n          }\n        },\n        \"template\": {\n          \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n          \"contentVersion\": \"1.0.0.0\",\n          \"parameters\": {\n            \"resourceGroupName\": {\n              \"type\": \"string\"\n            },\n            \"resourceGroupLocation\": {\n              \"type\": \"string\"\n            },\n            \"resourceName\": {\n              \"type\": \"string\"\n            },\n            \"resourceLocation\": {\n              \"type\": \"string\"\n            }\n          },\n          \"variables\": {\n            \"storage_name\":\n              \"[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]\",\n            \"appServicePlan_name\":\n              \"[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n            \"storage_ResourceId\":\n              \"[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]\",\n            \"appServicePlan_ResourceId\":\n              \"[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]\",\n            \"function_ResourceId\":\n              \"[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]\"\n          },\n          \"resources\": [\n            {\n              \"location\": \"[parameters('resourceLocation')]\",\n              \"name\": \"[parameters('resourceName')]\",\n              \"type\": \"Microsoft.Web/sites\",\n              \"apiVersion\": \"2015-08-01\",\n              \"tags\": {\n                \"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]\": \"empty\"\n              },\n              \"dependsOn\": [\n                \"[variables('appServicePlan_ResourceId')]\",\n                \"[variables('storage_ResourceId')]\"\n              ],\n              \"kind\": \"functionapp\",\n              \"properties\": {\n                \"name\": \"[parameters('resourceName')]\",\n                \"kind\": \"functionapp\",\n                \"httpsOnly\": true,\n                \"reserved\": false,\n                \"serverFarmId\": \"[variables('appServicePlan_ResourceId')]\",\n                \"siteConfig\": {\n                  \"alwaysOn\": true,\n                  \"linuxFxVersion\": \"dotnet|3.1\"\n                }\n              },\n              \"identity\": {\n                \"type\": \"SystemAssigned\"\n              },\n              \"resources\": [\n                {\n                  \"name\": \"appsettings\",\n                  \"type\": \"config\",\n                  \"apiVersion\": \"2015-08-01\",\n                  \"dependsOn\": [\n                    \"[variables('function_ResourceId')]\"\n                  ],\n                  \"properties\": {\n                    \"AzureWebJobsStorage\":\n                      \"[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]\",\n                    \"FUNCTIONS_EXTENSION_VERSION\": \"~3\",\n                    \"FUNCTIONS_WORKER_RUNTIME\": \"dotnet\"\n                  }\n                }\n              ]\n            },\n            {\n              \"location\": \"[parameters('resourceGroupLocation')]\",\n              \"name\": \"[variables('storage_name')]\",\n              \"type\": \"Microsoft.Storage/storageAccounts\",\n              \"apiVersion\": \"2017-10-01\",\n              \"tags\": {\n                \"[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]\":\n                  \"empty\"\n              },\n              \"properties\": {\n                \"supportsHttpsTrafficOnly\": true\n              },\n              \"sku\": {\n                \"name\": \"Standard_LRS\"\n              },\n              \"kind\": \"Storage\"\n            },\n            {\n              \"location\": \"[parameters('resourceGroupLocation')]\",\n              \"name\": \"[variables('appServicePlan_name')]\",\n              \"type\": \"Microsoft.Web/serverFarms\",\n              \"apiVersion\": \"2015-02-01\",\n              \"kind\": \"linux\",\n              \"properties\": {\n                \"name\": \"[variables('appServicePlan_name')]\",\n                \"sku\": \"Standard\",\n                \"workerSizeId\": \"0\",\n                \"reserved\": true\n              }\n            }\n          ]\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Properties/launchSettings.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/launchsettings.json\",\n  \"profiles\": {\n    \"ContosoTraders.Api.Carts\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"applicationUrl\": \"https://localhost:62400;http://localhost:62401\",\n      \"dotnetRunMessages\": true\n    },\n    \"Docker\": {\n      \"commandName\": \"Docker\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"{Scheme}://{ServiceHost}:{ServicePort}/swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"publishAllPorts\": true,\n      \"useSSL\": true\n    }\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Carts/Usings.cs",
    "content": "﻿global using MediatR;\nglobal using Microsoft.AspNetCore.Mvc;\nglobal using ContosoTraders.Api.Core;\nglobal using ContosoTraders.Api.Core.Constants;\nglobal using ContosoTraders.Api.Core.Controllers;\nglobal using ContosoTraders.Api.Core.Models.Implementations.Dto;\nglobal using ContosoTraders.Api.Core.Requests.Definitions;"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/AuthConstants.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Constants;\n\npublic class AuthConstants\n{\n    public static readonly string DefaultJwtSigningKey = \"Ta!lwindTraderssssssss\";\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/CosmosConstants.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Constants;\n\npublic class CosmosConstants\n{\n    public const string DatabaseNameCarts = \"cartsdb\";\n    public const string DatabaseNameStocks = \"stocksdb\";\n\n    // keep the rest of the properties in alphabetical order\n    public const string ContainerNameCarts = \"carts\";\n    public const string ContainerNameStocks = \"stocks\";\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/KeyVaultConstants.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Constants;\n\ninternal class KeyVaultConstants\n{\n    #region secrets\n\n    public static readonly string SecretNameJwtAudience = \"jwtAudience\";\n\n    public static readonly string SecretNameJwtAuthority = \"jwtAuthority\";\n\n    public static readonly string SecretNameAppInsightsConnectionString = \"appInsightsConnectionString\";\n\n    public static readonly string SecretNameImagesEndpoint = \"imagesEndpoint\";\n\n    public static readonly string SecretNameCartsDbConnectionString = \"cartsDbConnectionString\";\n\n    public static readonly string SecretNameProductsDbConnectionString = \"productsDbConnectionString\";\n\n    public static readonly string SecretNameProfilesDbConnectionString = \"profilesDbConnectionString\";\n\n    public static readonly string SecretNameStocksDbConnectionString = \"stocksDbConnectionString\";\n\n    public static readonly string SecretNameCognitiveServicesEndpoint = \"cognitiveServicesEndpoint\";\n\n    public static readonly string SecretNameCognitiveServicesAccountKey = \"cognitiveServicesAccountKey\";\n\n    #endregion\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Constants/RequestHeaderConstants.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Constants;\n\npublic class RequestHeaderConstants\n{\n    public const string HeaderNameUserEmail = \"x-tt-email\";\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/ContosoTraders.Api.Core.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<ImplicitUsings>enable</ImplicitUsings>\n\t\t<Nullable>disable</Nullable>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<Compile Remove=\"Models\\Dto\\**\" />\n\t\t<EmbeddedResource Remove=\"Models\\Dto\\**\" />\n\t\t<None Remove=\"Models\\Dto\\**\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<Folder Include=\"Controllers\\\" />\n\t\t<Folder Include=\"Constants\\\" />\n\t\t<Folder Include=\"EventStreams\\Interfaces\\\" />\n\t\t<Folder Include=\"EventStreams\\Implementations\\\" />\n\t\t<Folder Include=\"Models\\Misc\\\" />\n\t\t<Folder Include=\"Utilities\\ExtensionMethods\\\" />\n\t\t<Folder Include=\"Utilities\\MiscHelpers\\\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<PackageReference Include=\"AutoMapper.Extensions.Microsoft.DependencyInjection\" Version=\"12.0.0\" />\n\t\t<PackageReference Include=\"Azure.Extensions.AspNetCore.Configuration.Secrets\" Version=\"1.2.2\" />\n\t\t<PackageReference Include=\"Azure.Identity\" Version=\"1.11.0\" />\n\t\t<PackageReference Include=\"FluentValidation\" Version=\"11.5.1\" />\n\t\t<PackageReference Include=\"FluentValidation.DependencyInjectionExtensions\" Version=\"11.5.1\" />\n\t\t<PackageReference Include=\"MediatR.Extensions.Microsoft.DependencyInjection\" Version=\"11.1.0\" />\n\t\t<PackageReference Include=\"Microsoft.ApplicationInsights.AspNetCore\" Version=\"2.21.0\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore\" Version=\"2.2.0\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc\" Version=\"2.2.0\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.ApiExplorer\" Version=\"2.2.0\" />\n\t\t<PackageReference Include=\"Microsoft.AspNetCore.Mvc.Core\" Version=\"2.2.5\" />\n\t\t<PackageReference Include=\"Microsoft.Azure.CognitiveServices.Vision.ComputerVision\" Version=\"7.0.1\" />\n\t\t<PackageReference Include=\"Microsoft.Azure.Cosmos\" Version=\"3.32.2\" />\n\t\t<PackageReference Include=\"Microsoft.Azure.Functions.Extensions\" Version=\"1.1.0\" />\n\t\t<PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"7.0.3\" />\n\t\t<PackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"7.0.3\">\n\t\t\t<PrivateAssets>all</PrivateAssets>\n\t\t\t<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n\t\t</PackageReference>\n\t\t<PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"7.0.3\" />\n\t\t<PackageReference Include=\"Microsoft.Identity.Web\" Version=\"2.5.0\" />\n\t\t<PackageReference Include=\"Microsoft.NET.Sdk.Functions\" Version=\"4.1.3\" />\n\t\t<PackageReference Include=\"Swashbuckle.AspNetCore\" Version=\"6.5.0\" />\n\t\t<PackageReference Include=\"Swashbuckle.AspNetCore.Swagger\" Version=\"6.5.0\" />\n\t\t<PackageReference Include=\"Swashbuckle.AspNetCore.SwaggerGen\" Version=\"6.5.0\" />\n\t\t<PackageReference Include=\"Swashbuckle.AspNetCore.SwaggerUI\" Version=\"6.5.0\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Controllers/ContosoTradersControllerBase.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Controllers;\n\n[ApiController]\npublic class ContosoTradersControllerBase : ControllerBase\n{\n    private readonly IMediator _mediator;\n\n    protected ContosoTradersControllerBase(IMediator mediator)\n    {\n        _mediator = mediator;\n    }\n\n    protected async Task<IActionResult> ProcessHttpRequestAsync(IRequest<IActionResult> request)\n    {\n        try\n        {\n            return await _mediator.Send(request);\n        }\n        catch (ContosoTradersBaseException contosoTradersBaseException)\n        {\n            return contosoTradersBaseException.ToActionResult();\n        }\n        catch (ValidationException validationException)\n        {\n            return new BadRequestObjectResult(validationException.Message);\n        }\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/DependencyInjection.cs",
    "content": "﻿using System.Reflection;\nusing Azure.Identity;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.Azure.Cosmos;\nusing Microsoft.Azure.Functions.Extensions.DependencyInjection;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.IdentityModel.Logging;\n\n[assembly: FunctionsStartup(typeof(DependencyInjection))]\n\nnamespace ContosoTraders.Api.Core;\n\npublic class DependencyInjection : FunctionsStartup\n{\n    private const string _allowSpecificOrigins = \"allowSpecificOrigins\";\n\n    public static void ConfigureServices(HostBuilderContext context, IServiceCollection services)\n    {\n        ConfigureServicesInternal(services, context.Configuration);\n    }\n\n\n    /// <remarks>\n    ///     This is implicitly called by the Azure Functions runtime.\n    /// </remarks>\n    public override void Configure(IFunctionsHostBuilder builder)\n    {\n        ConfigureServicesInternal(builder.Services, builder.GetContext().Configuration);\n    }\n\n\n    /// <remarks>\n    ///     To be explicitly called from an asp.net core application only\n    /// </remarks>\n    public static void ConfigureApp()\n    {\n        var builder = WebApplication.CreateBuilder();\n\n        if (builder.Environment.IsDevelopment())\n            builder.Configuration.AddAzureKeyVault(\n                new Uri(builder.Configuration[\"KeyVaultEndpoint\"]),\n                new DefaultAzureCredential());\n        else\n            builder.Configuration.AddAzureKeyVault(\n                new Uri(builder.Configuration[\"KeyVaultEndpoint\"]),\n                new DefaultAzureCredential(\n                    new DefaultAzureCredentialOptions\n                    {\n                        ManagedIdentityClientId = builder.Configuration[\"ManagedIdentityClientId\"]\n                    }));\n\n        ConfigureServicesInternal(builder.Services, builder.Configuration);\n\n        ConfigureAspNetCoreServices(builder.Services, builder.Configuration);\n\n        var app = builder.Build();\n\n        ConfigureAspNetCoreMiddleware(app);\n\n        app.Run();\n    }\n\n\n    /// <remarks>\n    ///     @TODO: Currently, this method is very 'aspnetcore' specific. Make it more generic (for azure functions) later.\n    /// </remarks>\n    private static void ConfigureServicesInternal(IServiceCollection services, IConfiguration configuration)\n    {\n        // injecting auto-mapper\n        services.AddAutoMapper(typeof(AutoMapperProfile));\n\n        // inject mediatr\n        services.AddMediatR(Assembly.GetExecutingAssembly());\n\n        // inject ef-core dbcontexts (after fetching connection string from azure keyvault).\n        var productsDbConnectionString = configuration[KeyVaultConstants.SecretNameProductsDbConnectionString];\n        services.AddDbContext<ProductsDbContext>(options => options.UseSqlServer(productsDbConnectionString));\n\n        var profilesDbConnectionString = configuration[KeyVaultConstants.SecretNameProfilesDbConnectionString];\n        services.AddDbContext<ProfilesDbContext>(options => options.UseSqlServer(profilesDbConnectionString));\n\n        // injecting the cosmosdb clients\n        var stocksDbConnectionString = configuration[KeyVaultConstants.SecretNameStocksDbConnectionString];\n        services.AddSingleton(_ => new CosmosClient(stocksDbConnectionString).GetDatabase(CosmosConstants.DatabaseNameStocks));\n\n        var cartsDbConnectionString = configuration[KeyVaultConstants.SecretNameCartsDbConnectionString];\n        services.AddSingleton(_ => new CosmosClient(cartsDbConnectionString).GetDatabase(CosmosConstants.DatabaseNameCarts));\n\n        // inject services\n        services\n            .AddScoped<ICartService, CartService>()\n            .AddScoped<IProductService, ProductService>()\n            .AddScoped<IStockService, StockService>()\n            .AddScoped<IProfileService, ProfileService>()\n            .AddScoped<IImageAnalysisService, ImageAnalysisService>()\n            .AddScoped<IImageSearchService, ImageSearchService>();\n\n        // inject repositories\n        services\n            .AddScoped<ICartRepository, CartRepository>()\n            .AddScoped<IStockRepository, StockRepository>();\n    }\n\n\n    private static void ConfigureAspNetCoreServices(IServiceCollection services, IConfiguration configuration)\n    {\n        services.AddControllers();\n        services.AddEndpointsApiExplorer();\n        services.AddSwaggerGen();\n\n        var appInsightsConnectionString = configuration[KeyVaultConstants.SecretNameAppInsightsConnectionString];\n        services.AddApplicationInsightsTelemetry(options => options.ConnectionString = appInsightsConnectionString);\n\n        // @TODO: Temporary. Fix later.\n        services.AddCors(options =>\n            options.AddPolicy(_allowSpecificOrigins,\n                policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));\n\n        IdentityModelEventSource.ShowPII = true;\n    }\n\n\n    /// <remarks>\n    ///     Add middleware to the asp.net core request pipeline.\n    ///     Note: COR pattern applies, ordering matters.\n    /// </remarks>\n    private static void ConfigureAspNetCoreMiddleware(WebApplication app)\n    {\n        app.UseSwagger();\n        app.UseSwaggerUI();\n\n        app.UseHttpsRedirection();\n        app.UseCors(_allowSpecificOrigins);\n\n#if TODO_INVESTIGATE_LATER\n        app.UseAuthorization(); // very important, else [Authorize] will not work in controllers.\n        app.UseAuthentication();\n#endif\n\n        app.MapControllers();\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/CartNotFoundException.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class CartNotFoundException : ContosoTradersBaseException\n{\n    public CartNotFoundException(string email)\n        : base($\"Shopping Cart for '{email}' could not be found.\")\n    {\n    }\n\n    public override IActionResult ToActionResult()\n    {\n        return new NotFoundObjectResult(Message);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ContosoTradersBaseException.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Exceptions;\n\npublic abstract class ContosoTradersBaseException : Exception\n{\n    protected ContosoTradersBaseException(string message) : base(message)\n    {\n    }\n\n    public abstract IActionResult ToActionResult();\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/MatchingProductsNotFoundException.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class MatchingProductsNotFoundException : ContosoTradersBaseException\n{\n    public MatchingProductsNotFoundException(string tags)\n        : base($\"No matching products found for tags :  {tags}\")\n    {\n    }\n\n    public override IActionResult ToActionResult()\n    {\n        return new NotFoundObjectResult(Message);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ProductNotFoundException.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class ProductNotFoundException : ContosoTradersBaseException\n{\n    public ProductNotFoundException(int productId)\n        : base($\"Product with id '{productId}' could not be found.\")\n    {\n    }\n\n    public override IActionResult ToActionResult()\n    {\n        return new NotFoundObjectResult(Message);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/ProfileNotFoundException.cs",
    "content": "namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class ProfileNotFoundException : ContosoTradersBaseException\n{\n    public ProfileNotFoundException(string email)\n        : base($\"Profile for email '{email}' could not be found.\")\n    {\n    }\n\n    public override IActionResult ToActionResult()\n    {\n        return new NotFoundObjectResult(Message);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Exceptions/StockNotFoundException.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Exceptions;\n\npublic class StockNotFoundException : ContosoTradersBaseException\n{\n    public StockNotFoundException(int productId)\n        : base($\"Stock not found for product id '{productId}'\")\n    {\n    }\n\n    public override IActionResult ToActionResult()\n    {\n        return new NotFoundObjectResult(Message);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/AutoMapperProfile.cs",
    "content": "﻿using Profile = AutoMapper.Profile;\nusing ProfileDao = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Models;\n\npublic class AutoMapperProfile : Profile\n{\n    public AutoMapperProfile()\n    {\n        #region DAO (storage model) to DTO (API/REST model) conversion\n\n        CreateMap<StockDao, StockDto>()\n            .ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => Convert.ToInt32(src.id)));\n\n        CreateMap<CartDao, CartDto>()\n            .ForMember(dest => dest.CartItemId, opt => opt.MapFrom(src => src.id));\n\n        // @TODO: Fix this later\n        //CreateMap<(Product, IEnumerable<Brand>, IEnumerable<Type>), ProductDto>()\n        //    .ForPath(dest => dest, opt => opt.MapFrom(src => MappingHelper.CustomJoin(src.Item1, src.Item2, src.Item3)));\n\n        #endregion\n\n        #region DTO (API/REST model) to DAO (storage model) conversion\n\n        CreateMap<StockDto, StockDao>()\n            .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.ProductId.ToString()));\n\n        CreateMap<CartDto, CartDao>()\n            .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.CartItemId));\n\n        #endregion\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Brand.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Brand\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/CartDao.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class CartDao : ICosmosDao<string>\n{\n    public string Email { get; set; } // partition key    \n\n    public int ProductId { get; set; }\n\n    public string Name { get; set; }\n\n    public int Price { get; set; }\n\n    public string ImageUrl { get; set; }\n\n    public int Quantity { get; set; }\n\n    public string id { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Feature.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Feature\n{\n    public int Id { get; set; }\n    public string Title { get; set; }\n    public string Description { get; set; }\n    public int? ProductItemId { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Product.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Product\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public decimal? Price { get; set; }\n    public string ImageName { get; set; }\n    public int? BrandId { get; set; }\n    public int? TypeId { get; set; }\n    public int? TagId { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Profile.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Profile\n{\n    public int Id { get; set; }\n\n    public string Name { get; set; }\n\n    public string Address { get; set; }\n\n    public string PhoneNumber { get; set; }\n\n    public string Email { get; set; }\n\n    public string ImageNameSmall { get; set; }\n\n    public string ImageNameLarge { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/StockDao.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class StockDao : ICosmosDao<string>\n{\n    public int StockCount { get; set; }\n\n    public string id { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Tag.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Tag\n{\n    public int Id { get; set; }\n    public string Value { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dao/Type.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dao;\n\npublic class Type\n{\n    public int Id { get; set; }\n    public string Code { get; set; }\n    public string Name { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/AccessToken.cs",
    "content": "﻿using Newtonsoft.Json;\n\nnamespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class AccessToken\n{\n    [JsonProperty(PropertyName = \"token\")] public string Token { get; set; }\n\n    [JsonProperty(PropertyName = \"token_type\")]\n    public string TokenType { get; set; }\n\n    [JsonProperty(PropertyName = \"expires_in\")]\n    public int ExpiresIn { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/CartDto.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class CartDto\n{\n    public string CartItemId { get; set; }\n\n    public string Email { get; set; }\n\n    public int ProductId { get; set; }\n\n    public string Name { get; set; }\n\n    public int Price { get; set; }\n\n    public string ImageUrl { get; set; }\n\n    public int Quantity { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/ProductDto.cs",
    "content": "﻿using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class ProductDto\n{\n    public int Id { get; set; }\n\n    public string Name { get; set; }\n\n    public decimal? Price { get; set; }\n\n    public string ImageUrl { get; set; }\n\n    public Brand Brand { get; set; }\n\n    public Type Type { get; set; }\n\n    public IEnumerable<Feature> Features { get; set; }\n\n    public int StockUnits { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/StockDto.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class StockDto\n{\n    public int ProductId { get; set; }\n\n    public int StockCount { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Implementations/Dto/TokenRequest.cs",
    "content": "﻿using Newtonsoft.Json;\n\nnamespace ContosoTraders.Api.Core.Models.Implementations.Dto;\n\npublic class TokenRequest\n{\n    public string Username { get; set; }\n\n    public string Password { get; set; }\n\n    [JsonProperty(PropertyName = \"grant_type\")]\n    public string GrantType { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Models/Interfaces/ICosmosDao.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Models.Interfaces;\n\npublic interface ICosmosDao<T>\n{\n    // ReSharper disable once InconsistentNaming\n#pragma warning disable IDE1006 // Naming Styles\n    public T id { get; set; }\n#pragma warning restore IDE1006 // Naming Styles\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/CartRepository.cs",
    "content": "﻿using Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npublic class CartRepository : CosmosGenericRepositoryBase<CartDao>, ICartRepository\n{\n    public CartRepository(IEnumerable<Database> cosmosDatabases)\n        : base(cosmosDatabases.Single(db => db.Id == CosmosConstants.DatabaseNameCarts), CosmosConstants.ContainerNameCarts)\n    {\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/CosmosGenericRepositoryBase.cs",
    "content": "﻿using System.Net;\nusing Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npublic abstract class CosmosGenericRepositoryBase<TEntity> : ICosmosGenericRepository<TEntity> where TEntity : class\n{\n    protected readonly string ContainerName;\n\n    protected readonly Database CosmosDatabase;\n\n    protected CosmosGenericRepositoryBase(Database cosmosDatabase, string containerName)\n    {\n        ContainerName = containerName;\n        CosmosDatabase = cosmosDatabase;\n    }\n\n    public async Task<IEnumerable<TEntity>> QueryAsync(string querySpec, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        return await ExecuteQueryAsync(querySpec, cancellationToken);\n    }\n\n    public async Task<IEnumerable<TEntity>> ListAsync(string filterClause = default, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        var querySpec = \"select * from c\";\n\n        if (!string.IsNullOrWhiteSpace(filterClause)) querySpec = $\"{querySpec} where {filterClause}\";\n\n        return await ExecuteQueryAsync(querySpec, cancellationToken);\n    }\n\n    public async Task<TEntity> GetAsync(string partitionKey, string id, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        try\n        {\n            var response = await CosmosDatabase\n                .GetContainer(ContainerName)\n                .ReadItemAsync<TEntity>(id, new PartitionKey(partitionKey), cancellationToken: cancellationToken);\n\n            return response.Resource;\n        }\n        catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)\n        {\n            return default; // i.e. null\n        }\n    }\n\n    public async Task AddAsync(string partitionKey, TEntity entity, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        await CosmosDatabase\n            .GetContainer(ContainerName)\n            .CreateItemAsync(entity, new PartitionKey(partitionKey), cancellationToken: cancellationToken);\n    }\n\n    public async Task UpsertAsync(string partitionKey, TEntity entity, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        await CosmosDatabase\n            .GetContainer(ContainerName)\n            .UpsertItemAsync(entity, new PartitionKey(partitionKey), cancellationToken: cancellationToken);\n    }\n\n    public async Task DeleteAsync(string partitionKey, string id, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        await CosmosDatabase\n            .GetContainer(ContainerName)\n            .DeleteItemAsync<TEntity>(id, new PartitionKey(partitionKey), cancellationToken: cancellationToken);\n    }\n\n    private async Task<IEnumerable<TEntity>> ExecuteQueryAsync(string querySpec, CancellationToken cancellationToken = default)\n    {\n        cancellationToken.ThrowIfCancellationRequested();\n\n        using var queryIterator = CosmosDatabase\n            .GetContainer(ContainerName)\n            .GetItemQueryIterator<TEntity>(new QueryDefinition(querySpec));\n\n        var results = new List<TEntity>();\n        while (queryIterator.HasMoreResults)\n        {\n            cancellationToken.ThrowIfCancellationRequested();\n\n            var response = await queryIterator.ReadNextAsync(cancellationToken);\n            results.AddRange(response.ToList());\n        }\n\n        return results;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Implementations/StockRepository.cs",
    "content": "﻿using Microsoft.Azure.Cosmos;\n\nnamespace ContosoTraders.Api.Core.Repositories.Implementations;\n\npublic class StockRepository : CosmosGenericRepositoryBase<StockDao>, IStockRepository\n{\n    public StockRepository(IEnumerable<Database> cosmosDatabases)\n        : base(cosmosDatabases.Single(db => db.Id == CosmosConstants.DatabaseNameStocks), CosmosConstants.ContainerNameStocks)\n    {\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/ICartRepository.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\npublic interface ICartRepository : ICosmosGenericRepository<CartDao>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/ICosmosGenericRepository.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\n/// <remarks>\n///     The type parameter constraint of 'class' is only required because of this open github issue:\n///     - https://github.com/dotnet/runtime/issues/41749\n///     Else we could have just used interface types for the type parameter.\n/// </remarks>\npublic interface ICosmosGenericRepository<TEntity> where TEntity : class\n{\n    Task<IEnumerable<TEntity>> QueryAsync(string querySpec, CancellationToken cancellationToken = default);\n\n    Task<IEnumerable<TEntity>> ListAsync(string filterClause = default, CancellationToken cancellationToken = default);\n\n    Task<TEntity> GetAsync(string partitionKey, string id, CancellationToken cancellationToken = default);\n\n    Task AddAsync(string partitionKey, TEntity entity, CancellationToken cancellationToken = default);\n\n    Task UpsertAsync(string partitionKey, TEntity entity, CancellationToken cancellationToken = default);\n\n    Task DeleteAsync(string partitionKey, string id, CancellationToken cancellationToken = default);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/Interfaces/IStockRepository.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Repositories.Interfaces;\n\npublic interface IStockRepository : ICosmosGenericRepository<StockDao>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/ProductsDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Repositories;\n\npublic class ProductsDbContext : DbContext\n{\n    public ProductsDbContext()\n    {\n    }\n\n    public ProductsDbContext(DbContextOptions<ProductsDbContext> options)\n        : base(options)\n    {\n    }\n\n    public virtual DbSet<Brand> Brands { get; set; }\n    public virtual DbSet<Feature> Features { get; set; }\n    public virtual DbSet<Product> Products { get; set; }\n    public virtual DbSet<Tag> Tags { get; set; }\n    public virtual DbSet<Type> Types { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity<Brand>(entity =>\n        {\n            entity.Property(e => e.Id).ValueGeneratedNever();\n\n            entity.Property(e => e.Name).HasMaxLength(255);\n        });\n\n        modelBuilder.Entity<Feature>(entity =>\n        {\n            entity.Property(e => e.Id).ValueGeneratedNever();\n\n            entity.Property(e => e.Title).HasMaxLength(255);\n        });\n\n        modelBuilder.Entity<Product>(entity =>\n        {\n            entity.Property(e => e.Id).ValueGeneratedNever();\n\n            entity.Property(e => e.ImageName).HasMaxLength(255);\n\n            entity.Property(e => e.Name).HasMaxLength(255);\n\n            entity.Property(e => e.Price).HasColumnType(\"decimal(9, 2)\");\n        });\n\n        modelBuilder.Entity<Tag>(entity =>\n        {\n            entity.Property(e => e.Id).ValueGeneratedNever();\n\n            entity.Property(e => e.Value).HasMaxLength(255);\n        });\n\n        modelBuilder.Entity<Type>(entity =>\n        {\n            entity.Property(e => e.Id).ValueGeneratedNever();\n\n            entity.Property(e => e.Code).HasMaxLength(255);\n\n            entity.Property(e => e.Name).HasMaxLength(255);\n        });\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Repositories/ProfilesDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Repositories;\n\npublic class ProfilesDbContext : DbContext\n{\n    public ProfilesDbContext()\n    {\n    }\n\n    public ProfilesDbContext(DbContextOptions<ProfilesDbContext> options)\n        : base(options)\n    {\n    }\n\n    public virtual DbSet<Profile> Profiles { get; set; }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {\n        modelBuilder.Entity<Profile>(entity =>\n        {\n            entity.HasKey(e => e.Email);\n\n            entity.Property(e => e.Email).HasMaxLength(50);\n\n            entity.Property(e => e.Address).HasMaxLength(256);\n\n            entity.Property(e => e.ImageNameLarge)\n                .HasMaxLength(256)\n                .IsFixedLength();\n\n            entity.Property(e => e.ImageNameSmall).HasMaxLength(256);\n\n            entity.Property(e => e.Name).HasMaxLength(50);\n\n            entity.Property(e => e.PhoneNumber).HasMaxLength(100);\n        });\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/AddItemToCartRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class AddItemToCartRequest : IRequest<IActionResult>\n{\n    public CartDto CartItem { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/DecrementStockCountRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class DecrementStockCountRequest : IRequest<IActionResult>\n{\n    public int ProductId { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetCartRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetCartRequest : IRequest<IActionResult>\n{\n    public string Email { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetPopularProductsRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetPopularProductsRequest : IRequest<IActionResult>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProductRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProductRequest : IRequest<IActionResult>\n{\n    public int ProductId { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProductsRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProductsRequest : IRequest<IActionResult>\n{\n    public int[] Brands { get; set; }\n\n    public string[] Types { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProfileRequest.cs",
    "content": "namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProfileRequest : IRequest<IActionResult>\n{\n    public string Email { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetProfilesRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetProfilesRequest : IRequest<IActionResult>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/GetStockRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class GetStockRequest : IRequest<IActionResult>\n{\n    public int ProductId { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/LoadTestRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class LoadTestRequest : IRequest<IActionResult>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/PostImageRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class PostImageRequest : IRequest<IActionResult>\n{\n    public IFormFile File { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/RemoveItemFromCartRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class RemoveItemFromCartRequest : IRequest<IActionResult>\n{\n    public CartDto CartItem { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/SearchTextRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class SearchTextRequest : IRequest<IActionResult>\n{\n    public string Text { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Definitions/UpdateCartItemQuantityRequest.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Definitions;\n\npublic class UpdateCartItemQuantityRequest : IRequest<IActionResult>\n{\n    public CartDto CartItem { get; set; }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/AddItemToCartRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class AddItemToCartRequestHandler : IRequestPreProcessor<AddItemToCartRequest>, IRequestHandler<AddItemToCartRequest, IActionResult>\n{\n    private readonly ICartService _cartService;\n\n    public AddItemToCartRequestHandler(ICartService cartService)\n    {\n        _cartService = cartService;\n    }\n\n    public async Task<IActionResult> Handle(AddItemToCartRequest request, CancellationToken cancellationToken)\n    {\n        await _cartService.AddItemToCartAsync(request.CartItem, cancellationToken);\n\n        var responseMessage = $\"{request.CartItem.Name} added to shopping cart, id: {request.CartItem.ProductId}\";\n\n        return new ObjectResult(responseMessage) { StatusCode = 201 };\n    }\n\n    public async Task Process(AddItemToCartRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new AddItemToCartRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/DecrementStockCountRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class DecrementStockCountRequestHandler : IRequestPreProcessor<DecrementStockCountRequest>, IRequestHandler<DecrementStockCountRequest, IActionResult>\n{\n    private readonly IStockService _stockService;\n\n    public DecrementStockCountRequestHandler(IStockService stockService)\n    {\n        _stockService = stockService;\n    }\n\n    public async Task<IActionResult> Handle(DecrementStockCountRequest request, CancellationToken cancellationToken)\n    {\n        var stockCountDto = await _stockService.DecrementStockCountAsync(request.ProductId, cancellationToken);\n\n        return new OkObjectResult(stockCountDto);\n    }\n\n    public async Task Process(DecrementStockCountRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new DecrementStockCountRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetCartRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetCartRequestHandler : IRequestPreProcessor<GetCartRequest>, IRequestHandler<GetCartRequest, IActionResult>\n{\n    private readonly ICartService _cartService;\n\n    public GetCartRequestHandler(ICartService cartService)\n    {\n        _cartService = cartService;\n    }\n\n    public async Task<IActionResult> Handle(GetCartRequest request, CancellationToken cancellationToken)\n    {\n        var cartItemsDto = await _cartService.GetCartAsync(request.Email, cancellationToken);\n\n        return new OkObjectResult(cartItemsDto);\n    }\n\n    public async Task Process(GetCartRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetCartRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetPopularProductsRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetPopularProductsRequestHandler : IRequestPreProcessor<GetPopularProductsRequest>, IRequestHandler<GetPopularProductsRequest, IActionResult>\n{\n    /// <remarks>\n    ///     @TODO: To be implemented later.\n    /// </remarks>\n    public async Task<IActionResult> Handle(GetPopularProductsRequest request, CancellationToken cancellationToken)\n    {\n        var result = new OkResult();\n\n        return await Task.FromResult(result);\n    }\n\n    public async Task Process(GetPopularProductsRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetPopularProductsRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProductRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProductRequestHandler : IRequestPreProcessor<GetProductRequest>, IRequestHandler<GetProductRequest, IActionResult>\n{\n    private readonly IProductService _productService;\n\n    private readonly IStockService _stockService;\n\n    public GetProductRequestHandler(IProductService productService, IStockService stockService)\n    {\n        _productService = productService;\n        _stockService = stockService;\n    }\n\n    public async Task<IActionResult> Handle(GetProductRequest request, CancellationToken cancellationToken)\n    {\n        var productDto = _productService.GetProduct(request.ProductId);\n\n        try\n        {\n            var stockDto = await _stockService.GetStockAsync(request.ProductId, cancellationToken);\n            productDto.StockUnits = stockDto.StockCount;\n        }\n        catch (StockNotFoundException)\n        {\n            productDto.StockUnits = 0;\n        }\n\n        return new OkObjectResult(productDto);\n    }\n\n    public async Task Process(GetProductRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetProductRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProductsRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProductsRequestHandler : RequestHandler<GetProductsRequest, IActionResult>, IRequestPreProcessor<GetProductsRequest>\n{\n    private readonly IProductService _productService;\n\n    public GetProductsRequestHandler(IProductService productService)\n    {\n        _productService = productService;\n    }\n\n    public async Task Process(GetProductsRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetProductsRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n\n    protected override IActionResult Handle(GetProductsRequest request)\n    {\n        var brands = _productService.GetBrands();\n\n        var types = _productService.GetTypes();\n\n        var typeIds = types\n            .Where(t => request.Types.Contains(t.Code))\n            .Select(t => t.Id)\n            .ToArray();\n\n        var productDtos = _productService.GetProducts(request.Brands, typeIds);\n\n        if (!productDtos.Any()) return new NoContentResult();\n\n        var aggregateResponse = new\n        {\n            Products = productDtos,\n            Brands = brands,\n            Types = types\n        };\n\n        return new OkObjectResult(aggregateResponse);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProfileRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProfileRequestHandler : RequestHandler<GetProfileRequest, IActionResult>, IRequestPreProcessor<GetProfileRequest>\n{\n    private readonly IProfileService _profileService;\n\n    public GetProfileRequestHandler(IProfileService profileService)\n    {\n        _profileService = profileService;\n    }\n\n    public async Task Process(GetProfileRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetProfileRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n\n    protected override IActionResult Handle(GetProfileRequest request)\n    {\n        var profileDto = _profileService.GetProfile(request.Email);\n\n        return new OkObjectResult(profileDto);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetProfilesRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetProfilesRequestHandler : RequestHandler<GetProfilesRequest, IActionResult>, IRequestPreProcessor<GetProfilesRequest>\n{\n    private readonly IProfileService _profileService;\n\n    public GetProfilesRequestHandler(IProfileService profileService)\n    {\n        _profileService = profileService;\n    }\n\n    public async Task Process(GetProfilesRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetProfilesRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n\n    protected override IActionResult Handle(GetProfilesRequest request)\n    {\n        var profileDtos = _profileService.GetAllProfiles();\n\n        return new OkObjectResult(profileDtos);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/GetStockRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class GetStockRequestHandler : IRequestPreProcessor<GetStockRequest>, IRequestHandler<GetStockRequest, IActionResult>\n{\n    private readonly IStockService _stockService;\n\n    public GetStockRequestHandler(IStockService stockService)\n    {\n        _stockService = stockService;\n    }\n\n    public async Task<IActionResult> Handle(GetStockRequest request, CancellationToken cancellationToken)\n    {\n        var stockDto = await _stockService.GetStockAsync(request.ProductId, cancellationToken);\n\n        return new OkObjectResult(stockDto);\n    }\n\n    public async Task Process(GetStockRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new GetStockRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/LoadTestRequestHandler.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class LoadTestRequestHandler : IRequestHandler<LoadTestRequest, IActionResult>\n{\n    private readonly ICartService _cartService;\n\n    public LoadTestRequestHandler(ICartService cartService)\n    {\n        _cartService = cartService;\n    }\n\n    public async Task<IActionResult> Handle(LoadTestRequest request, CancellationToken cancellationToken)\n    {\n        const string email = \"testuser@contosotraders.com\";\n\n        IEnumerable<CartDto> cartDtos;\n\n        try\n        {\n            cartDtos = await _cartService.GetCartAsync(email, cancellationToken);\n        }\n        catch (CartNotFoundException)\n        {\n            var newCartDto = new CartDto\n            {\n                Email = email,\n                Quantity = 1,\n                ProductId = 17,\n                Name = \"Dell Optiplex 380 17 inch (43.18 cms) Desktop\",\n                Price = 1399,\n                ImageUrl = \"https://contoso-traders-imagesctprod.azureedge.net/product-details/PID17-1.jpg\",\n                CartItemId = Guid.NewGuid().ToString()\n            };\n\n            await _cartService.UpdateCartItemQuantityAsync(newCartDto, cancellationToken); // upsert\n\n            cartDtos = await _cartService.GetCartAsync(email, cancellationToken);\n        }\n\n        return new OkObjectResult(cartDtos);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/PostImageRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class PostImageRequestHandler : IRequestPreProcessor<PostImageRequest>, IRequestHandler<PostImageRequest, IActionResult>\n{\n    private readonly IImageSearchService _imageSearchService;\n\n    public PostImageRequestHandler(IImageSearchService imageSearchService)\n    {\n        _imageSearchService = imageSearchService;\n    }\n\n    public async Task<IActionResult> Handle(PostImageRequest request, CancellationToken cancellationToken = default)\n    {\n        if (!request.File.ContentType.Contains(\"image\"))\n            return new BadRequestObjectResult(\"Invalid file type.\");\n\n        var products = await _imageSearchService.GetSimilarProductsAsync(request.File.OpenReadStream(), cancellationToken);\n\n        return new OkObjectResult(products);\n    }\n\n    public async Task Process(PostImageRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new PostImageRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/RemoveItemFromCartRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class RemoveItemFromCartRequestHandler : IRequestPreProcessor<RemoveItemFromCartRequest>, IRequestHandler<RemoveItemFromCartRequest, IActionResult>\n{\n    private readonly ICartService _cartService;\n\n    public RemoveItemFromCartRequestHandler(ICartService cartService)\n    {\n        _cartService = cartService;\n    }\n\n    public async Task<IActionResult> Handle(RemoveItemFromCartRequest request, CancellationToken cancellationToken)\n    {\n        await _cartService.RemoveItemFromCartAsync(request.CartItem, cancellationToken);\n\n        return new OkObjectResult($\"Product removed from cart, id: {request.CartItem.ProductId}\");\n    }\n\n    public async Task Process(RemoveItemFromCartRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new RemoveItemFromCartRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/SearchTextRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class SearchTextRequestHandler : IRequestPreProcessor<SearchTextRequest>, IRequestHandler<SearchTextRequest, IActionResult>\n{\n    private readonly IProductService _productService;\n\n    public SearchTextRequestHandler(IProductService productService)\n    {\n        _productService = productService;\n    }\n\n    public async Task<IActionResult> Handle(SearchTextRequest request, CancellationToken cancellationToken = default)\n    {\n        var products = await Task.FromResult(_productService.GetProducts(request.Text));\n\n        return new OkObjectResult(products);\n    }\n\n    public async Task Process(SearchTextRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new SearchTextRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Handlers/UpdateCartItemQuantityRequestHandler.cs",
    "content": "﻿using MediatR.Pipeline;\n\nnamespace ContosoTraders.Api.Core.Requests.Handlers;\n\ninternal class UpdateCartItemQuantityRequestHandler : IRequestPreProcessor<UpdateCartItemQuantityRequest>, IRequestHandler<UpdateCartItemQuantityRequest, IActionResult>\n{\n    private readonly ICartService _cartService;\n\n    public UpdateCartItemQuantityRequestHandler(ICartService cartService)\n    {\n        _cartService = cartService;\n    }\n\n    public async Task<IActionResult> Handle(UpdateCartItemQuantityRequest request, CancellationToken cancellationToken)\n    {\n        await _cartService.UpdateCartItemQuantityAsync(request.CartItem, cancellationToken);\n\n        var responseMessage = $\"Product quantity updated, id: {request.CartItem.ProductId}\";\n\n        return new ObjectResult(responseMessage) { StatusCode = 201 };\n    }\n\n    public async Task Process(UpdateCartItemQuantityRequest request, CancellationToken cancellationToken)\n    {\n        var validator = new UpdateCartItemQuantityRequestValidator();\n\n        await validator.ValidateAndThrowAsync(request, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/AddItemToCartRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class AddItemToCartRequestValidator : AbstractValidator<AddItemToCartRequest>\n{\n    public AddItemToCartRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.CartItem)\n            .NotNull();\n\n        RuleFor(request => request.CartItem.CartItemId)\n            .NotEmpty()\n            .WithMessage(\"CartItemId cannot be empty/null\");\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/DecrementStockCountRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class DecrementStockCountRequestValidator : AbstractValidator<DecrementStockCountRequest>\n{\n    public DecrementStockCountRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.ProductId)\n            .GreaterThan(0);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetCartRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetCartRequestValidator : AbstractValidator<GetCartRequest>\n{\n    public GetCartRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.Email)\n            .NotEmpty()\n            .WithMessage(\"Email cannot be null/empty.\");\n\n        RuleFor(request => request.Email)\n            .EmailAddress()\n            .WithMessage(\"Incorrect format for Email.\");\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetPopularProductsRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetPopularProductsRequestValidator : AbstractValidator<GetPopularProductsRequest>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProductRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProductRequestValidator : AbstractValidator<GetProductRequest>\n{\n    public GetProductRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.ProductId)\n            .GreaterThan(0);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProductsRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProductsRequestValidator : AbstractValidator<GetProductsRequest>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProfileRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProfileRequestValidator : AbstractValidator<GetProfileRequest>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetProfilesRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetProfilesRequestValidator : AbstractValidator<GetProfilesRequest>\n{\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/GetStockRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class GetStockRequestValidator : AbstractValidator<GetStockRequest>\n{\n    public GetStockRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.ProductId)\n            .GreaterThan(0);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/PostImageRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class PostImageRequestValidator : AbstractValidator<PostImageRequest>\n{\n    public PostImageRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.File)\n            .NotNull();\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/RemoveItemFromCartRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class RemoveItemFromCartRequestValidator : AbstractValidator<RemoveItemFromCartRequest>\n{\n    public RemoveItemFromCartRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.CartItem)\n            .NotNull();\n\n        RuleFor(request => request.CartItem.CartItemId)\n            .NotEmpty()\n            .WithMessage(\"CartItemId cannot be null/empty.\");\n\n        RuleFor(request => request.CartItem.Email)\n            .NotEmpty()\n            .WithMessage(\"Email cannot be null/empty.\");\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/SearchTextRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class SearchTextRequestValidator : AbstractValidator<SearchTextRequest>\n{\n    public SearchTextRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.Text)\n            .NotNull();\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Requests/Validators/UpdateCartItemQuantityRequestValidator.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Requests.Validators;\n\npublic class UpdateCartItemQuantityRequestValidator : AbstractValidator<UpdateCartItemQuantityRequest>\n{\n    public UpdateCartItemQuantityRequestValidator()\n    {\n        RuleFor(request => request)\n            .NotNull();\n\n        RuleFor(request => request.CartItem)\n            .NotNull();\n\n        RuleFor(request => request.CartItem.CartItemId)\n            .NotNull()\n            .WithMessage(\"CartItemId cannot be null/empty.\");\n\n        RuleFor(request => request.CartItem.Quantity)\n            .NotNull()\n            .WithMessage(\"Quantity cannot be null/empty.\");\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/ContosoTradersServiceBase.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services;\n\ninternal abstract class ContosoTradersServiceBase\n{\n    protected readonly IConfiguration Configuration;\n\n    protected readonly ILogger<ContosoTradersServiceBase> Logger;\n\n    protected readonly IMapper Mapper;\n\n    protected ContosoTradersServiceBase(IMapper mapper, IConfiguration configuration, ILogger<ContosoTradersServiceBase> logger)\n    {\n        Mapper = mapper;\n        Configuration = configuration;\n        Logger = logger;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/CartService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class CartService : ContosoTradersServiceBase, ICartService\n{\n    private readonly ICartRepository _cartRepository;\n\n    public CartService(ICartRepository cartRepository, IMapper mapper, IConfiguration configuration, ILogger<CartService> logger) : base(mapper, configuration, logger)\n    {\n        _cartRepository = cartRepository;\n    }\n\n    public async Task<IEnumerable<CartDto>> GetCartAsync(string email, CancellationToken cancellationToken = default)\n    {\n        var filterClause = $\"LOWER(c.Email) = '{email.ToLower()}'\";\n\n        var cartItemDaos = await _cartRepository.ListAsync(filterClause, cancellationToken);\n\n        if (!cartItemDaos.Any()) throw new CartNotFoundException(email);\n\n        var cartItemDtos = cartItemDaos.Select(item => Mapper.Map<CartDto>(item)).ToList();\n\n        return cartItemDtos;\n    }\n\n    public async Task AddItemToCartAsync(CartDto cartItemDto, CancellationToken cancellationToken = default)\n    {\n        var cartItemDao = Mapper.Map<CartDao>(cartItemDto);\n        cartItemDao.id = Guid.NewGuid().ToString();\n\n        await _cartRepository.AddAsync(cartItemDao.Email, cartItemDao, cancellationToken);\n    }\n\n    public async Task UpdateCartItemQuantityAsync(CartDto cartItemDto, CancellationToken cancellationToken = default)\n    {\n        var cartItemDao = Mapper.Map<CartDao>(cartItemDto);\n\n        await _cartRepository.UpsertAsync(cartItemDao.Email, cartItemDao, cancellationToken);\n    }\n\n    public async Task RemoveItemFromCartAsync(CartDto cartItemDto, CancellationToken cancellationToken = default)\n    {\n        var cartItemDao = Mapper.Map<CartDao>(cartItemDto);\n\n        await _cartRepository.DeleteAsync(cartItemDao.Email, cartItemDao.id, cancellationToken);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ImageAnalysisService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ImageAnalysisService : ContosoTradersServiceBase, IImageAnalysisService\n{\n    public ImageAnalysisService(IMapper mapper, IConfiguration configuration, ILogger<ImageAnalysisService> logger) : base(mapper, configuration, logger)\n    {\n    }\n\n    public async Task<IEnumerable<string>> AnalyzeImageAsync(Stream imageStream, CancellationToken cancellationToken = default)\n    {\n        var client = GetComputerVisionClient();\n\n        var features = new List<VisualFeatureTypes?>\n        {\n            VisualFeatureTypes.Tags\n        };\n\n        var results = await client.AnalyzeImageInStreamAsync(imageStream, features, cancellationToken: cancellationToken);\n\n        var searchTerms = results.Tags\n            .Select(tag => tag.Name)\n            .ToList();\n\n        return searchTerms;\n    }\n\n    private ComputerVisionClient GetComputerVisionClient()\n    {\n        var accountKey = Configuration[KeyVaultConstants.SecretNameCognitiveServicesAccountKey];\n\n        var endpoint = Configuration[KeyVaultConstants.SecretNameCognitiveServicesEndpoint];\n\n        var credentials = new ApiKeyServiceClientCredentials(accountKey);\n\n        var client = new ComputerVisionClient(credentials)\n        {\n            Endpoint = endpoint\n        };\n\n        return client;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ImageSearchService.cs",
    "content": "namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ImageSearchService : ContosoTradersServiceBase, IImageSearchService\n{\n    private readonly IImageAnalysisService _imageAnalysisService;\n\n    private readonly IProductService _productService;\n\n    public ImageSearchService(\n        IProductService productService,\n        IImageAnalysisService imageAnalysisService,\n        IMapper mapper,\n        IConfiguration configuration,\n        ILogger<ImageSearchService> logger)\n        : base(mapper, configuration, logger)\n    {\n        _productService = productService;\n        _imageAnalysisService = imageAnalysisService;\n    }\n\n    public async Task<IEnumerable<ProductDto>> GetSimilarProductsAsync(Stream imageStream, CancellationToken cancellationToken = default)\n    {\n        var searchTerms = await _imageAnalysisService.AnalyzeImageAsync(imageStream, cancellationToken);\n\n        var products = new List<ProductDto>();\n\n        foreach (var term in searchTerms)\n        {\n            var matchingProducts = _productService.GetProducts(term);\n\n            if (matchingProducts.Any())\n                products.AddRange(matchingProducts);\n        }\n\n        if (!products.Any())\n        {\n            var searchTags = string.Empty;\n\n            searchTerms.ToList().ForEach(tag => { searchTags += $\"{tag}, \"; });\n\n            searchTags = searchTags.Remove(searchTags.Length - 2, 2) + '.';\n\n            throw new MatchingProductsNotFoundException(searchTags);\n        }\n\n        var productList = products\n            .GroupBy(p => p.Id)\n            .Select(p => p.First());\n\n        return productList;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ProductService.cs",
    "content": "﻿using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ProductService : ContosoTradersServiceBase, IProductService\n{\n    private readonly ProductsDbContext _productRepository;\n\n    public ProductService(ProductsDbContext productDbContext, IMapper mapper, IConfiguration configuration, ILogger<ProductService> logger) : base(mapper, configuration, logger)\n    {\n        _productRepository = productDbContext;\n    }\n\n    /// <remarks>\n    ///     @TODO: Just a placeholder implementation for now. Fix this later.\n    /// </remarks>\n    public ProductDto GetProduct(int id)\n    {\n        var productDao = _productRepository.Products.SingleOrDefault(product => product.Id == id);\n\n        if (productDao is null) throw new ProductNotFoundException(id);\n\n        var productDto = CustomMapping(productDao,\n            _productRepository.Brands.ToArray(),\n            _productRepository.Types.ToArray(),\n            _productRepository.Features.ToArray(),\n            false);\n\n        return productDto;\n    }\n\n    /// <remarks>\n    ///     @TODO: Just a placeholder implementation for now. Fix this later.\n    /// </remarks>\n    public IEnumerable<ProductDto> GetProducts(int[] brands, int[] typeIds)\n    {\n        var responseDaos = brands.Any() || typeIds.Any()\n            ? GetProductsByFilter(brands, typeIds)\n            : GetAllProducts();\n\n        var allBrands = _productRepository.Brands.ToArray();\n        var allTypes = _productRepository.Types.ToArray();\n        var allFeatures = _productRepository.Features.ToArray();\n\n        var responseDtos = responseDaos.ToArray()\n            .Select(dao => CustomMapping(dao, allBrands, allTypes, allFeatures));\n\n        return responseDtos;\n    }\n\n    public IEnumerable<ProductDto> GetProducts(string searchTerm)\n    {\n        searchTerm = searchTerm.ToLower();\n\n        var allTypes = _productRepository.Types.ToArray();\n\n        var allFeatures = _productRepository.Features.ToArray();\n\n        var responseDaos = _productRepository.Products.AsEnumerable()\n            .Where(product => searchTerm.Split(' ', StringSplitOptions.RemoveEmptyEntries)\n                .Any(term =>\n                    product.Name.ToLower().Contains(term) ||\n                    allTypes.FirstOrDefault(type => type.Id == product.TypeId).Name.ToLower().Contains(term) ||\n                    allFeatures.Where(feature => feature.ProductItemId == product.Id)\n                        .Any(item => item.Description.Contains(term))));\n\n        var responseDtos = responseDaos.ToArray()\n            .Select(dao => CustomMapping(dao, null, allTypes, null));\n\n        return responseDtos;\n    }\n\n    public IEnumerable<Brand> GetBrands()\n    {\n        return _productRepository.Brands.ToList();\n    }\n\n    public IEnumerable<Type> GetTypes()\n    {\n        return _productRepository.Types.ToList();\n    }\n\n    #region Private Helper Methods\n\n    private IEnumerable<Product> GetAllProducts()\n    {\n        return _productRepository.Products.ToList();\n    }\n\n    private IEnumerable<Product> GetProductsByFilter(int[] brands, int[] typeIds)\n    {\n        var filteredProducts = _productRepository.Products\n            .ToList()\n            .Where(p =>\n                (brands.Any() ? brands.Contains(p.BrandId.GetValueOrDefault()) : true) &&\n                (typeIds.Any() ? typeIds.Contains(p.TypeId.GetValueOrDefault()) : true));\n\n        return filteredProducts;\n    }\n\n    private ProductDto CustomMapping(Product productDao, IEnumerable<Brand> brands, IEnumerable<Type> types, IEnumerable<Feature> features, bool thumbnailImages = true)\n    {\n        var imagesEndpoint = Configuration[KeyVaultConstants.SecretNameImagesEndpoint];\n\n        var imagesType = thumbnailImages ? \"product-list\" : \"product-details\";\n\n        var productDto = new ProductDto\n        {\n            Id = productDao.Id,\n            Name = productDao.Name,\n            Price = productDao.Price,\n            ImageUrl = $\"{imagesEndpoint}/{imagesType}/{productDao.ImageName}\",\n            Brand = brands?.FirstOrDefault(brand => brand.Id == productDao.BrandId),\n            Type = types?.FirstOrDefault(type => type.Id == productDao.TypeId),\n            Features = features?.Where(feature => feature.ProductItemId == productDao.Id).ToList()\n        };\n\n        return productDto;\n    }\n\n    #endregion\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/ProfileService.cs",
    "content": "﻿using Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class ProfileService : ContosoTradersServiceBase, IProfileService\n{\n    private readonly ProfilesDbContext _profileRepository;\n\n    public ProfileService(ProfilesDbContext profileRepository, IMapper mapper, IConfiguration configuration, ILogger<ProfileService> logger) : base(mapper, configuration, logger)\n    {\n        _profileRepository = profileRepository;\n    }\n\n    public IEnumerable<Profile> GetAllProfiles()\n    {\n        return _profileRepository.Profiles.ToList();\n    }\n\n    public Profile GetProfile(string email)\n    {\n        var profile = _profileRepository.Profiles.SingleOrDefault(profile => profile.Email == email);\n\n        if (profile is null) throw new ProfileNotFoundException(email);\n\n        return profile;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Implementations/StockService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Implementations;\n\ninternal class StockService : ContosoTradersServiceBase, IStockService\n{\n    private readonly IStockRepository _stockRepository;\n\n    public StockService(IStockRepository stockRepository, IMapper mapper, IConfiguration configuration, ILogger<StockService> logger) : base(mapper, configuration, logger)\n    {\n        _stockRepository = stockRepository;\n    }\n\n    public async Task<StockDto> GetStockAsync(int productId, CancellationToken cancellationToken = default)\n    {\n        var requestDto = new StockDto { ProductId = productId, StockCount = 0 };\n\n        var requestDao = Mapper.Map<StockDao>(requestDto);\n\n        var responseDao = await _stockRepository.GetAsync(requestDao.id, requestDao.id, cancellationToken);\n\n        if (responseDao is null) throw new StockNotFoundException(productId);\n\n        var responseDto = Mapper.Map<StockDto>(responseDao);\n\n        return responseDto;\n    }\n\n    public async Task<StockDto> DecrementStockCountAsync(int productId, CancellationToken cancellationToken)\n    {\n        var requestDto = new StockDto { ProductId = productId, StockCount = 0 };\n\n        var requestDao = Mapper.Map<StockDao>(requestDto);\n\n        var responseDao = await _stockRepository.GetAsync(requestDao.id, requestDao.id, cancellationToken);\n\n        if (responseDao is null) throw new StockNotFoundException(productId);\n\n        responseDao.StockCount -= 1;\n\n        await _stockRepository.UpsertAsync(responseDao.id, responseDao, cancellationToken);\n\n        var responseDto = Mapper.Map<StockDto>(responseDao);\n\n        return responseDto;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/ICartService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface ICartService\n{\n    /// <summary>\n    /// </summary>\n    /// <param name=\"email\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    /// <exception cref=\"CartNotFoundException\"></exception>\n    Task<IEnumerable<CartDto>> GetCartAsync(string email, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"cartItemDto\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    Task AddItemToCartAsync(CartDto cartItemDto, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"cartItemDto\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    Task UpdateCartItemQuantityAsync(CartDto cartItemDto, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"cartItemDto\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    Task RemoveItemFromCartAsync(CartDto cartItemDto, CancellationToken cancellationToken = default);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IImageAnalysisService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IImageAnalysisService\n{\n    /// <summary>\n    /// </summary>\n    /// <param name=\"imageStream\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    Task<IEnumerable<string>> AnalyzeImageAsync(Stream imageStream, CancellationToken cancellationToken = default);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IImageSearchService.cs",
    "content": "namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IImageSearchService\n{\n    /// <summary>\n    /// </summary>\n    /// <param name=\"imageStream\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    Task<IEnumerable<ProductDto>> GetSimilarProductsAsync(Stream imageStream, CancellationToken cancellationToken = default);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IProductService.cs",
    "content": "﻿using Type = ContosoTraders.Api.Core.Models.Implementations.Dao.Type;\n\nnamespace ContosoTraders.Api.Core.Services.Interfaces;\n\npublic interface IProductService\n{\n    /// <summary>\n    /// </summary>\n    /// <param name=\"id\"></param>\n    /// <returns></returns>\n    /// <exception cref=\"ProductNotFoundException\"></exception>\n    ProductDto GetProduct(int id);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"brands\"></param>\n    /// <param name=\"typeIds\"></param>\n    /// <returns></returns>\n    IEnumerable<ProductDto> GetProducts(int[] brands, int[] typeIds);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"searchTerm\"></param>\n    /// <returns></returns>\n    IEnumerable<ProductDto> GetProducts(string searchTerm);\n\n    /// <summary>\n    /// </summary>\n    /// <returns></returns>\n    IEnumerable<Brand> GetBrands();\n\n    /// <summary>\n    /// </summary>\n    /// <returns></returns>\n    IEnumerable<Type> GetTypes();\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IProfileService.cs",
    "content": "﻿using Profile = ContosoTraders.Api.Core.Models.Implementations.Dao.Profile;\n\nnamespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IProfileService\n{\n    IEnumerable<Profile> GetAllProfiles();\n\n    Profile GetProfile(string email);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Services/Interfaces/IStockService.cs",
    "content": "﻿namespace ContosoTraders.Api.Core.Services.Interfaces;\n\ninternal interface IStockService\n{\n    /// <summary>\n    /// </summary>\n    /// <param name=\"productId\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    /// <exception cref=\"StockNotFoundException\"></exception>\n    Task<StockDto> GetStockAsync(int productId, CancellationToken cancellationToken = default);\n\n    /// <summary>\n    /// </summary>\n    /// <param name=\"productId\"></param>\n    /// <param name=\"cancellationToken\"></param>\n    /// <returns></returns>\n    /// <exception cref=\"StockNotFoundException\"></exception>\n    Task<StockDto> DecrementStockCountAsync(int productId, CancellationToken cancellationToken = default);\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Core/Usings.cs",
    "content": "﻿global using AutoMapper;\nglobal using ContosoTraders.Api.Core;\nglobal using ContosoTraders.Api.Core.Constants;\nglobal using ContosoTraders.Api.Core.Exceptions;\nglobal using ContosoTraders.Api.Core.Models;\nglobal using ContosoTraders.Api.Core.Models.Implementations.Dao;\nglobal using ContosoTraders.Api.Core.Models.Implementations.Dto;\nglobal using ContosoTraders.Api.Core.Models.Interfaces;\nglobal using ContosoTraders.Api.Core.Repositories;\nglobal using ContosoTraders.Api.Core.Repositories.Implementations;\nglobal using ContosoTraders.Api.Core.Repositories.Interfaces;\nglobal using ContosoTraders.Api.Core.Requests.Definitions;\nglobal using ContosoTraders.Api.Core.Requests.Validators;\nglobal using ContosoTraders.Api.Core.Services.Implementations;\nglobal using ContosoTraders.Api.Core.Services.Interfaces;\nglobal using FluentValidation;\nglobal using MediatR;\nglobal using Microsoft.AspNetCore.Http;\nglobal using Microsoft.AspNetCore.Mvc;\nglobal using Microsoft.Azure.CognitiveServices.Vision.ComputerVision;\nglobal using Microsoft.Azure.CognitiveServices.Vision.ComputerVision.Models;\nglobal using Microsoft.Extensions.Configuration;\nglobal using Microsoft.Extensions.Logging;"
  },
  {
    "path": "src/ContosoTraders.Api.Products/ContosoTraders.Api.Products.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n\t<PropertyGroup>\n\t\t<TargetFramework>net7.0</TargetFramework>\n\t\t<Nullable>disable</Nullable>\n\t\t<ImplicitUsings>enable</ImplicitUsings>\n\t\t<UserSecretsId>23a3ea2a-a58a-475e-92b3-30d4960d8125</UserSecretsId>\n\t\t<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t  <PackageReference Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" Version=\"1.17.0\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\ContosoTraders.Api.Core\\ContosoTraders.Api.Core.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<Content Update=\"appsettings.json\">\n\t\t\t<CopyToOutputDirectory>Always</CopyToOutputDirectory>\n\t\t</Content>\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <Folder Include=\"Migration\\\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/LoginController.cs",
    "content": "﻿using System.IdentityModel.Tokens.Jwt;\nusing System.Security.Claims;\nusing System.Text;\nusing ContosoTraders.Api.Core.Constants;\nusing Microsoft.IdentityModel.Tokens;\n\nnamespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic class LoginController : ContosoTradersControllerBase\n{\n    private readonly IConfiguration config;\n\n    public LoginController(IConfiguration config, IMediator mediator) : base(mediator)\n    {\n        this.config = config;\n    }\n\n    [HttpPost]\n    public IActionResult Login([FromBody] TokenRequest request)\n    {\n        if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password)) return BadRequest(\"Could not verify username and password\");\n\n        return Ok(new\n        {\n            access_token = CreateAccessToken(request.Username),\n            refresh_token = \"\"\n        });\n    }\n\n    private AccessToken CreateAccessToken(string username)\n    {\n        var claims = new[]\n        {\n            new Claim(ClaimTypes.Name, username),\n            new Claim(ClaimTypes.Sid, Guid.NewGuid().ToString())\n        };\n\n        // demo only, do not do this in real life!\n        var securityKey = config[\"SecurityKey\"] ?? AuthConstants.DefaultJwtSigningKey;\n        var encoding = Encoding.UTF8.GetBytes(securityKey);\n        var key = new SymmetricSecurityKey(encoding);\n        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);\n        var expiresInDays = 365;\n\n        var token = new JwtSecurityToken(\n            claims: claims,\n            issuer: config[\"Issuer\"] ?? \"ContosoWebsite\",\n            expires: DateTime.Now.AddDays(expiresInDays),\n            signingCredentials: creds);\n\n        return new AccessToken\n        {\n            Token = new JwtSecurityTokenHandler().WriteToken(token),\n            ExpiresIn = expiresInDays * 24 * 60 * 60,\n            TokenType = \"bearer\"\n        };\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/ProductsController.cs",
    "content": "﻿namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic class ProductsController : ContosoTradersControllerBase\n{\n    public ProductsController(IMediator mediator) : base(mediator)\n    {\n    }\n\n\n    [HttpGet]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetProducts(\n        [FromQuery(Name = \"brand\")] int[] brands,\n        [FromQuery(Name = \"type\")] string[] types)\n    {\n        var request = new GetProductsRequest\n        {\n            Brands = brands,\n            Types = types\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n\n    [HttpGet(\"{id:int}\")]\n    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]\n    [ProducesResponseType(StatusCodes.Status404NotFound)]\n    public async Task<IActionResult> GetProduct(int id)\n    {\n        var request = new GetProductRequest\n        {\n            ProductId = id\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n\n    [HttpGet(\"landing\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetPopularProducts()\n    {\n        var request = new GetPopularProductsRequest();\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n\n    [HttpPost(\"imageclassifier\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    [ProducesResponseType(StatusCodes.Status404NotFound)]\n    [ProducesResponseType(StatusCodes.Status400BadRequest)]\n    public async Task<IActionResult> PostImage(IFormFile file)\n    {\n        var request = new PostImageRequest\n        {\n            File = file\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpGet(\"search/{text}\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    [ProducesResponseType(StatusCodes.Status404NotFound)]\n    [ProducesResponseType(StatusCodes.Status400BadRequest)]\n    public async Task<IActionResult> Search(string text)\n    {\n        var request = new SearchTextRequest\n        {\n            Text = text\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/ProfilesController.cs",
    "content": "﻿namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic class ProfilesController : ContosoTradersControllerBase\n{\n    public ProfilesController(IMediator mediator) : base(mediator)\n    {\n    }\n\n    [HttpGet]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetProfiles()\n    {\n        var request = new GetProfilesRequest();\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpGet(\"me\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetProfile()\n    {\n        var email = \"admin@contosotraders.com\";\n        var request = new GetProfileRequest\n        {\n            Email = email\n        };\n        return await ProcessHttpRequestAsync(request);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Controllers/StocksController.cs",
    "content": "﻿namespace ContosoTraders.Api.Products.Controllers;\n\n[Route(\"v1/[controller]\")]\n[Produces(\"application/json\")]\npublic class StocksController : ContosoTradersControllerBase\n{\n    public StocksController(IMediator mediator) : base(mediator)\n    {\n    }\n\n    [HttpGet(\"{id:int}\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> GetStock(int id)\n    {\n        var request = new GetStockRequest\n        {\n            ProductId = id\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n\n    [HttpPost(\"{id:int}/consume\")]\n    [ProducesResponseType(StatusCodes.Status200OK)]\n    public async Task<IActionResult> DecrementStockCount(int id)\n    {\n        var request = new DecrementStockCountRequest\n        {\n            ProductId = id\n        };\n\n        return await ProcessHttpRequestAsync(request);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Dockerfile",
    "content": "FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:7.0 AS build\nWORKDIR /src\nCOPY [\"ContosoTraders.Api.Products/ContosoTraders.Api.Products.csproj\", \"ContosoTraders.Api.Products/\"]\nCOPY [\"ContosoTraders.Api.Core/ContosoTraders.Api.Core.csproj\", \"ContosoTraders.Api.Core/\"]\nRUN dotnet restore \"ContosoTraders.Api.Products/ContosoTraders.Api.Products.csproj\"\nCOPY . .\nWORKDIR \"/src/ContosoTraders.Api.Products\"\nRUN dotnet build \"ContosoTraders.Api.Products.csproj\" -c Release -o /app/build\n\nFROM build AS publish\nRUN dotnet publish \"ContosoTraders.Api.Products.csproj\" -c Release -o /app/publish\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nENTRYPOINT [\"dotnet\", \"ContosoTraders.Api.Products.dll\"]"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Certificate.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: tls-secret\nspec:\n  secretName: tls-secret\n  dnsNames:\n    #Note: The '{AKS_FQDN}' token will be substituted with the actual DNS label of the ingress controller's public IP by github workflow.\n    - {AKS_FQDN}\n  issuerRef:\n    name: letsencrypt\n    kind: ClusterIssuer\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/ClusterIssuer.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt\nspec:\n  acme:\n    # The ACME server URL\n    server: https://acme-v02.api.letsencrypt.org/directory\n    # Email address used for ACME registration\n    email: mithun.shanbhag@spektrasystems.com\n    # Name of a secret used to store the ACME account private key\n    privateKeySecretRef:\n      name: letsencrypt\n    # Enable HTTP01 validations\n    solvers:\n      - http01:\n          ingress:\n            class: nginx\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/ClusterRole.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: containerHealth-log-reader\nrules:\n  - apiGroups: [\"\", \"metrics.k8s.io\", \"extensions\", \"apps\"]\n    resources:\n      - \"pods/log\"\n      - \"events\"\n      - \"nodes\"\n      - \"pods\"\n      - \"deployments\"\n      - \"replicasets\"\n    verbs: [\"get\", \"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: containerHealth-read-logs-global\nroleRef:\n  kind: ClusterRole\n  name: containerHealth-log-reader\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n  - kind: User\n    name: clusterUser\n    apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: contoso-traders-products\nspec:\n  #Note: The '{AKS_REPLICAS}' token will be substituted by the github workflow.\n  replicas: {AKS_REPLICAS}\n  selector:\n    matchLabels:\n      app: contoso-traders-products\n  template:\n    metadata:\n      labels:\n        app: contoso-traders-products\n    spec:\n      nodeSelector:\n        \"kubernetes.io/os\": linux\n      containers:\n        - name: contoso-traders-products\n          #Note: The '{SUFFIX}' token will be substituted with the value of the SUFFIX github variable by github workflow.\n          image: contosotradersacr{SUFFIX}.azurecr.io/contosotradersapiproducts:latest\n          env:\n            - name: KeyVaultEndpoint\n              valueFrom:\n                secretKeyRef:\n                  name: contoso-traders-kv-endpoint\n                  key: contoso-traders-kv-endpoint\n            - name: ManagedIdentityClientId\n              valueFrom:\n                secretKeyRef:\n                  name: contoso-traders-mi-clientid\n                  key: contoso-traders-mi-clientid\n          resources:\n            requests:\n              cpu: 100m\n              memory: 128Mi\n            limits:\n              #Note: The '{AKS_CPU_LIMIT}', '${AKS_MEMORY_LIMIT}' tokens will be substituted by the github workflow.\n              cpu: {AKS_CPU_LIMIT}\n              memory: {AKS_MEMORY_LIMIT}\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: contoso-traders-products\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/rewrite-target: /$1\n    nginx.ingress.kubernetes.io/use-regex: \"true\"\n    nginx.ingress.kubernetes.io/ssl-redirect: \"false\"\n    cert-manager.io/cluster-issuer: letsencrypt\nspec:\n  tls:\n    - hosts:\n        #Note: The '{AKS_FQDN}' token will be substituted with the actual DNS label of the ingress controller's public IP by github workflow.\n        - {AKS_FQDN}\n      secretName: tls-secret\n  rules:\n    #Note: The '{AKS_FQDN}' token will be substituted with the actual DNS label of the ingress controller's public IP by github workflow.\n    - host: {AKS_FQDN}\n      http:\n        paths:\n          - path: /(.*)\n            pathType: ImplementationSpecific\n            backend:\n              service:\n                name: contoso-traders-products\n                port:\n                  number: 80\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/NamespaceCertManager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: cert-manager\n  labels:\n    cert-manager.io/disable-validation: \"true\"\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/NamespaceChaosTesting.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: chaos-testing\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Manifests/Service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: contoso-traders-products\nspec:\n  type: ClusterIP\n  ports:\n    - port: 80\n  selector:\n    app: contoso-traders-products\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Migration/productsdb.sql",
    "content": "use productsdb\n\nDROP TABLE IF EXISTS Brands\n\nCREATE TABLE Brands\n(\n    Id int NOT NULL,\n    Name nvarchar(255) NULL,\n    CONSTRAINT PK_Brands PRIMARY KEY(Id)\n)\n\nINSERT INTO Brands\nVALUES\n    (1, 'Microsoft'),\n    (2, 'ASUS'),\n    (3, 'Dell'),\n    (4, 'Acer'),\n    (5, 'OnePlus'),\n    (6, 'HP'),\n    (7, 'Lenovo'),\n    (8, 'Samsung'),\n    (9, 'ViewSonic'),\n    (10,'Zebronics')\n\nDROP TABLE IF EXISTS Features\n\nCREATE TABLE Features\n(\n    Id int NOT NULL,\n    Title nvarchar(255) NULL,\n    Description nvarchar(max) NULL,\n    ProductItemId int NULL,\n    CONSTRAINT PK_Features PRIMARY KEY(Id)\n)\n\nINSERT INTO Features\nValues\n    (1,'Model Number','Model Number: Xbox Series X',1),\n    (2,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',1),\n    (3,'Model Number','Model Number: Xbox Series X',2),\n    (4,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',2),\n    (5,'Model Number','Model Number: Xbox Series X',3),\n    (6,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',3),\n    (7,'Model Number','Model Number: Xbox Series X',4),\n    (8,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',4),\n    (9,'Model Number','Model Number: Xbox Series X',5),\n    (10,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',5),\n    (11,'Series','Series : Surface Pro 7',6),\n    (12,'Specifications','Specifications : Touchscreen 2-in-1 Laptop (10th Gen Intel Core i5/8GB/256GB SSD/Windows 10 Home/Intel Iris Plus Graphics, 25% Off on Microsoft 365), Black',6),\n    (13,'Series','Series : Surface Pro 7',7),\n    (14,'Specifications','Specifications : Touchscreen Laptop (8GB/256GB SSD/Windows 11 Home /Radeon Graphics/Platinum/1.265 kg) - 5PB-00023',7),\n    (15,'Series','Series : Surface Pro X ',8),\n    (16,'Specifications','Specifications :  (Qualcomm Microsoft Sq1/8Gb/128Gb Ssd/Windows 10 Home/Microsoft Sq1 Adreno 685 Gpu Graphics, Wi-Fi), Matte Black',8),\n    (17,'Series','Series : Surface GO 2 ',9),\n    (18,'Specifications','Specifications :   (Gold Processor 4425Y/8GB/128GB SSD/Windows 10 Home in S Mode/Intel UHD 615 Graphics), Platinum',9),\n    (19,'Series','Series : Series : Surface Laptop 3',10),\n    (20,'Specifications','Specifications :   (8GB/128GB SSD/Windows 10 Home/Integrated Graphics/Platinum/1.265kg, 25% Off on Microsoft 365), VGY-00021',10),\n    (21,'Series','Series : Vivobook 15',11),\n    (22,'Specifications','Specifications :(4GB RAM/256GB SSD/Integrated Graphics/Windows 11 Home/Transparent Silver/1.8 Kg), X515MA-BR011W',11),\n    (23,'Series','Series : Vivobook S15',12),\n    (24,'Specifications','Specifications :Intel Core Evo i5-12500H 12th Gen, Thin and Light Laptop (16GB/512GB SSD/Iris Xe Graphics/Windows 11/Office 2021/Black/1.8 kg) K3502ZA-L502WS',12),\n    (25,'Series','Series : TUF Gaming F15',13),\n    (26,'Specifications','Specifications :  Intel Core i5-10300H 10th Gen, GTX 1650 4GB Graphics, Gaming Laptop (8GB RAM/512GB NVMe SSD/Windows 11/Black/2.30 Kg), FX506LH-HN258W',13),\n    (27,'Series','Series : TUF Gaming A15',14),\n    (28,'Specifications','Specifications :AMD Ryzen 7 4800H, 4GB GeForce RTX 3050 Graphics, Gaming Laptop (16GB/512GB SSD/90WHrs Battery/Windows 11/with Office/Black/2.3 kg), FA506ICB-HN075W',14),\n    (29,'Series','Series :Vivobook 14',15),\n    (30,'Specifications','Specifications :14\" (35.56 cms) 2.8K OLED 16:10 90Hz, Thin & Laptop (8GB/512GB SSD/Wins 11/Office 2021/Alexa/Backlit KB/Fingerprint/Black/1.6 kg), X1405ZA-KM311WS',15),\n    (31,'Series','Series :  Dell 5410',16),\n    (32,'Specifications','Specifications : 16 GB DDR4, 1 TB + 256GB SSD, Windows 11 Home, Office 2021, Dark Shadow Grey Molded (Black), 3 Years, 23.5\" FHD',16),\n    (33,'Series','Series : Dell 380',17),\n    (34,'Specifications','Specifications  (Core2Duo /8 GB RAM / 500 GB HDD/ Windows7 Pro, MS Office/Intel Hd Graphics 17 inches Monitor(1398 x 780)) Silver',17),\n    (35,'Series','Series : Dell Optiplex',18),\n    (36,'Specifications','Specifications :(Intel i5 3470, 8GB, 500GB HDD, 19 inches HD Monitor, Keyboard, Mouse, HD Webcam, Mic, Speakers, Wifi, Display Port), Windows 10 Pro',18),\n    (37,'Series','Series : Acer EK220Q',19),\n    (38,'Specifications','Specifications :250 Nits HDMI and VGA Ports Eye Care Features Like Bluelight Shield, Flickerless and Comfy View (1920 x 1080 Pixels, Black)',19),\n    (39,'Series','Series : Acer Ha220Q',20),\n    (40,'Specifications','Specifications :1920 X 1080 Pixels Full Hd IPS Ultra Slim (6.6Mm Thick) Monitor I Frameless Design I AMD Free Sync I Eye Care Features I Stereo Speakers (White)',20),\n    (41,'Series','Series : Acer Aspire C24',21),\n    (42,'Specifications','Specifications : Intel Core i5-1035G1 I 8GB DDR4 I 512GB SSD I Windows 11 Home I MS Office Home & Student 2021 I Full HD Camera I Wireless Keyboard & Mouse',21),\n    (43,'Model','Model : ROG Phone 2 ZS660KL',22),\n    (44,'Specifications','Specifications :8GB RAM, 128GB ROM Snapdragon 855 Plus Processor, 6000 mAh Battery',22),\n    (45,'Model','Model : Zenfone 5Z ZS621KL-2A012IN',23),\n    (46,'Specifications','Specifications :(Midnight Blue, 6GB RAM, 128GB Storage), Dual SIM, Proximity, Fingerprint sensor, E-compass, Ambient light sensor, Hall sensor, GPS, RGB sensor, Gyro, Video Player, Music Player, Accelerator',23),\n    (47,'Model','Model : ZS630KL-2A037WW-cr',24),\n    (48,'Specifications','Specifications : (Black, 64 GB) (6 GB RAM),Dual Front Camera, Camera,Flip, Smartphone, 5000 Milliamp Hours',24),\n    (49,'Model','Model : Nord CE 2 Lite',25),\n    (50,'Specifications','Specifications : 5G (Blue Tide, 6GB RAM, 128GB Storage), 64MP Main Camera with EIS; 2MP Depth Lens and 2MP Macro Lens; Front (Selfie) Camera: 16MP Sony IMX471, 6.59 Inches; 120 Hz Refresh Rate, Qualcomm Snapdragon 695 5G Processor, 5000 mAh Battery with 33W SuperVOOC Charge support',25),\n    (51,'Model','Model : Nord 2T ',26),\n    (52,'Specifications','Specifications :5G (Gray Shadow, 8GB RAM, 128GB Storage), 50MP Main Camera with Sony IMX766 and OIS, 8MP Ultrawide Camera with 120 degree FOV and 2MP mono lens with Dual LED Flash; 32MP Front (Selfie) Camera with Sony IMX615, 6.43 Inches; 90 Hz AMOLED Display with Corning Gorilla Glass 5, Mediatek Dimensity 1300 Processor, Battery 4500 mAh with 80W SuperVOOC',26),\n    (53,'Model','Model : 10R',27),\n    (54,'Specifications','Specifications : 5G (Sierra Black, 8GB RAM, 128GB Storage, 80W SuperVOOC), 0MP Main Camera with Sony IMX766 and 2MP Macro Camera with Dual LED Flash; 16MP Front (Selfie) Camera with Sony IMX471, 6.7 Inches; 120 Hz IRIS Display; Resolution: 2400 X 1080 pixels 394 ppi; Aspect Ratio: 20:9, OxygenOS based on Android 12, MTK D8100 Max Processor, In-Display Fingerprint Sensor',27),\n    (55,'Model','Model : 10T',28),\n    (56,'Specifications','Specifications : 5G (Moonstone Black, 16GB RAM, 256GB Storage)',28),\n    (57,'Model Number','Model Number: Xbox Series X ',29),\n    (58,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',29),\n    (59,'Model Number','Model Number: Xbox Series X ',30),\n    (60,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',30),\n    (61,'Model Number','Model Number: Xbox Series X ',31),\n    (62,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',31),\n    (63,'Model Number','Model Number: Xbox Series X ',32),\n    (64,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',32),\n    (65,'Model Number','Model Number: Xbox Series X ',33),\n    (66,'Additional Content','Additional Content: Series X console, One Wireless Controller, A high-speed HDMI Cable and Power Cable.',33),\n    (67,'Series','Series : Dell IdeaCentre AIO 3 ',34),\n    (68,'Specifications','Specifications :  (AMD Ryzen 3/8GB/1TB HDD/Windows 11/MS Office 2021/AMD Radeon Graphics/HD 720p Camera/Wired Keyboard, Mouse), Black (F0G6008AIN)',34),\n    (69,'Series','Series : HP AIO ',35),\n    (70,'Specifications',':  Specifications: 21.5 inch(54.6 cm) FHD Anti Glare Display (8GB/1TB HDD+256GB SSD/Win 11/Intel UHD Graphics/Wireless Keyboard & Mouse Combo/MS Office/Jet Black,22-dd2456in',35),\n    (71,'Series','Series : HP AIO ',36),\n    (72,'Specifications',' :Specifications :   27inches/68.6 cm 8GB RAM/512GB SSD/FHD, Micro-Edge, Anti-Glare Display/Wireless Keyboard & Mouse/Intel UHD Graphics/Windows 11/MSO 2021, 27-cb1345in',36),\n    (73,'Series','Series : Dell Optiplex ',37),\n    (74,'Specifications',' :Specifications :   Core i5 8 GB 500GB HDD Windows MS Office Intel HD Graphics), Black, RAM Size 8 GB, Memory Technology - DDR3, Computer Memory Type GDDR3, Maximum Memory Supported 8 GB, Hard Drive Size 500 GB, Hard Disk Description HDD',37),\n    (75,'Series','Series : Dell Optiplex 19 ',38),\n    (76,'Specifications',' :Specifications : (Intel i5 3470/8 GB/1TB HDD /19\" HD Monitor+Keyboard+Mouse+ HD Webcam+Mic+Speakers+WiFi+Display Port/Windows 10 Pro/MS Office)',38),\n    (77,'Series','Series : HP AIO',39),\n    (78,'Specifications',' : VA, 3-Sided Micro-Edge, Anti-Glare/4GB RAM/1TB HDD/Wired Keyboard & Mouse/Dual Speakers/Win 11/Intel UHD Graphics/MSO 2021/5.7Kg, 22-dd2342in, White',39),\n    (79,'Model','Model : M13',40),\n    (80,'Specifications',' :Specifications : Aqua Green, 6GB, 128GB Storage | 6000mAh Battery | Upto 12GB RAM with RAM Plus, 6000mAh lithium-ion battery, 1 year manufacturer warranty for device and 6 months manufacturer warranty for in-box accessories including batteries from the date of purchase, Upto 12GB RAM with RAM Plus | 128GB internal memory expandable up to 1TB| Dual Sim (Nano), 50MP+5MP+2MP Triple camera setup- True 50MP (F1.8) main camera +5MP(F2.2)+ 2MP (F2.4) | 8MP (F2.2) front cam, Android 12,One UI Core 4 with a powerful Octa Core Processor, 16.72 centimeters (6.6-inch) FHD+ LCD - infinity O Display, FHD+ resolution with 1080 x 2408 pixels resolution, 401 PPI with 16M color',40),\n    (81,'Model','Model : F13',41),\n    (82,'Specifications',' :Specifications : (Nightsky Green, 4GB RAM 64GB Storage), 4 GB RAM | 64 GB ROM | Expandable Upto 1 TB, 16.76 cm (6.6 inch) Full HD+ Display, 50MP + 5MP + 2MP | 8MP Front Camera, 6000 mAh Lithium Ion Battery, Exynos 850 Processor',41),\n    (83,'Model','Model : S20',42),\n    (84,'Specifications',' :Specifications : Cloud Lavender, 8GB RAM, 128GB Storage, 5G Ready powered by Qualcomm Snapdragon 865 Octa-Core processor, 8GB RAM, 128GB internal memory expandable up to 1TB, Android 11.0 operating system and dual SIM, Triple Rear Camera Setup - 12MP (Dual Pixel) OIS F1.8 Wide Rear Camera + 8MP OIS Tele Camera + 12MP Ultra Wide | 30X Space Zoom, Single Take & Night Mode | 32MP F2.2 Front Punch Hole Camera 6.5-inch(16.40 centimeters) Infinity-O Super AMOLED Display with 120Hz Refresh rate, 1080 x 2400 (FHD+) Resolution \" 4500 mAh battery (Non -removable) with Super Fast Charging, FAst Wireless Charging & Finger Print sensor',42),\n    (85,'Series','Series : D-Series',43),\n    (86,'Specifications',' :Specifications :  |16.7 Mn Colors, 3000:1 Contrast Ratio, 75Hz, 4ms, AMD FreeSync, HDMI, VESA Wall Mount, TUV Eye Comfort & Low Blue Light -D22e-20',43),\n    (87,'Series','Series : VA2432-MH ',44),\n    (88,'Specifications',' :Specifications :  IPS Panel, 1920 x 1080 Full HD Pixels Display, Super Clear IPS Technology, 75 Hz Refresh Rate, 3 Side Borderless Design, Dual Integrated Speakers, HDMI | VGA Enabled',44),\n    (89,'Series','Series : LF24T350FHWXXL ',45),\n    (90,'Specifications',' :Specifications : IPS, 75 Hz, Bezel Less Design, AMD FreeSync, Flicker Free, HDMI, D-sub, Dark Blue Gray), 24 inch Samsung Monitor - 1,920 x 1,080 Resolution IPS Panel Monitor 3-sided borderless display for All-expansive view, Fluid pictures with 75hz refresh rate | 5 ms response time | 250cd/m2 Brightness (Typical)',45),\n    (91,'Series','Series : EK220Q ',46),\n    (92,'Specifications',' :Specifications : 250 Nits HDMI and VGA Ports Eye Care Features Like Bluelight Shield, Flickerless and Comfy View (1920 x 1080 Pixels, Black), 21.5 Inch VA Panel Full HD 1920 X 1080 Resolution Monitor I 250 Nits Brightness I 178 / 178 View Angle, Connectivity Options : HDMI and VGA Ports with Inbox HDMI Cable, 5MS Response Time 75Hz Fast Refresh Rate, Eye Care Featues Includes Bluelight Shield I Flickerless I Comfyview, Wall Mount Ready I -5 to -20 Tilt Adjustment',46),\n    (93,'Series','Series : Ha220Q',47),\n    (94,'Specifications',' :Specifications : 1920 X 1080 Pixels Full Hd IPS Ultra Slim (6.6Mm Thick) Monitor I Frameless Design I AMD Free Sync I Eye Care Features I Stereo Speakers (White), Exceptional Full HD IPS 21.5 Inch Ultra Thin Display : Enjoy immaculate image quality with 1920x1080 resolution and 178 degree wide viewing angles, Connectivity Ports : 1 VGA Port, 1 HDMI, 1 Audio-In Port, Inbox HDMI and VGA Cable, Eye Care Features Includes Blue Light Shield, Flickerless, Comfyview helps to protect the eyes and gives comfortable viewing experience, 250 Nits Brightness, 4MS Response Time 75Hz Refresh Rate with AMD Free Sync Technology',47),\n    (95,'Series','Series : HA270',48),\n    (96,'Specifications',' :Specifications : 6.6mm Thick Frameless Design AMD Free Sync LCD Monitor with Eye Care Features and Stereo Speakers (White), Exceptional Full HD IPS 27 Inch Ultra Thin Display : Enjoy immaculate image quality with 1920x1080 resolution and 178 degree wide viewing angles, Connectivity Ports : 1 VGA Port, 1 HDMI, 1 Audio-In Port, Inbox HDMI and VGA Cable, Eye Care Features Includes Blue Light Shield, Flickerless, Comfyview helps to protect the eyes and gives comfortable viewing experience, 250 Nits Brightness, 4MS Response Time 75Hz Refresh Rate with AMD Free Sync Technology',48),\n    (97,'Series','Series : Zeb-V16HD',49),\n    (98,'Specifications',' :Specifications : 15.4 with Supporting HDMI, has VGA Input, HD 1280 x 800, Glossy Panel, Slim Feature and Wall mountable, VGA / HDMI input.Aspect ratio 16:10. Viewing angle H90/V50, Slim design, Anti glare, Built-in power supply',49),\n    (99,'Series','Series :  Zeb-V19HD',50),\n    (100,'Specifications',' :Specifications : Supporting Hdmi, Vga Input, Hd 1366 X 768 Pixels, 16.7M Colors, Glossy Panel, Slim Design & Wall Mountable, Black, HD 1366 x 768 Native resolution with 16:9 Aspect Ratio, Supports 16.7 Million Colors.High quality 220cd/m² Maximum Brightness Dynamic Contrast Ratio 500000:1, Viewing Angle H170°/ V160°, 8ms Response Time & 0.3 x 0.3 mm Pixel Pitch Green',50)\n  \n\nDROP TABLE IF EXISTS Tags\n\nCREATE TABLE Tags\n(\n    Id int NOT NULL,\n    Value nvarchar(255) NULL,\n    CONSTRAINT PK_Tags PRIMARY KEY(Id)\n)\n\nINSERT INTO Tags\nVALUES\n    (1, 'RechargeableScrewdriver'),\n    (2, 'Multitool'),\n    (3, 'HardHat')\n\nDROP TABLE IF EXISTS Types\n\nCREATE TABLE Types\n(\n    Id int NOT NULL,\n    Code nvarchar(255) NULL,\n    Name nvarchar(255) NULL,\n    CONSTRAINT PK_Types PRIMARY KEY(Id)\n)\n\nINSERT INTO Types\nVALUES\n    (1, 'controllers' , 'Controllers'),\n    (2, 'laptops' , 'Laptops'),\n    (3, 'desktops' , 'Desktops'),\n    (4, 'mobiles' , 'Mobiles'),\n    (5, 'monitors' , 'Monitors')  \n \n\nDROP TABLE IF EXISTS Products\n\nCREATE TABLE Products\n(\n    Id int NOT NULL,\n    Name nvarchar(255) NULL,\n    Price decimal(9, 2) NULL,\n    ImageName nvarchar(255) NULL,\n    BrandId int NULL,\n    TypeId int NULL,\n    TagId int NULL,\n    CONSTRAINT PK_Products PRIMARY KEY(Id)\n)\n\nINSERT INTO Products (Id,Name,Price,ImageName,BrandId,TypeId,TagId)\nVALUES\n    (1,'Xbox Wireless Controller Lunar Shift Special Edition',99,'PID1-1.jpg',1,1,NULL),\n    (2,'Xbox Wireless Controller Mineral Camo Special Edition',112,'PID2-1.jpg',1,1,NULL),\n    (3,'Xbox Elite Wireless Controller Series 2 Core (White)',139,'PID3-1.jpg',1,1,NULL),\n    (4,'Xbox Wireless Controller 20th Anniversary Special Edition',89,'PID4-1.jpg',1,1,NULL),\n    (5,'Xbox Elite Wireless Controller Series 2 Halo Infinite Limited Edition',139,'PID5-1.jpg',1,1,NULL),\n    (6,'Microsoft Surface Pro 7 PUV-00028 12.3\" Black',249,'PID6-1.jpg',1,2,NULL),\n    (7,'Microsoft Surface Laptop 4 AMD Ryzen 5 4680U 13.5 inches',299,'PID7-1.jpg',1,2,NULL),\n    (8,'Microsoft Surface Pro X 1876 13 Inches Laptop',699,'PID8-1.jpg',1,2,NULL),\n    (9,'Microsoft Surface GO 2 STQ-00013 10.1\" (26.54 cms) Laptop',549,'PID9-1.jpg',1,2,NULL),\n    (10,'Microsoft Surface Laptop 3 Intel Core i5 10th Gen 13.5\" (34.29 cms) Touchscreen Laptop',349,'PID10-1.jpg',1,2,NULL),\n    (11,'ASUS VivoBook 15 (2021), 15.6-inch (39.62 cm) HD, Dual Core Intel Celeron N4020, Thin and Light Laptop',429,'PID11-1.jpg',2,2,NULL),\n    (12,'ASUS Vivobook S15 OLED 2022, 15.6\" 39.62 cm FHD OLED Laptop',899,'PID12-1.jpg',2,2,NULL),\n    (13,'ASUS TUF Gaming F15 (2021), 15.6\" (39.62 cms) FHD 144Hz Laptop',1299,'PID13-1.jpg',2,2,NULL),\n    (14,'ASUS TUF Gaming A15, 15.6\" (39.62 cms) FHD 144Hz Laptop',1199,'PID14-1.jpg',2,2,NULL),\n    (15,'ASUS Vivobook 14 OLED, 12th Gen Intel Core i3-1215U Laptop',1099,'PID15-1.jpg',2,2,NULL),\n    (16,'Dell 24\" All-in-One 5410 Core I3 12th Gen desktop',1199,'PID16-1.jpg',3,3,NULL),\n    (17,'Dell Optiplex 380 17 inch (43.18 cms) Desktop',1399,'PID17-1.jpg',3,3,NULL),\n    (18,'Dell Optiplex 19 inch, All in One Desktop Set',899,'PID18-1.jpg',3,3,NULL),\n    (19,'Acer EK220Q 54.61 cm Full HD VA Panel Backlit LED Monitor',899,'PID19-1.jpg',4,5,NULL),\n    (20,'Acer Ha220Q 21.5 Inch (54.61 cm) LCD Monitor',899,'PID20-1.jpg',4,5,NULL),\n    (21,'Acer Aspire C24 23.8 inch Full HD IPS All in One Desktop',1399,'PID21-1.jpg',4,3,NULL),\n    (22,'ASUS ROG Phone 2',699,'PID22-1.jpg',2,4,NULL),\n    (23,'Asus Zenfone 5Z ',699,'PID23-1.jpg',2,4,NULL),\n    (24,'ASUS 6Z',859,'PID24-1.jpg',2,4,NULL),\n    (25,'OnePlus Nord CE 2 Lite',679,'PID25-1.jpg',5,4,NULL),\n    (26,'OnePlus Nord 2T ',578,'PID26-1.jpg',5,4,NULL),\n    (27,'OnePlus 10R',999,'PID27-1.jpg',5,4,NULL),\n    (28,'OnePlus 10T',999,'PID28-1.jpg',5,4,NULL),\n    (29,'Xbox Wireless Controller Forza Horizon 5 Limited Edition',299,'PID29-1.jpg',1,1,NULL),\n    (30,'Xbox Wireless Controller Aqua Shift Special Edition',299,'PID30-1.jpg',1,1,NULL),\n    (31,'Xbox Wireless Controller Daystrike Camo Special Edition',299,'PID31-1.jpg',1,1,NULL),\n    (32,'Xbox Wireless Controller Black',299,'PID32-1.jpg',1,1,NULL),\n    (33,'Xbox Wireless Controller White',299,'PID33-1.jpg',1,1,NULL),\n    (34,'Lenovo IdeaCentre AIO 3 21.5 inches Full HD IPS All-in-One Desktop',899,'PID34-1.jpg',7,3,NULL),\n    (35,'HP AIO PC 12th Gen Intel Core i3-1215U desktop',899,'PID35-1.jpg',6,3,NULL),\n    (36,'HP All-in-One 12th Gen Intel Core i3 desktop',899,'PID36-1.jpg',6,3,NULL),\n    (37,'Dell Optiplex Desktop',899,'PID37-1.jpg',3,3,NULL),\n    (38,'Dell Optiplex 19\" All-in-One Desktop Computer Set',899,'PID38-1.jpg',3,3,NULL),\n    (39,'HP AIO Intel Celeron J4025-54.6cm(21.5 inch) FHD desktop',899,'PID39-1.jpg',6,3,NULL),\n    (40,'Samsung Galaxy M13',999,'PID40-1.jpg',8,4,NULL),\n    (41,'SAMSUNG Galaxy F13',999,'PID41-1.jpg',8,4,NULL),\n    (42,'Samsung Galaxy S20 FE 5G',1899,'PID42-1.jpg',8,4,NULL),\n    (43,'Lenovo D-Series 54.61 cm (22 inch) FHD VA 3-Side NearEdgeless Monitor',799,'PID43-1.jpg',7,5,NULL),\n    (44,'ViewSonic VA2432-MH 23.8 inches Monitor',799,'PID44-1.jpg',9,5,NULL),\n    (45,'Samsung 24-inch(60.46cm) FHD Monitor',799,'PID45-1.jpg',8,5,NULL),\n    (46,'Acer EK220Q 54.61 cm Full HD VA Panel Backlit LED Monitor',799,'PID46-1.jpg',4,5,NULL),\n    (47,'Acer Ha220Q 21.5 Inch (54.61 Cm) LCD Monitor',799,'PID47-1.jpg',4,5,NULL),\n    (48,'Acer HA270 27 Inch (68.58 cm) Full HD 1920 x 1080 IPS Ultra Slim Monitor',799,'PID48-1.jpg',4,5,NULL),\n    (49,'Zebronics Zeb-V16HD LED Monitor',799,'PID49-1.jpg',10,5,NULL),\n    (50,'ZEBRONICS Zeb-V19Hd 18.5 Inch (46.99 Cm) Led Monitor',799,'PID50-1.jpg',10,5,NULL)\n"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Program.cs",
    "content": "DependencyInjection.ConfigureApp();"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/ServiceDependencies/tailwind-traders-product - Web Deploy/profile.arm.json",
    "content": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"metadata\": {\n    \"_dependencyType\": \"compute.appService.windows\"\n  },\n  \"parameters\": {\n    \"resourceGroupName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-sandbox-rg\",\n      \"metadata\": {\n        \"description\":\n          \"Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking.\"\n      }\n    },\n    \"resourceGroupLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"eastus\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support.\"\n      }\n    },\n    \"resourceName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-product\",\n      \"metadata\": {\n        \"description\": \"Name of the main resource to be created by this template.\"\n      }\n    },\n    \"resourceLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"[parameters('resourceGroupLocation')]\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource. By default use resource group's location, unless the resource provider is not supported there.\"\n      }\n    }\n  },\n  \"variables\": {\n    \"appServicePlan_name\":\n      \"[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n    \"appServicePlan_ResourceId\":\n      \"[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]\"\n  },\n  \"resources\": [\n    {\n      \"type\": \"Microsoft.Resources/resourceGroups\",\n      \"name\": \"[parameters('resourceGroupName')]\",\n      \"location\": \"[parameters('resourceGroupLocation')]\",\n      \"apiVersion\": \"2019-10-01\"\n    },\n    {\n      \"type\": \"Microsoft.Resources/deployments\",\n      \"name\":\n        \"[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n      \"resourceGroup\": \"[parameters('resourceGroupName')]\",\n      \"apiVersion\": \"2019-10-01\",\n      \"dependsOn\": [\n        \"[parameters('resourceGroupName')]\"\n      ],\n      \"properties\": {\n        \"mode\": \"Incremental\",\n        \"template\": {\n          \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n          \"contentVersion\": \"1.0.0.0\",\n          \"resources\": [\n            {\n              \"location\": \"[parameters('resourceLocation')]\",\n              \"name\": \"[parameters('resourceName')]\",\n              \"type\": \"Microsoft.Web/sites\",\n              \"apiVersion\": \"2015-08-01\",\n              \"tags\": {\n                \"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]\": \"empty\"\n              },\n              \"dependsOn\": [\n                \"[variables('appServicePlan_ResourceId')]\"\n              ],\n              \"kind\": \"app\",\n              \"properties\": {\n                \"name\": \"[parameters('resourceName')]\",\n                \"kind\": \"app\",\n                \"httpsOnly\": true,\n                \"reserved\": false,\n                \"serverFarmId\": \"[variables('appServicePlan_ResourceId')]\",\n                \"siteConfig\": {\n                  \"metadata\": [\n                    {\n                      \"name\": \"CURRENT_STACK\",\n                      \"value\": \"dotnetcore\"\n                    }\n                  ]\n                }\n              },\n              \"identity\": {\n                \"type\": \"SystemAssigned\"\n              }\n            },\n            {\n              \"location\": \"[parameters('resourceLocation')]\",\n              \"name\": \"[variables('appServicePlan_name')]\",\n              \"type\": \"Microsoft.Web/serverFarms\",\n              \"apiVersion\": \"2015-08-01\",\n              \"sku\": {\n                \"name\": \"S1\",\n                \"tier\": \"Standard\",\n                \"family\": \"S\",\n                \"size\": \"S1\"\n              },\n              \"properties\": {\n                \"name\": \"[variables('appServicePlan_name')]\"\n              }\n            }\n          ]\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/ServiceDependencies/tailwind-traders-product - Web Deploy1/profile.arm.json",
    "content": "{\n  \"$schema\": \"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"metadata\": {\n    \"_dependencyType\": \"compute.appService.windows\"\n  },\n  \"parameters\": {\n    \"resourceGroupName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-sandbox-rg\",\n      \"metadata\": {\n        \"description\":\n          \"Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking.\"\n      }\n    },\n    \"resourceGroupLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"eastus\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support.\"\n      }\n    },\n    \"resourceName\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"contoso-traders-product\",\n      \"metadata\": {\n        \"description\": \"Name of the main resource to be created by this template.\"\n      }\n    },\n    \"resourceLocation\": {\n      \"type\": \"string\",\n      \"defaultValue\": \"[parameters('resourceGroupLocation')]\",\n      \"metadata\": {\n        \"description\":\n          \"Location of the resource. By default use resource group's location, unless the resource provider is not supported there.\"\n      }\n    }\n  },\n  \"variables\": {\n    \"appServicePlan_name\":\n      \"[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n    \"appServicePlan_ResourceId\":\n      \"[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]\"\n  },\n  \"resources\": [\n    {\n      \"type\": \"Microsoft.Resources/resourceGroups\",\n      \"name\": \"[parameters('resourceGroupName')]\",\n      \"location\": \"[parameters('resourceGroupLocation')]\",\n      \"apiVersion\": \"2019-10-01\"\n    },\n    {\n      \"type\": \"Microsoft.Resources/deployments\",\n      \"name\":\n        \"[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]\",\n      \"resourceGroup\": \"[parameters('resourceGroupName')]\",\n      \"apiVersion\": \"2019-10-01\",\n      \"dependsOn\": [\n        \"[parameters('resourceGroupName')]\"\n      ],\n      \"properties\": {\n        \"mode\": \"Incremental\",\n        \"template\": {\n          \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n          \"contentVersion\": \"1.0.0.0\",\n          \"resources\": [\n            {\n              \"location\": \"[parameters('resourceLocation')]\",\n              \"name\": \"[parameters('resourceName')]\",\n              \"type\": \"Microsoft.Web/sites\",\n              \"apiVersion\": \"2015-08-01\",\n              \"tags\": {\n                \"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]\": \"empty\"\n              },\n              \"dependsOn\": [\n                \"[variables('appServicePlan_ResourceId')]\"\n              ],\n              \"kind\": \"app\",\n              \"properties\": {\n                \"name\": \"[parameters('resourceName')]\",\n                \"kind\": \"app\",\n                \"httpsOnly\": true,\n                \"reserved\": false,\n                \"serverFarmId\": \"[variables('appServicePlan_ResourceId')]\",\n                \"siteConfig\": {\n                  \"metadata\": [\n                    {\n                      \"name\": \"CURRENT_STACK\",\n                      \"value\": \"dotnetcore\"\n                    }\n                  ]\n                }\n              },\n              \"identity\": {\n                \"type\": \"SystemAssigned\"\n              }\n            },\n            {\n              \"location\": \"[parameters('resourceLocation')]\",\n              \"name\": \"[variables('appServicePlan_name')]\",\n              \"type\": \"Microsoft.Web/serverFarms\",\n              \"apiVersion\": \"2015-08-01\",\n              \"sku\": {\n                \"name\": \"S1\",\n                \"tier\": \"Standard\",\n                \"family\": \"S\",\n                \"size\": \"S1\"\n              },\n              \"properties\": {\n                \"name\": \"[variables('appServicePlan_name')]\"\n              }\n            }\n          ]\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Properties/launchSettings.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/launchsettings.json\",\n  \"profiles\": {\n    \"ContosoTraders.Api.Products\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"applicationUrl\": \"https://localhost:62300;http://localhost:62301\",\n      \"dotnetRunMessages\": true\n    },\n    \"Docker\": {\n      \"commandName\": \"Docker\",\n      \"launchBrowser\": true,\n      \"launchUrl\": \"{Scheme}://{ServiceHost}:{ServicePort}/swagger\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"publishAllPorts\": true,\n      \"useSSL\": true\n    }\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Products/Usings.cs",
    "content": "﻿global using MediatR;\nglobal using Microsoft.AspNetCore.Mvc;\nglobal using ContosoTraders.Api.Core;\nglobal using ContosoTraders.Api.Core.Services.Interfaces;\nglobal using ContosoTraders.Api.Core.Controllers;\nglobal using ContosoTraders.Api.Core.Models.Implementations.Dto;\nglobal using ContosoTraders.Api.Core.Requests.Definitions;"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# Azure Functions localsettings file\nlocal.settings.json\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n#*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/ContosoTraders.Api.Profiles.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net7.0</TargetFramework>\n    <AzureFunctionsVersion>v4</AzureFunctionsVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.Azure.WebJobs.Extensions.OpenApi\" Version=\"1.5.1\" />\n    <PackageReference Include=\"Microsoft.NET.Sdk.Functions\" Version=\"4.1.3\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\ContosoTraders.Api.Core\\ContosoTraders.Api.Core.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Update=\"host.json\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n    <None Update=\"local.settings.json\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n      <CopyToPublishDirectory>Never</CopyToPublishDirectory>\n    </None>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Controllers/ProfilesController.cs",
    "content": "using System.IO;\nusing System.Net;\nusing System.Threading.Tasks;\nusing MediatR;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Azure.WebJobs;\nusing Microsoft.Azure.WebJobs.Extensions.Http;\nusing Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;\nusing Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.OpenApi.Models;\nusing Newtonsoft.Json;\n\nnamespace ContosoTraders.Api.Profiles.Controllers;\n\npublic class ProfilesController : ContosoTradersControllerBase\n{\n    private readonly ILogger<ProfilesController> _logger;\n\n    public ProfilesController(IMediator mediator, ILogger<ProfilesController> log) : base(mediator)\n    {\n        _logger = log;\n    }\n\n    [FunctionName(\"Function1\")]\n    [OpenApiOperation(\"Run\", \"name\")]\n    [OpenApiSecurity(\"function_key\", SecuritySchemeType.ApiKey, Name = \"code\", In = OpenApiSecurityLocationType.Query)]\n    [OpenApiParameter(\"name\", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = \"The **Name** parameter\")]\n    [OpenApiResponseWithBody(HttpStatusCode.OK, \"text/plain\", typeof(string), Description = \"The OK response\")]\n    public async Task<IActionResult> Run(\n        [HttpTrigger(AuthorizationLevel.Function, \"get\", \"post\", Route = null)]\n        HttpRequest req)\n    {\n        _logger.LogInformation(\"C# HTTP trigger function processed a request.\");\n\n        string name = req.Query[\"name\"];\n\n        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();\n        dynamic data = JsonConvert.DeserializeObject(requestBody);\n        name = name ?? data?.name;\n\n        var responseMessage = string.IsNullOrEmpty(name)\n            ? \"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.\"\n            : $\"Hello, {name}. This HTTP triggered function executed successfully.\";\n\n        return new OkObjectResult(responseMessage);\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Properties/serviceDependencies.json",
    "content": "{\n  \"dependencies\": {\n    \"appInsights1\": {\n      \"type\": \"appInsights\"\n    },\n    \"storage1\": {\n      \"type\": \"storage\",\n      \"connectionId\": \"AzureWebJobsStorage\"\n    }\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Properties/serviceDependencies.local.json",
    "content": "{\n  \"dependencies\": {\n    \"appInsights1\": {\n      \"type\": \"appInsights.sdk\"\n    },\n    \"storage1\": {\n      \"type\": \"storage.emulator\",\n      \"connectionId\": \"AzureWebJobsStorage\"\n    }\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/Usings.cs",
    "content": "﻿global using ContosoTraders.Api.Core.Controllers;"
  },
  {
    "path": "src/ContosoTraders.Api.Profiles/host.json",
    "content": "{\n  \"version\": \"2.0\",\n  \"logging\": {\n    \"applicationInsights\": {\n      \"samplingSettings\": {\n        \"isEnabled\": true,\n        \"excludedTypes\": \"Request\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n.env.playwright.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\nappsettings.Development.json\n\n# Playwright tests\n/test-results/\n/playwright-report/\n/playwright/.cache/\n/test-results/\n/playwright-report/\n/playwright/.cache/\n/.auth/\n/playwright-report-junit\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/README.md",
    "content": "# Getting Started with Create React App\n\nThis project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Available Scripts\n\nIn the project directory, you can run:\n\n### `npm start`\n\nRuns the app in the development mode.\\\nOpen [http://localhost:3000](http://localhost:3000) to view it in your browser.\n\nThe page will reload when you make changes.\\\nYou may also see any lint errors in the console.\n\n### `npm test`\n\nLaunches the test runner in the interactive watch mode.\\\nSee the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.\n\n### `npm run build`\n\nBuilds the app for production to the `build` folder.\\\nIt correctly bundles React in production mode and optimizes the build for the best performance.\n\nThe build is minified and the filenames include the hashes.\\\nYour app is ready to be deployed!\n\nSee the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.\n\n### `npm run eject`\n\n**Note: this is a one-way operation. Once you `eject`, you can't go back!**\n\nIf you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.\n\nInstead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.\n\nYou don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.\n\n## Learn More\n\nYou can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).\n\nTo learn React, check out the [React documentation](https://reactjs.org/).\n\n### Code Splitting\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)\n\n### Analyzing the Bundle Size\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)\n\n### Making a Progressive Web App\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)\n\n### Advanced Configuration\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)\n\n### Deployment\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)\n\n### `npm run build` fails to minify\n\nThis section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/package.json",
    "content": "{\n  \"name\": \"website-ui\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@mui/icons-material\": \"^5.13.7\",\n    \"@mui/material\": \"^5.14.0\",\n    \"axios\": \"^1.6.0\",\n    \"formik\": \"^2.4.4\",\n    \"history\": \"^5.3.0\",\n    \"msal\": \"^1.4.18\",\n    \"mui-file-dropzone\": \"^4.0.2\",\n    \"qs\": \"^6.11.2\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-material-ui-carousel\": \"^3.4.2\",\n    \"react-redux\": \"^8.1.1\",\n    \"react-router-dom\": \"^6.14.1\",\n    \"react-scripts\": \"5.0.1\",\n    \"redux\": \"^4.2.1\",\n    \"web-vitals\": \"^3.3.2\",\n    \"yup\": \"^1.2.0\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.43.1\",\n    \"@types/node\": \"^18.11.18\",\n    \"babel-plugin-macros\": \"^3.1.0\",\n    \"csv-parse\": \"^5.5.0\",\n    \"sass\": \"^1.63.6\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"overrides\": {\n    \"typescript\": \"^5.1.6\"\n  }\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/playwright.config.ts",
    "content": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\nrequire('dotenv').config({ path: '.env.playwright.local' })\n\nexport default defineConfig({\n  testDir: './tests',\n  /* Maximum time one test can run for. */\n  timeout: 80 * 1000,\n  expect: {\n    // Account for pixel difference between login being enabled/disabled\n    toHaveScreenshot: { maxDiffPixels: 100 }\n  },\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: [\n    ['list'],\n    ['html'],\n    [\"junit\", { outputFile: \"playwright-report-junit/e2e-junit-results.xml\" }],\n    ...(process.env.CI ? [['github'] as ['github']] : []),\n  ],\n\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* https://github.com/microsoft/playwright/issues/14440 - TODO - Investigate later */\n    ignoreHTTPSErrors: true,\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    baseURL: process.env.REACT_APP_BASEURLFORPLAYWRIGHTTESTING || 'https://production.contosotraders.com/',\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n    video: 'on-first-retry',\n  },\n\n  projects: [\n    // Setup project\n    { name: 'setup', testMatch: /.*\\.setup\\.ts/ },\n    // Test project that requires authentication\n    {\n      name: 'authenticated',\n      testMatch: /.account\\.ts/,\n      use: {\n        ...devices['Desktop Chrome'],\n        // Use prepared auth state.\n        storageState: '.auth/user.json',\n      },\n      dependencies: ['setup'],\n    },\n    // Test projects that don't require authentication\n    {\n      name: 'chromium',\n      use: {\n        ...devices['Desktop Chrome']\n      },\n      testIgnore: /api/,\n    },\n    {\n      name: 'firefox',\n      use: {\n        ...devices['Desktop Firefox']\n      },\n      testIgnore: /api/,\n    },\n    {\n      name: 'webkit',\n      use: {\n        ...devices['Desktop Safari']\n      },\n      testIgnore: /api/,\n    },\n    {\n      name: 'api',\n      testMatch: 'tests/api/**/*.spec.ts',\n    }\n  ],\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#ffffff</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/index.html",
    "content": "﻿<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n    />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"180x180\"\n      href=\"%PUBLIC_URL%/apple-touch-icon.png\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"32x32\"\n      href=\"%PUBLIC_URL%/favicon-32x32.png\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"16x16\"\n      href=\"%PUBLIC_URL%/favicon-16x16.png\"\n    />\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/site.webmanifest\" />\n    <link\n      rel=\"mask-icon\"\n      href=\"%PUBLIC_URL%/safari-pinned-tab.svg\"\n      color=\"#2f4b66\"\n    />\n    <meta name=\"msapplication-TileColor\" content=\"#ffffff\" />\n    <meta name=\"theme-color\" content=\"#ffffff\" />\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css\"\n      integrity=\"sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2\"\n      crossorigin=\"anonymous\"\n    />\n    <!--\n      manifest.json provides metadata used when your web app is added to the\n      homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Poppins&family=Roboto&display=swap\" rel=\"stylesheet\">\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>Contoso Traders</title>\n  </head>\n  <body>\n    <noscript> You need to enable JavaScript to run this app. </noscript>\n    <div id=\"root\"></div>\n\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/manifest.json",
    "content": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/public/site.webmanifest",
    "content": "{\n    \"name\": \"Contoso Traders\",\n    \"short_name\": \"Contoso Traders\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-256x256.png\",\n            \"sizes\": \"256x256\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.css",
    "content": ".App {\n  text-align: center;\n}\n\n.App-logo {\n  height: 40vmin;\n  pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .App-logo {\n    animation: App-logo-spin infinite 20s linear;\n  }\n}\n\n.App-header {\n  background-color: #282c34;\n  min-height: 100vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  font-size: calc(10px + 2vmin);\n  color: white;\n}\n\n.App-link {\n  color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.js",
    "content": "import React, { Fragment, useCallback, useEffect, useState } from \"react\";\nimport { Route, Routes } from \"react-router-dom\";\nimport { connect } from \"react-redux\";\n// import { CartService } from \"./services\";\n// import Meeting from './pages/home/components/videoCall/Meeting';\n\nimport Header from \"./components/header/header\";\nimport HeaderMessage from \"./components/header/headerMessage\";\nimport Appbar from \"./components/header/appbar\";\nimport Footer from \"./components/footer/footer\";\nimport {\n  Home,\n  List,\n  // MyCoupons,\n  Detail,\n  SuggestedProductsList,\n  Profile,\n  // ShoppingCart,\n  Arrivals,\n  RefundPolicy,\n  TermsOfService,\n  AboutUs,\n  ErrorPage,\n  Cart,\n} from \"./pages\";\n\n// import \"./i18n\";\nimport \"./main.scss\";\nimport warningIcon from './assets/images/original/Contoso_Assets/Icons/information_icon.svg'\nimport { useLocation } from \"react-router-dom\";\nimport { CartService } from \"./services\";\nimport { getCartQuantity } from \"./actions/actions\";\n\n\n  function App(props) {\n    const location = useLocation()\n    // const [shoppingCart, setShoppingCart] = useState([])\n    const [quantity, setQuantity] = useState(0)\n\n    const getQuantity = useCallback(async() => {\n      let quantity = 0;\n      //Show cart using API\n      if (props.userInfo.token) {\n        const shoppingcart = await CartService.getShoppingCart(\n          props.userInfo.token\n        );\n        // if (shoppingcart) {\n        //   setShoppingCart({ shoppingcart });\n        // }\n        quantity = shoppingcart.length;\n      }else{\n        let cart = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : [];\n        quantity = cart.length;\n      }\n      setQuantity(quantity);\n    },[props])\n    \n    useEffect(() => {\n      props.getCartQuantity(quantity)\n    }, [quantity, props]);\n\n    React.useEffect(() => {\n      getQuantity()\n    }, [getQuantity]);\n\n    React.useEffect(() => {\n        window.scrollTo(0, 0);\n    }, [location.pathname]);\n\n    return (\n      <div className={`App ${props.theme ? 'dark' : 'light' }`}>\n        <Fragment>\n          <div className=\"mainHeader\">\n            <HeaderMessage type=\"warning\" icon={warningIcon} message=\"This Is A Demo Store For Testing Purposes — No Orders Shall Be Fulfilled.\"/>\n            <Appbar />\n            {location.pathname === '/' || location.pathname === '/new-arrivals' ?\n              <Header/>\n              :\n              <div id=\"box\"></div>}\n          </div>\n          <Routes>\n            <Route exact path=\"/\" element={<Home/>} />\n            <Route exact path=\"/new-arrivals\" element={<Arrivals/>} />\n            {/* <Route exact path=\"/meeting\" element={Meeting} /> */}\n            <Route exact path=\"/list\" element={<List/>} />\n            <Route exact path=\"/list/:code\" element={<List/>} />\n            <Route\n              path=\"/suggested-products-list\"\n              element={<SuggestedProductsList/>}\n            />\n            <Route\n              path=\"/product/detail/:productId\"\n              element={<Detail/>}\n            />\n            <Route path=\"/refund-policy\" element={<RefundPolicy/>} />\n            <Route path=\"/terms-of-service\" element={<TermsOfService/>} />\n            <Route path=\"/about-us\" element={<AboutUs/>} />\n            {/* <PrivateRoute path=\"/coupons\" element={MyCoupons} /> */}\n            {props.userInfo.loggedIn === true ?\n            <>\n            <Route path=\"/profile/:page\" element={<Profile/>} />\n            </>:null}\n            <Route path=\"/cart\" element={<Cart/>}/>\n            {/* <PrivateRoute\n              path=\"/shopping-cart\"\n              element={ShoppingCart}\n              ShoppingCart={this.ShoppingCart}\n              quantity={this.state.quantity}\n            /> */}\n            <Route path=\"*\" element={<ErrorPage/>} />\n          </Routes>\n          <Footer />\n        </Fragment>\n      </div>\n    );\n  }\n// }\n\nconst mapStateToProps = (state) => { \n  return { \n    userInfo : state.login.userInfo,\n    theme :  state.login.theme\n  }\n};\nconst mapDispatchToProps = (dispatch) => ({\n  getCartQuantity: (value) => dispatch(getCartQuantity(value)),\n})\nexport default connect(mapStateToProps, mapDispatchToProps)(App);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/App.test.js",
    "content": "import { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () => {\n  render(<App />);\n  const linkElement = screen.getByText(/learn react/i);\n  expect(linkElement).toBeInTheDocument();\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/actions/actions.js",
    "content": "import { FORM_EMAIL, SAVE_USER, REMOVE_USER, THEME_CHANGE, GET_QUANTITY } from '../types/types';\n\nexport const textAction = email => ({\n    type: FORM_EMAIL,\n    email,\n});\n\nexport const submitAction = userInfo => ({\n    type: SAVE_USER,\n    userInfo\n});\n\nexport const clickAction = () => ({\n    type: REMOVE_USER,\n});\n\nexport const handleThemeChange = (value) => ({\n    type  : THEME_CHANGE,\n    value : value,\n    field : 'theme'\n});\n\nexport const getCartQuantity = (quantity) => ({\n    type : GET_QUANTITY,\n    quantity : quantity\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/Input/checkbox.js",
    "content": "import React from \"react\";\n\nimport PropTypes from \"prop-types\";\n\nconst Checkbox = ({ type = \"checkbox\", name, checked = false, onChange, code }) => (\n    <input type={type} name={name} checked={checked} onChange={onChange} id={code} />\n);\n\nCheckbox.propTypes = {\n    type: PropTypes.string,\n    name: PropTypes.string.isRequired,\n    checked: PropTypes.bool,\n    onChange: PropTypes.func.isRequired,\n    id: PropTypes.string,\n};\n\nexport default Checkbox;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/accordion.js",
    "content": "import React from \"react\";\nimport MuiAccordion from \"@mui/material/Accordion\";\nimport MuiAccordionSummary from \"@mui/material/AccordionSummary\";\nimport MuiAccordionDetails from \"@mui/material/AccordionDetails\";\nimport Typography from \"@mui/material/Typography\";\nimport description_off from \"../../assets/images/original/Contoso_Assets/product_page_assets/description_off_icon.svg\";\nimport description_on from \"../../assets/images/original/Contoso_Assets/product_page_assets/description_on_icon.svg\";\nimport './accordion.scss'\n\nconst Accordion = (MuiAccordion);\n\nconst AccordionSummary = (MuiAccordionSummary);\n\nconst AccordionDetails = (MuiAccordionDetails);\n\nexport default function CustomizedAccordions(props) {\n  const { accordionItems } = props\n  const [expanded, setExpanded] = React.useState(null);\n\n  const handleChange = (panel) => (event, newExpanded) => {\n    setExpanded(newExpanded ? panel : false);\n  };\n\n  return (\n    <div className=\"AccordionSection\">\n      {accordionItems.map((item, key) => {\n        return(\n          <Accordion\n            key={key}\n            square\n            expanded={expanded === item.name}\n            onChange={handleChange(item.name)}\n          >\n            <AccordionSummary\n              className=\"panelSummary\"\n              aria-controls=\"panel1d-content\"\n              id=\"panel1d-header\"\n              expandIcon={\n                expanded === item.name ? (\n                  <img src={description_on} className=\"descpicon\" alt=\"\"/>\n                ) : (\n                  <img src={description_off} className=\"descpicon\" alt=\"\"/>\n                )\n              }\n            >\n              <Typography>{item.title}</Typography>\n            </AccordionSummary>\n            <AccordionDetails>\n              {item.body}\n            </AccordionDetails>\n          </Accordion>\n        )\n      })}\n    </div>\n  );\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/accordion.scss",
    "content": ".MuiAccordion-root{\n    .panelSummary {\n      flex-direction: row-reverse;\n      // padding-left: 10px;\n      border: 1px solid rgba(112, 112, 112, 0.1);\n      border-right: none;\n      border-left: none;\n      .MuiAccordionSummary-content{\n        font-family: 'Heebo';\n        font-size: 16px;\n        margin: 23.5px 0;\n        margin-left: 10px;\n      }\n      .MuiIconButton-root{\n        padding-left: 0;\n      }\n    }\n    &:last-child{\n      .panelSummary {\n        border-bottom: none;\n      }\n    }\n  }\n.AccordionSection {\n    margin-top: 30px;\n    font-family: \"Heebo\";\n    font-size: 14px;\n    font-weight: 400;\n    .detail_feature p{\n      font-family: 'Heebo';\n      font-size: 14px;\n    }\n  }\n  \n  .AccordionSection .descpAttributes {\n    letter-spacing: -0.25px;\n    color: #878787;\n    opacity: 0.92;\n    padding: 4px 16px 4px 8px;\n  }\n  .AccordionSection .descpDetails {\n    letter-spacing: -0.25px;\n    color: #000000;\n    opacity: 0.92;\n    padding: 4px 0;\n  }\n  .AccordionSection .descpicon {\n    margin-right: 16px;\n  }\n  .AccordionSection .Offerslist {\n    margin-bottom: 11px;\n  }\n  .AccordionSection .discount_icon {\n    width: 16px;\n    height: 16px;\n    margin-right: 12px;\n  }\n  .AccordionSection .TClink {\n    text-decoration: none;\n    color: #2874f0;\n  }\n  \n  .MuiAccordionSummary-expandIcon.Mui-expanded {\n    transform: none !important;\n  }\n\n  //Responsive\n  @media screen and (min-width : 320px) and (max-width : 767px) {\n    .AccordionSection{\n        margin-top: 0;\n    }\n  }"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/accordion/sidebarAccordion.js",
    "content": "\nimport React, { useRef, createRef } from \"react\";\nimport MuiAccordion from \"@mui/material/Accordion\";\nimport MuiAccordionSummary from \"@mui/material/AccordionSummary\";\nimport MuiAccordionDetails from \"@mui/material/AccordionDetails\";\nimport Typography from \"@mui/material/Typography\";\nimport { Grid } from \"@mui/material\";\nimport description_off from '../../assets/images/original/Contoso_Assets/Icons/plus.png'\nimport description_on from '../../assets/images/original/Contoso_Assets/Icons/minus.png'\nimport { useParams } from \"react-router-dom\";\n\nconst Accordion = (MuiAccordion);\n\nconst AccordionSummary = (MuiAccordionSummary);\n\nconst AccordionDetails = (MuiAccordionDetails);\n\nexport default function SidebarAccordion(props) {\n  const [expanded, setExpanded] = React.useState(\"panel1\");\n  const { code } = useParams();\n  const dataType = props.id;\n  const checkRef = useRef([])\n  checkRef.current = props.data && props.data.map((_, i) => checkRef.current[i] ?? createRef());\n  // const [color, setColorState] = React.useState({ blue : true });\n  const [checkedItems, setCheckedItems] =  React.useState(new Map());\n  const handleChange = (panel) => (event, newExpanded) => {\n    setExpanded(newExpanded ? panel : false);\n  };\n  // const handleColorChange = (event) => {\n  //   setColorState({ ...color, [event.target.name]: event.target.checked });\n  // };\n  const handleChangeBrands = (e, dataType) => {\n    const item = e.target.name;\n    const isChecked = e.target.checked;\n    setCheckedItems(checkedItems.set(item, isChecked));\n    props.onFilterChecked(e, dataType);\n  };\n\n  React.useEffect(() => {\n    \n    if(code === 'all-products'){\n      props.data && props.data.forEach((item)=>{\n        setCheckedItems(new Map().set(`brand${item.id}`, false));\n      });\n      checkRef.current && checkRef.current.forEach((item)=>{\n        if(item.current.checked && item.current.checked === true){\n          item.current.checked = false;\n          let ele = item.current;\n          ele.target = item.current;\n          ele.target.name = item.current.name;\n          props.onFilterChecked(ele, dataType);\n        }\n      })      \n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [code]);\n\n  \n  return (\n    <div className=\"AccordionSection\">\n      <Accordion\n        square\n        expanded={expanded === \"panel1\"}\n        onChange={handleChange(\"panel1\")}\n      >\n        <AccordionSummary\n          className=\"panelSummary\"\n          aria-controls=\"panel1d-content\"\n          id=\"panel1d-header\"\n          expandIcon={\n            expanded === \"panel1\" ? (\n              <img src={description_on} className=\"descpicon\" alt=\"\"/>\n            ) : (\n              <img src={description_off} className=\"descpicon\" alt=\"\"/>\n            )\n          }\n        >\n          <Typography>Brand</Typography>\n        </AccordionSummary>\n        <AccordionDetails>\n          <Grid container spacing={2}>\n            {props.data && props.data.map((item,key) => (\n              <Grid key={key} item xs={12} className=\"descpAttributes\">\n                <input type='checkbox' \n                  className=\"MuiCheckbox-root\"\n                  ref={checkRef.current[key]} \n                  checked={checkedItems.get(`brand${item.id}`)} \n                  name={`brand${item.id}`} \n                  id={item.id}\n                  onChange={(e)=>{\n                    handleChangeBrands(e, dataType)\n                  }}\n                />\n                <label className={`label ${checkedItems.get(`brand${item.id}`) ? \"Mui-checked\" : \"\" }`}>{item.name}</label>\n                {/* <FormControlLabel\n                  control={\n                    <Checkbox\n                      inputRef={checkRef.current[key]}\n                      checked={checkedItems.get(item.name)}\n                      onChange={(e)=>{\n                        handleChangeBrands(e, dataType)\n                      }}\n                      name={`brand${item.id}`}\n                      color=\"primary\"\n                      code={`${item.code || item.id}`}\n                      id={item.id}\n                    />\n                  }\n                  label={item.name}\n                /> */}\n              </Grid>\n            ))}\n          </Grid>\n        </AccordionDetails>\n      </Accordion>\n    </div>\n  );\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/breadcrumb/breadcrumb.js",
    "content": "import React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport './breadcrumb.scss'\n\nconst Breadcrumb = ({parentPath, parentUrl, currentPath}) => {\n    return(\n        <div className=\"breadcrump\">\n            <p>\n                <b><Link to='/'>Home</Link> / </b>\n                {parentPath ? <b><Link to={parentUrl}>{parentPath}</Link> / </b> : null}\n                {currentPath ? <span>{currentPath}</span> : null}\n            </p>\n        </div>\n    );\n}\nexport default Breadcrumb;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/breadcrumb/breadcrumb.scss",
    "content": ".breadcrump {\n  padding: 24px 70px;\n\n  p {\n    margin: 0;\n    text-transform: capitalize;\n\n    span {\n      font-family: 'Heebo';\n      font-size: 14px;\n      font-weight: 400;\n    }\n\n    b {\n      font-family: 'Heebo';\n      font-size: 14px;\n      font-weight: 500;\n\n      a {\n        color: #000000;\n\n        &:hover {\n          color: #2974f0 !important;\n          text-decoration: none;\n        }\n      }\n    }\n  }\n}\n\n//Responsive\n@media screen and (min-width : 320px) and (max-width : 767px) {\n  .breadcrump {\n    padding: 12px 35px;\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/corousel/corousel.js",
    "content": "import React from 'react';\nimport Carousel from 'react-material-ui-carousel'\nimport { Button, Grid } from '@mui/material'\nimport './corousel.scss'\n\nexport default function Corousel(props)\n{\n    const { items } = props;\n\n    return (\n        <Carousel\n            navButtonsAlwaysVisible={true}\n            autoPlay={false}\n            navButtonsProps={{\n                style: {\n                    border: '1px solid #C4C4C4',\n                    background: 'rgba(255, 255, 255, 0.2)',\n                    color: '#000'\n                }\n            }}\n            indicatorContainerProps={{\n                style: {\n                    zIndex:'1',\n                    display: 'flex',\n                    position:'absolute',\n                    justifyContent:'center',\n                    bottom:'20px'\n                }\n        \n            }}\n            indicatorIconButtonProps={{\n                style: {\n                    color:'white',\n                    backgroundColor:'white',\n                    margin:'0 2px'\n                }\n        \n            }}\n            activeIndicatorIconButtonProps={{\n                style: {\n                    color:'#2874F0',\n                    backgroundColor:'#2874F0',\n                    margin:'0 2px'\n                }\n        \n            }}\n            >\n            {\n                items.map( (item, i) => <Item key={i} item={item} /> )\n            }\n        </Carousel>\n    )\n}\n\nfunction Item(props)\n{\n    return (\n        <div className=\"courousel-style\" style={{ backgroundImage: 'url('+props.item.bg+')'}}>\n            <Grid container spacing={3}>\n                <Grid item lg={6} xs={12} className=\"BannerGrid\">\n                    <div className=\"BannerHeading\">\n                        {props.item.name}\n                    </div>\n                    <div className=\"BannerContent\">\n                        {props.item.description}\n                    </div>\n                    <div className=\"BannerButtondiv\">\n                    {props.item.buttons && props.item.buttons.map((btn, key) => {\n                        return (\n                            <Button\n                                key={key}\n                                aria-controls=\"customized-menu\"\n                                aria-haspopup=\"true\"\n                                variant=\"contained\"\n                                color={btn.color}\n                                className={`box-shadow-0 text-transform-capitalize fw-regular ${btn.class}`}\n                                endIcon={btn.endIcon}\n                                size=\"large\"\n                                onClick={btn.onClickFn}\n                            >\n                                {btn.title}\n                            </Button>\n                        )\n                    })}\n                    </div>\n                </Grid>\n                <Grid item xs={6}>\n                </Grid>\n            </Grid>\n        </div>\n    )\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/corousel/corousel.scss",
    "content": ".courousel-style {\n  padding: 70px;\n  /* background-image: url('./assets/images/original/Contoso_Assets/Slider_section/hero_banner.jpg') !important; */\n  background-repeat: no-repeat !important;\n  background-size: cover !important;\n  background-position: right !important;\n  min-height: 594px;\n}\n\n.courousel-style {\n  background: #000000;\n  cursor: pointer;\n  max-height: 594px;\n}\n\n.courousel-style .BannerGrid {\n  padding: 100px 100px 0px 70px !important;\n}\n\n.courousel-style .BannerHeading {\n  text-align: left;\n  font-family: var(--fontInter) !important;\n  font-size: 34px !important;\n  font-weight: 600 !important;\n  color: #ffffff;\n  opacity: 1;\n  padding: 0px 0px 0px 0px;\n  text-transform: capitalize;\n  letter-spacing: -1.59px;\n}\n\n.courousel-style .BannerContent {\n  text-align: left;\n  font-family: var(--fontHeebo) !important;\n  font-size: 16px !important;\n  font-weight: 400 !important;\n  color: #ffffff;\n  opacity: 1;\n  padding: 16px 0px 0px 0px;\n}\n\n.courousel-style .BannerButtondiv {\n  padding: 50px 0px 0px 0px;\n}\n\n.courousel-style .BannerButton1 {\n  max-width: 142px;\n  opacity: 1;\n  width: 224px;\n  height: 49px;\n  background: #2874F0 !important;\n  border-radius: 5px;\n  cursor: pointer;\n  text-align: center;\n  font-family: var(--fontHeebo) !important;\n  font-size: 16px !important;\n  font-weight: 500 !important;\n  color: #ffffff;\n  box-shadow: none;\n  margin-right: 24px;\n}\n\n.courousel-style .BannerButton2 {\n  max-width: 142px;\n  opacity: 1;\n  width: 224px;\n  height: 49px;\n  background: #666666 !important;\n  border-radius: 5px;\n  cursor: pointer;\n  text-align: center;\n  font-family: var(--fontHeebo) !important;\n  font-size: 16px !important;\n  font-weight: 500 !important;\n  letter-spacing: -0.36px;\n  color: #ffffff;\n  box-shadow: none;\n  margin-right: 24px;\n}\n\n/* Button test */\nbutton[aria-label='Previous'] {\n  margin-left: 70px;\n}\n\nbutton[aria-label='Next'] {\n  margin-right: 70px;\n}\n\n@media screen and (min-width : 320px) and (max-width : 767px) {\n  button[aria-label='Previous'] {\n    margin-left: 10px;\n  }\n\n  button[aria-label='Next'] {\n    margin-right: 10px;\n  }\n\n  .courousel-style {\n    padding: 70px 50px 0 80px;\n    background-position: unset !important;\n\n    .BannerGrid {\n      padding: 0 !important;\n    }\n\n    .BannerButton1,\n    .BannerButton2 {\n      margin: 10px 0 10px 0 !important;\n      width: 100% !important;\n      max-width: unset !important;\n    }\n  }\n}\n\n@media screen and (min-width : 768px) and (max-width : 899px) {\n  .courousel-style {\n      background-position: unset !important;\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/dropdowns/categories.js",
    "content": "import React from 'react';\nimport Button from '@mui/material/Button';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';\n// import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';\nimport { useNavigate } from 'react-router-dom';\nimport './categories.scss' \n\nconst StyledMenu = ((props) => (\n  <Menu\n    elevation={0}\n    getcontentanchorel={null}\n    anchorOrigin={{\n      vertical: 'bottom',\n      horizontal: 'center',\n    }}\n    transformOrigin={{\n      vertical: 'top',\n      horizontal: 'center',\n    }}\n    {...props}\n  />\n));\n\nconst StyledMenuItem = (MenuItem);\n\nexport default function Categories(props) {\n  const { categories } = props\n  const history = useNavigate();\n  const [anchorEl, setAnchorEl] = React.useState(null);\n\n  const handleClick = (event) => {\n    setAnchorEl(event.currentTarget);\n  };\n\n  const handleClose = () => {\n    setAnchorEl(null);\n  };\n\n  const redirectUrl = (url) => {\n    history(url)\n  }\n\n  return (\n    <div>\n      <Button\n        aria-controls=\"customized-menu\"\n        aria-haspopup=\"true\"\n        variant=\"contained\"\n        color=\"inherit\"\n        onClick={handleClick}\n        className=\"categories-btn box-shadow-0 text-transform-capitalize fw-regular\"\n        startIcon={<MenuIcon />}\n        endIcon={<ArrowDropDownIcon />}\n        size=\"large\"\n      >\n        {categories.title}\n      </Button>\n      <StyledMenu\n        id=\"customized-menu\"\n        anchorEl={anchorEl}\n        keepMounted\n        open={Boolean(anchorEl)}\n        onClose={handleClose}\n      >\n        {categories.categorylist.map((item, key) => {\n          return (\n            <StyledMenuItem onClick={() => redirectUrl(item.url)} key={key}>\n              <ListItemIcon>\n                <img src={item.img} alt=\"\" />\n              </ListItemIcon>\n              <ListItemText primary={item.name} />\n              <ListItemIcon className='justify-content-end'></ListItemIcon>\n            </StyledMenuItem>\n          )\n        })}\n      </StyledMenu>\n    </div>\n  );\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/dropdowns/categories.scss",
    "content": ".mainHeader .categories-btn {\n    min-width: 198px;\n    background-color: #F8F8F8 !important;\n    border: 1px solid #EFEFEF !important;\n}\n\n.dark .mainHeader .categories-btn {\n    color: #000000 !important;\n}\n\n#customized-menu .MuiPopover-paper {\n    left: 70px !important;\n}\n\n#customized-menu .MuiMenu-paper {\n    box-shadow: 0px 3px 26px #00000029;\n    border: 1px solid #E5E5E5;\n    border-radius: 8px;\n}\n\n#customized-menu .MuiPopover-paper .MuiTypography-root {\n    font-family: 'Poppins' !important;\n    font-size: 16px;\n}\n\n#customized-menu .MuiPopover-paper .MuiListItemIcon-root {\n    min-width: 40px;\n}\n\n#customized-menu .MuiPopover-paper ul li {\n    min-width: 341px;\n    padding: 12px 19px;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/footer/footer.js",
    "content": "import React, { useCallback } from 'react'\nimport { Link } from 'react-router-dom';\nimport { Grid } from '@mui/material';\n\nimport phoneLogo from '../../assets/images/original/Contoso_Assets/Icons/telephone_icon.svg'\nimport emailLogo from '../../assets/images/original/Contoso_Assets/Icons/email_icon.svg'\nimport { ReactComponent as Logo } from '../../assets/images/logo-horizontal.svg';\nimport './footer.scss'\n\nconst Footer = () => {\n    const getLocation = useCallback(() => {\n        if (navigator.geolocation) {\n            navigator.geolocation.getCurrentPosition(showPosition);\n        } else {\n            setStatus(\"Geolocation is not supported by this browser.\");\n        }\n    }, []);\n    React.useEffect(() => {\n        getLocation();\n    }, [getLocation]);\n    const [lat, setLat] = React.useState(null);\n    const [lng, setLng] = React.useState(null);\n    const [status, setStatus] = React.useState(null);\n    const [location, setLocation] = React.useState(null);\n\n\n    function showPosition(position) {\n        setLat(position.coords.latitude);\n        setLng(position.coords.longitude);\n        let point = position.coords.latitude + ',' + position.coords.longitude\n        const geoApiUrlForAddress = `${process.env.REACT_APP_GEOAPIBASEURL}/Locations/${point}?key=${process.env.REACT_APP_BINGMAPSKEY}`;\n        fetch(geoApiUrlForAddress)\n            .then(res => res.json())\n            .then(dat => {\n                setStatus(null);\n                let address = dat.resourceSets[0].resources[0].address.locality + ', ' + dat.resourceSets[0].resources[0].address.countryRegion;\n                setLocation(address)\n            })\n    }\n\n\n    return (\n        <div className='footer-container'>\n            <Grid container className='footer-grid-container'>\n                <Grid item lg={4} xs={12} className='section-1'>\n                    <Link to=\"/\">\n                        <Logo />\n                    </Link>\n                    <p className='mt-2 text-justify'>\n                        Contoso Traders is an e-commerce platform that specializes in electronic items. Our website offers a wide range of electronics, including smartphones, laptops, and other popular gadgets.\n                        We pride ourselves on providing high-quality products at competitive prices, and our dedicated customer service team is always on hand to assist with any queries or concerns. With fast and secure shipping, convenient payment options, and a user-friendly interface, Contoso Traders is the perfect place to shop for all your electronic needs.\n                    </p>\n                </Grid>\n                <Grid item lg={2} md={3} sm={6} xs={12} className='section-2'>\n                    <ul>\n                        <li className='main-element'>Catalog</li>\n                        <li className='list-element'><Link to='/list/all-products'>All Products</Link></li>\n                        <li className='list-element'><Link to='/list/controllers'>Controllers</Link></li>\n                        <li className='list-element'><Link to='/list/laptops'>Laptops</Link></li>\n                        <li className='list-element'><Link to='/list/monitors'>Monitors</Link></li>\n                    </ul>\n                </Grid>\n                <Grid item lg={2} md={3} sm={6} xs={12} className='section-3'>\n                    <ul>\n                        <li className='main-element'>Legals</li>\n                        <li className='list-element'><Link to='/terms-of-service'>Terms of Service</Link></li>\n                        <li className='list-element'><Link to='/refund-policy'>Refund Policy</Link></li>\n                        <li className='list-element'><Link to='/about-us'>About Us</Link></li>\n                    </ul>\n                </Grid>\n                <Grid item lg={4} md={6} xs={12} className='section-4'>\n                    <ul>\n                        <li className='main-element'>Contact us</li>\n                        <li className='list-element'>Feel free to get in touch with us via phone or send us a message</li>\n                        <li>\n                            <div className='contact-div' >\n                                <div className='logo-div' >\n                                    <img src={phoneLogo} alt='phone-logo' />\n                                </div>\n                                <div className='list-element'>\n                                    {' '}+123456768910\n                                </div>\n                            </div>\n                        </li>\n                        <li>\n                            <div className='contact-div' >\n                                <div className='logo-div' >\n                                    <img src={emailLogo} alt='email-logo' />\n                                </div>\n                                <div className='list-element'>\n                                    {' '}support@contosotraders.com\n                                </div>\n                            </div>\n                        </li>\n                    </ul>\n                    <ul>\n                        <li className='main-element'>Your Current location</li>\n                        {status && <li id=\"status\" className='list-element'>{status}</li>}\n                        {lat && <input id=\"latitude\" value={lat} type=\"hidden\" />}\n                        {lng && <input id=\"longitude\" value={lng} type=\"hidden\" />}\n                        {location && <address id=\"current-location\">{location}</address>}\n                        {lat && lng ?\n                            <div id=\"mapviewer\" className='w-100'><iframe title='geolocation' id=\"map\" scrolling=\"no\" width=\"400\" height=\"200\" frameBorder=\"0\" src={`https://www.bing.com/maps/embed/?h=200&w=400&cp=${lat}~${lng}&lvl=10`}></iframe></div>\n                            : null}\n                    </ul>\n                </Grid>\n            </Grid>\n        </div>\n    )\n}\n\nexport default Footer"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/footer/footer.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains all styles related to the footer of the site/application.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n.foo {\n    background-color: $color-background-medium;\n    display: flex;\n    flex-wrap: wrap;\n    padding: $padding-l;\n\n    &__content {\n        align-items: center;\n        display: flex;\n        flex-direction: column;\n        margin-left: auto;\n        margin-right: auto;\n        width: 100%;\n\n        @media (min-width: $mq-s) {\n            width: $grid-max-width-s;\n        }\n\n        @media (min-width: $mq-m) {\n            width: $grid-max-width-m;\n        }\n\n        @media (min-width: $mq-l) {\n            width: $grid-max-width-l;\n        }\n\n        @media (min-width: $mq-xl) {\n            width: $grid-max-width-xl;\n        }\n    }\n\n    &__disclaimer {\n        margin-top: $margin-s;\n    }\n\n    &-text {\n        @include text-xs();\n        color: $color-text-primary;\n        margin-top: $margin-m;\n        text-align: center;\n        margin-bottom: 0;\n    }\n\n    svg {\n        fill: $color-primary;\n        display: block;\n        margin-bottom: $margin-m;\n    }\n\n    &__links {\n        margin-top: $margin-m;\n        display: flex;\n        flex-direction: column;\n        flex-wrap: wrap;\n        text-align: center;\n\n        @media (min-width: $mq-m) {\n            margin-top: $margin-s;\n            flex-direction: row;\n        }\n    }\n\n    &__link {\n        @include text-s();\n        color: $color-text-primary;\n        font-weight: $font-weight-medium;\n        white-space: nowrap;\n\n        &:not(:last-of-type) {\n            @media (min-width: $mq-m) {\n                margin-right: $margin-l;\n            }\n        }\n    }\n}\n\n/* sha********************* */\n.footer-container {\n    padding: 60px 70px;\n    font-size: smaller;\n}\n\n.footer-container .MuiGrid-container {\n    justify-content: space-around;\n}\n\n.footer-container .footer-grid-container ul {\n    list-style: none;\n}\n\n.footer-container .footer-grid-container li {\n    padding-bottom: 20px;\n}\n\n.footer-container .footer-grid-container li a {\n    color: #302F2F;\n    text-decoration: none !important;\n}\n\n.footer-container .footer-grid-container p {\n    font-family: 'Roboto' !important;\n    font-size: 16px !important;\n    color: #302F2F !important;\n}\n\n.footer-container .section-2 {\n    padding-left: 20px;\n}\n\n.footer-container .section-3 {\n    padding-left: 60px;\n}\n\n.footer-container .section-4 {\n    padding-left: 100px;\n    float: right;\n}\n\n.footer-container .contact-div {\n    display: inline;\n}\n\n.footer-container .logo-div {\n    float: left;\n    padding-right: 5px;\n}\n\n.footer-container .list-element {\n    font-family: 'Roboto' !important;\n    font-size: 14px !important;\n    color: #302F2F !important;\n}\n\n.footer-container .main-element {\n    font-family: 'Poppins' !important;\n    font-size: 14px !important;\n    color: #302F2F !important;\n    font-weight: 600;\n}\n\n//Responsive\n@media screen and (min-width : 320px) and (max-width : 767px) {\n\n\n    .footer-container {\n        padding: 30px 35px;\n\n        .section-2,\n        .section-3,\n        .section-4 {\n            padding: 0;\n\n            ul {\n                padding: 0;\n            }\n        }\n\n        #mapviewer {\n            iframe {\n                width: 100%;\n            }\n        }\n    }\n}\n\n@media screen and (min-width : 900px) and (max-width : 1350px) {\n    #mapviewer {\n        iframe {\n            width: 100%;\n        }\n    }\n}\n\n@media screen and (min-width : 768px) and (max-width : 899px) {\n    .footer-container {\n        padding: 30px 35px;\n\n        .section-2,\n        .section-3,\n        .section-4 {\n            padding: 0;\n            ul {\n                padding: 0;\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/appbar.js",
    "content": "import React, { useCallback, useEffect, useRef } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport { connect } from 'react-redux';\nimport { AppBar, InputAdornment, TextField, Button } from '@mui/material';\n//#region Uncomment below lines to run dark mode tests\n// import {FormGroup, FormControlLabel, Switch } from '@mui/material';\n//#endregion\nimport Toolbar from '@mui/material/Toolbar';\nimport IconButton from '@mui/material/IconButton';\nimport Badge from '@mui/material/Badge';\nimport MenuItem from '@mui/material/MenuItem';\nimport Menu from '@mui/material/Menu';\nimport AccountCircle from '@mui/icons-material/AccountCircle';\n// import MenuIcon from '@mui/icons-material/Menu';\nimport MailIcon from '@mui/icons-material/Mail';\nimport NotificationsIcon from '@mui/icons-material/Notifications';\nimport Logo from '../../assets/images/logo-horizontal.svg';\nimport SearchIconNew from '../../assets/images/original/Contoso_Assets/Icons/image_search_icon.svg'\nimport ProfileIcon from '../../assets/images/original/Contoso_Assets/Icons/profile_icon.svg'\nimport BagIcon from '../../assets/images/original/Contoso_Assets/Icons/cart_icon.svg'\nimport UploadFile from '../uploadFile/uploadFile';\nimport { clickAction, submitAction, handleThemeChange, getCartQuantity } from '../../actions/actions';\nimport AuthB2CService from '../../services/authB2CService';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\n// import Alert from \"react-s-alert\";\nimport personal_information_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/personal_information_icon.svg\";\nimport logout_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/logout_icon.svg\";\n// import delete_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/delete_icon.svg\";\nimport { CartService, ProductService } from '../../services';\nimport './header.scss'\n\nconst StyledMenu = ((props) => (\n  <Menu\n    elevation={0}\n    getcontentanchorel={null}\n    anchorOrigin={{\n      vertical: 'bottom',\n      horizontal: 'center',\n    }}\n    transformOrigin={{\n      vertical: 'top',\n      horizontal: 'center',\n    }}\n    {...props}\n  />\n));\n\nconst StyledMenuItem = (MenuItem);\nfunction TopAppBar(props) {\n  const history = useNavigate();\n  const searchRef = useRef();\n  const [anchorEl, setAnchorEl] = React.useState(null);\n  const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);\n  const [searchUpload, setSearchUpload] = React.useState(false)\n  const [mobileSearch, setMobileSearch] = React.useState(false)\n  const authService = new AuthB2CService();\n\n  const isMenuOpen = Boolean(anchorEl);\n  const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);\n\n  React.useEffect(() => {\n    if (searchUpload === true) {\n      window.addEventListener('click', function (e) {\n        if (!document.getElementById('searchbox').contains(e.target)) {\n          setSearchUpload(false)\n        }\n      });\n    }\n  }, [searchUpload]);\n\n  // React.useEffect(() => {\n  //   setSearchUpload(false)\n  // }, [history.location.pathname]);\n\n  const redirectUrl = (url) => {\n    history(url);\n  }\n\n  const handleProfileMenuOpen = (event) => {\n    setAnchorEl(event.currentTarget);\n  };\n\n  const handleMobileMenuClose = () => {\n    setMobileMoreAnchorEl(null);\n  };\n\n  const handleMenuClose = () => {\n    setAnchorEl(null);\n    handleMobileMenuClose();\n  };\n\n  const handleMobileMenuOpen = (event) => {\n    // setMobileMoreAnchorEl(event.currentTarget);\n    setMobileSearch(!mobileSearch)\n  };\n\n  const { loggedIn } = props.userInfo;\n\n  const getQuantity = useCallback(async () => {\n    if (props.userInfo.token) {\n      const shoppingcart = await CartService.getShoppingCart(\n        props.userInfo.token\n      );\n      if (shoppingcart) {\n        let quantity = shoppingcart.length;\n        props.getCartQuantity(quantity)\n      }\n      if (localStorage.getItem('cart_items')) {\n        const email = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')).userName : null\n        var tempProps = JSON.parse(localStorage.getItem('cart_items'));\n        tempProps.map(async (temp) => {\n          temp.email = email;\n          temp.id = temp.productId;\n          Object.preventExtensions(temp);\n\n          const productToCart = await CartService.addProduct(props.userInfo.token, temp)\n          if (productToCart.errMessage) {\n            // adding product to cart failed\n          } else {\n            getQuantity()\n          }\n        })\n        localStorage.removeItem('cart_items')\n      }\n    } else {\n      let cartItem = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : []\n      let quantity = cartItem.length;\n      props.getCartQuantity(quantity)\n    }\n  }, [props])\n\n\n  useEffect(() => {\n      getQuantity()\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  const onClickLogIn = async () => {\n    let user = await authService.login();\n    if (user) {\n      user['loggedIn'] = true;\n      user['isB2c'] = true;\n      user['token'] = sessionStorage.getItem('msal.idtoken');\n      localStorage.setItem('state', JSON.stringify(user))\n      props.submitAction(user);\n      window.location.reload()\n    }\n  }\n  const onClickLogout = () => {\n    localStorage.clear();\n\n    if (props.userInfo.isB2c) {\n      authService.logout();\n    }\n    props.clickAction();\n    history('/');\n  }\n  const setTextSearch = () => {\n    if (searchRef.current.value.length > 0) {\n      let searchData = searchRef.current.value;\n      ProductService.getSearchResults(searchData)\n        .then((relatedProducts) => {\n          searchRef.current.value = '';\n          if (relatedProducts.length > 1) {\n            history(\"/suggested-products-list\", {\n              state: { relatedProducts },\n            });\n          } else if (relatedProducts.length === 1) {\n            history(`/product/detail/${relatedProducts[0].id}`);\n          } else {\n            props.history.push(\"/suggested-products-list\", {\n              state: { relatedProducts },\n            });\n          }\n        })\n        .catch(() => {\n          searchRef.current.value = '';\n          // Alert.error(\"There was an error, please try again\", {\n          //     position: \"top\",\n          //     effect: \"scale\",\n          //     beep: true,\n          //     timeout: 6000,\n          // });\n        });//search function\n    }\n  }\n  React.useEffect(() => {\n    const listener = event => {\n      if (searchRef.current.value.length > 0 && (event.code === \"Enter\" || event.code === \"NumpadEnter\")) {\n        event.preventDefault();\n        setTextSearch();\n      }\n    };\n    document.addEventListener(\"keydown\", listener);\n    return () => {\n      document.removeEventListener(\"keydown\", listener);\n    };\n  });\n  const menuId = 'primary-search-account-menu';\n  const renderMenu = (\n    <StyledMenu\n      id=\"profile-dropdown\"\n      anchorEl={anchorEl}\n      keepMounted\n      open={isMenuOpen}\n      onClose={handleMenuClose}\n    >\n      <StyledMenuItem onClick={() => redirectUrl('/profile/personal')}>\n        <ListItemIcon>\n          <img src={personal_information_icon} alt=\"\" />\n        </ListItemIcon>\n        <ListItemText primary=\"Personal Information\" />\n        <ListItemIcon className='justify-content-end'></ListItemIcon>\n      </StyledMenuItem>\n      {/* <StyledMenuItem onClick={() => redirectUrl('/profile/orders')}>\n        <ListItemIcon>\n          <img src={my_orders_icon} alt=\"\"/>\n        </ListItemIcon>\n        <ListItemText primary=\"My Orders\" />\n        <ListItemIcon className='justify-content-end'>\n        </ListItemIcon>\n      </StyledMenuItem> */}\n      {/* <StyledMenuItem onClick={() => redirectUrl('/profile/wishlist')}>\n        <ListItemIcon>\n          <img src={my_wishlist_icon} alt=\"\"/>\n        </ListItemIcon>\n        <ListItemText primary=\"My Wishlist\" />\n        <ListItemIcon className='justify-content-end'>\n        </ListItemIcon>\n      </StyledMenuItem>\n      <StyledMenuItem onClick={() => redirectUrl('/profile/address')}>\n        <ListItemIcon>\n          <img src={my_address_book_icons} alt=\"\"/>\n        </ListItemIcon>\n        <ListItemText primary=\"My Address Book\" />\n        <ListItemIcon className='justify-content-end'>\n        </ListItemIcon>\n      </StyledMenuItem> */}\n      <StyledMenuItem onClick={onClickLogout}>\n        <ListItemIcon>\n          <img src={logout_icon} alt=\"\" />\n        </ListItemIcon>\n        <ListItemText primary=\"Logout\" />\n        <ListItemIcon className='justify-content-end'>\n        </ListItemIcon>\n      </StyledMenuItem>\n    </StyledMenu>\n  );\n\n  const mobileMenuId = 'primary-search-account-menu-mobile';\n  const renderMobileMenu = (\n    <Menu\n      anchorEl={mobileMoreAnchorEl}\n      anchorOrigin={{ vertical: 'top', horizontal: 'right' }}\n      id={mobileMenuId}\n      keepMounted\n      transformOrigin={{ vertical: 'top', horizontal: 'right' }}\n      open={isMobileMenuOpen}\n      onClose={handleMobileMenuClose}\n    >\n      <MenuItem>\n        <IconButton aria-label=\"show 4 new mails\" color=\"inherit\">\n          <Badge badgeContent={4} color=\"secondary\" overlap=\"rectangular\">\n            <MailIcon />\n          </Badge>\n        </IconButton>\n        <p>Messages</p>\n      </MenuItem>\n      <MenuItem>\n        <IconButton aria-label=\"show 11 new notifications\" color=\"inherit\">\n          <Badge badgeContent={11} color=\"secondary\" overlap=\"rectangular\">\n            <NotificationsIcon />\n          </Badge>\n        </IconButton>\n        <p>Notifications</p>\n      </MenuItem>\n      <MenuItem onClick={handleProfileMenuOpen}>\n        <IconButton\n          aria-label=\"account of current user\"\n          aria-controls=\"primary-search-account-menu\"\n          aria-haspopup=\"true\"\n          color=\"inherit\"\n        >\n          <AccountCircle />\n        </IconButton>\n        <p>Profile</p>\n      </MenuItem>\n    </Menu>\n  );\n  useEffect(() => {\n    if (window.innerWidth >= '768') {\n      setMobileSearch(true)\n    }\n  }, []);\n  return (\n    <div style={{ flexGrow: 1 }}>\n      <AppBar color='inherit' className='appbar box-shadow-0' position=\"static\">\n        <Toolbar className='p-0'>\n          <div className='headerLogo'>\n            <Link to=\"/\">\n              <img src={Logo} alt=\"\" />\n            </Link>\n          </div>\n          {mobileSearch && <div className={`searchBar`} id=\"searchbox\">\n            <TextField\n              // label=\"Search by product name or search by image\"\n              placeholder='Search by product name or search by image'\n              variant=\"outlined\"\n              fullWidth\n              onBlur={() => setTextSearch()}\n              onChange={() => setSearchUpload(false)}\n              onFocus={() => setSearchUpload(true)}\n              inputRef={searchRef}\n              InputProps={{\n                endAdornment: (\n                  <InputAdornment position='end'>\n                    <IconButton onClick={() => searchRef.current.value.length === 0 ? setSearchUpload(!searchUpload) : null} className=\"searchBtn\">\n                      <img src={SearchIconNew} alt=\"iconimage\" />\n                    </IconButton>\n                  </InputAdornment>\n                )\n              }}\n            />\n            {searchUpload ?\n              <div className='searchbar-upload'>\n                Search by an image\n                <UploadFile\n                  title=\"\"\n                  subtitle=\"Drag an image or upload a file\"\n                />\n              </div>\n              : null}\n          </div>}\n          <div style={{ flexGrow: 1 }} />\n          {loggedIn && loggedIn ? <div className={`sectionDesktop d-none d-md-block`}>\n            {/* <IconButton className='iconButton' aria-label=\"show 4 new mails\" color=\"inherit\" onClick={()=>redirectUrl('/wishlist')}>\n              <Badge badgeContent={0} color=\"secondary\" overlap=\"rectangular\">\n                <img src={WishlistIcon} alt=\"iconimage\" />\n              </Badge>\n            </IconButton> */}\n            <IconButton\n              className='iconButton'\n              // edge=\"end\"\n              aria-label=\"account of current user\"\n              aria-controls={menuId}\n              aria-haspopup=\"true\"\n              onClick={handleProfileMenuOpen}\n              color=\"inherit\"\n            >\n              <img src={ProfileIcon} alt=\"iconimage\" />\n            </IconButton>\n            {/* <IconButton className='iconButton' aria-label=\"cart\" color=\"inherit\" onClick={()=>redirectUrl('/cart')} >\n              <Badge badgeContent={props.quantity} color=\"secondary\" overlap=\"rectangular\">\n                <img src={BagIcon} alt=\"iconimage\" />\n              </Badge>\n            </IconButton> */}\n          </div> :\n            // null\n            process.env.REACT_APP_B2CCLIENTID && <>\n              <Button className='iconButton' aria-label=\"show 4 new mails\" color=\"inherit\" onClick={() => onClickLogIn()} >\n                Login\n              </Button></>\n          }\n          <IconButton className='iconButton' aria-label=\"cart\" color=\"inherit\" onClick={() => redirectUrl('/cart')} >\n            <Badge badgeContent={props.quantity} color=\"secondary\" overlap=\"rectangular\">\n              <img src={BagIcon} alt=\"iconimage\" />\n            </Badge>\n          </IconButton>\n          {/* #region Uncomment below lines to run dark mode tests */}\n          {/* <FormGroup className='theme-class'>\n            <FormControlLabel labelPlacement=\"start\" control={<Switch aria-label='theme change' id=\"theme\" color=\"primary\" onChange={(e) => props.handleThemeChange(e.target.checked)}/>} label=\"Dark Mode\" />\n          </FormGroup> */}\n          {/* #endregion */}\n          <div className={`sectionMobile d-block d-md-none d-lg-none`}>\n            <IconButton\n              aria-label=\"show more\"\n              aria-controls={mobileMenuId}\n              aria-haspopup=\"true\"\n              onClick={handleMobileMenuOpen}\n              color=\"inherit\"\n            >\n              <img src={SearchIconNew} alt=\"iconimage\" />\n            </IconButton>\n          </div>\n        </Toolbar>\n      </AppBar>\n      {renderMobileMenu}\n      {renderMenu}\n    </div>\n  );\n}\nconst mapStateToProps = (state) => {\n  return {\n    userInfo: state.login.userInfo,\n    theme: state.login.theme,\n    quantity: state.login.quantity\n  }\n};\nconst mapDispatchToProps = (dispatch) => ({\n  handleThemeChange: (value) => dispatch(handleThemeChange(value)),\n  submitAction: (user) => dispatch(submitAction(user)),\n  clickAction: () => dispatch(clickAction()),\n  getCartQuantity: (value) => dispatch(getCartQuantity(value)),\n})\nexport default (connect(mapStateToProps, mapDispatchToProps)(TopAppBar));"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/header.js",
    "content": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Link } from 'react-router-dom';\n\n// import { NamespacesConsumer } from 'react-i18next';\n\nimport { ConfigService } from '../../services';\nimport AuthB2CService from '../../services/authB2CService';\n// import { withRouter } from \"react-router-dom\";\n\n// import LoginContainer from './components/loginContainer';\n// import LoginComponent from './components/loginComponent';\n// import UserPortrait from './components/userPortrait';\n\nimport { ReactComponent as Close } from '../../assets/images/icon-close.svg';\n// import { ReactComponent as Hamburger } from '../../assets/images/icon-menu.svg';\n// import { ReactComponent as Cart } from '../../assets/images/icon-cart.svg';\n\nimport { clickAction, submitAction } from \"../../actions/actions\";\nimport Categories from '../dropdowns/categories';\n\n// const Login = LoginContainer(LoginComponent);\nimport laptopsImg from '../../assets/images/original/Contoso_Assets/Mega_menu_dropdown_assets/laptop_icon.svg';\nimport controllersImg from '../../assets/images/original/Contoso_Assets/Mega_menu_dropdown_assets/controllers_icon.svg';\nimport desktopsImg from '../../assets/images/original/Contoso_Assets/Mega_menu_dropdown_assets/desktops_icon.svg';\nimport mobilesImg from '../../assets/images/original/Contoso_Assets/Mega_menu_dropdown_assets/mobiles_icon.svg';\nimport monitorImg from '../../assets/images/original/Contoso_Assets/Mega_menu_dropdown_assets/monitor_icon.svg';\nimport ProfileIcon from '../../assets/images/original/Contoso_Assets/Icons/profile_icon.svg'\nimport BagIcon from '../../assets/images/original/Contoso_Assets/Icons/cart_icon.svg'\nimport logout_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/logout_icon.svg\";\nimport { Badge, IconButton } from '@mui/material';\nimport MenuIcon from '@mui/icons-material/Menu';\nimport LoginIcon from '@mui/icons-material/Login';\n\nclass Header extends Component {\n    constructor() {\n        super();\n        this.authService = new AuthB2CService();\n        this.state = {\n            isopened: false,\n            ismodalopened: false,\n            profile: {},\n            UseB2C: null\n        };\n        this.loginModalRef = React.createRef();\n    }\n\n    async componentDidMount() {\n        this.loadSettings();\n\n        if (this.props.userInfo.token) {\n            // const profileData = await UserService.getProfileData(this.props.userInfo.token);\n            // this.setState({ ...profileData });\n        }\n\n        const setComponentVisibility = this.setComponentVisibility.bind(this);\n        setComponentVisibility(document.documentElement.clientWidth);\n        window.addEventListener('resize', function () {\n            setComponentVisibility(document.documentElement.clientWidth);\n        });\n    }\n\n    loadSettings = async () => {\n        await ConfigService.loadSettings();\n        const UseB2C = ConfigService._UseB2C;\n        this.setState({ UseB2C })\n    }\n\n    setComponentVisibility(width) {\n        if (width > 1280) {\n            this.setState({ isopened: false });\n        }\n    }\n\n    toggleClass = () => {\n        this.setState(prevState => ({\n            isopened: !prevState.isopened,\n        }));\n    };\n\n    toggleModalClass = () => {\n        if (!document.body.classList.contains(\"is-blocked\")) {\n            document.body.classList.add(\"is-blocked\");\n        } else {\n            document.body.classList.remove(\"is-blocked\");\n        }\n\n        this.setState(prevState => ({\n            ismodalopened: !prevState.ismodalopened\n        }));\n    };\n\n    onClickClose = () => {\n        this.toggleModalClass();\n    }\n\n    onClickLogout = () => {\n        localStorage.clear();\n\n        if (this.props.userInfo.isB2c) {\n            this.authService.logout();\n        }\n\n        this.props.clickAction();\n        this.props.history.push('/');\n    }\n\n    onClickLogIn = async () => {\n        let user = await this.authService.login();\n        if (user) {\n            user['loggedIn'] = true;\n            user['isB2c'] = true;\n            user['token'] = sessionStorage.getItem('msal.idtoken');\n            localStorage.setItem('state', JSON.stringify(user))\n            this.props.submitAction(user);\n        }\n    }\n\n\n    render() {\n        const categories = {\n            title: 'All Categories',\n            categorylist: [\n                {\n                    name: 'Laptops',\n                    url: '/list/laptops',\n                    img: laptopsImg\n                },\n                {\n                    name: 'Controllers',\n                    url: '/list/controllers',\n                    img: controllersImg\n                },\n                {\n                    name: 'Desktops',\n                    url: '/list/desktops',\n                    img: desktopsImg\n                },\n                {\n                    name: 'Mobiles',\n                    url: '/list/mobiles',\n                    img: mobilesImg\n                },\n                {\n                    name: 'Monitors',\n                    url: '/list/monitors',\n                    img: monitorImg\n                },\n            ]\n        }\n        // const { profile } = this.state;\n        const { loggedIn } = this.props.userInfo;\n        return (\n            <header className=\"header\">\n                <Categories categories={categories} />\n                <nav className={this.state.isopened ? 'main-nav is-opened' : 'main-nav'}>\n                    <Link className={window.location.pathname === '/list/all-products' ? \"main-nav__item_active\" : \"main-nav__item\"} to=\"/list/all-products\">\n                        All Products\n                    </Link>\n                    {categories.categorylist.map((item, key) => {\n                        return (\n                            <Link key={key} className={window.location.pathname === item.url ? \"main-nav__item_active\" : \"main-nav__item\"} to={item.url}>\n                                {item.name}\n                            </Link>\n                        )\n                    })}\n                    <div className=\"main-nav__actions\">\n                        <Link className=\"main-nav__item\" to=\"/profile/personal\">\n                            Profile\n                        </Link>\n                        <button className=\"u-empty main-nav__item\" onClick={this.onClickLogout}>\n                            Logout\n                        </button>\n                    </div>\n                    <button className=\"u-empty btn-close\" onClick={this.toggleClass}>\n                        <Close />\n                    </button>\n                </nav>\n                <nav className=\"secondary-nav\">\n                    {loggedIn && <Link to=\"/profile/personal\">\n                        <IconButton\n                            className='iconButton'\n                            // edge=\"end\"\n                            aria-label=\"account of current user\"\n                            aria-haspopup=\"true\"\n                            //   onClick={handleProfileMenuOpen}\n                            color=\"inherit\"\n                        >\n                            <img src={ProfileIcon} alt=\"iconimage\" />\n                        </IconButton>\n                    </Link>}\n                    <Link className=\"secondary-nav__cart\" to=\"/cart\">\n                        <IconButton className='iconButton' aria-label=\"cart\" color=\"inherit\" >\n                            <Badge badgeContent={this.props.quantity} color=\"secondary\" overlap=\"rectangular\">\n                                <img src={BagIcon} alt=\"iconimage\" />\n                            </Badge>\n                        </IconButton>\n                    </Link>\n                    {loggedIn ? <div className=\"secondary-nav__login\" onClick={this.onClickLogout}>\n                        <IconButton className='iconButton' aria-label=\"cart\" color=\"inherit\" >\n                            <img src={logout_icon} alt=\"iconimage\" />\n                        </IconButton>\n                    </div>\n                        : <div className=\"secondary-nav__login\" onClick={this.onClickLogIn}>\n                            <IconButton\n                                aria-label=\"show more\"\n                                aria-haspopup=\"true\"\n                                // onClick={handleMobileMenuOpen}\n                                color=\"inherit\"\n                            >\n                                <LoginIcon />\n                            </IconButton>\n                        </div>}\n                    <button className=\"u-empty\" onClick={this.toggleClass}>\n                        {/* <Hamburger /> */}\n                        <IconButton\n                            aria-label=\"show more\"\n                            aria-haspopup=\"true\"\n                            // onClick={handleMobileMenuOpen}\n                            color=\"inherit\"\n                        >\n                            <MenuIcon />\n                        </IconButton>\n                    </button>\n                </nav>\n                {/* {this.state.ismodalopened ?\n                            <Login UseB2C={this.state.UseB2C} toggleModalClass={this.state.ismodalopened} onClickClose={this.onClickClose} />\n                            : null} */}\n            </header>\n\n        );\n    }\n}\n\nconst mapStateToProps = (state) => { \n    return { \n      userInfo : state.login.userInfo,\n      theme :  state.login.theme,\n      quantity : state.login.quantity\n    }\n  };\n\nexport default (connect(mapStateToProps, { clickAction, submitAction })(Header));\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/header.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains all styles related to the header of the site/application.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n.header {\n    align-items: center;\n    display: flex;\n    height: auto;\n    // justify-content: space-between;\n    padding-top: 37px;\n    padding-left: 70px;\n    padding-right: 70px;\n    overflow: hidden;\n\n    svg {\n        fill: $color-primary;\n        height: $icon-l;\n\n        &:hover {\n            cursor: pointer;\n        }\n    }\n\n    >svg {\n        // Logo\n        height: 2.35625rem;\n        width: 10.875rem;\n        min-width: 10.875rem;\n    }\n}\n\n.main-nav {\n    align-items: center;\n    background-color: #eaf2f9; //$color-background-primary;\n    display: flex;\n    height: 100vh;\n    order: 2;\n    // overflow: hidden;\n    // padding: $space-m;\n    position: fixed;\n    right: -100vw;\n    text-align: center;\n    text-transform: uppercase;\n    top: 0;\n    transition: right $animation-speed-regular $animation-type-regular;\n    width: 60%;\n    will-change: right;\n    z-index: $z-index-l;\n\n    @media (min-width: $mq-sl) {\n        background-color: transparent;\n        height: auto;\n        // justify-content: space-around;\n        order: 0;\n        position: relative;\n        right: auto;\n        top: auto;\n\n        &__actions {\n            display: none;\n        }\n\n        button:last-child {\n            display: none;\n        }\n    }\n\n    &:not(.is-opened) .main-nav__item {\n        color: $color-text-primary;\n    }\n\n    &.is-opened {\n        flex-direction: column;\n        padding-top: $padding-xl;\n        overflow: auto;\n        right: 0;\n\n        .main-nav__item {\n            display: block;\n            color: $color-text-tertiary;\n            font-weight: $font-weight-medium;\n            margin-bottom: $margin-m;\n\n            &:nth-child(7) {\n                margin-bottom: $margin-xl;\n            }\n        }\n\n        button.main-nav__item {\n            background-color: transparent;\n            border: 0;\n        }\n\n        .btn-close {\n            display: block;\n            position: absolute;\n            right: 20px;\n            top: 20px;\n\n            svg {\n                fill: $color-svg-tertiary;\n            }\n        }\n    }\n\n    &__item {\n        @include text-xxs();\n        color: $color-link;\n        display: block;\n        text-decoration: none;\n        text-transform: uppercase;\n        white-space: nowrap;\n\n        &:hover {\n            cursor: pointer;\n            text-decoration: underline;\n        }\n    }\n}\n\n.secondary-nav {\n    display: flex;\n    justify-content: space-around;\n    min-width: 13rem;\n    width: 10rem;\n\n    svg:nth-child(2) {\n        display: none;\n    }\n\n    @media (min-width: $mq-sl) {\n        svg:nth-child(2) {\n            display: inline;\n        }\n\n        button:last-child {\n            display: none;\n        }\n    }\n\n    &__cart {\n        position: relative;\n\n        &-number {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            position: absolute;\n            width: 1.25rem;\n            height: 1.25rem;\n            font-size: 0.5rem;\n            border-radius: 50%;\n            background-color: $color-background-light;\n            border: 1px solid currentColor;\n            top: -.5rem;\n            right: -.5rem;\n            color: $color-text-primary;\n        }\n    }\n\n    &__login {\n        color: $color-text-primary;\n        cursor: pointer;\n    }\n\n    .portrait {\n        width: 1.875rem;\n        height: 1.875rem;\n        border-radius: 50%;\n    }\n}\n\n//Profile dropdown popup in header\n#profile-dropdown .MuiPopover-paper {\n    right: 122px !important;\n    left: unset !important;\n}\n\n#profile-dropdown .MuiMenu-paper {\n    box-shadow: 0px 3px 26px #00000029;\n    border: 1px solid #E5E5E5;\n    border-radius: 8px;\n}\n\n#profile-dropdown .MuiPopover-paper .MuiTypography-root {\n    font-family: 'Poppins' !important;\n    font-size: 16px;\n}\n\n#profile-dropdown .MuiPopover-paper .MuiListItemIcon-root {\n    min-width: 40px;\n}\n\n#profile-dropdown .MuiPopover-paper ul li {\n    min-width: 336px;\n    padding: 12px 19px;\n}\n\n\n/* Header Message */\n.headerMessageDiv {\n    height: 55px;\n    font-family: 'Poppins';\n    letter-spacing: 0.42px;\n    font-weight: 500;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.headerMessageDiv.warning {\n    background-color: #F0A428;\n    color: #fff;\n}\n\n.headerMessageDiv.error {\n    background-color: #E81D1D;\n    color: #fff;\n}\n\n//Top bar of application : Appbar \n.appbar {\n    box-shadow: none !important;\n    padding-left: 70px;\n    padding-right: 70px;\n    padding-top: 31px;\n}\n\n.appbar .searchBar {\n    width: 41%;\n    position: relative;\n    margin-left: 50px;\n    margin-right: 16px;\n    border-radius: 4px;\n    background-color: rgba(255, 255, 255, 0.15);\n}\n\n.appbar .sectionDesktop {\n    display: flex;\n}\n\n.appbar .sectionMobile {\n    display: flex;\n}\n\n.appbar .searchBar input {\n    font-family: 'Roboto' !important;\n    font-size: 14px !important;\n    font-weight: 300 !important;\n}\n\n.appbar .searchBar .MuiOutlinedInput-adornedEnd {\n    height: 48px;\n    padding-right: 8px;\n}\n\n.appbar .searchBar input::placeholder {\n    font-family: 'Roboto' !important;\n    font-size: 14px !important;\n    font-weight: 300 !important;\n    color: #686868 !important;\n}\n\n.appbar .iconButton {\n    padding: 12px 18px;\n}\n\n.appbar .iconButton:focus {\n    outline: none !important;\n}\n\n.appbar .headerLogo {\n    height: 71px;\n    width: 195px;\n}\n\n.appbar svg {\n    fill: #2f4b66;\n    overflow: unset;\n}\n\n.appbar .searchBtn {\n    border-radius: 5px;\n    padding: 5px;\n}\n\n.appbar .searchBtn:focus {\n    outline: none;\n}\n\n.appbar .upload__subtitle {\n    font-family: 'Roboto' !important;\n}\n\n//Main menu\n\n.main-nav {\n    margin-left: 50px;\n}\n\n.main-nav__item {\n    text-decoration: none !important;\n    text-transform: capitalize !important;\n    text-align: left;\n    font-family: 'Roboto' !important;\n    letter-spacing: 0px !important;\n    color: #302F2F !important;\n    opacity: 0.92 !important;\n    font-size: 14px !important;\n    font-weight: 400 !important;\n    padding: 14px 24px 20px 24px;\n    text-align: center;\n}\n\n.main-nav__item:hover {\n    text-decoration: none !important;\n    text-transform: capitalize !important;\n    text-align: left;\n    font-family: 'Roboto', sans-serif !important;\n    letter-spacing: 0px !important;\n    color: #2F4B66 !important;\n    opacity: 0.92 !important;\n    font-size: 14px;\n    font-weight: 500;\n    padding: 14px 24px 14px 24px;\n    border-bottom: 5px solid #2874F0 !important;\n    background: rgba(40, 116, 240, 0.1) !important;\n    text-align: center;\n}\n\n.main-nav__item_active {\n    text-decoration: none !important;\n    text-transform: capitalize !important;\n    text-align: left;\n    font-family: 'Roboto', sans-serif !important;\n    letter-spacing: 0px !important;\n    color: #2F4B66 !important;\n    opacity: 0.92 !important;\n    font-size: 14px;\n    font-weight: 500;\n    padding: 14px 24px 20px 24px;\n    border-bottom: 5px solid #2874F0 !important;\n    background: rgba(40, 116, 240, 0.1) !important;\n    text-align: center;\n}\n\n//dark mode\n.theme-class>label {\n    border: 1px solid lightgray;\n    background-color: #fff;\n    border-radius: 20px;\n    padding: 3px 12px;\n    margin: 0;\n    color: #000;\n}\n\n\n//Responsive\n@media (max-width: 65em) {\n    .sectionDesktop {\n        display: none !important;\n    }\n\n    .mainHeader .categories-btn>.MuiButton-startIcon {\n        display: none !important;\n    }\n\n    .mainHeader .categories-btn {\n        min-width: 135px !important;\n        padding: 0;\n    }\n\n    .appbar .iconButton {\n        display: none;\n    }\n\n    .secondary-nav {\n        justify-content: end;\n        align-items: center;\n    }\n\n    .header {\n        justify-content: space-between;\n    }\n\n    .mainHeader {\n        position: relative;\n    }\n\n    .appbar .searchbar-upload {\n        padding: 15px;\n    }\n\n    .appbar .searchbar-upload .upload p {\n        font-size: 12px;\n    }\n\n    .appbar .searchbar-upload .upload>div {\n        min-height: 160px;\n    }\n\n    .main-nav__item {\n        font-weight: bold !important;\n    }\n\n    .home,\n    .list,\n    .ProductContainerSectionMain,\n    .profileMain,\n    .CartMain,\n    .refund-policy-section {\n        margin-top: 0 !important;\n    }\n}\n\n@media screen and (min-width : 320px) and (max-width : 340px) {\n    .secondary-nav {\n        justify-content: start;\n        align-items: center;\n    }\n}\n\n@media screen and (min-width : 320px) and (max-width : 767px) {\n    .appbar{\n        padding: 0 15px !important;\n        flex-direction: column !important;\n    }\n    .appbar > div {\n        flex-direction: column !important;\n    }\n    .appbar .searchBar{\n        margin: 0;\n        width: 100%;\n    }\n    .appbar .sectionMobile{\n        position: absolute;\n        z-index: 1;\n        right: 0;\n        top: 15px;\n    }\n    .header{\n        padding: 0 15px !important;\n        justify-content: space-between;\n    }\n    .theme-class{\n        padding-top: 10px;\n        position: relative;\n        width: 100%;\n    }\n    .theme-class > label{\n        position: absolute;\n        top: 60px;\n        min-width: 140px;\n        right: 0;\n    }\n}\n@media screen and (min-width : 320px) and (max-width : 1039px) {\n    .theme-class > label > span {\n        font-size: 11px;\n    }\n    .dark .main-nav{\n        background-color: #302F2F !important;\n    }\n    .dark .main-nav__item{\n        color: #fff !important;\n    }\n    .dark .header img {\n        background-color: #fff;\n        border-radius: 50%;\n        padding: 3px;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/header/headerMessage.js",
    "content": "import React from 'react';\n\nfunction HeaderMessage(props) {\n    const { type, icon, message } = props\n    return ( \n        <div className={`headerMessageDiv ${type}`}>\n            <img className='icon' src={icon} alt=\"\"/>\n            <p className='message m-0'>{message}</p>\n        </div> \n    );\n}\n\nexport default HeaderMessage;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/imageSlider/imageSlider.js",
    "content": "import React from \"react\";\nimport productdetailimg1 from \"../../assets/images/original/Contoso_Assets/product_page_assets/product_image_main.jpg\";\nimport productdetailimg2 from \"../../assets/images/original/Contoso_Assets/product_page_assets/image_2.jpg\";\nimport productdetailimg3 from \"../../assets/images/original/Contoso_Assets/product_page_assets/image_3.jpg\";\nimport productdetailimg4 from \"../../assets/images/original/Contoso_Assets/product_page_assets/image_4.jpg\";\nimport chevron_up from \"../../assets/images/original/Contoso_Assets/product_page_assets/chevron_up.svg\";\nimport chevron_down from \"../../assets/images/original/Contoso_Assets/product_page_assets/chevron_down.svg\";\nimport { Button } from \"@mui/material\";\nimport './imageSlider.scss'\n\nfunction ImageSlider(props) {\n  const [min, setMin] = React.useState(0)\n  const [max, setMax] = React.useState(4)\n  const sliderImages = [\n    {\n      id : 1,\n      img : productdetailimg1\n    },\n    {\n      id : 2,\n      img : props.imageUrl\n    },\n    {\n      id : 3,\n      img : productdetailimg3\n    },\n    {\n      id : 4,\n      img : productdetailimg4\n    },\n    {\n      id : 2,\n      img : productdetailimg2\n    },\n    {\n      id : 1,\n      img : productdetailimg1\n    },\n    {\n      id : 4,\n      img : productdetailimg4\n    },\n    {\n      id : 3,\n      img : productdetailimg3\n    },\n  ];\n\n  const decrementMinMax = () => {\n    setMin(min-4)\n    setMax(max-4)\n  }\n  const incrementMinMax = () => {\n    setMin(min+4)\n    setMax(max+4)\n  }\n  return (\n    <div>\n      <div className=\"slidesection\">\n        <div className=\"slideimagelist\">\n          <div> <Button className={`${min === 0 ? \"chevron_btn disabled\" : \"chevron_btn\"} chevron_prevbtn`} disabled={min === 0 ? true : false} onClick={() => decrementMinMax()} ><img src={chevron_up} alt=\"\"/></Button> </div>\n            {sliderImages.slice(min,max).map((item, key)=>(\n               <div className={`${props.sliderImg === item.img ? \"imgdiv active\" : \"imgdiv\"}`}> <img src={item.img} alt=\"image1\" className=\"slideimage\" onClick={()=>props.setSliderImg(item.img)} /> </div>\n            ))}\n          <div> <Button className={`${max === sliderImages.length ? \"chevron_btn disabled\" : \"chevron_btn\"} chevron_nextbtn`} disabled={max === sliderImages.length ? true : false} onClick={() => incrementMinMax()} ><img src={chevron_down} alt=\"\"/></Button> </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default ImageSlider;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/imageSlider/imageSlider.scss",
    "content": ".slidesection {\n    margin-right: 16px;\n    display: flex;\n    justify-content: center;\n}\n\n.slideimagelist {\n    flex-direction: row-reverse;\n\n    .imgdiv.active {\n        border: 2px solid #2874F0;\n    }\n\n    .imgdiv {\n        width: 100px;\n        height: 100px;\n        margin: 4px 0;\n    }\n}\n\n.slideimage {\n    width: 100%;\n}\n\n.chevron_prevbtn {\n    margin-bottom: 8px;\n}\n\n.chevron_nextbtn {\n    margin-top: 10px;\n}\n\n.chevron_btn {\n    width: 100px;\n    height: 38px;\n    background: #ffffff 0% 0% no-repeat padding-box;\n    border: 1px solid #2F4B66 !important;\n    border-radius: 2px;\n    opacity: 1;\n}\n\n.chevron_btn.disabled {\n    border: 1px solid #e5e5e5 !important;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/loadingSpinner/loadingSpinner.js",
    "content": "import React from \"react\";\nimport './loadingSpinner.scss'\n\nconst LoadingSpinner = () => (\n    <div className=\"loader-wrapper\">\n        <div className=\"loader\" />\n    </div>\n);\n\nexport default LoadingSpinner;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/loadingSpinner/loadingSpinner.scss",
    "content": "@import '../../styles/abstracts/mixins/loader';\n\n.loader-wrapper {\n    align-items: center;\n    display: flex;\n    height: 100vh;\n    justify-content: center;\n}\n\n.loader {\n    @include loader();\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/minimalSelect/minimalSelect.js",
    "content": "import React, { useState } from 'react';\nimport minimalSelectStyles from './minimalSelect.styles';\nimport Select from '@material-ui/core/Select';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport FormControl from '@material-ui/core/FormControl';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\n\nconst MinimalSelect = () => {\n  const [val,setVal] = useState(1);\n  const handleChange = (event) => {\n    setVal(event.target.value);\n  };\n\n  const minimalSelectClasses = minimalSelectStyles;\n\n  const iconComponent = (props) => {\n    return (\n      <ExpandMoreIcon className={props.className + \" \" + minimalSelectClasses.icon}/>\n    )};\n\n\n  return (\n    <FormControl>\n      <Select\n        disableUnderline\n        className='minimalSelect'\n        IconComponent={iconComponent}\n        value={val}\n        onChange={handleChange}\n      >\n        <MenuItem value={0}>Principle</MenuItem>\n        <MenuItem value={1}>Recommended</MenuItem>\n        <MenuItem value={2}>Photoshop</MenuItem>\n        <MenuItem value={3}>Framer</MenuItem>\n      </Select>\n    </FormControl>\n  );\n};\n\n\nexport default MinimalSelect;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/minimalSelect/minimalSelect.scss",
    "content": ".minimalSelect {\n    background-color: #0000000A;\n    min-width: 238px;\n    padding: 18px 0 18px 32px;\n    border-radius: 114px;\n    height: 56px;\n}\n\n.minimalSelect svg {\n    margin-right: 32px;\n}\n\n.minimalSelect .MuiSelect-select {\n    background-color: transparent !important;\n    font-family: var(--fontInter);\n    font-weight: 500;\n    color: #626262;\n    font-size: 16px;\n}\n\n.minimalSelect .MuiSelect-select:focus {\n    background-color: transparent !important;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/minimalSelect/minimalSelect.styles.js",
    "content": "import { deepPurple } from '@material-ui/core/colors';\n\nexport default () => ({\n  select: {\n    minWidth: 200,\n    background: 'white',\n    color: deepPurple[500],\n    fontWeight:200,\n    borderStyle:'none',\n    borderWidth: 2,\n    borderRadius: 12,\n    paddingLeft: 24,\n    paddingTop: 14,\n    paddingBottom: 15,\n    boxShadow: '0px 5px 8px -3px rgba(0,0,0,0.14)',\n    \"&:focus\":{\n      borderRadius: 12,\n      background: 'white',\n      borderColor: deepPurple[100]\n    },\n  },\n  icon:{\n    color: deepPurple[300],\n    right: 12,\n    position: 'absolute',\n    userSelect: 'none',\n    pointerEvents: 'none'\n  },\n  paper: {\n    borderRadius: 12,\n    marginTop: 8\n  },\n  list: {\n    paddingTop:0,\n    paddingBottom:0,\n    background:'white',\n    \"& li\":{\n      fontWeight:200,\n      paddingTop:12,\n      paddingBottom:12,\n    },\n    \"& li:hover\":{\n      background: deepPurple[100]\n    },\n    \"& li.Mui-selected\":{\n      color:'white',\n      background: deepPurple[400]\n    },\n    \"& li.Mui-selected:hover\":{\n      background: deepPurple[500]\n    }\n  }\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/productCard/product.js",
    "content": "import React from 'react';\nimport Card from '@mui/material/Card';\nimport CardMedia from '@mui/material/CardMedia';\nimport CardContent from '@mui/material/CardContent';\nimport IconButton from '@mui/material/IconButton';\nimport Typography from '@mui/material/Typography';\n// import WishlistIcon from '../../../../assets/images/original/Contoso_Assets/Icons/wishlist_icon.svg'\nimport { useNavigate } from 'react-router-dom';\nimport './product.scss'\n\nexport default function Product(props) {\n  const navigate = useNavigate()\n  const  { prodImg, imageUrl, name, price, id, type } = props;\n  const productDetailPage = (id = 1) => {\n   navigate('/product/detail/'+id)\n  }\n\n  const discountOffer = (price) => {\n    let dicsount = price - ((price/100)*15)\n    return(\n      <Typography variant=\"h6\" color=\"initial\" component=\"h6\" style={{marginRight:'auto'}} className=\"productOrgPrice m-0 mr-1\">\n        ${parseInt(dicsount).toFixed(2)}\n      </Typography>\n    )\n  }\n\n  return (\n    <Card className=\"productCard\" onClick={() => productDetailPage(id)}>\n      <CardMedia\n        image={prodImg?prodImg:imageUrl}\n        title={name?name:''}\n      />\n      <CardContent>\n        <div style={{display:'flex',alignItems:'center',marginBottom:'8.25px'}}>\n            <Typography variant=\"h6\" color=\"initial\" component=\"h6\" className='productName' style={{marginRight:'auto'}}>\n                {name?name:'Lunar Shift Special Edition'}\n            </Typography>\n            <IconButton className='wishlist_icon' aria-label=\"add to favorites\">\n                {/* <img src={WishlistIcon} alt=\"like\"/> */}\n            </IconButton>\n        </div>\n        <Typography variant=\"body2\" color=\"textSecondary\" className='productType' component=\"p\">\n          {type?type.name:'Controller'}\n        </Typography>\n        <div style={{display:'flex',alignItems:'center',paddingTop:'10px'}}>\n            {discountOffer(price)}\n            <Typography paragraph color=\"textSecondary\" className=\"productOldPrice m-0 mr-1\">\n              ${price?price.toFixed(2):'00.00'} \n            </Typography>\n            <Typography paragraph color=\"error\" className=\"productOffer m-0 mr-1 \">\n                15% OFF\n            </Typography>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/productCard/product.scss",
    "content": ".productCard {\n    box-shadow: none !important;\n}\n\n.productCard:hover {\n    cursor: pointer;\n    box-shadow: 0px 3px 6px #00000029 !important;\n    border: 1px solid #E5E5E5;\n    border-radius: 4px;\n}\n\n.productCard .MuiCardMedia-root {\n    width: auto;\n    height: 306px;\n    margin: auto;\n    background-size: contain;\n}\n\n.productCard .MuiCardContent-root {\n    padding: 8px 8px 0 8px !important;\n}\n\n.productCard .wishlist_icon {\n    padding: 0;\n}\n\n.productName {\n    font-family: var(--fontInter) !important;\n    font-weight: 600 !important;\n    font-size: 16px !important;\n    color: #171520 !important;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n.productType {\n    font-family: var(--fontInter) !important;\n    font-weight: 400 !important;\n    font-size: 14px !important;\n    color: #626262 !important;\n}\n\n.productOrgPrice {\n    font-family: var(--fontInter) !important;\n    font-weight: 500 !important;\n    font-size: 20px !important;\n    color: #171520 !important;\n}\n\n.productOldPrice {\n    font-family: var(--fontInter) !important;\n    font-weight: 400 !important;\n    font-size: 14px !important;\n    color: #626262 !important;\n    text-decoration: line-through;\n\n}\n\n.productOffer {\n    font-family: var(--fontInter) !important;\n    font-weight: 400 !important;\n    font-size: 14px !important;\n    color: #E21D1D !important;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/quantityCounter/productCounter.js",
    "content": "import React, { Component } from 'react';\nimport { CartService } from '../../services';\nimport './productCounter.scss'\n\nexport default class QuantityPicker extends Component {\n\n  constructor(props) {\n    super(props);\n\n    this.state = { value: this.props.qty || this.props.min, disableDec: true, disableInc: false }\n    this.increment = this.increment.bind(this);\n    this.decrement = this.decrement.bind(this);\n  }\n  //cartItemId\n\n  componentDidMount() {\n    if(this.state.value > this.props.min){\n      this.setState({ disableDec: false });\n    }\n    if(this.state.value >= this.props.max){\n      this.setState({ disableInc: true });\n    }\n  }\n\n  componentDidUpdate(prevProps, prevState) {\n    if (prevState.value !== this.state.value && this.props?.page === 'cart') {\n      this.updateProductQty()\n    }\n    if (this.props.setQty) {\n      this.props.setQty(this.state.value)\n    }\n  }\n\n  async updateProductQty() {\n    if(this.props.loggedIn){\n      let response = await CartService.updateQuantity(this.props.detailProduct, this.state.value, this.props.token)\n      if (response) {\n        this.props.getCartItems()\n      }\n    }else{\n      let cart_items = JSON.parse(localStorage.getItem('cart_items'));\n      let objIndex = cart_items.findIndex((obj => obj.productId === this.props.detailProduct.productId));\n      cart_items[objIndex].quantity = this.state.value\n      localStorage.setItem('cart_items',JSON.stringify(cart_items))\n      this.props.getCartItems()\n    }\n  }\n\n  increment() {\n    const plusState = this.state.value + 1;\n    if (this.state.value < this.props.max) {\n      this.setState({ value: plusState });\n      this.setState({ disable: false });\n    }\n    if (this.state.value === (this.props.max - 1)) {\n      this.setState({ disableInc: true });\n    }\n    if (this.state.value === this.props.min) {\n      this.setState({ disableDec: false });\n    }\n  }\n\n  decrement() {\n    const minusState = this.state.value - 1;\n    if (this.state.value > this.props.min) {\n      this.setState({ value: minusState });\n      if (this.state.value === this.props.min + 1) {\n        this.setState({ disableDec: true });\n      }\n    } else {\n      this.setState({ value: this.props.min });\n    }\n    if (this.state.value === this.props.max) {\n      this.setState({ disableInc: false });\n    }\n  }\n\n  render() {\n    const { disableDec, disableInc } = this.state;\n\n    return (\n      <span>\n        <span className=\"quantity-picker\">\n          <button className={`${disableDec ? 'mod-disable ' : ''}quantity-modifier modifier-left`} onClick={this.decrement}>-</button>\n          <input className=\"quantity-display\" type=\"text\" value={this.state.value} readOnly />\n          <button className={`${disableInc ? 'mod-disable ' : ''}quantity-modifier modifier-right`} onClick={this.increment}>+</button>\n        </span>\n      </span>\n    );\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/quantityCounter/productCounter.scss",
    "content": "/*---------------------------------quantitypicker----------------------------*/\n.quantity-picker {\n    display: inline-flex;\n    width: 89px;\n    height: 40px;\n    align-items: center;\n    justify-content: space-around;\n    vertical-align: middle;\n    border: 1px solid #707070;\n    border-radius: 8px;\n\n}\n\n.quantity-input:focus {\n    background: red;\n}\n\n.quantity-modifier,\n.quantity-display {\n    -webkit-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n    outline: none;\n}\n\n.quantity-modifier {\n    font-size: 1.5rem;\n    background: none !important;\n    border: 0 solid #dbdbdb;\n    text-align: center;\n    cursor: pointer;\n    width: 24px;\n    color: #171520 !important;\n}\n\n.quantity-modifier:hover {\n    background: #dadada;\n    color: #555555;\n}\n\n.quantity-modifier:focus {\n    outline: 0;\n}\n\n.left-modifier {\n    border-radius: 3px 0 0 3px;\n}\n\n.mod-disable {\n    color: #d9d9d9 !important;\n}\n\n.mod-disable:hover {\n    color: #d9d9d9;\n}\n\n.right-modifier {\n    border-radius: 0 3px 3px 0;\n}\n\n.quantity-display {\n    font-family: \"Inter\";\n    font-weight: 600;\n    font-size: 14px;\n    width: 23px;\n    height: 28px;\n    border: 0;\n    text-align: center;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/shared/index.js",
    "content": "import Header from '../header/header';\nimport Appbar from '../header/appbar';\nimport Footer from '../footer/footer';\nimport HeaderMessage from '../header/headerMessage';\nexport {\n    Header,\n    Appbar,\n    Footer,\n    HeaderMessage,\n};\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/slider/slider.js",
    "content": "import React from 'react';\nimport Carousel from 'react-material-ui-carousel'\nimport { Grid } from '@mui/material'\nimport Product from '../productCard/product';\nimport productImg1 from '../../assets/images/original/Contoso_Assets/Caurosal/product_1.jpg'\nimport productImg2 from '../../assets/images/original/Contoso_Assets/Caurosal/product_2.jpg'\nimport productImg3 from '../../assets/images/original/Contoso_Assets/Caurosal/product_3.jpg'\nimport productImg4 from '../../assets/images/original/Contoso_Assets/Caurosal/product_4.jpg'\nimport productImg5 from '../../assets/images/original/Contoso_Assets/Caurosal/product_5.jpg'\n\nexport default function Slider(props) {\n    var items = [\n        {\n            name: \"The Fastest, Most Powerful Xbox Ever.\",\n            description: \"Elevate your game with the all-new Xbox Wireless Controller - Lunar Shift Special Edition\",\n            page: 1\n        },\n        {\n            name: \"The Fastest, Most Powerful Xbox Ever.\",\n            description: \"Elevate your game with the all-new Xbox Wireless Controller - Lunar Shift Special Edition\",\n            page: 2\n        },\n    ]\n\n    return (\n        <div className='slider-section'>\n            <div className='topSection'>\n                <div className=\"LapHeadSection\">\n                    <div className=\"LapHead\">{props.firstHeading}</div>\n                    <p className=\"LapHeadmain\">{props.secondHeading}</p>\n                </div>\n                <div className='LapButtonSection'>\n                    <button className='outlined-primary-btn'>See All</button>\n                </div>\n            </div>\n            <Carousel\n                className='product-slider-corousel'\n                navButtonsAlwaysVisible={true} autoPlay={false} indicators={false} animation='slide'\n                navButtonsWrapperProps={{\n                    style:{\n                        top: '-70px',\n                    }\n                }}\n                navButtonsProps={{\n                    style: {\n                        border: '1px solid #C4C4C4',\n                        background: 'rgba(255, 255, 255, 0.5)',\n                        color: '#000'\n                    }\n                }}\n            >\n                {\n                    items.map((item, i) => <Item key={i} item={item} {...props} />)\n                }\n            </Carousel>\n        </div>\n    )\n}\n\nfunction Item(props) {\n    return (\n        <div className=\"slider-style\">\n            <Grid container spacing={1} className=\"slider-grid\">\n                <Grid item xs={12}>\n                    {/* <div className='topSection'>\n                        <div className=\"LapHeadSection\">\n                            <div className=\"LapHead\">{props.firstHeading}</div>\n                            <p className=\"LapHeadmain\">{props.secondHeading}</p>\n                        </div>\n                        <div className='LapButtonSection'>\n                            <button className='outlined-primary-btn'>See All</button>\n                        </div>\n                    </div> */}\n                    <div className=\"LapSectionContent\">\n                        {props.item.page === 1 ?\n                            <Grid container justifyContent=\"center\" spacing={4}>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg1} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg2} />\n                                </Grid>\n                                <Grid item xs={3} >\n                                    <Product prodImg={productImg3} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg4} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg5} />\n                                </Grid>\n                            </Grid>\n                            : null}\n                        {props.item.page === 2 ?\n                            <Grid container justifyContent=\"center\" spacing={4}>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg5} />\n                                </Grid>\n                                <Grid item xs={3} >\n                                    <Product prodImg={productImg3} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg2} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg4} />\n                                </Grid>\n                                <Grid item xs={3}>\n                                    <Product prodImg={productImg1} />\n                                </Grid>\n                            </Grid>\n                            : null}\n                    </div>\n                </Grid>\n            </Grid>\n        </div>\n    )\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/slider/slider.scss",
    "content": ".slider-style {\n    padding: 0 70px;\n}\n\n.slider-style .productCard {\n    box-shadow: none !important;\n}\n\n.slider-style .productCard .MuiCardMedia-root {\n    width: auto !important;\n    height: 270px !important;\n    margin: auto;\n}\n\n.slider-style .productCard:hover {\n    box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.16) !important;\n    border: 1px solid #E5E5E5;\n    border-radius: 4px;\n    cursor: pointer;\n}\n\n/* product slider */\n.product-slider-corousel>div {\n    min-height: 454px;\n}\n\n.slider-section button[aria-label='Previous'] {\n    margin-left: 86px;\n}\n\n.slider-section button[aria-label='Next'] {\n    margin-right: 86px;\n}\n\n.slider-section .topSection {\n    display: flex;\n    margin-bottom: 30px;\n    align-items: center;\n    padding: 70px 70px 0 70px;\n}\n\n.slider-section .LapHeadSection {\n    margin-right: auto;\n}\n\n.slider-section .LapButtonSection {\n    display: flex;\n}\n\n.slider-section .MuiGrid-root {\n    overflow: hidden;\n}\n\n.slider-section .LapSectionContent>div {\n    width: calc(112% + 40px) !important;\n    flex-wrap: nowrap !important;\n}\n\n.slider-section .LapSectionContent .MuiCard-root {\n    margin: auto !important;\n}\n\n.slider-section .LapHead {\n    font: normal normal normal 14px/21px \"Poppins\";\n    letter-spacing: -0.31px;\n    color: #302f2f;\n    text-transform: capitalize;\n    opacity: 1;\n}\n\n.slider-section .LapHeadmain {\n    line-height: unset !important;\n    font: normal normal 600 25px/56px \"Poppins\";\n    color: #111111;\n    text-transform: uppercase;\n    opacity: 1;\n    margin-bottom: 0;\n}\n\n@media screen and (max-width : 1250px) {\n    .slider-style .LapSectionContent>div {\n        width: calc(100% + 40px) !important;\n    }\n\n    .slider-style .LapSectionContent>div>div:last-child {\n        display: none;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/uploadFile/uploadFile.js",
    "content": "import React from \"react\";\nimport { connect } from 'react-redux';\n// import { withRouter } from \"react-router-dom\";\n// import Alert from \"react-s-alert\";\nimport { ProductService } from '../../services';\n\nimport SearchIconNew from '../../assets/images/original/Contoso_Assets/product_page_assets/upload_icon.svg'\nimport { DropzoneArea } from 'mui-file-dropzone'\nimport { useNavigate } from \"react-router-dom\";\nimport './uploadFile.scss'\n// class UploadFile extends Component {\n    // constructor(props) {\n    //     super(props);\n    //     this.uploadFile = this.uploadFile.bind(this);\n    // }\n\n    // componentDidMount() {\n    //     // const html = '<label className=\"upload__label\" htmlFor=\"upload_image\"><img src='+`${SearchIconNew}`+' alt=\"upload\" /><span className=\"upload__info\"><span className=\"upload__subtitle fs-14\" style=\"color: black, fontSize: 14px\">Drag an image or upload a file</span><span className=\"upload__title\"></span></span></label>';\n    //     // document.getElementsByClassName('MuiDropzoneArea-root')[0].innerHTML += html;\n    // }\n\n    \n    function UploadFile(props) {\n        const navigate = useNavigate();\n        const uploadFile = (e) => {\n            const file = e[0];\n            if (file) {\n                const formData = new FormData();\n                formData.append(\"file\", file);\n    \n                ProductService.getRelatedProducts(formData, props.userInfo.token)\n                    .then((relatedProducts) => {\n                        if (relatedProducts.length > 1) {\n                            navigate(\"/suggested-products-list\",{\n                                state: { relatedProducts },\n                            });\n                        } else {\n                            navigate({\n                                pathname: `/product/detail/${relatedProducts[0].id}`,\n                            });\n                        }\n                    })\n                    .catch(() => {\n                        // Alert.error(\"There was an error uploading the image, please try again\", {\n                        //     position: \"top\",\n                        //     effect: \"scale\",\n                        //     beep: true,\n                        //     timeout: 6000,\n                        // });\n                    });\n            }\n        }\n    \n        // const resetFileValue = (e) => {\n        //     e.target.value = null;\n        // }\n        const { title, subtitle } = props;\n        return (\n            <form className=\"upload\">\n                {/* <Alert stack={{ limit: 1 }} /> */}\n                <DropzoneArea\n                    showPreviews={false}\n                    id=\"searchByImage\"\n                    acceptedFiles={['image/jpeg', 'image/png', 'image/bmp']}\n                    onChange={uploadFile.bind(this)}\n                    filesLimit={1}\n                />\n                {/* <input\n                    className=\"upload__input\"\n                    id=\"upload_image\"\n                    name=\"upload_image\"\n                    accept=\"image/png, image/jpeg\"\n                    type=\"file\"\n                    onChange={this.uploadFile}\n                    onClick={this.resetFileValue}\n                /> */}\n                <label className=\"upload__label\" htmlFor=\"upload_image\">\n                    <img src={SearchIconNew} alt=\"upload\" />\n                    <span className=\"upload__info\">\n                        {subtitle ? <span className=\"upload__subtitle fs-14\" style={{ color: 'black', fontSize: '14px' }}>{subtitle}</span> : null}\n                        <span className=\"upload__title\">{title}</span>\n                    </span>\n                </label>\n            </form>\n        );\n    }\n// }\n\nconst mapStateToProps = state => state.login;\n\nexport default connect(mapStateToProps)(UploadFile);"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/components/uploadFile/uploadFile.scss",
    "content": ".appbar .searchbar-upload{\n    position: absolute;\n    z-index: 110;\n    width: 100%;\n    text-align: center;\n    padding: 30px;\n    background-color: #fff;\n    border: 1px solid #DFDFDF;\n  }\n  .appbar .searchbar-upload .upload{\n    margin: auto;\n    max-width: 100%;\n    margin-top: 10px;\n    background: transparent !important;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n  .appbar .searchbar-upload .upload .MuiDropzoneArea-textContainer, .appbar .searchbar-upload .upload .MuiDropzoneArea-textContainer .MuiDropzonePreviewList-root{\n    display: none;\n  }\n  .appbar .searchbar-upload .upload .MuiDropzoneArea-root{\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    /* background: #FAFAFA !important; */\n    background: transparent;\n  }\n  .appbar .searchbar-upload .upload .upload__label{\n    box-shadow: none;\n    /* background: #FAFAFA !important; */\n    position: absolute;\n    z-index: -1;\n  }\n  .appbar .searchbar-upload .upload .upload__label img{\n    width: 50px;\n  }"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/helpers/errorsHandler.js",
    "content": "import axios from 'axios'\n// import toast from './toast'\nimport { handleUnathenticatedRequest } from './refreshJWTHelper'\n\n\nfunction errorResponseHandler(error) {\n    // if it's token expired error, solve it silently\n    if (isTokenExpiredError(error)) {\n        return handleUnathenticatedRequest(error);\n    }\n\n    // otherwise, if has response show the error\n    // if (error.response) {\n    //     toast.error(error.response.data || error.response.statusText);\n    // }\n\n    // check for errorHandle config\n    if (error.config.hasOwnProperty('errorHandle') && error.config.errorHandle === false) {\n        return Promise.reject(error);\n    }\n}\n\nfunction isTokenExpiredError(error) {    \n    if (error.response && error.response.status === 401) {\n        return true;\n    }\n\n    if (error.response && error.response.data) {\n\n        const errorResponseData = error.response.data;\n\n        if (typeof errorResponseData === 'string') {\n            return !!errorResponseData.match(/401|[Uu]nauthorized/);\n        }\n    }\n\n    return false;\n}\n\n// apply interceptor on response\naxios.interceptors.response.use(\n    response => response,\n    errorResponseHandler   \n);\n\nexport default errorResponseHandler;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/helpers/localStorage.js",
    "content": "export const loadState = () => {\n  try {\n    const serializedState = localStorage.getItem('state');\n    if (serializedState === null) {\n      return undefined;\n    }\n    return JSON.parse(serializedState);\n  } catch (err) {\n    return undefined;\n  }\n}; \n\nexport const getItemValue = (key) => {\n    const state = loadState();\n    return state[key];\n}  \nexport const setItemValue = (key, value) => {\n    const state = loadState();\n    const newState = { ...state, [key]: value };\n    saveState(newState);\n}\n\nexport const saveState = (state) => {\n  try {\n      const serializedState = JSON.stringify(state);\n    localStorage.setItem('state', serializedState);\n  } catch {\n    // ignore write errors\n  }\n};"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/helpers/refreshJWTHelper.js",
    "content": "import axios from 'axios';\nimport {\n    getRefreshToken,\n    setAccessToken,\n    setRefreshToken\n} from './tokensHelper';\nimport { ConfigService } from '../services'\nimport AuthB2CService from '../services/authB2CService';\n\nlet failedRequestToRetry = [];\nlet isAlreadyFetchingAccessToken = false;\n\nconst addSubscriber = (callback) => failedRequestToRetry.push(callback);\n\nconst fetchNewJWTokens = async (refreshToken) => {\n    const dataToken = {\n        token: refreshToken\n    }\n    const response = await axios.put(`${ConfigService._apiUrl}/auth/refresh`, dataToken);\n\n    if (!response.data) {\n        return null;\n    }\n\n    return {\n        newAccessToken: response.data.access_token.token,\n        newRefreshToken: response.data.refresh_token\n    };\n}\n\nconst onAccessTokenFetched = (accessToken) => {\n    failedRequestToRetry.forEach(callback => callback(accessToken));\n    failedRequestToRetry = [];\n}\n\nexport const handleUnathenticatedRequest = async (authenticationError) => {\n    try {\n        const { response: errorResponse } = authenticationError;\n        const useB2cFromEnv = process.env.REACT_APP_USE_B2C ? JSON.parse(process.env.REACT_APP_USE_B2C.toLowerCase()) : false;\n\n        if (useB2cFromEnv) {\n            return handleUnathenticatedRequestFromB2c(errorResponse, authenticationError);\n        }\n\n        return handleUnathenticatedRequestFromFake(errorResponse, authenticationError);\n    } catch (err) {\n        return Promise.reject(err);\n    }\n}\n\nconst handleUnathenticatedRequestFromFake = async (errorResponse, authenticationError) => {\n    const refreshToken = getRefreshToken();\n\n    if (!refreshToken) {\n        // We can't refresh, throw the error\n        return Promise.reject(authenticationError);\n    }\n\n    if (!isAlreadyFetchingAccessToken) {\n        isAlreadyFetchingAccessToken = true;\n\n        const newJWTokens = await fetchNewJWTokens(refreshToken);\n\n        if (!newJWTokens) {\n            return Promise.reject(authenticationError);\n        }\n\n        setAccessToken(newJWTokens.newAccessToken);\n\n        setRefreshToken(newJWTokens.newRefreshToken);\n\n        isAlreadyFetchingAccessToken = false;\n        onAccessTokenFetched(newJWTokens.newAccessToken);\n    }\n\n    return retryOriginalRequest(errorResponse);\n}\n\nconst handleUnathenticatedRequestFromB2c = async (errorResponse, authenticationError) => {\n    const authB2CService = new AuthB2CService();\n    let accessToken;\n\n    try {\n        accessToken = await authB2CService.getToken();\n    } catch (e) {\n        await authB2CService.login();\n        accessToken = await authB2CService.getToken();\n   }\n \n    if (!accessToken) {\n        // We can't refresh, throw the error\n        return Promise.reject(authenticationError);\n    }\n\n    setAccessToken(accessToken);\n\n    return retryOriginalRequest(errorResponse);\n}\n\nconst retryOriginalRequest = (errorResponse) => {\n    return new Promise(resolve => {\n        addSubscriber(accessToken => {\n            errorResponse.config.headers.Authorization = `Bearer ${accessToken}`;\n            resolve(axios(errorResponse.config));\n        });\n    });\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/helpers/toast.js",
    "content": "import 'izitoast/dist/css/iziToast.min.css'\nimport iZtoast from 'izitoast'\n\nconst toast = {\n    error: (message, title = 'Error') => {\n        return iZtoast.error({\n            title: title,\n            message: message,\n            position: 'bottomCenter'\n        });\n    },\n    success: (message, title = 'Success') => {\n        return iZtoast.success({\n            title: title,\n            message: message,\n            position: 'bottomCenter'\n        });\n    }\n};\n\nexport default toast;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/helpers/tokensHelper.js",
    "content": "import { getItemValue, setItemValue } from \"./localStorage\";\n\nexport const getRefreshToken = () => getItemValue('refreshToken');\nexport const getAccessToken = () => getItemValue('token');\n\nexport const setRefreshToken = (newRefreshToken) => setItemValue('refreshToken', newRefreshToken);\nexport const setAccessToken = (newAccessToken) => setItemValue('token', newAccessToken);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/index.css",
    "content": ":root {\n  --primaryblue: #2974f0;\n  --fontInter: 'Inter';\n  --fontRoboto: 'Roboto';\n  --fontHeebo: 'Heebo';\n  --white: #ffffff;\n}\nbody {\n  margin: 0;\n  padding: 0;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  font-family: var(--fontRoboto) !important;\n}\n.fs-12{\n  font-size: 12px!important;\n}\n.fs-14{\n  font-size: 14px!important;\n}\n.fs-16{\n  font-size: 16px!important;\n}\n.fs-18{\n  font-size: 18px!important;\n}\n.fs-20{\n  font-size: 20px!important;\n}\n.fs-22{\n  font-size: 22px!important;\n}\n.fs-24{\n  font-size: 24px!important;\n}\n.p-0{\n  padding: 0 !important;\n}\n.pt-10{\n  padding-top: 10px;\n}\n.m-0{\n  margin: 0 !important;\n}\n.mr-1{\n  margin-right: 10px !important;\n}\n.mb-1{\n  margin-bottom: 10px !important;\n}\n.mb-3{\n  margin-bottom: 30px !important;\n}\n.mt-1{\n  margin-top: 10px !important;\n}\n.mt-2{\n  margin-top: 20px !important;\n}\n.fw-regular{\n  font-weight: 400 !important;\n}\n.text-danger{\n  color: red !important;\n}\n.outlined-primary-btn{\n  text-align: center;\n  font: normal normal medium 16px/21px var(--fontRoboto);\n  color: #302f2f;\n  text-transform: capitalize;\n  opacity: 1;\n  border: 1px solid #707070;\n  cursor: pointer;\n  min-width: 112px;\n  height: 53px;\n  background: none;\n  border-radius: 5px;\n  font-weight: 500;\n  font-size: 16px;\n  font-family: var(--fontRoboto);\n}\n.outlined-primary-btn:hover{\n  border-color: #2874F0 !important;\n  color: #2874F0 !important;\n}\n.box-shadow-0{\n  box-shadow: none !important;\n}\n.justify-content-end{\n  justify-content: flex-end;\n}\n.text-transform-capitalize{\n  text-transform: capitalize !important;\n}\n#box {\n  width: 100%;\n  height: 35px;\n  box-shadow: 0px 4px 6px #00000029;\n}\n\n#box:after {\n  content:\"\";\n}\n\n/* mainHeader */\n.mainHeader{\n  position: fixed;\n  z-index: 100;\n  width: 100%;\n}\n\n@media screen and (min-width : 320px) and (max-width : 767px) {\n  .flex-xs-fill {\n      -ms-flex: 1 1 auto!important;\n      flex: 1 1 auto!important;\n  }\n  .flex-xs-column{\n      flex-direction: column !important;\n  }\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/index.js",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\nimport { Provider } from 'react-redux';\nimport store from './store';\nimport { BrowserRouter } from \"react-router-dom\";\n\nconst root = ReactDOM.createRoot(document.getElementById('root'));\nroot.render(\n  <React.StrictMode>\n    <Provider store={store}>\n      <BrowserRouter>\n        <App />\n      </BrowserRouter>\n    </Provider>\n  </React.StrictMode>\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/main.scss",
    "content": "// 1. Configuration and helpers\n@import\n    'styles/abstracts/variables',\n    'styles/abstracts/mixins/ellipsis',\n    'styles/abstracts/mixins/fonts',\n    'styles/abstracts/mixins/font-scale',\n    'styles/abstracts/mixins/font-placeholders',\n    'styles/abstracts/mixins/loader';\n\n// 2. Vendors\n@import\n    'styles/vendor/normalize';\n\n// 3. Base stuff\n@import\n    'styles/base/base',\n    'styles/base/typography',\n    'styles/base/utilities';\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/arrivals/arrivals.js",
    "content": "import React from \"react\";\n\nimport Slider from \"../../components/slider/slider\";\nimport Banner from \"../home/sections/banner\";\nimport './arrivals.scss'\nconst Arrivals = (props) => {\n    return (\n        <div className=\"arrivals\">\n            <Banner firstHeading=\"Newly Launched Lunar Shift Controller\" secondHeading=\"Textured triggers and bumpers | Hybrid D-pad | Button mapping | Bluetooth® technology\"/>\n            <Slider firstHeading=\"Newly Arrived\" secondHeading=\"CONTROLLERS\"/>\n            <hr/>\n            <Slider firstHeading=\"Newly Arrived\" secondHeading=\"LAPTOPS\"/>\n            <hr/>\n            <Slider firstHeading=\"Newly Arrived\" secondHeading=\"GAMING ACCESSORIES\"/>\n            <hr/>\n        </div>\n    );\n};\n\nexport default Arrivals;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/arrivals/arrivals.scss",
    "content": ".arrivals{\n    margin-top: 190px;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/cart/cart.js",
    "content": "import { Grid, Button, TextField, InputAdornment, Chip } from \"@mui/material\";\nimport React, { useCallback, useEffect } from \"react\";\nimport QuantityPicker from \"../../components/quantityCounter/productCounter\";\nimport Breadcrumb from \"../../components/breadcrumb/breadcrumb\";\nimport { Link, useLocation, useNavigate } from 'react-router-dom';\nimport { CartService } from \"../../services\";\nimport { connect } from \"react-redux\";\nimport LoadingSpinner from \"../../components/loadingSpinner/loadingSpinner\";\nimport { getCartQuantity } from \"../../actions/actions\";\nimport './cart.scss'\n\nfunction Cart(props) {\n  const textInput = React.useRef(null);\n  const validCoupons = ['discount10','discount15'];\n  const [coupon, setCoupon] = React.useState('DISCOUNT15');\n  const [invalidCoupon, setInvalidCoupon] = React.useState(false);\n  const [discountPrice, setDiscountPrice] = React.useState(0);\n  const [discountPercentage, setDiscountPercentage] = React.useState(15);\n  const [cartItems, setCartItems] = React.useState([]);\n  const [loading, setLoading] = React.useState(false)\n  const [total, setTotal] = React.useState(0);\n  const [grandtotal, setgrandTotal] = React.useState(0);\n  const [delivery, setDelivery] = React.useState(10);\n  const navigate = useNavigate();\n\n  const getCartItems = useCallback(async () => {\n    setLoading(true)\n    let items;\n    //After logging take up cart detail from API\n    if (props.userInfo.loggedIn) {\n      let res = await CartService.getShoppingCart(props.userInfo.token)\n      items = res ? res : []\n    } else {\n      items = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : []\n    }\n      let sum = 0;\n      if (items.length > 0) {\n        items.map((item) => {\n          return (\n            sum += item.price * item.quantity\n          )\n        })\n        setTotal(sum);\n        let discount = (sum/100)*discountPercentage;\n        setDiscountPrice(Math.ceil(discount));\n        let deliveryChrge = 10;\n        setDelivery(deliveryChrge)\n        let totalval = parseInt((sum - discount) + deliveryChrge);\n        setgrandTotal(totalval);\n      }\n      setCartItems(items)\n      setLoading(false)\n      let quantity = items.length;\n      props.getCartQuantity(quantity)\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [props])\n\n  useEffect(() => {\n    getCartItems()\n  }, [getCartItems]);\n\n  useEffect(() => {\n    if(total > 0){\n      let discount = (total/100)*discountPercentage;\n      setDiscountPrice(Math.ceil(discount));\n      let totalval = parseInt((total - discount) + delivery);\n      setgrandTotal(totalval);\n    }\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [discountPercentage]);\n\n  const location = useLocation();\n  const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-', ' ');\n  const checkDiscount = () => {\n    if(validCoupons.includes(textInput.current.value.toLowerCase())){\n      switch (textInput.current.value.toLowerCase()) {\n        case 'discount15':\n          setDiscountPercentage(15);\n          break;\n        case 'discount10':\n          setDiscountPercentage(10);\n          break;\n        default:\n          break;\n      }\n      setCoupon(textInput.current.value);\n      textInput.current.value = ''\n      setInvalidCoupon(false);\n    }else{\n      setInvalidCoupon(true)\n    }\n  }\n\n\n  const removeFromCart = async (item) => {\n    if (props.userInfo.loggedIn) {\n      await CartService.deleteProduct(item, props.userInfo.token)\n    }else{\n      let cartItem = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : [];\n      var filtered = cartItem.filter(function(el) { return el.name !== item.name; });\n      localStorage.setItem('cart_items',JSON.stringify(filtered))\n    }\n    getCartItems()\n  }\n\n  return (\n    <>\n      {loading ? <LoadingSpinner /> : <div className=\"CartMain\">\n        <Breadcrumb currentPath={currentCategory} />\n        <div className=\"CartSection\">\n          <div className=\"CartTopHeadPart\">\n            <h5 className=\"MyCartHeading\">My Cart</h5>\n            {cartItems.length > 0 && <>\n              <h5 className=\"CartTopHeadTotal\">Grand Total:</h5>\n              <h5 className=\"CartTopGrandTotal\">${grandtotal.toFixed(2)}</h5>\n              <Button variant=\"contained\" className=\"PlaceOrderButton\">Place Order</Button>\n            </>}\n          </div>\n          <hr />\n\n          <div className=\"innerCart\">\n            {cartItems.length === 0 &&\n              <Grid container>\n                <Grid item xs={12} container className=\"CartHeadings justify-content-center flex-column align-items-center\">\n                  <h1 className=\"text-dark\">Your Cart is empty</h1>\n                  <Button variant=\"contained\" className=\"PlaceOrderButton\" onClick={() => navigate('/list/all-products')}>Start Shopping</Button>\n                </Grid>\n              </Grid>\n            }\n            {cartItems.length > 0 && <div className=\"cart-header d-none d-lg-block d-md-block\">\n              <Grid container>\n                <Grid item xs={12} container className=\"CartHeadings\">\n                  <Grid item xs={1}>\n                    <span style={{ position: 'absolute' }}>Product Name</span>\n                  </Grid>\n                  <Grid item xs={11} container className=\"CartProducts\">\n                    <Grid item xs={2}></Grid>\n                    <Grid item xs={2}>\n                      Price\n                    </Grid>\n                    <Grid item xs={2}>\n                      Qty\n                    </Grid>\n                    <Grid item xs={2}>\n                      Subtotal\n                    </Grid>\n                  </Grid>\n                </Grid>\n              </Grid>\n              <hr />\n            </div>}\n            {cartItems.map((item, key) => (\n              <div key={key}>\n                <Grid container className=\"allProductlist\">\n                  <Grid item lg={1} md={1} sm={8} xs={12} onClick={() => navigate('/product/detail/'+item.productId)} role=\"button\">\n                    <img src={item.imageUrl} className=\"imagesection\" alt=\"\" />\n                  </Grid>\n                  <Grid item lg={11} md={11} xs={12} className=\"CartProducts\">\n                    <Grid item xs={12} className=\"Productname\">\n                      {item.name}\n                    </Grid>\n                    <Grid item xs={12} className=\"Producttype\">\n                      Price / Unit : ${item.price.toFixed(2)}\n                    </Grid>\n                    <Grid item xs={12} container className=\"align-items-center\">\n                      <Grid item lg={2} md={2} xs={12} className=\"Productqty\">\n                        Qty&nbsp;&nbsp;\n                        <QuantityPicker max={10} min={1} qty={item.quantity} detailProduct={item} token={props.userInfo.token} getCartItems={getCartItems} page=\"cart\" loggedIn={props.userInfo.loggedIn} />\n                      </Grid>\n                      <Grid item lg={2} md={2} xs={12} className=\"Productprice\">\n                        <b className=\"cart-hidden-detail mt-2 mb-2 mr-2 d-lg-none  d-inline-block\">Price : </b>${item.price.toFixed(2)}\n                      </Grid>\n                      <Grid item lg={2} md={2} xs={12} className=\"Productprice\">\n                        <b className=\"cart-hidden-detail mt-2 mb-2 mr-2 d-lg-none  d-inline-block\">Qty : </b>{item.quantity}\n                      </Grid>\n                      <Grid item lg={2} md={2} xs={12} className=\"Productprice\">\n                        <b className=\"cart-hidden-detail mt-2 mb-2 mr-2 d-lg-none  d-inline-block\">Subtotal : </b>${(item.price * item.quantity).toFixed(2)}\n                      </Grid>\n                      {/* <Grid item lg={2} md={2} xs={6} className=\"Productlinks\">\n                      <Link to=\"#\" className=\"wishlistlink\">\n                        Move to wishlist\n                      </Link>\n                    </Grid> */}\n                      <Grid item lg={2} md={2} xs={12} className=\"Productlinks\">\n                        <Link to=\"#\" className=\"removelink\" onClick={() => removeFromCart(item)}>\n                          Remove\n                        </Link>\n                      </Grid>\n                    </Grid>\n                  </Grid>\n                </Grid>\n                <hr />\n              </div>\n            ))}\n\n            {cartItems.length > 0 && <div>\n              <Grid container className=\"couponOrderSection\">\n                <Grid item lg={4} md={5} xs={12}>\n                  <Grid container>\n                    <Grid item xs={12}>\n                      <h2 className=\"CouponHeading \"> Coupons </h2>\n                    </Grid>\n\n                    <Grid item xs={12} className=\"Couponbarsection\">\n                      <div className=\"pincodebar\">\n                        <span>\n                          <TextField\n                            className=\"pincodesearchbar\"\n                            // label=\"Enter coupon code\"\n                            id=\"outlined-error-helper-text\"\n                            error={invalidCoupon}\n                            helperText={invalidCoupon ? \"This coupon is invalid\" : \"\"}\n                            placeholder=\"Enter coupon code\"\n                            variant=\"outlined\"\n                            inputRef={textInput}\n                            InputProps={{\n                              endAdornment: (\n                                <InputAdornment position='end'>\n                                  <Button className={`${coupon.length >= 1 ? \"pinsearchbtn\" : \"pinsearchbtn\"}`} onClick={() => checkDiscount()}>CHECK</Button>\n                                </InputAdornment>\n                              ),\n                            }}\n                          />\n                        </span>\n                      </div>\n                    </Grid>\n\n                    {coupon && <Grid item xs={12} >\n                      <Chip label={coupon} onDelete={() => {setCoupon('');setDiscountPercentage(0)}} className=\"CouponChip\" />\n                      <hr />\n                    </Grid>}\n                  </Grid>\n                </Grid>\n                <Grid item lg={3} md={2} className=\"d-none d-lg-block d-md-block\"></Grid>\n                <Grid item lg={5} md={5} xs={12}>\n                  <Grid container>\n                    <Grid item xs={12} className=\"CouponHeading \">\n                      Order Summary <hr style={{ margin: '8px 0 18px 0' }} />\n                    </Grid>\n                    <Grid item xs={10} className=\"OrderSubHeading\">\n                      Sub Total\n                    </Grid>\n                    <Grid item xs={2} className=\"OrderSubPrice\" data-testid=\"subtotal\">\n                      ${total.toFixed(2)}\n                    </Grid>\n                    <Grid item xs={10} className=\"OrderSubHeading\">\n                      Discount\n                    </Grid>\n                    <Grid item xs={2} className=\"OrderSubPrice text-success\" data-testid=\"discount\">\n                      -${discountPrice.toFixed(2)}\n                    </Grid>\n                    <Grid item xs={10} className=\"OrderSubHeading\">\n                      Delivery Fee\n                    </Grid>\n                    <Grid item xs={2} className=\"OrderSubPrice\">\n                      ${delivery.toFixed(2)}\n                    </Grid>\n                    <Grid item xs={10} className=\"OrdertotalHeading\">\n                      Grand Total\n                    </Grid>\n                    <Grid item xs={2} className=\"OrderTotalPrice\">\n                      ${grandtotal.toFixed(2)}\n                    </Grid>\n                    <Grid item xs={12}>\n                      <hr />\n                    </Grid>\n                  </Grid>\n                </Grid>\n\n                <Grid item xs={12} className=\"OrderButtonsection\">\n                  <Button variant=\"contained\" className=\"PlaceOrderButton\">\n                    Place Order\n                  </Button>\n                </Grid>\n              </Grid>\n            </div>}\n          </div>\n        </div>\n      </div>}\n    </>\n  );\n}\nconst mapStateToProps = state => state.login;\nconst mapDispatchToProps = (dispatch) => ({\n  getCartQuantity: (value) => dispatch(getCartQuantity(value)),\n})\nexport default connect(mapStateToProps, mapDispatchToProps)(Cart);"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/cart/cart.scss",
    "content": ".CartMain{\n  margin-top: 192px;\n.CartSection {\n    padding: 0 70px 0 70px;\n    .innerCart{\n        padding: 0 30px;\n    }\n      .CartTopHeadPart{\n        display: flex;\n        align-items: center;\n        padding: 8px 0;\n      }\n  }\n  .CartSection .MyCartHeading {\n    font: normal normal 600 24px/44px \"Inter\";\n    letter-spacing: 0px;\n    color: #000000;\n    margin: 0;\n    margin-right: auto;\n  }\n  .CartSection .imagesection {\n    width: 100%;\n    // height: 100px;\n  }\n  .CartSection .CartTopGrandTotal {\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #171520;\n    margin: 0 16px;\n    font-weight: 600;\n  }\n  .CartSection .CartTopHeadTotal {\n    font: normal normal normal 14px/20px \"Inter\";\n    letter-spacing: 0px;\n    color: #171520;\n    margin: 0;\n  }\n  \n  /*===================search bar===========================*/\n  .CartSection {\n    font-family: \"Inter\";\n  }\n  .CartSection .pincodesearchbar {\n    min-width: 100%;\n    padding: 14px 0 16px 0;\n    &:focus{\n      border: none\n    }\n    input::placeholder{\n      font-size: 14px;\n      font-family: 'Inter';\n      font-weight: 400;\n      color: rgba(98, 98, 98, 0.9);\n    }\n  }\n  .CartSection .pincodebar {\n    width: 100%;\n  }\n  .CartSection .pincodebar input::placeholder{\n    text-align: left;\n    font: normal normal normal 14px/20px \"Inter\";\n    letter-spacing: 0px;\n  }\n  \n  \n  .CartSection .pinsearchbtn {\n    border-left: 1px solid #f1f1f1;\n    height: 56px;\n    float: right;\n    position: absolute;\n    right: 1px;\n    background: #fafafa;\n    width: 82px;\n    padding: 16px;\n    &:focus{\n      outline: none !important;\n    }\n  }\n  .CartSection .pinsearchbtnactive{\n    background: #2874f0;\n    color: white;\n    &:hover{\n        background: #2874f0;\n        color: white;\n    }\n  }\n  .CartSection .PlaceOrderButton {\n    background: #2874f0 0% 0% no-repeat padding-box;\n    border-radius: 8px;\n    width: 180px;\n    height: 40px;\n    float: right;\n    text-align: center;\n    letter-spacing: 0px;\n    color: #ffffff;\n    text-transform: capitalize;\n    font-weight: 400;\n    font-size: 16px !important;\n    box-shadow: none !important;\n  }\n  .CartSection .allProductlist {\n    margin: 23px 0px 0px 0px;\n  }\n  .CartSection .CartHeadings {\n    margin: 34px 0px 0px 0px;\n    text-align: left;\n    letter-spacing: 0px;\n    color: #000000;\n    font-size: 16px;\n    font-weight: 500;\n  }\n  .CartSection .Productname {\n    letter-spacing: 0px;\n    color: #171520;\n    text-align: left;\n    font-size: 16px;\n  }\n  .CartSection .Producttype {\n    text-align: left;\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #626262;\n    margin: 8px 0px 18px 0px;\n  }\n  .CartSection .Producttype {\n    text-align: left;\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #626262;\n    margin-bottom: 10px;\n  }\n  \n  \n  \n  .CartSection .Productprice {\n    text-align: left;\n    font-size: 14px;\n    letter-spacing: 0px;\n    color: #171520;\n  }\n  .CartSection .Productlinks {\n    text-align: center;\n    text-decoration: underline;\n    font-size: 14px;\n    font-weight: 600;\n    letter-spacing: 0px;\n  }\n  .CartSection .wishlistlink {\n    color: #000000;\n  }\n  .CartSection .removelink {\n    color: #b00020;\n  }\n  .CartSection .CartProducts {\n    padding-left: 16px;\n    .Productqty{\n        color: #626262\n    }\n  }\n  \n  \n  .CartSection .CouponHeading {\n    text-align: left;\n    font-size: 20px;\n    font-weight: 600;\n    margin: 42px 0px 0px 0px;\n    letter-spacing: 0px;\n    color: #000000;\n  }\n  .CartSection .OrderSubHeading {\n    text-align: left;\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #626262;\n    margin-top: 12px;\n    font-weight: 500;\n  }\n  .CartSection .OrdertotalHeading {\n    text-align: left;\n    font-weight: 600;\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #171520;\n    margin: 12px 0px 14px 0px;\n  }\n  .CartSection .OrderSubPrice {\n    text-align: right;\n    font-size: 16px;\n    letter-spacing: 0px;\n    color: #171520;\n    margin-top: 12px;\n    font-weight: 500;\n  }\n  .CartSection .OrderTotalPrice {\n    font-weight: 600;\n    font-size: 16px;\n    text-align: right;\n    margin-top: 12px;\n    letter-spacing: 0px;\n    color: #171520;\n    margin: 12px 0px 14px 0px;\n  }\n  \n  .CartSection .CouponChip {\n    background: #29ae49 0% 0% no-repeat padding-box;\n    border: 1px solid #f1f1f1;\n    border-radius: 74px;\n    // margin: 16px 0px 8px 0px; \n  }\n  .CartSection .MuiChip-root {\n    height: 40px;\n    margin: 0;\n  }\n  \n  .CartSection .MuiChip-label {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n    text-align: left;\n    font: normal normal normal 14px/20px \"Inter\";\n    letter-spacing: 0px;\n    color: #ffffff;\n    padding-left: 16px;\n    padding-right: 8px;\n  }\n  \n  .CartSection .MuiChip-deleteIcon {\n    color: white !important;\n    margin: 0 16px 0 0 !important;\n  }\n  .CartSection .nocouponheading {\n    text-align: left;\n    font: normal normal normal 14px/20px \"Inter\";\n    letter-spacing: 0px;\n    color: #62626280;\n  }\n  \n  .CartSection .OrderButtonsection{\n    margin-top: 34px;\n  }\n}\n\n//Responsive\n@media screen and (min-width : 320px) and (max-width : 767px) {\n  .CartMain .CartSection {\n      padding: 0 35px;\n  }\n  .CartMain .CartSection .CartTopHeadPart{\n      flex-direction: column;\n      align-items: end;\n  }\n  .CartMain .CartSection .CartTopGrandTotal{\n      margin: 10px 0;\n  }\n  .CartMain .CartSection .Productlinks{\n      margin: 20px 0;\n  }\n  .CartMain .CartSection .Productprice{\n      display: flex;\n      align-items: center;\n  }\n  .CartMain .CartSection .Productprice > b{\n      margin-right: auto !important;\n  }\n  .CartMain .CartSection .Productname, .CartMain .CartSection .Producttype, .CartMain .CartSection .CartProducts .Productqty{\n      text-align: center;\n  }\n}\n\n@media screen and (min-width : 768px) and (max-width : 899px) {\n  .CartMain .CartSection .Productlinks{\n      margin: 20px 0;\n  }\n  .cart-header{\n      display: none !important;\n  }\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/detail/detail.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains styles that are specific to the Detail Products page.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n.ProductContainerSectionMain{\n  margin-top:192px;\n  .ProductContainerSection{\n  padding: 0px 70px;\n  .ProductDetailsSection {\n    font-family: \"Inter\";\n    padding-top: 16px;\n    .MuiGrid-container{\n      align-items: center;\n      justify-content: center;\n      padding: 20px;\n    .productdetailsimagediv{\n      background-size:contain;\n      background-position-x:center;\n      background-position-y:center;\n      background-repeat:no-repeat;\n      min-height: 600px;\n      .productdetailsimage {\n        margin: auto;\n        display: block;\n        mix-blend-mode: darken;\n        opacity: 0;\n        width: 100%;\n        max-height: 600px;\n        // height: 100%;\n      }\n    }\n  }\n    .CartButton {\n      background: #2874f0 0% 0% no-repeat padding-box;\n      border-radius: 8px;\n      opacity: 1;\n      width: 309px;\n      height: 56px;\n      text-transform: none;\n      box-shadow: none!important;\n      font-weight: 600;\n      color: white;\n      margin: 30px 0 0 0;\n      text-align: center;\n      font: normal normal 600 14px/24px Inter;\n      letter-spacing: 0px;\n      opacity: 1;\n      margin-right: 16px;\n      .MuiButton-startIcon{\n        margin-right: 10px;\n      }\n    }\n    \n    .WishListButton {\n      background: #ffffff 0% 0% no-repeat padding-box;\n      border: 1px solid #000000;\n      border-radius: 8px;\n      opacity: 1;\n      width: 309px;\n      height: 56px;\n      // margin-left: 16px;\n      text-transform: none;\n      margin-top: 30px;\n      text-align: center;\n      font: normal normal 600 14px/24px \"Inter\";\n      letter-spacing: 0px;\n      color: #000000;\n      opacity: 1;\n      font-weight: 600;\n      .MuiButton-startIcon{\n        margin-right: 10px;\n      }\n    }\n    \n    .productdetailName {\n      font-size: 18px;\n      font-weight: 400;\n      margin-bottom: 16px;\n    }\n    .ProductImagesSection {\n      padding: 0px 16px 40px 0px;\n    }\n    \n    .newprice {\n      font-size: 28px;\n      font-weight: 500;\n      margin-right: 8px;\n    }\n    .oldprice {\n      font-size: 18px;\n      font-weight: 400;\n      margin-right: 8px;\n      text-decoration: line-through;\n      letter-spacing: 0px;\n      color: #00000080;\n      opacity: 1;\n    }\n    .newoffer {\n      font-size: 14px;\n      font-weight: 500;\n      color: #ff404b;\n      opacity: 1;\n    }\n    .detailsection {\n      padding-left: 16px;\n    }\n    .pincodebar {\n      margin: 30px 0px;\n      display: flex;\n      align-items: center;\n    }\n    .prodattributes {\n      font-size: 16px;\n      font-weight: 400;\n      margin-right: 70px;\n    }\n    .pincodesearchbar {\n      min-width: 381px;\n      input::placeholder{\n        font-size: 14px;\n        font-family: 'Inter';\n        font-weight: 400;\n        color: rgba(98, 98, 98, 0.9);\n      }\n      fieldset{\n        border: 1px solid #F1F1F1;\n      }\n    }\n    .ProductButtonGrp {\n      border: 1px solid #1b4b66;\n      border-radius: 4px;\n      opacity: 1;\n    }\n    .Buttngrpbtn {\n      width: 21px;\n      height: 30px;\n    }\n    \n    .pinsearchbtn {\n      font-family: 'Inter';\n      font-weight: 400;\n      font-size: 14px;\n      border-left: 1px solid #f1f1f1;\n      height: 49px;\n      float: right;\n      position: absolute;\n      right: 1px;\n      background: #fafafa;\n      width: 82px;\n      padding: 16px;\n    }\n  }\n  }\n\n.detail {\n    &__wrapper {\n        background-color: $color-background-light;\n        margin-bottom: 3rem;\n        margin-left: auto;\n        margin-right: auto;\n        padding-left: $padding-m;\n        padding-right: $padding-m;\n        width: 100%;\n\n        @media (min-width: 36em) {\n            max-width: 46rem;\n            padding-left: 0;\n            padding-right: 0;\n        }\n\n        @media (min-width: $mq-r) {\n            display: flex;\n            max-width: 76.75rem;\n            padding-top: 2.5rem;\n        }\n\n        @media (min-width: $mq-l) {\n            padding-top: 2.5rem;\n        }\n\n        @media (min-width: $mq-xl) {\n            max-width: 87.5rem;\n        }\n    }\n\n    &__image {\n        background-position: center;\n        background-size: cover;\n        height: 14rem;\n        width: 100%;\n\n        @media (min-width: $mq-r) {\n            flex: 0 0 50%;\n            height: 45.5rem;\n            margin-top: 3.75rem;\n        }\n    }\n\n    &__info {\n        background: $color-background-light;\n        padding-left: $padding-s;\n        padding-right: $padding-s;\n\n        @media (min-width: $mq-r) {\n            box-shadow: $box-shadow-l;\n            flex: 0 0 50%;\n            margin-left: -5rem;\n            min-height: 53.125rem;\n            padding-bottom: $padding-l;\n            padding-left: $padding-l;\n            padding-right: $padding-l;\n        }\n    }\n\n    &__label {\n        color: $color-text-primary;\n        font-weight: $font-weight-medium;\n\n        @include text-s;\n    }\n\n    &__data {\n        margin-bottom: 2rem;\n    }\n\n    &__title {\n        color: $color-text-primary;\n        font-weight: $font-weight-medium;\n        margin-right: $margin-m;\n\n        @include text-l;\n    }\n\n    &__tag {\n        background: #f2f2f2;\n        border-radius: $border-radius;\n        color: $color-text-primary;\n        font-size: 1rem;\n        font-weight: $font-weight-medium;\n        padding-left: $padding-m;\n        padding-right: $padding-m;\n        white-space: nowrap;\n\n        @media (min-width: $mq-m) {\n            font-size: 1.25rem;\n        }\n\n        &:not(:last-of-type) {\n            margin-right: 0.625rem;\n        }\n\n        &:nth-child(3),\n        &:nth-child(4) {\n            background-color: $color-background-primary;\n            color: $color-text-light;\n            font-weight: $font-weight-light;\n        }\n\n        &.nostock {\n            background: #ff7777;\n        }\n    }\n\n\n    &__data-price {\n        @include text-l-price;\n    }\n\n    .detail__buttons {\n        display: flex;\n    }\n\n    .btn--primary {\n        margin-right: $margin-m;\n        max-width: 20rem;\n        width: 100%;\n\n        &.btn--cart:active {\n            background-color: $color-background-success-altert;\n        }\n    }\n\n    .btn--secondary {\n        border: 2px solid currentColor;\n        padding: calc(#{$padding-m} - 4px);\n        box-shadow: initial;\n    }\n\n    &__description {\n        @media (max-width: $mq-r) {\n            display: none;\n        }\n    }\n\n    &__feature {\n        color: $color-text-primary;\n        font-size: 1rem;\n        margin-bottom: $margin-m;\n\n        &-title {\n            font-weight: $font-weight-medium;\n            margin-right: $margin-s;\n        }\n\n        &-description {\n            font-weight: $font-weight-light;\n        }\n\n        @media (min-width: $mq-m) {\n            font-size: 1.25rem;\n        }\n    }\n}\n}\n\n//Responsive\n@media screen and (min-width : 320px) and (max-width : 767px) {\n  .ProductContainerSectionMain .ProductContainerSection{\n      padding: 0 35px;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .MuiGrid-container, .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .detailsection, .CartMain .CartSection .innerCart{\n      padding: 0;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .pincodesearchbar{\n      min-width: unset;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .prodattributes{\n      margin-right: 10px;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .CartButton{\n      width: 100% !important;\n      margin: 30px 0;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .MuiGrid-container .productdetailsimagediv{\n    min-height: unset;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .MuiGrid-container .productdetailsimagediv .productdetailsimage{\n    width: 100%;\n  }\n}\n@media screen and (min-width : 768px) and (max-width : 899px) {\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .MuiGrid-container .productdetailsimagediv{\n    min-height: unset;\n  }\n  .ProductContainerSectionMain .ProductContainerSection .ProductDetailsSection .MuiGrid-container .productdetailsimagediv .productdetailsimage{\n    width: 100%;\n  }\n}\n@media screen and (max-width : 1439px) {\n\n  .ProductContainerSection .ProductDetailsSection .CartButton,\n  .ProductContainerSection .ProductDetailsSection .WishListButton {\n      width: 250px !important;\n  }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/detail/detailContainer.js",
    "content": "import React, { useCallback } from \"react\";\nimport { connect } from 'react-redux';\n\n// import { animateScroll as scroll } from \"react-scroll\";\nimport LoadingSpinner from \"../../components/loadingSpinner/loadingSpinner\";\n// import Alert from \"react-s-alert\";\n\n// import Detail from \"./detail\";\n// import CartService from \"../../services\";\nimport { ProductService, CartService } from '../../services';\nimport ProductDetails from \"./productDetails\";\nimport Breadcrump from \"../../components/breadcrumb/breadcrumb\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { Alert, Snackbar } from \"@mui/material\";\nimport { getCartQuantity } from \"../../actions/actions\";\n// import Slider from \"../home/components/slider/slider\";\nimport './detail.scss'\n\nfunction DetailContainer(props) {\n    const { productId } = useParams();\n    const navigate = useNavigate();\n    const [detailProduct, setDetailProduct] = React.useState({})\n    const [loadingRelated, setLoadingRelated] = React.useState(null)\n    const [loading, setLoading] = React.useState(true)\n    const [alert, setAlert] = React.useState({ open: false, type: 'error', message: '' })\n    const relatedDetailProducts = []\n    const [qty, setQty] = React.useState(1);\n\n\n    const getDetailPageData = useCallback( async (productId) => {\n        let detailProducts = await ProductService.getDetailProductData(productId);\n        if(detailProducts){\n            setDetailProduct(detailProducts)\n        }else{\n            navigate('/product-not-found');\n        }\n        setLoading(false)\n    },[navigate]);\n\n    React.useEffect(() => {\n        getDetailPageData(productId);\n    }, [productId, getDetailPageData]);\n\n\n    const getQuantity = async () => {\n        if (props.userInfo.token) {\n            const shoppingcart = await CartService.getShoppingCart(\n                props.userInfo.token\n            );\n            if (shoppingcart) {\n                let quantity = shoppingcart.length;\n                props.getCartQuantity(quantity)\n            }\n        }else{\n            let cartItem = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : []\n            let quantity = cartItem.length;\n            props.getCartQuantity(quantity)\n        }\n    }\n\n\n    const addProductToCart = async () => {\n        var tempProps = JSON.parse(JSON.stringify(detailProduct));\n        if(!loggedIn){\n            let cartItem = {\n                imageUrl: detailProduct.imageUrl,\n                name: detailProduct.name,\n                price: detailProduct.price,\n                productId: detailProduct.id,\n                quantity: qty,\n                type: detailProduct.type\n            }\n            let arr = localStorage.getItem('cart_items') ? JSON.parse(localStorage.getItem('cart_items')) : []\n            let objIndex = arr.findIndex((obj => obj.productId === detailProduct.id));\n            if(objIndex === -1){\n                arr.push(cartItem)\n                localStorage.setItem('cart_items',JSON.stringify(arr))\n                showSuccesMessage(`Added ${detailProduct.name} to Cart`)\n            }else{\n                showErrorMessage({errMessage : `Already Added to Cart`})\n            }\n            setLoadingRelated(true)\n            getQuantity()\n        }else{\n\n            const email = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')).userName : null\n\n            tempProps.email = email;\n            tempProps.quantity = qty;\n            Object.preventExtensions(tempProps);\n\n            setDetailProduct(tempProps)\n\n\n            const productToCart = await CartService.addProduct(props.userInfo.token, tempProps)\n\n            if (productToCart.errMessage) {\n                return showErrorMessage(productToCart)\n            } else {\n                showSuccesMessage(productToCart)\n                getQuantity()\n            }\n        }\n\n        // setLoadingRelated(true)\n\n        // setTimeout(async () => {\n        //     let relatedDetailProducts = await CartService\n        //         .getRelatedDetailProducts(this.props.userInfo.token, this.state.detailProduct.type.id);\n\n        //     if (relatedDetailProducts) {\n        //         relatedDetailProducts = relatedDetailProducts.recommendations.slice(0, 3);\n        //     }\n\n        //     this.setState({ relatedDetailProducts, loadingRelated: false });\n        // }, 2000);\n\n        // props.sumProductInState();\n    }\n\n    const showSuccesMessage = (data) => {\n        setAlert({ open: true, type: 'success', message: data })\n        // Alert.success(data.message, {\n        //     position: \"top\",\n        //     effect: \"scale\",\n        //     beep: true,\n        //     timeout: 1500,\n        // });\n    }\n\n    const showErrorMessage = (data) => {\n        setAlert({ open: true, type: 'error', message: data.errMessage })\n        // Alert.error(data.errMessage, {\n        //     position: \"top\",\n        //     effect: \"scale\",\n        //     beep: true,\n        //     timeout: 3000,\n        // });\n    }\n    const handleClose = () => {\n        setAlert({ open: false, type: 'error', message: '' })\n    }\n    const { loggedIn } = props.userInfo\n    return (\n        <div className=\"ProductContainerSectionMain\">\n            <Breadcrump parentPath='Products' parentUrl=\"/list/all-products\" currentPath={detailProduct.name} />\n            <div className=\"ProductContainerSection\">\n                <Snackbar anchorOrigin={{ vertical:'bottom', horizontal:'right' }} open={alert.open} autoHideDuration={6000} onClose={handleClose}>\n                    <Alert onClose={handleClose} severity={alert.type} sx={{ width: '100%' }}>\n                        {alert.message}\n                    </Alert>\n                </Snackbar>\n                {loading ? <LoadingSpinner /> :\n                    <ProductDetails\n                        loggedIn={loggedIn}\n                        detailProductData={detailProduct}\n                        addProductToCart={addProductToCart}\n                        loadingRelated={loadingRelated}\n                        relatedDetailProducts={relatedDetailProducts}\n                        setQty={setQty}\n                    />\n                }\n            </div>\n            <hr className=\"mb-3\" />\n            {/* <Slider firstHeading=\"Explore Awesome Products\" secondHeading=\"RECOMMENDED FOR YOU\"/> */}\n            {/* <hr className=\"m-0\" /> */}\n        </div>\n    );\n}\n// }\n\nconst mapStateToProps = state => state.login;\n\nconst mapDispatchToProps = (dispatch) => ({\n    getCartQuantity: (value) => dispatch(getCartQuantity(value)),\n})\n\nexport default connect(mapStateToProps,mapDispatchToProps)(DetailContainer);"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/detail/productDetails.js",
    "content": "import React from \"react\";\nimport { Grid, Button } from \"@mui/material\";\nimport CustomizedAccordions from \"../../components/accordion/accordion\";\nimport QuantityPicker from \"../../components/quantityCounter/productCounter\";\nimport add_to_bag_icon from \"../../assets/images/original/Contoso_Assets/product_page_assets/add_to_bag_icon.svg\";\nimport { Link } from \"react-router-dom\";\nimport discount_icon from \"../../assets/images/original/Contoso_Assets/product_page_assets/discount.png\";\n\nfunction ProductDetails(props) {\n  const { name, price, imageUrl, loggedIn } = props.detailProductData;\n  const { features } = props.detailProductData;\n\n  const [loading, setLoading] = React.useState(false)\n  const addToCart = async () => {\n    setLoading(true);\n    await props.addProductToCart();\n    setLoading(false);\n  };\n\n  const discountOffer = (price) => {\n    let dicsount = price - ((price / 100) * 15)\n    return (\n      <span className=\"newprice\">\n        ${parseInt(dicsount).toFixed(2)}\n      </span>\n    )\n  }\n\n  const accordionItems = [\n    {\n      name: 'panel1',\n      title: 'Description',\n      body:\n        <Grid container spacing={2}>\n          {features && features.map((feature, index) => {\n            return (\n              <>\n                <Grid item xs={4} className=\"descpAttributes\">\n                  {feature.title}\n                </Grid>\n                <Grid item xs={8} className=\"descpDetails\">\n                  {feature.description}\n                </Grid>\n              </>\n            )\n          })}\n        </Grid>\n    },\n    {\n      name : 'panel2',\n      title : 'Offers',\n      body :\n      <div className=\"OffersSection\">\n      <div className=\"Offerslist\">\n        <span><img src={discount_icon} className=\"discount_icon\" alt=\"\"/></span>\n        <span>\n          10% off on SBI Credit Card, up to ₹1,750, on orders of ₹5000 and\n          above <Link to=\"/\" className=\"TClink\">T&C</Link>\n        </span>\n      </div>\n      <div className=\"Offerslist\">\n      <span><img src={discount_icon} className=\"discount_icon\" alt=\"\"/></span>\n        <span>\n          10% off on SBI Credit Card EMI Transactions, up to ₹2,250, on\n          orders of ₹5000 and above <Link to=\"/\" className=\"TClink\">T&C</Link>\n        </span>\n      </div>\n      <div className=\"Offerslist\">\n      <span><img src={discount_icon} className=\"discount_icon\" alt=\"\"/></span>\n        <span>\n          Additional ₹750 discount on SBI Credit Card and EMI txns on net\n          cart value of INR 29,999 and above <Link to=\"/\" className=\"TClink\">T&C</Link>\n        </span>\n      </div>\n      <div className=\"Offerslist\">\n      <span><img src={discount_icon} className=\"discount_icon\" alt=\"\"/></span>\n        <span>\n          No cost EMI ₹8,815/month. Standard EMI also available <Link to=\"/\" className=\"TClink\">View plans</Link>\n        </span>\n      </div>\n      </div>\n    }\n  ]\n\n\n  return (\n    <div className=\"ProductDetailsSection\">\n      <Grid container>\n        <Grid item lg={6} md={5} xs={12} className=\"ProductImagesSection\">\n          <Grid container>\n            <Grid item xs={10} className=\"productdetailsimagediv\" style={{backgroundImage:`url(${imageUrl})`}}>\n              <img src={imageUrl} className=\"productdetailsimage\" alt=\"\" />\n            </Grid>\n          </Grid>\n        </Grid>\n        <Grid item lg={6} md={7} xs={12}>\n          <div className=\"detailsection\">\n            <div className=\"productdetailName\">\n              {name ? name : 'Xbox Series S Fortnite & Rocket League Bundle 512 GB (White)'}\n            </div>\n            <div >\n              {discountOffer(price)}\n              <span className=\"oldprice\">{'$' + price.toFixed(2)}</span>\n              <span className=\"newoffer\">15%Off</span>\n            </div>\n            <div>\n              <span className=\"prodattributes\">Quantity</span>\n              <span>\n                <QuantityPicker min={1} max={10} setQty={props.setQty} loggedIn={loggedIn}/>\n              </span>\n            </div>\n            <div>\n              <Button\n                variant=\"contained\"\n                color=\"primary\"\n                startIcon={<img src={add_to_bag_icon} alt=\"\" />}\n                className=\"CartButton\"\n                onClick={() => addToCart()}\n                disabled={loading}\n              >\n                {loading ? 'Adding...' : 'Add To Bag'}\n              </Button>\n            </div>\n            <div>\n              <div>\n                <CustomizedAccordions accordionItems={accordionItems} />\n              </div>\n            </div>\n          </div>\n        </Grid>\n      </Grid>\n    </div>\n  );\n}\n\nexport default ProductDetails;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/error/errorPage.js",
    "content": "import { Button } from '@mui/material';\nimport React from 'react';\nimport errorpic from \"../../assets/images/original/Contoso_Assets/404_page_assets/404_image.svg\";\nimport { useNavigate } from 'react-router-dom';\nimport './errorPage.scss'\n\nconst ErrorPage = () => {\n    const history = useNavigate();\n    const redirectTo = (path) => {\n        history(path)\n    }\n    return (\n        <>\n            <div className='ErrorSection'>\n                <img src={errorpic} className='errorpic' alt=\"error404\"></img>\n                <h1 className='errorsecHeading'>Something wrong here…</h1>\n                <h5 className='errorsecContent'>Sorry, We’re having some technical issues (as you can see) <br /> Try to refresh the page, something work.</h5>\n                <div className=\"ButtonSection\">\n                    <Button className='RefreshButton' variant=\"outlined\" onClick={() => window.location.reload()} >Refresh page</Button>\n                    <Button className='GoHomeButton' onClick={() => redirectTo('/')} > Go back to homepage</Button>\n                </div>\n            </div>\n            <hr />\n        </>\n    )\n}\n\nexport default ErrorPage;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/error/errorPage.scss",
    "content": ".ErrorSection {\n    margin-top: 137px;\n    background-image: url(\"../../assets/images/original/Contoso_Assets/404_page_assets/background_image.jpg\");\n    background-size: cover;\n    padding-top: 130px;\n    padding-bottom: 130px;\n\n}\n\n.ErrorSection .errorpic {\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n}\n\n.ErrorSection .errorsecHeading {\n    text-align: center;\n\n    font-weight: 600;\n    font-family: \"Poppins\";\n    font-size: 32px;\n    letter-spacing: -1.53px;\n    color: #111111;\n    margin-bottom: 0 !important;\n\n}\n\n.ErrorSection .errorsecContent {\n    text-align: center;\n\n    font-family: \"Poppins\";\n    font-size: 18px;\n    font-weight: 400;\n    letter-spacing: -0.86px;\n    color: #111111;\n    margin: 8px auto 30px auto !important;\n}\n\n.ErrorSection .RefreshButton {\n    border: 1px solid #2874f0;\n    border-radius: 8px;\n    font-family: \"Inter\";\n    font-size: 16px;\n    font-weight: 500;\n    letter-spacing: 0px;\n    color: #2874f0;\n    text-transform: capitalize;\n    box-shadow: none !important;\n    width: 134px;\n    height: 36px;\n    margin-right: 16px;\n}\n\n.ErrorSection .GoHomeButton {\n    background: #2874f0 0% 0% no-repeat padding-box;\n    border-radius: 8px;\n    text-transform: capitalize;\n    box-shadow: none !important;\n    text-align: center;\n\n    font-family: \"Inter\";\n    font-size: 16px;\n    font-weight: 500;\n    letter-spacing: 0px;\n    color: #ffffff;\n    width: 200px;\n    height: 36px;\n}\n\n.ErrorSection .ButtonSection {\n    text-align: center;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/home.js",
    "content": "import React, { useEffect } from \"react\";\n\nimport Hero from \"./sections/hero\";\nimport { ConfigService } from './../../services'\n// import Slider from \"./components/slider/slider\";\n// import Banner from \"./components/Banner/banner\";\nimport Gridsection from \"./sections/gridSection\";\nimport Finalsection from \"./sections/finalSection\";\nconst Home = ({ recommendedProducts, popularProducts, loggedIn }) => {\n    // const [customerSupportEnabled, setCustomerSupportEnabled] = useState(false);\n    useEffect(() => {\n        async function loadSettings() {\n            await ConfigService.loadSettings();\n            // setCustomerSupportEnabled(ConfigService._customerSupportEnabled);\n        }\n        loadSettings();\n    },[])\n    return (\n        <div className=\"home\">\n            <Hero />\n            {/* <Slider firstHeading=\"Explore Awesome Products\" secondHeading=\"RECOMMENDED FOR YOU\"/> */}\n            {/* <Banner firstHeading=\"Xbox Wireless Controller – Mineral Camo Special Edition\" secondHeading=\"Textured triggers and bumpers | Hybrid D-pad | Button mapping | Bluetooth® technology\"/> */}\n            <Gridsection />\n            <Finalsection />\n            {/* <Recommended recommendedProductsData={recommendedProducts} loggedIn={loggedIn} /> */}\n            {/* {loggedIn && <Popular popularProductsData={popularProducts} />}  */}\n        </div>\n    );\n};\n\nexport default Home;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/home.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains styles that are specific to the Home page.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n$dark: #000;\n$light: #fff;\n$darkgrey: #222222;\n\n.home {\n    margin-top: 250px;\n\n    .upload {\n        margin-bottom: 1.5rem;\n    }\n}\n\n.light {\n    .mainHeader {\n        background-color: $light;\n        color: $dark !important;\n    }\n}\n\n.dark {\n    background-color: $darkgrey !important;\n    color: $light !important;\n\n    .appbar .iconButton {\n        background: #fff;\n        color: #000;\n        width: 40px;\n        height: 40px;\n        margin: 0 10px;\n    }\n\n    a {\n        color: $light !important;\n    }\n\n    .mainHeader {\n        background-color: $dark !important;\n        color: $light !important;\n\n        header {\n            background-color: $dark !important;\n        }\n    }\n\n    .home {\n        .LaptopSection {\n            background-color: $dark !important;\n            color: $light !important;\n\n            .LapHeadmain {\n                color: $light !important;\n            }\n\n            .LapHead {\n                color: $light !important;\n            }\n\n            .LaptopButton {\n                color: $light !important;\n                border: 1px solid $light !important;\n            }\n        }\n\n        .final-section-container {\n            background-color: #e6e6e6;\n        }\n    }\n\n    .footer-container {\n        background-color: $dark !important;\n        color: $light !important;\n\n        p,\n        li,\n        .logo-div,\n        .list-element {\n            color: $light !important;\n        }\n    }\n}\n\n\n/* **************LAPTOPS DESIGNED BY MICROSOFT******************* */\n.BannerSection {\n    background-image: url(\"../../assets/images/original/Contoso_Assets/Static_Banner/banner_controller.jpg\");\n    background-repeat: no-repeat;\n    background-size: 100%;\n    cursor: pointer;\n    min-height: 540px;\n}\n\n.BannerSection .BannerHeading {\n    font-family: 'Poppins' !important;\n    font-weight: 600 !important;\n    font-size: 34px !important;\n    text-align: left;\n    letter-spacing: -1.59px;\n    color: #ffffff;\n    opacity: 1;\n    padding: 144px 0px 0px 150px;\n}\n\n.BannerSection .BannerContent {\n    font: var(--unnamed-font-style-normal) normal var(--unnamed-font-weight-normal) 16px/24px \"Poppins\";\n    color: var(--unnamed-color-ffffff);\n    text-align: left;\n    font: normal normal normal 16px/24px \"Poppins\";\n    letter-spacing: -0.75px;\n    color: #ffffff;\n    opacity: 1;\n    padding: 16px 0px 0px 150px;\n}\n\n.BannerSection .BannerButtondiv {\n    padding: 50px 0px 0px 150px;\n}\n\n.BannerSection .BannerButton {\n    border: 1px solid #ffffff;\n    opacity: 1;\n    width: 224px;\n    height: 56px;\n    background: none;\n    border-radius: 5px;\n    cursor: pointer;\n\n    color: var(--unnamed-color-ffffff);\n    text-align: center;\n    font: normal normal medium 16px/21px var(--fontRoboto);\n    letter-spacing: -0.36px;\n    color: #ffffff;\n\n    font-weight: 500;\n    font-size: 16px;\n    font-family: var(--fontRoboto);\n}\n\n\n\n/* laptopsection starts -------------------------------------------------------------------------------------------------------------*/\n\n.LaptopSection {\n    background-color: #fff;\n}\n\n.LaptopSection .LapHeadSection {\n    padding-top: 70px;\n}\n\n.LaptopSection .LapHead {\n    text-align: center;\n    font: normal normal normal 14px/21px \"Poppins\";\n    letter-spacing: -0.31px;\n    color: #302f2f;\n    text-transform: uppercase;\n    opacity: 1;\n}\n\n.LaptopSection .LapHeadmain {\n    text-align: center;\n    font: normal normal 600 30px/56px \"Poppins\";\n    letter-spacing: -1.44px;\n    color: #111111;\n    text-transform: uppercase;\n    opacity: 1;\n}\n\n.LaptopSection .ProductSection {\n    margin: 30px 38px 70px 70px;\n}\n\n.LaptopSection .LaptopButtondiv {\n    text-align: center;\n    padding: 50px 0px 50px 0px;\n}\n\n.LaptopSection .LaptopButton {\n    text-align: center;\n    font: normal normal medium 16px/21px var(--fontRoboto);\n    letter-spacing: -0.36px;\n    color: #302f2f;\n    text-transform: capitalize;\n    opacity: 1;\n    border: 1px solid #707070;\n    cursor: pointer;\n    width: 224px;\n    height: 56px;\n    background: none;\n    border-radius: 5px;\n    font-weight: 500;\n    font-size: 16px;\n    font-family: var(--fontRoboto);\n}\n\n.LaptopSection .lapsecimage {\n    border-radius: 5px;\n    width: 100%;\n}\n\n.LaptopSection .laptopimage {\n    width: 75%;\n    margin: auto;\n    display: block;\n    margin-bottom: 23px !important;\n    margin-top: 2px !important;\n}\n\n.LaptopSection .cardlap {\n    min-height: 310px;\n    background: #f5f5f580 0% 0% no-repeat padding-box;\n    border-radius: 10px;\n    opacity: 1;\n    box-shadow: none;\n}\n\n.LaptopSection .Productname {\n    font: var(--unnamed-font-style-normal) normal 600 18px / var(--unnamed-line-spacing-32) \"Poppins\";\n    text-align: center;\n    font: normal normal 600 18px/32px \"Poppins\";\n    letter-spacing: -0.33px;\n    color: #111111;\n    opacity: 1;\n    margin-right: auto;\n}\n\n.LaptopSection .price {\n    text-align: center;\n    letter-spacing: -0.36px;\n    color: #2874f0;\n    opacity: 1;\n    background-color: white !important;\n    border-radius: 10px;\n\n    background: #ffffff 0% 0% no-repeat padding-box;\n    border-radius: 17px;\n    width: 63px;\n    height: 34px;\n    justify-content: center;\n    display: flex;\n    align-items: center;\n    font-weight: 600;\n    font-size: 16px;\n    font-family: \"Poppins\";\n}\n\n.LaptopSection .productdetails {\n    display: flex;\n    justify-content: space-around;\n    align-items: center;\n}\n\n.LaptopSection .proddetails {\n    padding: 16px 30px;\n    box-shadow: none;\n}\n\n\n/* ************** */\n.final-section-container {\n    background-color: #80808030;\n    height: 800px;\n    background-image: url('../../assets/images/original/Contoso_Assets/5_Static_Banner/static_banner_2.jpg');\n    background-repeat: no-repeat;\n}\n\n.final-section-container .grid-container {\n    padding-top: 30px;\n}\n\n.final-section-container .image-section img {\n    min-height: 100%;\n    min-width: 100%;\n    max-width: 100%;\n    max-height: 100%;\n}\n\n.final-section-container .content-section {\n    padding-top: 170px;\n    padding-right: 70px;\n}\n\n.final-section-container .content-section h1 {\n    font-family: 'Poppins' !important;\n    font-size: 34px !important;\n    color: #000000 !important;\n    font-weight: 600;\n    margin: 16px 0;\n}\n\n.final-section-container .content-section p {\n    font-family: 'Poppins' !important;\n    font-size: 16px !important;\n    color: #000000 !important;\n    font-weight: 400;\n}\n\n.final-section-container .start-btn {\n    text-align: center;\n    color: #302f2f;\n    text-transform: capitalize;\n    opacity: 1;\n    border: 1px solid #707070;\n    cursor: pointer;\n    width: 224px;\n    height: 56px;\n    background: none;\n    border-radius: 5px;\n    font-weight: 500;\n    font-size: 16px;\n    font-family: var(--fontRoboto) !important;\n    margin-top: 34px;\n}\n\n// -----------------------------------------------------------------------------\n// This file contains all styles related to the hero component of the site/application.\n// -----------------------------------------------------------------------------\n.hero {\n    position: relative;\n\n    // svg {\n    //     fill: $color-svg-tertiary;\n    // }\n\n    &__banner {\n        align-items: center;\n        background-color: $color-background-primary;\n        box-shadow: $box-shadow-m;\n        color: $color-text-tertiary;\n        display: flex;\n        height: 3rem;\n        justify-content: center;\n        left: 50%;\n        margin-top: -$margin-s;\n        position: absolute;\n        top: -$margin-s;\n        transform: translateX(-50%) translateZ(0);\n        width: 100%;\n        z-index: $z-index-s;\n\n        @media (min-width: $mq-s) {\n            width: 22.5rem;\n        }\n\n        @media (min-width: $mq-m) {\n            height: 4rem;\n            width: 63.5rem;\n        }\n\n        @media (min-width: $mq-m) {\n            width: 94%;\n        }\n\n        @media (min-width: $mq-l) {\n            width: 80%;\n        }\n    }\n\n    &__inner {\n        align-items: center;\n        display: flex;\n        justify-content: space-between;\n        width: 16rem;\n\n        @media (min-width: $mq-s) {\n            width: 17.8125rem;\n        }\n\n        @media (min-width: $mq-m) {\n            width: 18rem;\n        }\n\n        @media (min-width: $mq-l) {\n            width: 22rem;\n        }\n\n        & svg:nth-child(1) {\n            display: none;\n\n            @media (min-width: 23.625em) {\n                display: block;\n            }\n        }\n    }\n\n    &__text {\n        white-space: nowrap;\n\n        &--strong {\n            @include text-s();\n            font-weight: $font-weight-medium;\n        }\n\n        &--light {\n            @include text-xs();\n            font-weight: $font-weight-light;\n        }\n    }\n    &__image-wrapper {\n        background-color: $color-background-primary;\n    }\n\n    &__image {\n        height: 20rem;\n        background-size: cover;\n        opacity: 0;\n        transition: opacity $animation-speed-regular $animation-type-regular;\n\n        @media (min-width: $mq-m) {\n            height: 38.5rem;\n        }\n    }\n\n    .u-fade-in {\n        opacity: 1;\n    }\n}\n\n//Responsive\n@media screen and (min-width : 320px) and (max-width : 767px) {\n    .final-section-container .content-section {\n        padding: 170px 30px 0 30px;\n    }\n\n    .final-section-container {\n        background-size: contain;\n        height: 600px;\n    }\n\n    .LaptopSection {\n        .img-class,\n        .lapsecimage-top {\n            padding-left: 0 !important;\n        }\n    }\n}\n\n@media screen and (min-width : 420px) and (max-width : 767px) {\n    .final-section-container .content-section {\n        padding: 350px 30px 0 30px;\n    }\n\n    .final-section-container {\n        background-size: contain;\n        height: 700px;\n    }\n}\n\n@media screen and (min-width : 900px) and (max-width : 1350px) {\n    .final-section-container {\n        background-position: bottom;\n    }\n}\n\n@media screen and (min-width : 768px) and (max-width : 899px) {\n    .LaptopSection {\n        .img-class,\n        .lapsecimage-top {\n            padding-left: 0 !important;\n        }\n    }\n\n    .final-section-container .content-section {\n        padding: 430px 30px 0 30px;\n    }\n\n    .final-section-container {\n        background-size: contain;\n        height: 800px;\n    }\n}\n\n@media screen and (max-width : 1350px) {\n    .LaptopSection .cardlap {\n        min-height: 256px;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/homeContainer.js",
    "content": "import React, { Component, Fragment } from \"react\";\n// import { withRouter } from \"react-router-dom\";\nimport { connect } from 'react-redux';\nimport Electrical from \"../../assets/images/home_electrical.jpg\";\nimport Garden from \"../../assets/images/home_gardencenter.jpg\";\nimport Plumbing from \"../../assets/images/home_plumbing.jpg\";\nimport Powertools from \"../../assets/images/home_powertools.jpg\";\nimport { ProductService } from \"../../services\";\nimport Home from \"./home\";\nimport './home.scss'\nclass HomeContainer extends Component {\n    constructor() {\n        super();\n        this.state = {\n            recommendedProducts: [\n            ],\n            defaultProducts: [\n                {\n                    title: \"Power Tools\",\n                    imageUrl: Powertools,\n                    cssClass: \"grid__item-a\",\n                    url: \"/list/diytools\"\n                },\n                {\n                    title: \"Plumbing\",\n                    imageUrl: Plumbing,\n                    cssClass: \"grid__item-b\",\n                    url: \"/list/kitchen\"\n                },\n                {\n                    title: \"Electrical\",\n                    imageUrl: Electrical,\n                    cssClass: \"grid__item-c\",\n                    url: \"/list/home\"\n                },\n                {\n                    title: \"Garden Center\",\n                    imageUrl: Garden,\n                    cssClass: \"grid__item-d\",\n                    url: \"/list/gardening\"\n                }\n            ],\n            popularProducts: [],\n            loading: true,\n        };\n    }\n\n    async componentDidMount() {\n        // if (this.props.userInfo.loggedIn) {\n        //     await this.renderPopularProducts()\n        // }\n        // this.getRank()\n    }\n\n    async shouldComponentUpdate(nextProps) {\n        if ((this.props.userInfo.loggedIn !== nextProps.userInfo.loggedIn) && nextProps.userInfo.loggedIn) {\n            await this.renderPopularProducts(nextProps.userInfo.token)\n        }\n    }\n\n    async getRank() {\n        var categories = { categories: this.state.defaultProducts.map((product) => { return product.title }) };\n        const response = await fetch(\"/api/personalizer/rank\", {\n            method: \"POST\",\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify(categories)\n        })\n        if (!response.ok || response.statusText === \"No Content\") {\n            if (response.error) {\n                console.error(response.error);\n            }\n            this.setState({ recommendedProducts: this.state.defaultProducts });\n            return;\n        } else {\n            const data = await response.json();\n            console.log(`Rank request sent. EventId: ${data.eventId}`);\n            this.setState({ recommendedProducts: this.getRerankedProducts(data) });\n        }\n    }\n\n    getRerankedProducts(data) {\n        var cssEnum = [\"grid__item-a\", \"grid__item-b\", \"grid__item-c\", \"grid__item-d\"];\n\n        var recommendSource = this.state.defaultProducts;\n        var newHeroIndex = recommendSource.findIndex(obj => obj.title === data.rewardActionId);\n\n        var newRecommend = [];\n        var counter;\n        for (counter = 0; counter < recommendSource.length; counter++) {\n            newRecommend.push(Object.assign({}, recommendSource[counter]));\n        }\n\n        newRecommend.unshift(newRecommend.splice(newHeroIndex, 1)[0]);\n\n        newRecommend.map((category, index) => {\n            category.cssClass = cssEnum[index];\n            category.eventId = data.eventId;\n            return category;\n        })\n\n        return newRecommend;\n    }\n\n    async renderPopularProducts(token) {\n        token = token || this.props.userInfo.token;\n\n        let popularProducts = await ProductService.getHomePageData(token);\n\n        if (popularProducts && popularProducts.data.popularProducts) {\n            popularProducts = popularProducts.data.popularProducts.slice(0, 3);\n            this.setState({ popularProducts, loading: false });\n        }\n    }\n\n    render() {\n        const { recommendedProducts, popularProducts } = this.state;\n        const { loggedIn } = this.props.userInfo\n        return (\n            <Fragment>\n                <Home\n                    recommendedProducts={recommendedProducts}\n                    popularProducts={popularProducts}\n                    loggedIn={loggedIn}\n                />\n            </Fragment>\n        );\n    }\n}\n\nconst mapStateToProps = state => state.login;\n\nexport default connect(mapStateToProps)(HomeContainer);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/sections/banner.js",
    "content": "import React from \"react\";\nimport { Grid } from \"@mui/material\";\nimport { useNavigate } from \"react-router-dom\";\nfunction Banner(props) {\n  const history = useNavigate()\n  const startShopping = () => {\n    history('/list/controllers')\n  }\n  return (\n    <section>\n      <Grid className=\"BannerSection\" container>\n        <Grid item xs={6}>\n          <div className=\"BannerHeading\">\n            {props.firstHeading}\n          </div>\n          <div className=\"BannerContent\">\n            {props.secondHeading}\n          </div>\n          <div className=\"BannerButtondiv\">\n            <button className=\"BannerButton\" onClick={() => startShopping()} >Start Shopping</button>\n          </div>\n        </Grid>\n      </Grid>\n    </section>\n  );\n}\n\nexport default Banner;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/sections/finalSection.js",
    "content": "import React from 'react'\n\nimport { Grid } from '@mui/material';\nimport Button from '@mui/material/Button';\nimport { useNavigate } from 'react-router-dom';\n\nconst Finalsection = () => {\n  const history = useNavigate()\n  const startShopping = () => {\n    history('/list/controllers')\n  }\n  return (\n    <div className='final-section-container'>\n      <Grid container className='grid-container'>\n        <Grid item lg={7} md={7} xs={12}>\n        </Grid>\n        <Grid item lg={5} md={5} xs={12}>\n          <div className='content-section'>\n            <h1>Play more, wait less</h1>\n            <p>A streamlined dashboard designed to get you into the games and entertainment you love quickly.</p>\n            <Button className='start-btn' variant=\"outlined\" onClick={()=>startShopping()} >Start Shopping</Button>\n            </div>\n        </Grid>\n      </Grid>\n    </div>\n  )\n}\n\nexport default Finalsection"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/sections/gridSection.js",
    "content": "import React from \"react\";\nimport {\n  Card,\n  CardContent,\n  Grid,\n  CardActionArea,\n} from \"@mui/material\";\nimport laptopgirl from \"../../../assets/images/original/Contoso_Assets/Grid_Products_Collection/banner_1.jpg\";\nimport laptoppic from \"../../../assets/images/original/Contoso_Assets/Grid_Products_Collection/product_image.png\";\nimport { useNavigate } from \"react-router-dom\";\nfunction Gridsection() {\n  const history = useNavigate()\n  const startShopping = () => {\n    history('/list/laptops')\n  }\n  return (\n    <div className=\"LaptopSection\">\n      <div className=\"LapHeadSection\">\n        <div className=\"LapHead\">MICROSOFT LAPTOPS</div>\n        <div className=\"LapHeadmain\">LAPTOPS DESIGNED BY MICROSOFT</div>\n      </div>\n      <div className=\"ProductSection\">\n        <div className=\"productgrid\">\n          <Grid container spacing={4}>\n            <Grid item lg={5} md={4} xs={12} className=\"lapsecimage-top\">\n              <img\n                src={laptopgirl}\n                className=\"lapsecimage\"\n                alt=\"girl with laptop\"\n              />\n            </Grid>\n            <Grid item lg={7} md={8} xs={12} className=\"img-class\">\n              <Grid container spacing={4}>\n                <Grid container item xs={12} spacing={4}>\n                  <Grid item lg={6} sm={6} md={6} xs={12}>\n                    <Card className=\"cardlap\">\n                      <CardActionArea onClick={() => startShopping()}>\n                        <img\n                          src={laptoppic}\n                          className=\"laptopimage\"\n                          alt=\"girl with laptop\"\n                        />\n                      </CardActionArea>\n                      <CardContent className=\"proddetails\">\n                        <div className=\"productdetails\">\n                          <div className=\"Productname\">Surface Pro 8</div>\n                          <div className=\"price\">$279</div>\n                        </div>\n                      </CardContent>\n                    </Card>\n                  </Grid>\n                  <Grid item lg={6} sm={6} md={6} xs={12}>\n                    <Card className=\"cardlap\">\n                      <CardActionArea onClick={() => startShopping()}>\n                        <img\n                          src={laptoppic}\n                          className=\"laptopimage\"\n                          alt=\"girl with laptop\"\n                        />\n                      </CardActionArea>\n                      <CardContent className=\"proddetails\">\n                      <div className=\"productdetails\">\n                          <div className=\"Productname\">Surface Pro X</div>\n                          <div className=\"price\">$499</div>\n                        </div>\n                      </CardContent>\n                    </Card>\n                  </Grid>\n                </Grid>\n                <Grid container item xs={12} spacing={4}>\n                  <Grid item lg={6} sm={6} md={6} xs={12}>\n                    <Card className=\"cardlap\">\n                      <CardActionArea onClick={() => startShopping()}>\n                        <img\n                          src={laptoppic}\n                          className=\"laptopimage\"\n                          alt=\"girl with laptop\"\n                        />\n                      </CardActionArea>\n                      <CardContent className=\"proddetails\">\n                      <div className=\"productdetails\">\n                          <div className=\"Productname\">Surface Go 3</div>\n                          <div className=\"price\">$399</div>\n                        </div>\n                      </CardContent>\n                    </Card>\n                  </Grid>\n                  <Grid item lg={6} sm={6} md={6} xs={12}>\n                    <Card className=\"cardlap\">\n                      <CardActionArea onClick={() => startShopping()}>\n                        <img\n                          src={laptoppic}\n                          className=\"laptopimage\"\n                          alt=\"girl with laptop\"\n                        />\n                      </CardActionArea>\n                      <CardContent className=\"proddetails\">\n                      <div className=\"productdetails\">\n                          <div className=\"Productname\">Surface Pro 8</div>\n                          <div className=\"price\">$599</div>\n                        </div>\n                      </CardContent>\n                    </Card>\n                  </Grid>\n                </Grid>\n              </Grid>\n            </Grid>\n          </Grid>\n        </div>\n      </div>\n      <div className=\"LaptopButtondiv\">\n        <button className=\"LaptopButton\" onClick={() => startShopping()} >Explore Other Products</button>\n      </div>\n    </div>\n  );\n}\n\nexport default Gridsection;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/home/sections/hero.js",
    "content": "import React from \"react\";\nimport Corousel from \"../../../components/corousel/corousel\";\nimport heroBg from '../../../assets/images/original/Contoso_Assets/Slider_section/hero_banner.jpg';\nimport heroBg2 from '../../../assets/images/original/Contoso_Assets/Slider_section/hero_banner-2.jpg'\nimport LocalMallIcon from '../../../assets/images/original/Contoso_Assets/Icons/cart-icon-copy.svg'\nimport { useNavigate } from \"react-router-dom\";\nfunction Hero() {\n\n        const history = useNavigate()\n        const buyNow = (id) => {\n            history('/product/detail/'+id)\n        }\n        const moreDetails = () => {\n            history('/list/controllers')\n        }\n        const items = [\n            {\n                name: \"The Fastest, Most Powerful Xbox Ever.\",\n                description: \"Elevate your game with the all-new Xbox Wireless Controller - Lunar Shift Special Edition\",\n                bg: heroBg,\n                buttons : [\n                    {\n                        title : 'Buy Now',\n                        color : 'primary',\n                        class : 'BannerButton1',\n                        endIcon: <img src={LocalMallIcon} width={25} height='auto' alt=\"\"/>,\n                        onClickFn : () => buyNow(1)\n                    },\n                    {\n                        title : 'More Details',\n                        color : 'inherit',\n                        class : 'BannerButton2',\n                        endIcon : '',\n                        onClickFn : () => moreDetails()\n                    }\n                ]\n            },\n            {\n                name: \"Xbox Wireless Controller - Mineral Camo Special Edition\",\n                description: \"Textured triggers and bumpers | Hybrid D-pad | Button mapping | Bluetooth® technology\",\n                bg: heroBg2,\n                buttons : [\n                    {\n                        title : 'Buy Now',\n                        color : 'primary',\n                        class : 'BannerButton1',\n                        endIcon: <img src={LocalMallIcon} width={25} height='auto' alt=\"\"/>,\n                        onClickFn : () => buyNow(1)\n                    },\n                    {\n                        title : 'More Details',\n                        color : 'inherit',\n                        class : 'BannerButton2',\n                        endIcon : '',\n                        onClickFn : () => moreDetails()\n                    }\n                ]\n            },\n        ]\n        return (\n            <div className=\"hero\" data-testid=\"carousel\">\n                <Corousel items={items} />\n            </div>\n        );\n}\n\nexport default Hero;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/index.js",
    "content": "import Home from './home/homeContainer';\n// import MyCoupons from './mycoupons/myCouponsContainer';\nimport Detail from '../pages/detail/detailContainer';\nimport List from './list/listContainer';\nimport SuggestedProductsList from './suggestedProductsList/suggestedProductsList';\nimport Profile from './profile/profileForm';\n// import ShoppingCart from './shoppingcart/shoppingCart'\nimport Arrivals from './arrivals/arrivals';\nimport RefundPolicy from './legals/refundPolicy';\nimport TermsOfService from './legals/termsOfService';\nimport AboutUs from './legals/aboutUs';\nimport ErrorPage from './error/errorPage';\nimport Cart from './cart/cart';\nexport {\n    Home,\n    Arrivals,\n    // MyCoupons,\n    Detail,\n    List,\n    SuggestedProductsList,\n    Profile,\n    // ShoppingCart,\n    RefundPolicy,\n    TermsOfService,\n    AboutUs,\n    ErrorPage,\n    Cart,\n};\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/legals/aboutUs.js",
    "content": "import React from 'react';\nimport Breadcrump from '../../components/breadcrumb/breadcrumb'\nimport { useLocation } from 'react-router-dom';\nimport './legals.scss'\n\nconst AboutUs = (props) => {\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n    return (\n        <>\n            <div className='refund-policy-section'>\n                <Breadcrump currentPath={currentCategory} />\n                <div className=\"refund-policy\">\n                    <p className=\"mainHeading\">About Us</p>\n                    {/* <p className=\"subHeading\">Our Mission</p> */}\n                    <p className=\"paragraph\">\n                        Contoso Traders is an e-commerce platform that specializes in electronic items. Our website offers a wide range of electronics, including smartphones, laptops, and other popular gadgets.\n                        <br/><br/>\n                        We pride ourselves on providing high-quality products at competitive prices, and our dedicated customer service team is always on hand to assist with any queries or concerns. With fast and secure shipping, convenient payment options, and a user-friendly interface, Contoso Traders is the perfect place to shop for all your electronic needs. \n                    </p>\n                </div>\n            </div>\n            <hr/>\n        </>\n     );\n}\n\nexport default AboutUs;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/legals/legals.scss",
    "content": "\n.refund-policy-section{\n    margin-top: 192px;\n    font-family: var(--fontInter);\n  }\n  .refund-policy-section .refund-policy{\n    padding: 0 70px 36px 70px;\n  }\n  .refund-policy .mainHeading{\n    margin: 24px 0;\n    font-size: 24px;\n    font-weight: 600;\n  }\n  .refund-policy .subHeading{\n    margin-top: 35px;\n    font-size: 18px;\n    font-weight: 600;\n  }\n  .refund-policy .paragraph{\n    font-size: 14px;\n  }\n  .refund-policy .paragraph b{\n    font-family: var(--fontInter);\n    font-weight: 600;\n    font-size: 16px;\n    line-height: 2;\n  }\n\n  //Responsive\n  @media screen and (min-width : 320px) and (max-width : 767px) {\n    .refund-policy-section .refund-policy{\n        padding: 0 35px;\n        text-align: justify;\n    }\n}\n  "
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/legals/refundPolicy.js",
    "content": "import React from 'react';\nimport Breadcrump from '../../components/breadcrumb/breadcrumb'\nimport { useLocation } from 'react-router-dom';\nconst RefundPolicy = (props) => {\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n    return (\n        <>\n            <div className='refund-policy-section'>\n                <Breadcrump currentPath={currentCategory} />\n                <div className=\"refund-policy\">\n                    <p className=\"mainHeading\">Refund Policy</p>\n                    <p className=\"subHeading\">At Contoso Traders, we want our customers to be completely satisfied with their purchases.  </p>\n                    <p className=\"paragraph\">\n                        1. If for any reason you are not satisfied with your purchase, you may return it within 30 days of the original purchase date for a full refund.  \n                        <br/><br/>\n                        2. To be eligible for a refund, the item must be in its original condition, unused, and with all original packaging and tags.  \n                        <br/><br/>\n                        3. To initiate a refund, please contact our customer service team at <a href=\"mailto: support@contosotraders.com\">support@contosotraders.com</a>.  \n                        <br/><br/>\n                        4. Please note that original shipping charges are non-refundable, and the customer is responsible for the cost of return shipping. \n                        </p>\n                </div>\n            </div>\n            <hr/>\n        </>\n     );\n}\n\nexport default RefundPolicy;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/legals/termsOfService.js",
    "content": "import React from 'react';\nimport Breadcrump from '../../components/breadcrumb/breadcrumb'\nimport { useLocation } from 'react-router-dom';\nconst TermsOfService = (props) => {\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n    return (\n        <>\n            <div className='refund-policy-section'>\n                <Breadcrump currentPath={currentCategory} />\n                <div className=\"refund-policy\">\n                    <p className=\"mainHeading\">Terms Of Service</p>\n                    {/* <p className=\"subHeading\">OVERVIEW</p> */}\n                    <p className=\"paragraph\">\n                        <b>Introduction</b>\n                        <br/>\n                        These Website Standard Terms and Conditions written on this webpage shall manage your use of our website, Website Name accessible at Contoso traders. \n                        <br/><br/>\n                        By using our website, you accepted these terms and conditions in full. If you disagree with these terms and conditions or any part of these terms and conditions, you must not use our website. \n                        <br/><br/>\n                        <b>Intellectual Property Rights </b>\n                        <br/>\n                        Unless otherwise stated, we or our licensors own the intellectual property rights on the website and material on the website. Subject to the license below, all these intellectual property rights are reserved. \n                        <br/><br/>\n                        <b>License to use website </b>\n                        <br/>\n                        You may view, download for caching purposes only, and print pages from the website for your own personal use, subject to the restrictions set out below and elsewhere in these terms and conditions. \n                        <br/><br/>\n                        <b>You must not: </b>\n                        <ul>\n\n                        <li>republish material from this website (including republication on another website); </li>\n\n                        <li>sell, rent or sub-license material from the website. </li>\n\n                        <li>show any material from the website in public. </li>\n\n                        <li>reproduce, duplicate, copy or otherwise exploit material on our website for a commercial purpose. </li>\n\n                        <li>Edit or otherwise modify any material on the website; or </li>\n\n                        <li>Redistribute material from this website except for content specifically and expressly made available for redistribution. </li>\n\n                        </ul>\n                        Where content is specifically made available for redistribution, it may only be redistributed within your organisation. \n                        <br/><br/>\n                        <b>Acceptable use </b>\n                        <br/>\n                        - You must not use our website in any way that causes, or may cause, damage to the website or impairment of the availability or accessibility of the website; or in any way which is unlawful, illegal, fraudulent, or harmful, or in connection with any unlawful, illegal, fraudulent, or harmful purpose or activity. \n                        <br/><br/>\n                        - You must not use our website to copy, store, host, transmit, send, use, publish or distribute any material which consists of (or is linked to) any spyware, computer virus, Trojan horse, worm, keystroke logger, rootkit, or other malicious computer software. \n                        <br/><br/>\n                        - You must not conduct any systematic or automated data collection activities (including without limitation scraping, data mining, data extraction and data harvesting) on or in relation to our website without our express written consent. \n                        <br/><br/>\n                        - You must not use our website to transmit or send unsolicited commercial communications. \n                        <br/><br/>\n                        - You must not use our website for any purposes related to marketing without our express written consent. \n                        <br/><br/>\n\n                        <b>Restricted access </b>\n                        <br/>\n                        We reserve the right to restrict access to areas of our website, or indeed our whole website, at our discretion and without notice. \n                        <br/><br/>\n                        <b>User content </b>\n                        <br/>\n                        In these terms and conditions, “your user content” means material (including without limitation text, images, audio material, video material and audio-visual material) that you submit to our website, for whatever purpose. \n                        <br/><br/>\n                        You grant to us a worldwide, irrevocable, non-exclusive, royalty-free license to use, reproduce, adapt, publish, translate, and distribute your user content in any existing or future media. You also grant to us the right to sub-license these rights, and the right to bring an action for infringement of these rights. \n                        <br/><br/>\n                        Your user content must not be illegal or unlawful, must not infringe any third party's legal rights, and must not be capable of giving rise to legal action whether against you or us or a third party (in each case under any applicable law). \n                        <br/>\n                    <br/></p>\n                </div>\n            </div>\n            <hr/>\n        </>\n     );\n}\n\nexport default TermsOfService;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/list.js",
    "content": "import { Grid } from \"@mui/material\";\nimport React from \"react\";\nimport { OfferBanner, ListGrid, ListAside } from \"./sections\";\nimport Breadcrump  from \"../../components/breadcrumb/breadcrumb\";\nimport { useLocation } from \"react-router-dom\";\n\nconst List = ({ typesList, brandsList, onFilterChecked, productsList, loggedIn }) => {\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n\n    return (\n        <div className=\"list\">\n            {currentCategory === 'all products'?\n            <Breadcrump currentPath='Product Collection' />\n            :\n            <Breadcrump parentPath='Product Collection' parentUrl=\"/list/all-products\" currentPath={currentCategory} />}\n            <OfferBanner />\n            <div className=\"list__content\">\n                <h6 className=\"mainHeading\">{currentCategory}</h6>\n                <Grid container>\n                    <Grid item lg={3} xs={12}>\n                        <ListAside\n                            onFilterChecked={onFilterChecked}\n                            typesList={typesList}\n                            brandsList={brandsList}\n                        />\n                    </Grid>\n                    <Grid item lg={9} xs={12}>\n                        <ListGrid productsList={productsList} />\n                    </Grid>\n                </Grid>\n            </div>\n            <hr className=\"m-0\"/>\n        </div>\n    );\n};\n\nexport default (List);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/list.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains styles that are specific to the Products List pages.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n.list {\n    margin-top: 192px;\n    &__content {\n        padding: 0 70px;\n        .mainHeading{\n            font-family: 'Inter';\n            font-weight: 600;\n            font-size: 24px;\n            padding: 24px 0;\n            margin: 0;\n            text-transform: capitalize;\n        }\n        .list__aside{\n            padding-right: 32px;\n            .AccordionSection{\n                .MuiAccordion-root{\n                    border-bottom: 1px solid #ebe9ef !important;\n                    .panelSummary{\n                        // padding-left: 0;\n                        font-family: 'Inter';\n                        font-weight: 500;\n                        font-size: 16px;\n                        color: #13101E;\n                        border-top: none;\n                        border-bottom: 1px solid #ebe9ef !important;\n                        img{\n                            width: 20px;\n                            height: 20px;\n                        }\n                    }\n                    .MuiAccordionDetails-root{\n                        padding: 16px !important;\n                        .descpAttributes{\n                            display: flex;\n                            align-items: center;\n                            label{\n                                margin: 0 0 0 10px;\n                                color: #626262;\n                                font-family: 'Inter';\n                                font-weight: 500;\n                                font-size: 16px;\n                            }\n                            .MuiCheckbox-root{\n                                // padding: 0 9px;\n                                width: 18px;\n                                height: 18px;\n                                background: #2874F0 0% 0% no-repeat padding-box;\n                                border-radius: 2px;\n                                cursor: pointer;\n                            }\n                            .Mui-checked{\n                                color: #2874F0 !important;\n                            }\n                            \n                        }\n                    }\n                }\n            }\n        }\n    }\n    .offer_banner{\n        display: flex;\n        background-color: #ebeaef;//#ecf3f3\n        background-image: linear-gradient(180deg, #ebeaef 80%, #ecf3f3 20%);\n        align-items: center;\n        margin: 0 70px;\n        .banner__buttons{\n            grid-template-columns: repeat(2, 1fr) !important;\n            img{\n                height: 134px;\n                margin: 10px 0 10px 140px;\n            }\n        }\n        .firstHeading{\n            font-family: 'Poppins';\n            font-size: 24px;\n            color: #13101E;\n            margin: 0;\n        }\n        .secondHeading{\n            font-family: 'Inter';\n            font-size: 45px;\n            color:#13101E;\n            margin: 0;\n        }\n    }\n\n    // &__aside {\n    //     grid-column-end: span 3;\n    // }\n\n    // &__panel {\n    //     background-color: $color-background-light;\n    //     top: -100vh;\n    //     height: 100vh;\n    //     left: 0;\n    //     padding: $padding-xl;\n    //     position: absolute;\n    //     transition: top $animation-speed-regular $animation-type-regular;\n    //     width: 100%;\n    //     will-change: top;\n\n    //     @media (min-width: $mq-m) {\n    //         top: auto;\n    //         padding: 0;\n    //         position: relative;\n    //     }\n\n    //     &.is-opened {\n    //         top: 0;\n    //         padding: $padding-xl;\n    //         z-index: $z-index-l;\n    //     }\n\n    //     .btn--primary {\n    //         bottom: $space-l;\n    //         position: absolute;\n    //         left: 50%;\n    //         transform: translateX(-50%);\n\n    //         @media (min-width: $mq-m) {\n    //             display: none;\n    //         }\n    //     }\n    // }\n\n    // .btn__reset {\n    //     border: 0;\n    //     background-color: transparent;\n    // }\n\n    // @media (min-width: $mq-m) {\n    //     .btn--float {\n    //         display: none;\n    //     }\n    // }\n\n    .list-grid {\n        .filter-section{\n            display: flex;\n            align-items: center;\n            padding: 16px 0;\n            .page{\n                margin-right: auto;\n            }\n        }\n        .pagination{\n            display: flex;\n            padding: 44px 0 24px 0;\n            justify-content: flex-end;\n            .MuiPagination-ul{\n                background: #F1F1F1;\n                padding: 4px;\n                border-radius: 12px;\n                margin-right: 8px;\n            }\n            .MuiPaginationItem-textPrimary{\n                background-color: #F1F1F1;\n                width: 42px;\n                height: 28px;\n                border-radius: 8px;\n                color: #626262;\n                font-family: 'Inter';\n                font-weight: 500;\n                font-size: 12px;\n            }\n            .MuiPaginationItem-textPrimary.Mui-selected{\n                background-color: #000000;\n                color: #fff !important;\n            }\n            .nextBtn{\n                background-color: #000000;\n                color: #fff;\n                padding: 7px 20px;\n                min-width: 67px;\n                height: 36px;\n                border-radius: 12px;\n                border: none;\n                font-family: 'Inter';\n                font-weight: 500;\n                font-size: 12px;\n            }\n        }\n        // display: grid;\n        // grid-column-gap: $grid-gap-m;\n        // grid-template-columns: repeat(12, 1fr);\n        // grid-column-end: span 12;\n\n        // @media (min-width: $mq-m) {\n        //     grid-column-end: span 9;\n        //     grid-row-gap: 5rem;\n        // }\n\n        .card__item {\n            grid-column: span 12;\n\n            @media (min-width: 30rem) {\n                grid-column: span 6;\n            }\n\n            @media (min-width: 68.75rem) {\n                grid-column: span 4;\n            }\n        }\n\n        .card__photo {\n            height: 12rem;\n            margin-top: -2.125rem;\n            width: 12rem;\n\n            @media (min-width: 34.25em) {\n                height: 14rem;\n                width: 14rem;\n            }\n        }\n\n        .btn {\n            box-shadow: $box-shadow-l;\n            margin-bottom: -$margin-m;\n            margin-left: auto;\n            margin-right: auto;\n        }\n    }\n}\n@media screen and (min-width : 320px) and (max-width : 767px) {\n    .list{\n        margin-top: 0 !important;\n    }\n    .list .offer_banner{\n        margin: 0;\n    }\n    .list__content{\n        padding: 0 35px;\n    }\n    .list__content .list__aside{\n        padding-right: 0;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/listContainer.js",
    "content": "import React, { Fragment } from 'react';\n// import { withRouter } from 'react-router-dom';\nimport LoadingSpinner from '../../components/loadingSpinner/loadingSpinner';\nimport './list.scss'\nimport List from './list';\nimport { ProductService } from '../../services';\nimport { useParams } from 'react-router-dom';\n// class ListContainer extends Component {\n  // constructor(props) {\n  //   super(props);\n  //   this.state = {\n  //     typesList: [],\n  //     brandsList: [],\n  //     productsList: [],\n  //     queryString: '',\n  //     loading: true,\n  //   };\n\n  //   this.queryString = [\n  //     {\n  //       brand: [],\n  //       type: [],\n  //     },\n  //   ];\n  //   this.type = [];\n  //   this.brand = [];\n  // }\n\n  \n  const brand = []\n  function ListContainer() {\n    const [typesList, setTypesList] = React.useState([]);\n    const [brandsList, setBrandsList] = React.useState([]);\n    const [productsList, setProductsList] = React.useState('');\n    const queryString = \n      {\n        brand: brand,\n        type: '',\n      }\n    ;\n    const [loading, setLoading] = React.useState(true);\n    const [getType, setType] = React.useState([]);\n    // const type = []\n    const { code } = useParams(); \n    // React.useEffect(() => {\n    //   const filter = code || '';\n    //   getProductData(code);\n    //   // setPageState(filteredProductsPageData);\n    // }, []);\n  \n    React.useEffect(() => {\n        getProductData(code);\n    }, [code]);// eslint-disable-line react-hooks/exhaustive-deps\n\n  \n    const getProductData = async(type) => {\n      setType(type)\n      const filter = type === '' ? {} : (queryString.type = { type });\n      const filteredProductsPageData = await ProductService.getFilteredProducts(filter);\n      setPageState(filteredProductsPageData.data)\n      // return filteredProductsPageData.data;\n    }\n  \n    const setPageState = (filteredProductsPageData) => {\n      if (filteredProductsPageData === undefined) {\n        return;\n      }\n      const typesList = filteredProductsPageData.types;\n      const brandsList = filteredProductsPageData.brands;\n      const productsList = filteredProductsPageData.products;\n      // this.setState({ productsList, typesList, brandsList, loading: false });\n      setTypesList(typesList);\n      setBrandsList(brandsList);\n      setProductsList(productsList);\n      setLoading(false);\n    }\n  \n     const onFilterChecked = async (e, value) => {\n          const isChecked = e.target.checked;\n          const dataType = e.target.getAttribute('id');\n          setQueryStringState(isChecked, dataType, value);\n  \n          const apiCall = await ProductService.getFilteredProducts(queryString);\n          // setState({ productsList: apiCall.data.products });\n          setProductsList(apiCall.data.products)\n    };\n  \n    const setQueryStringState = (isChecked, dataType, value) => {\n      if (isChecked) {\n        brand.push(dataType);\n        queryString.brand = brand;\n        queryString.type = getType ? getType  : '';\n        queryString.type = queryString.type.type === undefined ?\n              queryString.type : queryString.type.type;\n      } else {\n        let index = queryString[value].indexOf(dataType);\n        if (index !== -1) {\n          queryString[value].splice(index, 1);\n        }\n        queryString.type = getType ? getType  : '';\n      }\n    }\n    return (\n      <Fragment>\n        {loading ? (\n          <LoadingSpinner />\n        ) : (\n          <List\n            onFilterChecked={onFilterChecked}\n            typesList={typesList}\n            brandsList={brandsList}\n            productsList={productsList}\n          />\n        )}\n      </Fragment>\n    );\n  }\n// }\n\nexport default (ListContainer);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/sections/banner/offerBanner.js",
    "content": "import React from \"react\";\n// import { withNamespaces } from \"react-i18next\";\nimport offerImg from '../../../../assets/images/original/Contoso_Assets/product_page_assets/collection_page_banner.jpg';\n\nconst OfferBanner = ({ t, loggedIn }) => {\n    return (\n        <div className=\"offer_banner\">\n            <img src={offerImg} width=\"100%\" alt=\"\"/>\n        </div>\n    );\n};\n\nexport default (OfferBanner);\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/sections/index.js",
    "content": "import ListAside from './listAside/listAside';\nimport ListGrid from './listGrid/listGrid';\nimport OfferBanner from './banner/offerBanner';\nexport {\n    ListAside,\n    ListGrid,\n    OfferBanner,\n};\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/sections/listAside/listAside.js",
    "content": "import React, { Component } from \"react\";\n// import { NamespacesConsumer } from \"react-i18next\";\nimport SidebarAccordion from \"../../../../components/accordion/sidebarAccordion\";\nclass ListAside extends Component {\n    constructor() {\n        super();\n        this.filterPanel = React.createRef();\n        this.state = {\n            isopened: false,\n            showComponent: false,\n\n        };\n        this.openFilterPanel = this.openFilterPanel.bind(this);\n    }\n\n    componentDidMount() {\n        const setComponentVisibility = this.setComponentVisibility.bind(this);\n        setComponentVisibility(document.documentElement.clientWidth);\n        window.addEventListener(\"resize\", function () {\n            setComponentVisibility(document.documentElement.clientWidth);\n        });\n    }\n\n    setComponentVisibility(width) {\n        if (width > 768) {\n            this.setState({ showComponent: true });\n        } else {\n            this.setState({ showComponent: false, isopened: false });\n        }\n    }\n\n    toggleClass = () => {\n        document.body.classList.toggle(\"u-overflow-hidden\");\n        this.setState(prevState => ({\n            isopened: !prevState.isopened,\n            showComponent: !prevState.showComponent,\n        }));\n    };\n\n    openFilterPanel() {\n        if (this.state.showComponent === false) {\n            this.setState(\n                {\n                    showComponent: true,\n                    isopened: true,\n                },\n                () => {\n                    let currentPosition = window.pageYOffset;\n                    this.filterPanel.current.style.top = `${currentPosition}px`;\n                    document.body.classList.toggle(\"u-overflow-hidden\");\n                }\n            );\n        }\n    }\n\n    render() {\n        return (\n            <aside className=\"list__aside\">\n                <SidebarAccordion\n                    onFilterChecked={this.props.onFilterChecked}\n                    data={this.props.brandsList}\n                    title=\"Brands\"\n                    id=\"brand\"\n                />\n            </aside>\n        );\n    }\n}\n\nexport default ListAside;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/list/sections/listGrid/listGrid.js",
    "content": "import { Grid } from \"@mui/material\";\nimport React from \"react\";\nimport Product from \"../../../../components/productCard/product\";\n// import MinimalSelect from \"../../../../components/minimalselect\";\n// import Pagination from '@material-ui/lab/Pagination';\n\nconst ListGrid = ({ productsList }) => {\n    return (\n        <div className=\"list-grid\">\n            <div className=\"filter-section\">\n                <div className=\"page\">Showing 1 - {productsList ? productsList.length : 0} of {productsList ? productsList.length:0} items</div>\n                {/* <div>Sort By</div>&nbsp;&nbsp;&nbsp;&nbsp;\n                <MinimalSelect /> */}\n            </div>\n            {productsList && productsList.length > 0 ?\n            <Grid container justifyContent=\"center\" spacing={3}>             \n                {productsList && productsList.map((productsListInfo, index) => {\n                    return <Grid key={index} item lg={4} sm={6} xs={12}><Product {...productsListInfo} key={index} /></Grid>;\n                })}\n            </Grid>\n            :\n            <p className=\"text-left\">No Products Found</p>}\n            <Grid className=\"pagination\">\n                {/* <Pagination count={5} color=\"primary\" defaultPage={1} shape=\"rounded\" hidePrevButton hideNextButton/>\n                <button className=\"nextBtn\">Next</button> */}\n            </Grid>\n        </div> \n    );\n};\n\nexport default ListGrid;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/myAddressBook.js",
    "content": "import React from 'react';\nfunction MyAddressBook(props) {\n    return ( \n        <div>\n            Address\n        </div>\n    );\n}\n\nexport default MyAddressBook;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/myOrders.js",
    "content": "import React from \"react\";\nimport {\n  Button,\n  Grid,\n  IconButton,\n  Typography,\n  Divider,\n  Avatar,\n  FormLabel,\n  InputAdornment,\n  FormControl,\n  TextField,\n} from \"@mui/material\";\nimport DeleteOutline from \"@mui/icons-material/DeleteOutline\";\nimport VisibilityOff from \"@mui/icons-material/VisibilityOff\";\nimport Visibility from \"@mui/icons-material/Visibility\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\nimport { useFormik } from \"formik\";\nimport * as yup from \"yup\";\n\n\nconst validationSchema = yup.object({\n  email: yup\n    .string(\"Enter your email\")\n    .email(\"Enter a valid email\")\n    .required(\"Email is required\"),\n  newpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  currentpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  confirmpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  firstName: yup\n    .string(\"Enter your first Name\")\n    .min(2, \"Too Short!\")\n    .max(50, \"Too Long!\")\n    .required(\"First name is required\"),\n  lastName: yup\n    .string(\"Enter your last name\")\n    .min(2, \"Too Short!\")\n    .max(50, \"Too Long!\")\n    .required(\"Last name is required\"),\n  dob: yup\n    .string(\"Enter your date of birth\")\n    .required(\"Date of Birth is required\"),\n  mobile: yup\n    .number(\"Enter your mobile number\")\n    .positive(\"Invalid Number\")\n    .required(\"Mobile is required\"),\n});\n\n\nconst MyOrders = () => {\n  const [values, setValues] = React.useState({\n    password: \"\",\n    showPassword: false,\n  });\n\n\n  const handleClickShowPassword = () => {\n    setValues({\n      ...values,\n      showPassword: !values.showPassword,\n    });\n  };\n\n  const [mydata, setData] = React.useState(\"\");\n  const onImageChange = (event) => {\n    if (event.target.files && event.target.files[0]) {\n      let reader = new FileReader();\n      let file = event.target.files[0];\n      reader.onloadend = () => {\n        setData({\n          ...mydata,\n          imagePreview: reader.result,\n          file: file,\n        });\n      };\n      reader.readAsDataURL(file);\n    }\n  };\n\n  const formik = useFormik({\n    initialValues: {\n      firstName: \"\",\n      lastName: \"\",\n      email: \"\",\n      newpassword: \"\",\n      currentpassword: \"\",\n      confirmpassword: \"\",\n      dob: \"\",\n      mobile: \"\",\n    },\n    validationSchema: validationSchema,\n    onSubmit: (values) => {\n      alert(JSON.stringify(values, null, 2));\n    },\n  });\n\n  return (\n    <div className=\"PersonalSection\">\n      <form onSubmit={formik.handleSubmit} className=\"formsection\">\n        <div className=\"profile-container\">\n          <div className=\"form-container\">\n            <Grid item xs={12}>\n              <Typography variant=\"h7\" className=\"section-heading\">\n                My Address Book\n              </Typography>\n              <Divider style={{ marginTop: \"8px\" }} />\n            </Grid>\n            <div className=\"img-section\">\n              <div className=\"img-div\">\n                <Avatar\n                  alt=\"Remy Sharp\"\n                  src={mydata.imagePreview}\n                />\n              </div>\n              <div className=\"img-btn-div\">\n                <input\n                  accept=\"image/*\"\n                  id=\"contained-button-file\"\n                  multiple\n                  type=\"file\"\n                  onChange={onImageChange}\n                />\n                <label htmlFor=\"contained-button-file\">\n                  <Button\n                    variant=\"contained\"\n                    className=\"btn-upload\"\n                    color=\"primary\"\n                    component=\"span\"\n                  >\n                    Upload\n                  </Button>\n                </label>\n\n                <Button\n                  className=\"btn-delete\"\n                  variant=\"outlined\"\n                  startIcon={<DeleteOutline />}\n                  onClick={() => setData(\"\")}\n                >\n                  Delete\n                </Button>\n              </div>\n            </div>\n            <Grid item xs={6} container>\n              <div className=\"field-container\">\n                <FormControl>\n                  <Grid container>\n                    <Grid item xs={5}>\n                      <FormLabel htmlFor=\"first-name\" className=\"form-labels\">\n                        First Name\n                      </FormLabel>\n                      <TextField\n                        id=\"firstName\"\n                        className=\"formtextfields\"\n                        value={formik.values.firstName}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.firstName &&\n                          Boolean(formik.errors.firstName)\n                        }\n                        helperText={\n                          formik.touched.firstName && formik.errors.firstName\n                        }\n                        style={{\n                          margin: \"7px 0px 16px 0px\",\n                          width: \"286px\",\n                        }}\n                      />\n                    </Grid>\n                    <Grid item xs={1}></Grid>\n                    <Grid item xs={5}>\n                      <FormLabel htmlFor=\"last-name\" className=\"form-labels\">\n                        Last Name\n                      </FormLabel>\n                      <TextField\n                        id=\"lastName\"\n                        className=\"formtextfields\"\n                        value={formik.values.lastName}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.lastName &&\n                          Boolean(formik.errors.lastName)\n                        }\n                        helperText={\n                          formik.touched.lastName && formik.errors.lastName\n                        }\n                        style={{\n                          margin: \"7px 0px 16px 0px\",\n                          width: \"286px\",\n                        }}\n                      />\n                    </Grid>\n                    <Grid item xs={2} />\n                  </Grid>\n                  <Grid container className=\"email-container\">\n                    <Grid item xs={12}>\n                      <FormLabel htmlFor=\"email\" className=\"form-labels\">\n                        Email\n                      </FormLabel>\n                    </Grid>\n                    <Grid item xs={12}>\n                      <TextField\n                        id=\"email\"\n                        className=\"formtextfields\"\n                        value={formik.values.email}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.email && Boolean(formik.errors.email)\n                        }\n                        helperText={formik.touched.email && formik.errors.email}\n                      />\n                    </Grid>\n                  </Grid>\n                  <Grid container className=\"mobile-container\">\n                    <Grid item xs={12}>\n                      <FormLabel htmlFor=\"mobile\" className=\"form-labels\">\n                        Mobile Number\n                      </FormLabel>\n                    </Grid>\n                    <Grid item>\n                      <TextField\n                        id=\"mobilecode\"\n                        className=\"formtextfields\"\n                        style={{\n                          marginRight: \"8px\",\n                          width: \"80px\",\n                        }}\n                      />\n                    </Grid>\n                    <Grid item>\n                      <TextField\n                        id=\"mobile\"\n                        className=\"formtextfields\"\n                        value={formik.values.mobile}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.mobile && Boolean(formik.errors.mobile)\n                        }\n                        helperText={\n                          formik.touched.mobile && formik.errors.mobile\n                        }\n                        style={{\n                          width: \"502px\",\n                        }}\n                      />\n                    </Grid>\n                  </Grid>\n                  <Grid container className=\"dob-container\">\n                    <Grid item xs={5}>\n                      <FormLabel htmlFor=\"dob\" className=\"form-labels\">\n                        Date of birth\n                      </FormLabel>\n                    </Grid>\n                    <Grid item xs={7} />\n                    <Grid item xs={12}>\n                      <TextField\n                        id=\"dob\"\n                        type=\"date\"\n                        inputFormat=\"DD/MM/YYYY\"\n                        className=\"formtextfields\"\n                        value={formik.values.dob}\n                        onChange={formik.handleChange}\n                        error={formik.touched.dob && Boolean(formik.errors.dob)}\n                        helperText={formik.touched.dob && formik.errors.dob}\n                        endAdornment={\n                          <InputAdornment position=\"end\">\n                            <ExpandMoreIcon />\n                          </InputAdornment>\n                        }\n                        style={{\n                          height: \"55px\",\n                        }}\n                      ></TextField>\n                    </Grid>\n                  </Grid>\n                </FormControl>\n              </div>\n            </Grid>\n            <Grid item xs={6}></Grid>\n            <Grid item xs={12} className=\"passworddiv\">\n              <Typography variant=\"h7\" className=\"section-heading\">\n                Change Password\n              </Typography>\n              <Divider style={{ marginTop: \"8px\", marginBottom: \"30px\" }} />\n            </Grid>\n            <Grid item xs={6}>\n              <div className=\"passwd-section\">\n                <Grid container className=\"currentpassword-container\">\n                  <Grid item xs={5}>\n                    <FormLabel\n                      htmlFor=\"current-password\"\n                      className=\"form-labels\"\n                    >\n                      Current Password\n                    </FormLabel>\n                  </Grid>\n                  <Grid item xs={7} />\n                  <Grid item xs={12}>\n                    <TextField\n                      type=\"password\"\n                      id=\"currentpassword\"\n                      value={formik.values.currentpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.currentpassword &&\n                        Boolean(formik.errors.currentpassword)\n                      }\n                      helperText={\n                        formik.touched.currentpassword &&\n                        formik.errors.currentpassword\n                      }\n                      className=\"formtextfields\"\n                    />\n                  </Grid>\n                </Grid>\n\n                <Grid container className=\"newpassword-container\">\n                  <Grid item xs={5}>\n                    <FormLabel htmlFor=\"new-password\" className=\"form-labels\">\n                      New Password\n                    </FormLabel>\n                  </Grid>\n                  <Grid item xs={7} />\n                  <Grid item xs={12}>\n                    <TextField\n                      type={values.showPassword ? \"text\" : \"password\"}\n                      id=\"newpassword\"\n                      className=\"formtextfields newpassfield\"\n                      value={formik.values.newpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.newpassword &&\n                        Boolean(formik.errors.newpassword)\n                      }\n                      helperText={\n                        formik.touched.newpassword && formik.errors.newpassword\n                      }\n                      InputProps={{\n                        endAdornment: (\n                          <InputAdornment position=\"start\">\n                            <IconButton\n                              aria-label=\"toggle password visibility\"\n                              onClick={handleClickShowPassword}\n                              edge=\"end\"\n                            >\n                              {values.showPassword ? (\n                                <VisibilityOff />\n                              ) : (\n                                <Visibility />\n                              )}\n                            </IconButton>\n                          </InputAdornment>\n                        ),\n                      }}\n                    />\n                  </Grid>\n                </Grid>\n\n                <Grid container className=\"confirmpassword-container\">\n                  <Grid item xs={5}>\n                    <FormLabel\n                      htmlFor=\"confirm-password\"\n                      style={{ color: \"#000\" }}\n                    >\n                      Confirm Password\n                    </FormLabel>\n                  </Grid>\n                  <Grid item xs={7} />\n                  <Grid item xs={12}>\n                    <TextField\n                      type=\"password\"\n                      id=\"confirmpassword\"\n                      value={formik.values.confirmpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.confirmpassword &&\n                        Boolean(formik.errors.confirmpassword)\n                      }\n                      helperText={\n                        formik.touched.confirmpassword &&\n                        formik.errors.confirmpassword\n                      }\n                      className=\"formtextfields\"\n                    />\n                  </Grid>\n                </Grid>\n              </div>\n            </Grid>\n            <Grid item xs={6}></Grid>\n\n            <Grid item xs={12}>\n              <div className=\"submit-div\">\n                <Button\n                  variant=\"contained\"\n                  color=\"primary\"\n                  className=\"btn-submit\"\n                  type=\"Submit\"\n                >\n                  Save Changes\n                </Button>\n              </div>\n            </Grid>\n          </div>\n        </div>\n      </form>\n    </div>\n  );\n};\n\nexport default MyOrders;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/myWishlist.js",
    "content": "import React from 'react';\nimport { useLocation } from 'react-router-dom';\nimport Breadcrumb from '../../components/breadcrumb/breadcrumb';\nfunction Wishlist(props) {\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n\n    return (\n        <div className=\"CartSection\">\n            <Breadcrumb currentPath={currentCategory} />\n            <div className=\"CartTopHeadPart\">\n                <h5 className=\"MyCartHeading\">My Wishlist</h5>\n            </div>\n            <hr />\n        </div>\n    );\n}\n\nexport default Wishlist;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/personalInformation.js",
    "content": "import React from \"react\";\nimport {\n  Button,\n  Grid,\n  IconButton,\n  Typography,\n  Divider,\n  Avatar,\n  FormLabel,\n  InputAdornment,\n  TextField,\n} from \"@mui/material\";\nimport DeleteOutline from \"@mui/icons-material/DeleteOutline\";\nimport VisibilityOff from \"@mui/icons-material/VisibilityOff\";\nimport Visibility from \"@mui/icons-material/Visibility\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\nimport { useFormik } from \"formik\";\nimport * as yup from \"yup\";\n\n\nconst validationSchema = yup.object({\n  email: yup\n    .string(\"Enter your email\")\n    .email(\"Enter a valid email\")\n    .required(\"Email is required\"),\n  newpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  currentpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  confirmpassword: yup\n    .string(\"Enter your password\")\n    .min(8, \"Password should be of minimum 8 characters length\")\n    .required(\"Password is required\"),\n  firstName: yup\n    .string(\"Enter your first Name\")\n    .min(2, \"Too Short!\")\n    .max(50, \"Too Long!\")\n    .required(\"First name is required\"),\n  lastName: yup\n    .string(\"Enter your last name\")\n    .min(2, \"Too Short!\")\n    .max(50, \"Too Long!\")\n    .required(\"Last name is required\"),\n  dob: yup\n    .string(\"Enter your date of birth\")\n    .required(\"Date of Birth is required\"),\n  mobile: yup\n    .number(\"Enter your mobile number\")\n    .positive(\"Invalid Number\")\n    .required(\"Mobile is required\"),\n});\n\n\nconst MyOrders = () => {\n  const [values, setValues] = React.useState({\n    password: \"\",\n    showPassword: false,\n  });\n\n\n  const handleClickShowPassword = () => {\n    setValues({\n      ...values,\n      showPassword: !values.showPassword,\n    });\n  };\n\n  const [mydata, setData] = React.useState(\"\");\n  const onImageChange = (event) => {\n    if (event.target.files && event.target.files[0]) {\n      let reader = new FileReader();\n      let file = event.target.files[0];\n      reader.onloadend = () => {\n        setData({\n          ...mydata,\n          imagePreview: reader.result,\n          file: file,\n        });\n      };\n      reader.readAsDataURL(file);\n    }\n  };\n\n  const formik = useFormik({\n    initialValues: {\n      firstName: \"\",\n      lastName: \"\",\n      email: \"\",\n      newpassword: \"\",\n      currentpassword: \"\",\n      confirmpassword: \"\",\n      dob: \"\",\n      mobile: \"\",\n    },\n    validationSchema: validationSchema,\n    onSubmit: (values) => {\n      alert(JSON.stringify(values, null, 2));\n    },\n  });\n\n  return (\n    <div className=\"PersonalSection\">\n      <form onSubmit={formik.handleSubmit} className=\"formsection\">\n        <div className=\"profile-container\">\n          <div className=\"form-container\">\n            <Grid item xs={12}>\n              <Typography variant=\"h7\" className=\"section-heading\">\n                Personal Information\n              </Typography>\n              <Divider style={{ marginTop: \"8px\" }} />\n            </Grid>\n            <div className=\"img-section\">\n              <div className=\"img-div\">\n                <Avatar\n                  alt=\"Remy Sharp\"\n                  src={mydata.imagePreview}\n                  style={{height:'80px',width:'80px'}}\n                />\n              </div>\n              <div className=\"img-btn-div\">\n                <input\n                  accept=\"image/*\"\n                  style={{display:'none'}}\n                  id=\"contained-button-file\"\n                  multiple\n                  type=\"file\"\n                  onChange={onImageChange}\n                />\n                <label htmlFor=\"contained-button-file\">\n                  <Button\n                    variant=\"contained\"\n                    className=\"btn-upload\"\n                    color=\"primary\"\n                    component=\"span\"\n                  >\n                    Upload\n                  </Button>\n                </label>\n\n                <Button\n                  className=\"btn-delete\"\n                  variant=\"outlined\"\n                  startIcon={<DeleteOutline />}\n                  onClick={() => setData(\"\")}\n                >\n                  Delete\n                </Button>\n              </div>\n            </div>\n            <Grid container spacing={2}>\n              {/* <div className=\"field-container\"> */}\n                {/* <FormControl className=\"d-flex flex-lg-row flex-xs-column flex-md-fill flex-xs-fill\"> */}\n                  <Grid item lg={7} className=\"profile-div d-flex flex-lg-row flex-xs-column flex-xs-fill\">\n                    <div className=\"mr-2 profile-name flex-fill\">\n                      <FormLabel htmlFor=\"first-name\" className=\"form-labels d-block\">\n                        First Name\n                      </FormLabel>\n                      <TextField\n                        id=\"firstName\"\n                        className=\"formtextfields\"\n                        value={formik.values.firstName}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.firstName &&\n                          Boolean(formik.errors.firstName)\n                        }\n                        helperText={\n                          formik.touched.firstName && formik.errors.firstName\n                        }\n                        style={{\n                          margin: \"7px 0px 16px 0px\",\n                        }}\n                      />\n                    </div>\n                    {/* <Grid item xs={1}></Grid> */}\n                    <div className=\"profile-name flex-fill\">\n                      <FormLabel htmlFor=\"last-name\" className=\"form-labels d-block\">\n                        Last Name\n                      </FormLabel>\n                      <TextField\n                        id=\"lastName\"\n                        className=\"formtextfields\"\n                        value={formik.values.lastName}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.lastName &&\n                          Boolean(formik.errors.lastName)\n                        }\n                        helperText={\n                          formik.touched.lastName && formik.errors.lastName\n                        }\n                        style={{\n                          margin: \"7px 0px 16px 0px\",\n                        }}\n                      />\n                    </div>\n                    {/* <Grid item xs={2} /> */}\n                  </Grid>\n                  {/* <Grid container className=\"email-container\"> */}\n                    <Grid item lg={7} xs={12}>\n                      <FormLabel htmlFor=\"email\" className=\"form-labels d-block\">\n                        Email\n                      </FormLabel>\n                      <TextField\n                        id=\"email\"\n                        className=\"formtextfields\"\n                        value={formik.values.email}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.email && Boolean(formik.errors.email)\n                        }\n                        helperText={formik.touched.email && formik.errors.email}\n                      />\n                    </Grid>\n                  {/* </Grid> */}\n                  {/* <Grid container className=\"mobile-container\"> */}\n                    <Grid item xs={12}>\n                      <FormLabel htmlFor=\"mobile\" className=\"form-labels\">\n                        Mobile Number\n                      </FormLabel>\n                    </Grid>\n                    <Grid item lg={1} sm={2} xs={3} className=\"pt-0\">\n                      <TextField\n                        id=\"mobilecode\"\n                        className=\"formtextfields\"\n                        style={{\n                          marginRight: \"8px\",\n                          // width: \"80px\",\n                        }}\n                      />\n                    </Grid>\n                    <Grid item lg={6} sm={10} xs={9} className=\"pt-0\">\n                      <TextField\n                        id=\"mobile\"\n                        className=\"formtextfields\"\n                        value={formik.values.mobile}\n                        onChange={formik.handleChange}\n                        error={\n                          formik.touched.mobile && Boolean(formik.errors.mobile)\n                        }\n                        helperText={\n                          formik.touched.mobile && formik.errors.mobile\n                        }\n                        style={{\n                          // width: \"auto\",\n                        }}\n                      />\n                    </Grid>\n                  {/* </Grid> */}\n                  {/* <Grid container className=\"dob-container\"> */}\n                    <Grid item lg={7} xs={12}>\n                      <FormLabel htmlFor=\"dob\" className=\"form-labels\">\n                        Date of birth\n                      </FormLabel>\n                      <TextField\n                        id=\"dob\"\n                        type=\"date\"\n                        inputFormat=\"DD/MM/YYYY\"\n                        className=\"formtextfields\"\n                        value={formik.values.dob}\n                        onChange={formik.handleChange}\n                        error={formik.touched.dob && Boolean(formik.errors.dob)}\n                        helperText={formik.touched.dob && formik.errors.dob}\n                        endAdornment={\n                          <InputAdornment position=\"end\">\n                            <ExpandMoreIcon />\n                          </InputAdornment>\n                        }\n                        style={{\n                          height: \"55px\",\n                        }}\n                      ></TextField>\n                    </Grid>\n                  {/* </Grid> */}\n                {/* </FormControl> */}\n              {/* </div> */}\n            </Grid>\n            <Grid item xs={6}></Grid>\n            <Grid item xs={12} className=\"passworddiv\">\n              <Typography variant=\"h7\" className=\"section-heading\">\n                Change Password\n              </Typography>\n              <Divider style={{ marginTop: \"8px\", marginBottom: \"30px\" }} />\n            </Grid>\n            <Grid item xs={12}>\n              <div className=\"passwd-section\">\n                <Grid container className=\"currentpassword-container\">\n                  <Grid item lg={7} xs={12}>\n                    <FormLabel\n                      htmlFor=\"current-password\"\n                      className=\"form-labels\"\n                    >\n                      Current Password\n                    </FormLabel>\n                    <TextField\n                      type=\"password\"\n                      id=\"currentpassword\"\n                      value={formik.values.currentpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.currentpassword &&\n                        Boolean(formik.errors.currentpassword)\n                      }\n                      helperText={\n                        formik.touched.currentpassword &&\n                        formik.errors.currentpassword\n                      }\n                      className=\"formtextfields\"\n                    />\n                  </Grid>\n                </Grid>\n\n                <Grid container className=\"newpassword-container\">\n                  <Grid item lg={7} xs={12}>\n                    <FormLabel htmlFor=\"new-password\" className=\"form-labels d-block\">\n                      New Password\n                    </FormLabel>\n                    <TextField\n                      type={values.showPassword ? \"text\" : \"password\"}\n                      id=\"newpassword\"\n                      className=\"formtextfields newpassfield\"\n                      value={formik.values.newpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.newpassword &&\n                        Boolean(formik.errors.newpassword)\n                      }\n                      helperText={\n                        formik.touched.newpassword && formik.errors.newpassword\n                      }\n                      InputProps={{\n                        endAdornment: (\n                          <InputAdornment position=\"start\">\n                            <IconButton\n                              aria-label=\"toggle password visibility\"\n                              onClick={handleClickShowPassword}\n                              edge=\"end\"\n                            >\n                              {values.showPassword ? (\n                                <VisibilityOff />\n                              ) : (\n                                <Visibility />\n                              )}\n                            </IconButton>\n                          </InputAdornment>\n                        ),\n                      }}\n                    />\n                  </Grid>\n                </Grid>\n\n                <Grid container className=\"confirmpassword-container\">\n                  <Grid item lg={7} xs={12}>\n                    <FormLabel\n                      htmlFor=\"confirm-password\"\n                      style={{ color: \"#000\" }}\n                    >\n                      Confirm Password\n                    </FormLabel>\n                    <TextField\n                      type=\"password\"\n                      id=\"confirmpassword\"\n                      value={formik.values.confirmpassword}\n                      onChange={formik.handleChange}\n                      error={\n                        formik.touched.confirmpassword &&\n                        Boolean(formik.errors.confirmpassword)\n                      }\n                      helperText={\n                        formik.touched.confirmpassword &&\n                        formik.errors.confirmpassword\n                      }\n                      className=\"formtextfields\"\n                    />\n                  </Grid>\n                </Grid>\n              </div>\n            </Grid>\n            <Grid item xs={6}></Grid>\n\n            <Grid item xs={12}>\n              <div className=\"submit-div\">\n                <Button\n                  variant=\"contained\"\n                  color=\"primary\"\n                  className=\"btn-submit\"\n                  type=\"Submit\"\n                >\n                  Save Changes\n                </Button>\n              </div>\n            </Grid>\n          </div>\n        </div>\n      </form>\n    </div>\n  );\n};\n\nexport default MyOrders;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/profile.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains styles that are specific to the User Profile page.\n// -----------------------------------------------------------------------------\n@import \"../../main.scss\";\n\n.profileMain {\n    margin-top: 192px;\n    .ProfileSection{\n        padding: 0 70px 0 70px;\n        input[type=\"date\"]::-webkit-calendar-picker-indicator {\n            display: none;\n            -webkit-appearance: none;\n            appearance: none;\n        }\n        .MuiInputAdornment-positionStart{\n            position: absolute;\n            right: 15px;\n        }\n    }\n  \n  .ProfileSection .topHeaderSection {\n    display: flex;\n    align-items: center;\n  }\n  \n  .ProfileSection .myprofileHeader {\n    margin: 0px auto 0px 0px;\n    text-align: left;\n    font: normal normal 600 24px/44px \"Inter\";\n    letter-spacing: 0px;\n    color: #000000;\n  }\n  .ProfileSection .logout-btn {\n    background: #ffffff 0% 0% no-repeat padding-box;\n    border: 1px solid #2874f0;\n    border-radius: 8px;\n    width: 119px;\n    height: 40px;\n    text-align: center;\n    font: normal normal 600 16px/24px \"Inter\";\n    letter-spacing: 0px;\n    color: #2874f0;\n    text-transform: capitalize;\n    box-shadow: none;\n  }\n  \n  .ProfileSection .sidebar-container {\n    margin: 45px 0px 0px 0px;\n  }\n  \n  .ProfileSection .sidebar-item {\n    display: flex;\n    align-items: center;\n    background-color: #fff;\n    height: 56px;\n    margin-top: 20px;\n    max-width: 320px;\n    border-left: 4px solid white;\n    cursor: pointer;\n  }\n  .ProfileSection .sidebar-item.active {\n    \n    border-radius: 2px 0px 0px 2px;\n    border-left: 4px solid #2874F0;\n    background-color: rgba(40, 116, 240, 0.08);\n  }\n  \n  .ProfileSection .sidebarIcons{\n     margin: 0px 16px 0px 20px; \n     width: 24px;\n     height: 24px;\n  }\n  \n  .ProfileSection .item-content{\n    text-align: left;\n  font-family: \"Inter\";\n  font-size: 16px;\n  font-weight: 500;\n  letter-spacing: 0px;\n  color: #000000;\n  \n  }\n  .ProfileSection .item-arrow{\n  margin: 0px 31px 0px auto; \n  \n  }\n  \n  \n  \n  \n  \n  \n  .ProfileSection .form-container {\n    margin: 45px 0px 0px 32px;\n  }\n  .ProfileSection .section-heading {\n    text-align: left;\n    font: normal normal 600 20px/26px \"Inter\";\n    letter-spacing: 0px;\n    color: #131;\n  }\n  \n  .ProfileSection .img-section {\n    display: flex;\n    align-items: center;\n    margin: 30px 0px 31px 0px;\n  }\n  .ProfileSection .btn-upload {\n    background: #000000 0% 0% no-repeat padding-box;\n    border-radius: 8px;\n    color: #fff;\n    box-shadow: none !important;\n    width: 136px;\n    height: 38px;\n    text-align: center;\n    font: normal normal medium 14px/18px \"Inter\";\n    letter-spacing: 0px;\n    color: #ffffff;\n    text-transform: capitalize;\n    margin-right: 16px;\n  }\n  .ProfileSection .btn-delete {\n    background: #ffffff 0% 0% no-repeat padding-box;\n    border: 2px solid #b00020;\n    border-radius: 8px;\n    width: 136px;\n    height: 44px;\n    text-align: center;\n    font: normal normal medium 14px/18px \"Inter\";\n    letter-spacing: 0px;\n    color: #b00020;\n    text-transform: capitalize;\n    box-shadow: none !important;\n  }\n  .ProfileSection .img-div {\n    margin-right: 16px;\n  }\n  .ProfileSection .form-labels {\n    text-align: left;\n    font-family: \"Inter\" !important;\n    letter-spacing: 0px;\n    color: #171520 !important;\n    font-weight: 500 !important;\n    font-size: 16px;\n    margin-bottom: 7px;\n  }\n  .MuiInput-underline::before {\n    border-bottom: none !important;\n  }\n  \n  .ProfileSection .submit-div {\n    float: right;\n  }\n  .ProfileSection .btn-submit {\n    background: #2874f0 0% 0% no-repeat padding-box;\n    border-radius: 8px;\n    box-shadow: none !important;\n    text-transform: capitalize;\n    text-align: center;\n    margin-top: 50px;\n    font-size: 16px;\n    font-family: \"Inter\";\n    font-weight: 500;\n    letter-spacing: 0px;\n    color: #ffffff;\n  }\n  \n  .ProfileSection .formtextfields{\n    width: 100%;\n    background: #F1F1F1;\n    border-radius: 4px;\n    border: 0;\n    color: #626262;\n    font-family: \"Inter\";\n    font-weight: 500;\n    font-size: 16px;\n    margin: 7px 0px 16px 0px;\n  \n  \n  }\n  .ProfileSection .MuiInputBase-input {\n    padding: 17px 23px 14px 16px;\n    // width: 550px;\n  }\n  .ProfileSection .newpassword-container .MuiInputBase-input {\n    padding: 17px 23px 14px 16px;\n   width: 100%;\n  }\n  \n  .ProfileSection .MuiFormHelperText-root.Mui-error {\n    color: red;\n    background: white;\n    margin: 0;\n  }\n  .ProfileSection .formsection{\n    width: 100%;\n  }\n  .ProfileSection .passworddiv{\n    margin-top: 50px;\n  }\n  .PersonalSection{\n  width: 100%;\n  }\n  .ProfileSection .deleteIconsvg{\n    width:24px;\n    height: 24px;\n  }\n}\n.profile {\n    margin-bottom: 11.375rem;\n    margin-left: auto;\n    margin-right: auto;\n    max-width: 87.5rem;\n\n    .grid-container {\n        grid-template-columns: 1fr;\n\n        @media (min-width: $mq-m) {\n            margin: 0 auto;\n            display: grid;\n            grid-template-columns: 1.4fr 1fr;\n            grid-template-rows: 25rem;\n            grid-auto-rows: auto;\n            grid-column-gap: 1.5rem;\n            grid-row-gap: 5rem;\n            grid-template-areas: \"profile-card aside\" \"history aside\";\n\n            .profile-card {\n                grid-area: profile-card;\n            }\n\n            .history {\n                grid-area: history;\n            }\n\n            .aside {\n                grid-area: aside;\n            }\n        }\n    }\n\n    &__data {\n        &--empty {\n            margin-right: $margin-m;\n            margin-left: $margin-m;\n            box-shadow: $box-shadow-l;\n            padding-left: $grid-gap-m;\n            padding-right: $grid-gap-m;\n            padding-bottom: $grid-gap-m;\n            padding-top: $grid-gap-m;\n\n            @media (min-width: $mq-m) {\n                margin-right: $margin-m;\n                margin-left: 0;\n            }\n        }\n    }\n\n    &__heading {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n\n        .btn {\n            padding-left: $padding-m;\n            padding-right: $padding-m;\n            margin-right: $margin-m;\n\n            @media (min-width: $mq-s) {\n                padding-left: $padding-l;\n                padding-right: $padding-l;\n            }\n        }\n\n        &-title {\n            @include text-xl;\n\n            color: $color-text-primary;\n            text-align: center;\n            margin-top: 2.5rem;\n            margin-bottom: 2.5rem;\n            margin-left: $margin-l;\n            text-align: left;\n        }\n\n        &-history {\n            margin-left: -4rem;\n            padding-left: $padding-m;\n        }\n\n        &-recommended {\n            margin-bottom: 5rem;\n\n            @media (min-width: $mq-m) {\n                margin-bottom: 5.25rem;\n            }\n        }\n    }\n\n    &-wapper {\n        margin: $margin-m;\n        box-shadow: $box-shadow-l;\n        padding-left: 1.5rem;\n        padding-right: 1.5rem;\n        padding-bottom: 1.5rem;\n\n        @media (min-width: $mq-m) {\n            display: flex;\n            flex-direction: row;\n            align-items: center;\n            padding-top: 1.5rem;\n        }\n    }\n\n    &__image {\n        text-align: center;\n        margin-left: auto;\n        margin-right: auto;\n        padding-top: $padding-l;\n        padding-bottom: $padding-m;\n\n        width: auto;\n        text-align: center;\n        margin: 0 auto;\n\n        \n\n        @media (min-width: $mq-s) {\n            margin: 0 auto;\n            width: 10rem;\n        }\n\n        @media (min-width: $mq-m) {\n            margin-right: $margin-m;\n            margin-left: 0;\n        }\n\n        img {\n            max-width: 8.5rem;\n            border-radius: 50%;\n        }\n    }\n\n    &__info {\n        color: $color-text-primary;\n        font-size: $font-big;\n    }\n\n    &__title {\n        margin-top: 0;\n        margin-bottom: .5rem;\n        font-weight: $font-weight-light;\n    }\n\n    &__subtitle {\n        margin: 0;\n        margin-bottom: $margin-m;\n    }\n\n    .mycoupons {\n        &__grid {\n            max-width: 100%;\n            margin-left: $margin-m;\n            margin-right: $margin-m;\n\n            .coupon {\n                grid-column: span 12;\n\n                &__img {\n                    background-size: cover;\n                    background-position: center;\n                }\n            }\n        }\n    }\n\n    .history {\n        padding-left: 4rem;\n        padding-right: $padding-m;\n\n        @media (min-width: $mq-s) {\n            padding-left: 6rem;\n        }\n\n        &__data {\n            display: flex;\n            align-items: center;\n            margin-left: -2.875rem;\n\n            @media (min-width: $mq-s) {\n                margin-left: -4.875rem;\n            }\n        }\n\n        &__item {\n            background: #fff;\n            box-shadow: $box-shadow-l;\n            width: 100%;\n            margin-bottom: $margin-m;\n        }\n\n        &__image {\n            border-radius: 4.875rem;\n            width: 6rem;\n            height: 6rem;\n            z-index: 1;\n\n            @media (min-width: $mq-s) {\n                width: 9.75rem;\n                height: 9.75rem;\n            }\n        }\n\n        &__info {\n            @include text-m;\n\n            font-weight: $font-weight-medium;\n            color: $color-text-primary;\n            min-height: 6.875rem;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            line-height: 2rem;\n            margin-left: $margin-m;\n\n            @media (min-width: $mq-s) {\n                height: 9.5rem;\n                margin-left: 0;\n                padding-left: $padding-m;\n                font-size: 1.5rem;\n            }\n\n            &-name {\n                margin: 0;\n\n                @media (min-width: $mq-s) {\n                    margin-bottom: .5rem;\n                }\n            }\n\n            &-price {\n                margin: 0;\n            }\n        }\n    }\n\n    .cards {\n        margin-bottom: 0;\n\n        @media (min-width: $mq-m) {\n            margin-bottom: 5rem;\n        }\n\n        .btn {\n            box-shadow: 10px 10px 52px 0 rgba(0, 0, 0, 0.2);\n            display: block;\n            margin-bottom: -$margin-m;\n            margin-left: auto;\n            margin-right: auto;\n        }\n\n        .card {\n            &__description {\n                max-width: 11.25rem;\n                overflow: hidden;\n                text-overflow: ellipsis;\n                white-space: nowrap;\n\n                @media (min-width: $mq-s) {\n                    max-width: 11.25rem;\n                    line-height: 2rem;\n                }\n            }\n        }\n    }\n\n    .favorite {\n        display: flex;\n        flex-direction: column;\n\n        @media (min-width: $mq-s) {\n            flex-direction: row;\n        }\n\n        &__item {\n            background-size: cover;\n            background-repeat: no-repeat;\n            align-items: center;\n            display: flex;\n            grid-column-end: span 4;\n            justify-content: center;\n            min-height: 14.5rem; // 232px\n            background-position: center;\n            margin-bottom: $margin-m;\n            margin-left: $margin-m;\n            margin-right: $margin-m;\n\n            @media (min-width: $mq-s) {\n                width: 100%;\n\n                &:last-child {\n                    display: none;\n                }\n            }\n\n            @media (min-width: $mq-m) {\n                &:last-child {\n                    display: flex;\n                }\n            }\n        }\n    }\n}\n@media screen and (min-width : 320px) and (max-width : 767px) {\n    .profileMain .ProfileSection {\n        padding: 0 35px;\n    }\n    .profileMain .ProfileSection .form-container{\n        margin-left: 0\n    }\n    .profile-name{\n        margin: 0 !important;\n    }\n}\n\n@media screen and (min-width : 900px) and (max-width : 1350px) {\n    .profile-div{\n        -ms-flex: 1 1 auto!important;\n        flex: 1 1 auto!important;\n    }\n}\n\n@media screen and (min-width : 768px) and (max-width : 899px) {\n    .profile-div{\n        -ms-flex: 1 1 auto!important;\n        flex: 1 1 auto!important;\n    }\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/profile/profileForm.js",
    "content": "import React from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { connect } from 'react-redux';\nimport {\n  Button,\n  Grid,\n  Avatar,\n} from \"@mui/material\";\nimport NavigateNext from \"@mui/icons-material/NavigateNext\";\nimport PersonalInformation from \"./personalInformation\";\n// import MyWishlist from \"./MyWishlist\";\n// import MyOrders from \"./MyOrders\";\n// import MyAddressBook from \"./MyAddressBook\";\nimport logout_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/logout_icon.svg\";\nimport personal_information_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/personal_information_icon.svg\";\n// import my_wishlist_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/my_wishlist_icon.svg\";\n// import my_address_book_icons from \"../../assets/images/original/Contoso_Assets/profile_page_assets/my_address_book_icons.svg\";\n// import my_orders_icon from \"../../assets/images/original/Contoso_Assets/profile_page_assets/my_orders_icon.svg\";\nimport Breadcrump from \"../../components/breadcrumb/breadcrumb\";\nimport AuthB2CService from \"../../services/authB2CService\";\nimport './profile.scss'\n\nconst FormProfile = (props) => {\n  const authService = new AuthB2CService();\n  const { page } = useParams()\n  const [activeState, setActiveState] = React.useState(page);\n  const onClickLogout = () => {\n    localStorage.clear();\n\n    if (props.userInfo.isB2c) {\n      authService.logout();\n    }\n    props.clickAction();\n    props.history.push('/');\n  }\n  return (\n    <div className=\"profileMain\">\n    <Breadcrump currentPath=\"My Profile\"/>\n    <div className=\"ProfileSection\">\n      <div className=\"topHeaderSection\">\n        <h2 className=\"myprofileHeader\"> My Profile</h2>\n        <Button\n          className=\"logout-btn\"\n          variant=\"outlined\"\n          color=\"primary\"\n          onClick={onClickLogout}\n          startIcon={<Avatar src={logout_icon} className=\"deleteIconsvg\"/>}\n        >\n          Logout\n        </Button>\n      </div>\n      <div>\n        <Grid container>\n          <Grid item lg={3} md={4} xs={12}>\n            <div className=\"sidebar-container\">\n              <div\n                className={`${\n                  activeState === \"personal\"\n                    ? \"sidebar-item active\"\n                    : \"sidebar-item\"\n                }`}\n                onClick={() => {\n                  setActiveState(\"personal\");\n                }}\n              >\n                <div className=\"item-start\"></div>\n                <div className=\"item-logo\">\n                <Avatar src={personal_information_icon} className=\"sidebarIcons\" />\n                </div>\n                <div className=\"item-content\">Personal Information</div>\n                <div className=\"item-arrow\">\n                  <NavigateNext className=\"sidebarnavIcons\" />\n                </div>\n              </div>\n\n              {/* <div\n                className={`${\n                  activeState === \"orders\"\n                    ? \"sidebar-item active\"\n                    : \"sidebar-item\"\n                }`}\n                onClick={() => {\n                  setActiveState(\"orders\");\n                }}\n              >\n                <div className=\"item-logo\">\n                <Avatar src={my_orders_icon} className=\"sidebarIcons\" />\n                </div>\n                <div className=\"item-content\">My Orders</div>\n                <div className=\"item-arrow\">\n                  <NavigateNext className=\"sidebarnavIcons\" />\n                </div>\n              </div> */}\n\n              {/* <div\n                className={`${\n                  activeState === \"wishlist\"\n                    ? \"sidebar-item active\"\n                    : \"sidebar-item\"\n                }`}\n                onClick={() => {\n                  setActiveState(\"wishlist\");\n                }}\n              >\n                <div className=\"item-logo\">\n                <Avatar src={my_wishlist_icon} className=\"sidebarIcons\" />\n                </div>\n                <div className=\"item-content\">My Wishlist</div>\n                <div className=\"item-arrow\">\n                  <NavigateNext className=\"sidebarnavIcons\" />\n                </div>\n              </div> */}\n\n              {/* <div\n                className={`${\n                  activeState === \"address\"\n                    ? \"sidebar-item active\"\n                    : \"sidebar-item\"\n                }`}\n                onClick={() => {\n                  setActiveState(\"address\");\n                }}\n              >\n                <div className=\"item-logo\">\n                <Avatar src={my_address_book_icons} className=\"sidebarIcons\" />\n                </div>\n                <div className=\"item-content\">My Address Book</div>\n                <div className=\"item-arrow\">\n                  <NavigateNext className=\"sidebarnavIcons\" />\n                </div>\n              </div> */}\n            </div>\n          </Grid>\n          <Grid item lg={9} md={8} xs={12} container>\n            {activeState === \"personal\" ?  <PersonalInformation/> : null}\n            {/* {activeState === \"orders\" ?  <MyOrders/> : null}\n            {activeState === \"wishlist\" ?  <MyWishlist/> : null}\n            {activeState === \"address\" ?  <MyAddressBook/> : null} */}\n          </Grid>\n        </Grid>\n      </div>\n    </div>\n    </div>\n  );\n};\nconst mapStateToProps = (state) => { \n  return { \n    userInfo : state.login.userInfo,\n    theme :  state.login.theme\n  }\n};\nexport default connect(mapStateToProps, null)(FormProfile);"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/suggestedProductsList/suggestedProductsList.js",
    "content": "import { Grid } from \"@mui/material\";\nimport React from \"react\";\nimport Breadcrump from \"../../components/breadcrumb/breadcrumb\";\nimport { ListGrid } from \"../list/sections\";\nimport { useLocation } from \"react-router-dom\";\nconst SuggestedProductsList = (props) => {\n    const [suggestedProductsList, setSuggestedProductsList] = React.useState(null);\n    const location = useLocation();\n    const currentCategory = location.pathname.split(\"/\").pop().replaceAll('-',' ');\n    React.useEffect(() => {\n        const suggestedProducts = location.state;\n        setSuggestedProductsList(suggestedProducts.relatedProducts);\n    }, [location.state]);\n    return (\n        <div className=\"list\">\n            <Breadcrump currentPath={currentCategory}/>\n            <div className=\"list__content\">\n                <h6 className=\"mainHeading\">{currentCategory}</h6>\n                <Grid container>\n                    {/* <Grid item xs={3}>\n                        <ListAside/>\n                    </Grid> */}\n                    <Grid item xs={12}>\n                        <ListGrid productsList={suggestedProductsList} />\n                    </Grid>\n                </Grid>\n            </div>\n            <hr className=\"m-0\"/>\n        </div>\n    );\n};\n\nexport default SuggestedProductsList;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/pages/suggestedProductsList/suggestedproductslist.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains all styles related to the Suggested Products List page\n// -----------------------------------------------------------------------------\n.suggested-list-grid{\n    margin-top: 137px;\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/reducers/login.reducer.js",
    "content": "import { FORM_EMAIL, SAVE_USER, REMOVE_USER, THEME_CHANGE, GET_QUANTITY } from '../types/types';\n\nlet userInfo = JSON.parse(localStorage.getItem('state'));\n\nconst initialDefaultState = {\n    userInfo: {\n        loggedIn: false,\n        token: '',\n        user: {\n            email: '',\n            type: ''\n        }\n    },\n    theme : false,\n    quantity : 0\n}\nconst defaultState = userInfo ? { userInfo } : { ...initialDefaultState };\n\nconst login = (state = defaultState, action) => {\n    switch (action.type) {\n        case FORM_EMAIL:\n            return { ...state, ...action };\n        case SAVE_USER:\n            return { userInfo: action.userInfo };\n        case GET_QUANTITY:\n            return { ...state, quantity : action.quantity };\n        case THEME_CHANGE:\n            return { ...state, [action.field] : action.value };\n        case REMOVE_USER:\n            return { ...initialDefaultState };\n        default:\n            return defaultState;\n    }\n};\n\nexport default login;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/reducers/reducers.js",
    "content": "import { combineReducers } from 'redux';\n\nimport login from './login.reducer';\n// import theme from './theme.reducer';\n\nexport default combineReducers({ login });"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/reportWebVitals.js",
    "content": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/authB2CService.js",
    "content": "// import { config } from 'dotenv';\nimport * as Msal from 'msal';\nimport { ConfigService } from './'\nexport default class AuthB2CService {\n    constructor() {\n        this.applicationConfig = {\n            auth: {\n                clientId: ConfigService._B2cClientId,\n                authority: ConfigService._B2cAuthority,\n                validateAuthority: false,\n                redirectUri: `${window.location.origin}/authcallback`\n            },\n            cache:{\n                cacheLocation: \"sessionStorage\",\n                storeAuthStateInCookie: false\n            }\n        }\n\n        this.msalAgent = new Msal.UserAgentApplication(this.applicationConfig);\n        // this.msalAgent = new PublicClientApplication(this.applicationConfig);\n        this.msalAgent.handleRedirectCallback(async (error, response) => {\n            if(!response.accessToken) {\n                await this.login();\n            }\n        });\n    }\n\n    login = async () => {\n        let responseUser;\n        await this.msalAgent.loginPopup(\n            {\n                scopes: ConfigService._B2cScopes,\n                prompt: 'select_account'\n            }\n        ).then(response => {\n            responseUser = response.account;\n        }).catch((err) => {\n            console.log('err',err)\n        });\n        return (responseUser) ? responseUser : null;\n        // this.msalAgent.loginRedirect({scopes:ConfigService._B2cScopes});\n        // this.msalAgent.handleRedirectPromise((response) => {\n        //     console.log(response)\n        // });\n    }\n\n    logout = () => this.msalAgent.logout();\n\n    getToken = async () => {\n        return await this.msalAgent.acquireTokenSilent({ scopes: [ConfigService._B2cScopes], authority: ConfigService._B2cAuthority })\n            .then(accessToken => accessToken.accessToken);\n    };\n}"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/cartService.js",
    "content": "import axios from \"axios\";\nimport { ConfigService } from \"./\"\nrequire('../helpers/errorsHandler');\n\nconst CartService = {\n    async getShoppingCart(token) {\n        await ConfigService.loadSettings();\n        if (ConfigService._byPassShoppingCartApi) {\n            const items = await ConfigService._shoppingCartDao.find(token);\n            return items;\n        }\n\n        try {\n            const response = await axios.get(`${ConfigService._apiUrlShoppingCart}/shoppingcart`, ConfigService.HeadersConfig(token));\n            return response.data;\n        } catch(e) {\n            return null;\n        }\n    },\n\n    async postProductToCart(token, detailProduct) {\n        const products = await this.getShoppingCart(token);\n\n        const product = products.find(product => product.id === detailProduct.id);\n        if (product) {\n            return this.updateQuantity(product._cdbid, product.qty + 1, token)\n                .then(() => ({message: \"Product added on shopping cart\"}))\n                .catch(() => ({ errMessage: \"The product could not be added to the cart\" }))\n        }\n\n        return this.addProduct(token, detailProduct);\n    },\n\n    async addProduct(token, detailProduct) {\n        await ConfigService.loadSettings();\n\n        const productInfo = {\n            id: detailProduct.id,\n            name: detailProduct.name,\n            price: detailProduct.price,\n            imageUrl: detailProduct.imageUrl,\n            email: detailProduct.email,\n            typeid: detailProduct.type.id\n        };\n\n        const cartItems = {\n            \"cartItemId\": Math.floor(Math.random() * 1000).toString(),\n            \"email\": detailProduct.email.toLowerCase(),\n            \"productId\": detailProduct.id,\n            \"name\": detailProduct.name,\n            \"price\": detailProduct.price,\n            \"imageUrl\": detailProduct.imageUrl,\n            \"quantity\": detailProduct.quantity,\n        };\n\n        const dataToPost = { detailProduct: productInfo, qty: detailProduct.quantity };\n\n        if (ConfigService._byPassShoppingCartApi) {\n            await ConfigService._shoppingCartDao.addItem(dataToPost);\n            return { message: \"Product added on shopping cart\" };\n        }\n\n        const addProduct = axios.post(`${ConfigService._apiUrlShoppingCart}/shoppingcart`, cartItems, ConfigService.HeadersConfig(token))\n            .then((response) => {\n                return response.data;\n            })\n            .catch(() => {\n                return { errMessage: \"The product could not be added to the cart\" }\n            })\n\n        return addProduct;\n    },\n\n    async getRelatedDetailProducts(token, typeid = {}) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrlShoppingCart}/shoppingcart/relatedproducts/?type=${typeid}`, ConfigService.HeadersConfig(token));\n        return response.data[0];\n    },\n\n    async updateQuantity(detailProduct, qty, token) {\n        await ConfigService.loadSettings();\n\n        const product = {\n            \"cartItemId\": detailProduct.cartItemId,\n            \"email\": detailProduct.email,\n            \"productId\": detailProduct.productId,\n            \"name\": detailProduct.name,\n            \"price\": detailProduct.price,\n            \"imageUrl\": detailProduct.imageUrl,\n            \"quantity\": qty,\n        };\n\n        const response = await axios.put(`${ConfigService._apiUrlShoppingCart}/shoppingcart/product`, product, ConfigService.HeadersConfig(token));\n        return response;\n    },\n\n    async deleteProduct(detailProduct, token) {\n        await ConfigService.loadSettings();\n\n        let config = ConfigService.HeadersConfig(token);\n        config.data = {\n            // id: id,\n            \"cartItemId\": detailProduct.cartItemId,\n            \"email\": detailProduct.email,\n            \"productId\": detailProduct.productId,\n            \"name\": detailProduct.name,\n            \"price\": detailProduct.price,\n            \"imageUrl\": detailProduct.imageUrl,\n            \"quantity\": detailProduct.qty,\n        }\n        // const product = {\n        // };\n        const response = await axios.delete(`${ConfigService._apiUrlShoppingCart}/shoppingcart/product`, config);\n        return response;\n    }\n}\n\n\nexport default CartService;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/configService.js",
    "content": "import axios from \"axios\";\n\n// require(\"dotenv\").config();\nconst settingsUrl = \"/api/settings\";\n\n// Note: The '{PRODUCTS_API_ENDPOINT}', '{CARTS_API_ENDPOINT}' tokens will be substituted by github workflow.\nconst APIUrl = process.env.REACT_APP_APIURL;\nconst APIUrlShoppingCart = process.env.REACT_APP_APIURLSHOPPINGCART;\nconst UseB2C = process.env.REACT_APP_USEB2C;\nconst B2cAuthority = process.env.REACT_APP_B2CAUTHORITY;\nconst B2cClientId =  process.env.REACT_APP_B2CCLIENTID;\nconst B2cScopes = [process.env.REACT_APP_B2CSCOPES];\nconst userEmail = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')).userName : null;\n\nconst _HeadersConfig = (token, devspaces = undefined) => {\n  const headers = token ? { Authorization: `Bearer ${token}` } : {};\n  if(userEmail){\n    headers['x-tt-email'] = userEmail\n  }\n  if (devspaces) {\n    headers[\"azds-route-as\"] = devspaces;\n  }\n\n  return { headers: headers };\n};\n\nconst ConfigService = {\n  _needLoadSettings: !APIUrl || !APIUrlShoppingCart,\n  _apiUrl: APIUrl,\n  _apiUrlShoppingCart: APIUrlShoppingCart,\n  _UseB2C: UseB2C,\n  _B2cAuthority: B2cAuthority,\n  _B2cClientId: B2cClientId,\n  _B2cScopes: B2cScopes,\n  _applicationInsightsIntrumentationKey: \"\",\n  _debugInformation: {},\n  _acsConnectionString: \"\",\n  _acsResource: \"\",\n  _logicAppUrl: \"\",\n  _email: \"\",\n  _customerSupportEnabled: false,\n\n  async loadSettings() {\n    if (this._needLoadSettings) {\n      const settingsResponse = await axios.get(settingsUrl);\n      // const settingsResponse = {\n      //   data: {\n      //     auth: null,\n      //     apiUrl: \"https://f6b8ea119d5d435c846c.westus2.aksapp.io/webbff/v1\",\n      //     apiUrlShoppingCart: \"https://backend.contosotraders.com/cart-api\",\n      //     useB2C: false,\n      //     b2CAuth: { clientId: \"\", authority: \"\", scopes: \"\" },\n      //     cart: null,\n      //     applicationInsights: { instrumentationKey: null },\n      //     debugInformation: {\n      //       sqlServerName: null,\n      //       mongoServerName: null,\n      //       customText: \"\",\n      //       showDebug: false,\n      //     },\n      //     byPassShoppingCartApi: false,\n      //     devspacesName: \"\",\n      //     productImagesUrl: null,\n      //     personalizer: { apiKey: \"\", endpoint: \"\" },\n      //   },\n      // };\n      this._needLoadSettings = false;\n      this._apiUrl = settingsResponse.data.apiUrl;\n      this._apiUrlShoppingCart = settingsResponse.data.apiUrlShoppingCart;\n      this._UseB2C = settingsResponse.data.useB2C;\n\n      if (this._UseB2C) {\n        this._B2cAuthority = settingsResponse.data.b2CAuth.authority;\n        this._B2cClientId = settingsResponse.data.b2CAuth.clientId;\n        this._B2cScopes = settingsResponse.data.b2CAuth.scopes;\n      }\n\n      this._devspacesName = settingsResponse.data.devspacesName;\n      this._applicationInsightsIntrumentationKey =\n        settingsResponse.data.applicationInsights.instrumentationKey;\n      this._debugInformation = settingsResponse.data.debugInformation;\n      this._acsConnectionString = settingsResponse.data.acs.connectionString;\n      this._acsResource = settingsResponse.data.acs.resource;\n      this._logicAppUrl = settingsResponse.data.logicAppUrl;\n      this._email = settingsResponse.data.email;\n      this._customerSupportEnabled = \n            settingsResponse.data.email\n            && settingsResponse.data.acs.resource\n            && settingsResponse.data.acs.connectionString\n            && settingsResponse.data.logicAppUrl;\n    }\n  },\n\n  HeadersConfig(token = undefined) {\n    return _HeadersConfig(token, this._devspacesName);\n  },\n};\n\nexport default ConfigService;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/index.js",
    "content": "import UserService from './userService';\nimport CartService from './cartService';\nimport ProductService from './productsService';\nimport ConfigService from './configService';\n\nexport { UserService, CartService, ProductService, ConfigService };"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/productsService.js",
    "content": "import axios from \"axios\";\nimport { ConfigService } from \"./\"\nconst qs = require('qs');\nrequire('../helpers/errorsHandler');\n\n\nconst ProductService = { \n\n    async getHomePageData() {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/products/landing`, ConfigService.HeadersConfig(), { errorHandle: false })\n        return response;\n    },\n\n    async getCouponsPageData(token) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/coupons`, ConfigService.HeadersConfig(token), { errorHandle: false });\n        return response;\n    },\n\n    async getFilteredProducts(filters = {}) {\n        await ConfigService.loadSettings();\n        \n        filters.type = filters.type.type === undefined ? filters.type : filters.type.type;\n\n        const params = {\n            'params': filters,\n            'paramsSerializer': qs.stringify(filters, { arrayFormat: 'repeat' })\n        }\n        const response = await axios.get(`${ConfigService._apiUrl}/products/?`+params.paramsSerializer, ConfigService.HeadersConfig(), { errorHandle: false });\n        return response;\n    },\n\n    async getDetailProductData(productId) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/products/${productId}`, ConfigService.HeadersConfig(), { errorHandle: false });\n        return response && response.data ? response.data : null;\n    },\n\n    async getRelatedProducts(formData, token) {\n        await ConfigService.loadSettings();\n        const response = await axios.post(`${ConfigService._apiUrl}/products/imageclassifier`, formData, ConfigService.HeadersConfig(token));\n        return response.data;\n    },\n\n    async getSearchResults(term) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/Products/search/${term}`, ConfigService.HeadersConfig(), { errorHandle: false });\n        return response.data;\n    },\n}\n\nexport default ProductService;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/telemetryClient.js",
    "content": "import { ApplicationInsights } from '@microsoft/applicationinsights-web';\nimport { ReactPlugin } from '@microsoft/applicationinsights-react-js';\n\nclass TelemetryService {\n\n    constructor() {\n        this.reactPlugin = new ReactPlugin();\n    }\n\n    initialize(appInsightsInstrumentationKey, reactPluginConfig) {\n        this.appInsights = new ApplicationInsights({\n            config: {\n                instrumentationKey: appInsightsInstrumentationKey,\n                maxBatchInterval: 0,\n                disableFetchTracking: false,\n                extensions: [this.reactPlugin],\n                extensionConfig: {\n                    [this.reactPlugin.identifier]: reactPluginConfig\n                }\n            }\n        });\n        this.appInsights.loadAppInsights();\n    }\n}\n\nexport let ai = new TelemetryService();"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/services/userService.js",
    "content": "import axios from \"axios\";\nimport { ConfigService } from \"./\"\nrequire('../helpers/errorsHandler');\n\n\nconst UserService = { \n\n    async postLoginForm(formData) {\n        await ConfigService.loadSettings();\n        const response = await axios.post(`${ConfigService._apiUrl}/login`, formData, ConfigService.HeadersConfig(), { errorHandle: false });\n        return response;\n    },\n\n    async getUserInfoData(token) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/profiles/me`, ConfigService.HeadersConfig(token), { errorHandle: false });\n        return response.data;\n    },\n\n    async getProfileData(token) {\n        await ConfigService.loadSettings();\n        const response = await axios.get(`${ConfigService._apiUrl}/profiles/navbar/me`, ConfigService.HeadersConfig(token), { errorHandle: false });\n        return response.data;\n    }\n}\n\n\nexport default UserService;"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/store.js",
    "content": "import { createStore } from 'redux';\n\nimport reducers from './reducers/reducers';\n\nconst store = createStore(reducers);\n\nexport default store;\n\n\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/_variables.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains all application-wide Sass variables.\n// -----------------------------------------------------------------------------\n\n//\n// #Colors\n//\n// #Base-Color\n$color-base: #000000;\n$color-base-darker: lighten($color-base, 20);\n$color-base-dark: lighten($color-base, 40);\n$color-base-light: lighten($color-base, 60);\n$color-base-lighter: lighten($color-base, 80);\n\n// #Brand-Palette\n$color-primary: #2f4b66;\n$color-secondary: #36644a;\n$color-tertiary: #b4c8bd;\n\n// #Accents\n$color-accent-primary: #60524d;\n$color-accent-secondary: #f9ceb7;\n$color-accent-tertiary: #673c4f;\n$color-accent-tertiary-hover: #612c43;\n\n// #Alerts\n$color-warning: #8a001f;\n$color-success: #27ae60;\n\n// #Absolute-colors to avoid linting alerts on hardcoded values\n$color-white: #ffffff;\n$color-grey: #b2bac1;\n$color-grey-dark: #b4b4b4;\n$color-black: #000000;\n\n$color-background-light: $color-white;\n$color-background-medium: $color-grey;\n$color-background-primary: $color-primary;\n$color-background-secondary: $color-secondary;\n$color-background-tertiary: $color-tertiary;\n$color-background-accent-primary: $color-accent-primary;\n$color-background-accent-secondary: $color-accent-secondary;\n$color-background-accent-tertiary: $color-accent-tertiary;\n$color-background-accent-tertiary-hover: $color-accent-tertiary-hover;\n$color-background-success-altert: $color-success;\n$color-background-microsoft: #0076ff;\n$color-background-overlay: rgba($color-black, .3);\n\n$color-link: $color-secondary;\n\n$color-text-light: $color-white;\n$color-text-medium: $color-grey;\n$color-text-primary: $color-primary;\n$color-text-secondary: $color-secondary;\n$color-text-tertiary: $color-tertiary;\n$color-text-accent-tertiary: $color-accent-tertiary;\n\n$color-svg-primary: $color-primary;\n$color-svg-tertiary: $color-tertiary;\n\n$color-border: $color-primary;\n\n$color-element-hover: #4778d9;\n\n$box-shadow-xs: 2px 4px 10px 0 rgba($color-base, 0.6);\n$box-shadow-s: 4px 9px 20px 4px rgba($color-base, 0.6);\n$box-shadow-m: 8px 8px 26px 2px rgba($color-base, 0.6);\n$box-shadow-l: 10px 10px 52px 0 rgba($color-base, 0.2);\n$box-shadow-xl: 16px 16px 84px 0 rgba($color-base, 0.12);\n\n$border-radius-s: 0.5rem;\n$border-radius: 1rem;\n\n$linear-gradient-base: linear-gradient(to bottom, #f5f5f5, #f5f5f5);\n\n//\n// #Animations\n//\n$animation-speed-slow: 0.35s;\n$animation-speed-regular: 0.25s;\n$animation-speed-fast: 0.15s;\n$animation-type-regular: ease-in;\n\n//\n// #Fonts\n//\n\n// #Font-Family\n$font-family-body:\n    \"brandon-grotesque\",\n    -apple-system,\n    BlinkMacSystemFont,\n    \"Segoe UI\",\n    Roboto,\n    Helvetica,\n    Arial,\n    sans-serif,\n    \"Apple Color Emoji\",\n    \"Segoe UI Emoji\",\n    \"Segoe UI Symbol\";\n\n$font-family-heading:\n    \"gin\",\n    -apple-system,\n    BlinkMacSystemFont,\n    \"Segoe UI\",\n    Roboto,\n    Helvetica,\n    Arial,\n    sans-serif,\n    \"Apple Color Emoji\",\n    \"Segoe UI Emoji\",\n    \"Segoe UI Symbol\";\n\n//\n// #Font-Weight\n//\n$font-weight-light: 300;\n$font-weight-medium: 500;\n\n//\n// #Font-Size\n//\n$font-base: 1rem;\n$font-big: 1.125rem;\n\n// #Font-Variables\n$font-ratio: 1.15;\n$base-size: 16;\n$line-height: 24;\n\n//\n// #Spacing\n//\n$grid-gap-m: 1.5rem;\n$grid-gap-l: 2rem;\n$grid-item-min-size: 20.5rem;\n$grid-item-max-size: 24.5rem;\n$grid-max-width-s: $grid-item-min-size;\n$grid-max-width-m: 50.5rem;\n$grid-max-width-l: 76.625rem;\n$grid-max-width-xl: 87.5rem;\n\n$checkbox-size: 1.5rem;\n$container-width-xl: 76.5rem;\n\n$space-default: $font-base; // Is dependant on $font-base and should be 1em;\n$space-xxs: calc($space-default / 8); // 2px\n$space-xs: calc($space-default / 4); // 4px\n$space-s: calc($space-default / 2); // 8px\n$space-m: calc($space-default); // 16px\n$space-l: calc($space-default * 2); // 32px\n$space-xl: calc($space-default * 4); // 64px\n$space-xxl: calc($space-default * 6); // 96px\n\n// #Padding\n$padding-xxs: $space-xxs; // 2px\n$padding-xs: $space-xs; // 4px\n$padding-s: $space-s; // 8px\n$padding-m: $space-m; // 16px\n$padding-l: $space-l; // 32px\n$padding-xl: $space-xl; // 64px\n$history-padding: 4.875rem;\n\n// #Margin\n$margin-xxs: $space-xxs; // 2px\n$margin-xs: $space-xs; // 4px\n$margin-s: $space-s; // 8px\n$margin-m: $space-m; // 16px\n$margin-l: $space-l; // 32px\n$margin-xl: $space-xl; // 64px\n$margin-xxl: $space-xxl; // 96px\n\n// #Icon\n$icon-xxs: $space-xxs; // 2px\n$icon-xs: $space-xs; // 4px\n$icon-s: $space-s; // 8px\n$icon-m: $space-m; // 16px\n$icon-l: 1.75rem; // 28px\n$icon-xl: $space-xl; // 64px\n\n//\n// $Media-Queries\n//\n$mq-s: 23.4375em; // 375px\n$mq-m: 48em; // 768px\n$mq-sl: 65em; // 1280px\n$mq-r: 80em; // 1280px\n$mq-l: 85.375em; // 1366px\n$mq-xl: 120em; // 1920px\n\n//\n// $Z-Index\n//\n$z-index-s: 1;\n$z-index-m: 2;\n$z-index-l: 3;\n$z-index-xl: 4;\n$z-index-xxl: 5;\n\n//\n// #Loader\n//\n$loader-color: $color-background-accent-tertiary !default;\n$loader-size: $icon-xl !default;\n$loader-height: 1.25rem !default;\n$loader-border-size: 0.5rem !default;\n$loader-gap: 0.75rem !default;\n$loader-animation-duration: 1s !default;\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/mixins/_ellipsis.scss",
    "content": "// Add ellipsis at the end of a line to avoid text overflow\n// @author Ignacio Villanueva\n// @param {String} $width\n\n@mixin ellipsis($width: 100%) {\n\tmax-width: $width;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/mixins/_font-placeholders.scss",
    "content": "// Paragraph Extra-Extra Small\n@mixin text-xxs {\n\tfont-family: $font-family-body;\n\tfont-size: .875rem; // 14px\n}\n\n// Paragraph Extra Small\n@mixin text-xs {\n\tfont-family: $font-family-body;\n\tfont-size: 1rem; // 16px\n\n\t@media (min-width: $mq-l) {\n\t\tfont-size: $font-big; // 18px\n\t}\n}\n\n// Paragraph Small\n@mixin text-s {\n\tfont-family: $font-family-body;\n\tfont-size: $font-big; // 18px\n\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 1.25rem; // 20px;\n\t}\n}\n\n// Paragraph Medium\n@mixin text-m {\n\tfont-family: $font-family-body;\n\tfont-size: 1.25rem; // 20px\n\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 1.5rem; // 24px\n\t}\n}\n\n// Price numbers Card componnent only\n@mixin text-l-price {\n\tfont-family: $font-family-body;\n\tfont-size: 2rem; // 32px\n\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 2.125rem; // 34px\n\t}\n}\n\n// Heading 3 & Price\n@mixin text-l {\n\tfont-family: $font-family-body;\n\tfont-size: 1.5rem; // 24px\n\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 2.125rem; // 34px\n\t}\n}\n\n// Heading 2\n@mixin text-xl {\n\tfont-family: $font-family-heading;\n\tfont-size: 1.75rem; // 28px\n\nfont-weight: $font-weight-light;\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 2.625rem; // 42px\n\t}\n}\n\n// Heading 1\n@mixin text-xxl {\n\tfont-family: $font-family-heading;\n\tfont-size: 1.75rem; // 28px\n\nfont-weight: $font-weight-light;\n\t@media (min-width: $mq-l) {\n\t\tfont-size: 3.25rem; // 52px\n\t}\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/mixins/_font-scale.scss",
    "content": "@mixin font-size($font-size) {\n    font-size: $font-size + px;\n    font-size: #{$font-size} / #{$base-size} + rem;\n}\n\n@mixin line-height($font-size, $base-line-height) {\n    // Sets the line-height to a multiple\n    // of $base-line-height that's not smaller than\n    // $font-size\n    line-height: ceil($font-size / $base-line-height) * ($base-line-height / $font-size);\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/mixins/_fonts.scss",
    "content": "// @Author https://gist.github.com/jonathantneal/d0460e5c2d5d7f9bc5e6\n\n// =============================================================================\n// String Replace\n// =============================================================================\n\n@function str-replace($string, $search, $replace: \"\") {\n    $index: str-index($string, $search);\n\n    @if $index {\n        @return str-slice($string, 1, $index - 1) + $replace +\n            str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n    }\n\n    @return $string;\n}\n\n// =============================================================================\n// Font Face\n// =============================================================================\n\n@mixin font-face($name, $path, $weight: null, $style: null, $exts: eot woff ttf svg) {\n    $src: null;\n    $extmods: (\n        eot: \"?\",\n        svg: \"#\" + str-replace($name, \" \", \"_\"),\n    );\n    $formats: (\n        otf: \"opentype\",\n        ttf: \"truetype\",\n    );\n\n    @each $ext in $exts {\n        $extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);\n\n        $format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);\n\n        $src: append($src, url(quote($path + \".\" + $extmod)) format(quote($format)), comma);\n    }\n\n    @font-face {\n        font-family: quote($name);\n        font-style: $style;\n        font-weight: $weight;\n        src: $src;\n    }\n}\n\n@mixin font-include($font, $type, $weight: null, $style: null) {\n    @include font-face(\n        $font,\n        \"/assets/fonts/\" + $font + \"/\" + $font + \"-\" + $type,\n        $weight,\n        $style\n    );\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/abstracts/mixins/_loader.scss",
    "content": "@import '../variables';\n\n@mixin loader-rotate {\n    @keyframes loader-rotate {\n        0% {\n            transform: rotate(0);\n        }\n\n        100% {\n            transform: rotate(360deg);\n        }\n    }\n}\n\n@mixin loader-scale {\n    @keyframes loader-scale {\n        0% {\n            transform: scale(0);\n            opacity: 0;\n        }\n\n        50% {\n            opacity: 1;\n        }\n\n        100% {\n            transform: scale(1);\n            opacity: 0;\n        }\n    }\n}\n\n@mixin loader(\n    $size: $loader-size,\n    $color: $loader-color,\n    $border-size: $loader-border-size,\n    $duration: $loader-animation-duration,\n    $align: null\n) {\n    width: $size;\n    height: $size;\n    border: $border-size solid rgba($color, 0.25);\n    border-top-color: $color;\n    border-radius: 50%;\n    position: relative;\n    animation: loader-rotate $duration linear infinite;\n    @if ($align == center) {\n        margin: 0 auto;\n    }\n    @if ($align == middle) {\n        top: 50%;\n        margin: -$size / 2 auto 0;\n    }\n    @include loader-rotate;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/base/_base.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains very basic styles.\n// -----------------------------------------------------------------------------\n\n//\n// Set up a decent box model on the root element\n//\nhtml {\n\tbox-sizing: border-box;\n}\n\n//\n// Make all elements from the DOM inherit from the parent box-sizing\n// Since `*` has a specificity of 0, it does not override the `html` value\n// making all elements inheriting from the root box-sizing value\n// See: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/\n//\n*,\n*::before,\n*::after {\n\tbox-sizing: inherit;\n}\n\n.App {\n\toverflow-x: hidden;\n}\n\nbody.is-blocked {\n  overflow: hidden;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/base/_typography.scss",
    "content": "//\n// Basic typography style for copy text\n//\n\nbody {\n    color: $color-base;\n    font-family: $font-family-body;\n    font-size: 100%;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/base/_utilities.scss",
    "content": "// -----------------------------------------------------------------------------\n// This file contains CSS utiltiy classes.\n// -----------------------------------------------------------------------------\n\n//\n// Clear inner floats\n//\n.u-clearfix::after {\n    clear: both;\n    content: '';\n    display: table;\n}\n\n// Hide overflowed content\n.u-overflow-hidden {\n    overflow: hidden;\n}\n\n// Clean button default styles\n.u-empty {\n    background-color: transparent;\n    border: 0;\n\n    &:focus:not(.main-nav__item) {\n        outline: none;\n    }\n}\n\n//\n// Hide text while making it readable for screen readers\n// 1. Needed in WebKit-based browsers because of an implementation bug;\n//    See: https://code.google.com/p/chromium/issues/detail?id=457146\n//\n.u-hide-text {\n    overflow: hidden;\n    padding: 0; // 1\n    text-indent: 101%;\n    white-space: nowrap;\n}\n\n//\n// Hide element while making it readable for screen readers\n// Shamelessly borrowed from HTML5Boilerplate:\n// https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css#L119-L133\n//\n.u-visually-hidden {\n    border: 0;\n    clip: rect(0 0 0 0);\n    height: 1px;\n    margin: -1px;\n    overflow: hidden;\n    padding: 0;\n    position: absolute;\n    width: 1px;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/styles/vendor/_normalize.scss",
    "content": "/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n   ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n    -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n   ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n    margin: 0;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n    font-size: 2em;\n    margin: 0.67em 0;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n    box-sizing: content-box; /* 1 */\n    height: 0; /* 1 */\n    overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n    font-family: monospace, monospace; /* 1 */\n    font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n    background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n    border-bottom: none; /* 1 */\n    text-decoration: underline; /* 2 */\n    text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n    font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n    font-family: monospace, monospace; /* 1 */\n    font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n    font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n}\n\nsub {\n    bottom: -0.25em;\n}\n\nsup {\n    top: -0.5em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n    border-style: none;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n    font-family: inherit; /* 1 */\n    // font-size: 100%; /* 1 */\n    line-height: 1.15; /* 1 */\n    margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput {\n    /* 1 */\n    overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect {\n    /* 1 */\n    text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n    -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n    border-style: none;\n    padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n    outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n    padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n *    `fieldset` elements in all browsers.\n */\n\nlegend {\n    box-sizing: border-box; /* 1 */\n    color: inherit; /* 2 */\n    display: table; /* 1 */\n    max-width: 100%; /* 1 */\n    padding: 0; /* 3 */\n    white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n    vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n    overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n    box-sizing: border-box; /* 1 */\n    padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n    height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n    -webkit-appearance: textfield; /* 1 */\n    outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n    -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n    -webkit-appearance: button; /* 1 */\n    font: inherit; /* 2 */\n}\n\n/* Interactive\n   ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n    display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n    display: list-item;\n}\n\n/* Misc\n   ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n    display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n    display: none;\n}\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/src/types/types.js",
    "content": "export const FORM_EMAIL = '@@form/EMAIL';\nexport const SAVE_USER = '@@form/SUBMIT';\nexport const REMOVE_USER = '@@logout/CLICK';\nexport const THEME_CHANGE = 'THEME_CHANGE';\nexport const GET_QUANTITY = 'GET_QUANTITY';"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/account.ts",
    "content": "import { test, expect } from '@playwright/test';\nimport fs from 'fs';\nimport path from 'path';\nimport { parse } from 'csv-parse/sync';\n\ntest.beforeEach(async () => {\n    // skip test if environment variables for AAD creds are not set\n    test.skip(!process.env.REACT_APP_AADUSERNAME || !process.env.REACT_APP_AADPASSWORD, 'AADUSERNAME and AADPASSWORD environment variables must be set');\n});\n\ntest.describe('My Profile', () => {\n    test('should be able to fill out personal info', async ({ page }) => {\n        // Fill out the form using data from CSV\n        const records = parse(fs.readFileSync(path.join(__dirname, 'test-data.csv')), {\n            columns: true,\n            skip_empty_lines: true\n        });\n        // For each record in CSV, fill out the form\n        for (const record of records) {\n            // Fill out the form\n            await page.goto('/profile/personal');\n            await page.locator('#firstName').fill(`${record.firstName}`);\n            await page.locator('#lastName').fill(`${record.lastName}`);\n            await page.getByLabel('Email').fill(`${record.email}`);\n            await page.getByLabel('Mobile Number').fill(`${record.mobile}`);\n            await page.getByLabel('Date of birth').fill(`${record.dob}`);\n            await page.locator('#currentpassword').fill(`${record.currentpassword}`);\n            await page.locator('#newpassword').fill(`${record.newpassword}`);\n            await page.locator('#confirmpassword').fill(`${record.confirmpassword}`);\n            await page.getByRole('button', { name: 'Save Changes' }).click();\n            // Verify no validation errors are present\n            await expect(page.locator('.Mui-error')).toHaveCount(0);\n        }\n    });\n});\n\ntest.describe('My Cart', () => {\n    test('should be able to view cart', async ({ page }) => {\n        await page.goto('');\n        await page.getByRole('button', { name: 'cart' }).click();\n        await expect(page).toHaveURL('/cart');\n        await expect(page.getByRole('heading', { name: 'My Cart' })).toBeVisible();\n    });\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/api/cart.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\n// Shopping cart data\nconst USER = 'fake@outlook.com';\nconst PRODUCT = 'Xbox Wireless Controller Lunar Shift Special Edition';\nconst PRODUCTID = 1;\nconst PRODUCTIMAGE = 'PID1-1.jpg';\nconst PRODUCTPRICE = 99;\nconst PRODUCTQUANTITY = 1;\n\ntest.skip(() => !process.env.REACT_APP_APIURLSHOPPINGCART, 'requires REACT_APP_APIURLSHOPPINGCART');\n\ntest.use({\n    baseURL: process.env.REACT_APP_APIURLSHOPPINGCART + '/'\n});\n\n// SETUP: Create a new cart\ntest.beforeAll(async ({ request }) => {\n    const newCart = await request.post('./ShoppingCart', {\n        data: {\n            cartItemId: \"string\",\n            email: USER,\n            productId: PRODUCTID,\n            name: PRODUCT,\n            price: PRODUCTPRICE,\n            imageUrl: PRODUCTIMAGE,\n            quantity: PRODUCTQUANTITY\n        }\n    });\n    expect(newCart.status()).toBe(201);\n});\n\ntest.describe('Shopping Cart API', () => {\n    test('should be able to GET shopping cart', async ({ request }) => {\n        const cart = await request.get('./ShoppingCart', {\n            headers: {\n                'Accept': 'accept: */*',\n                'x-tt-email': USER,\n            }\n        });\n        await expect(cart).toBeOK();\n\n        expect(await cart.json()).toContainEqual(expect.objectContaining({\n            email: USER,\n            productId: PRODUCTID,\n            name: PRODUCT,\n            price: PRODUCTPRICE,\n            imageUrl: PRODUCTIMAGE,\n            quantity: PRODUCTQUANTITY\n        }));\n    });\n});\n\n// TEARDOWN: Delete the cart\ntest.afterAll(async ({ request }) => {\n    const cart = await request.get('./ShoppingCart', {\n        headers: {\n            'Accept': 'accept: */*',\n            'x-tt-email': USER,\n        }\n    });\n    await expect(cart).toBeOK();\n\n    // Loop through each cart item and delete it\n    const cartBody = JSON.parse(await cart.text());\n    for (let i = 0; i < cartBody.length; i++) {\n        const deleteCart = await request.delete('./ShoppingCart/product', {\n            data: {\n                cartItemId: cartBody[i].cartItemId,\n                email: USER,\n                productId: cartBody[i].productId,\n                name: cartBody[i].name,\n                price: cartBody[i].price,\n                imageUrl: cartBody[i].imageUrl,\n                quantity: cartBody[i].quantity\n            }\n        });\n        await expect(deleteCart).toBeOK();\n    }\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/api/data.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\ntype Product = ({ type: { name: string } });\n\ntest.skip(() => !process.env.REACT_APP_APIURL, 'requires REACT_APP_APIURL');\n\ntest.use({\n    baseURL: process.env.REACT_APP_APIURL + '/',\n});\n\ntest.describe('API data Assertions - category', () => {\n    // Filter products by category - Controller and then assert category\n    test('should be able to filter products by category - Controllers and then assert category', async ({ request }) => {\n        const response = await request.get('./products/?type=controllers');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Assert if each product falls under the category - Controllers and not any other\n        for (const product of productsFromResponse) {\n            expect(product.type.name === \"Controllers\").toBeTruthy();\n            expect(product.type.name === \"Desktops\").toBeFalsy();\n            expect(product.type.name === \"Laptops\").toBeFalsy();\n            expect(product.type.name === \"Mobiles\").toBeFalsy();\n            expect(product.type.name === \"Monitors\").toBeFalsy();\n        }\n    });\n\n    // Filter products by category - Desktops and then assert category\n    test('should be able to filter products by category - Desktops and then assert category', async ({ request }) => {\n        const response = await request.get('./products/?type=desktops');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Assert if each product falls under the category - Desktops and not any other\n        for (const product of productsFromResponse) {\n            expect(product.type.name === \"Controllers\").toBeFalsy();\n            expect(product.type.name === \"Desktops\").toBeTruthy();\n            expect(product.type.name === \"Laptops\").toBeFalsy();\n            expect(product.type.name === \"Mobiles\").toBeFalsy();\n            expect(product.type.name === \"Monitors\").toBeFalsy();\n        }\n    });\n\n    // Filter products by category - Laptops and then assert category\n    test('should be able to filter products by category - Laptops and then assert category', async ({ request }) => {\n        const response = await request.get('./products/?type=laptops');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Assert if each product falls under the category - Laptops and not any other\n        for (const product of productsFromResponse) {\n            expect(product.type.name === \"Controllers\").toBeFalsy();\n            expect(product.type.name === \"Desktops\").toBeFalsy();\n            expect(product.type.name === \"Laptops\").toBeTruthy();\n            expect(product.type.name === \"Mobiles\").toBeFalsy();\n            expect(product.type.name === \"Monitors\").toBeFalsy();\n        }\n    });\n\n    // Filter products by category - Mobiles and then assert category\n    test('should be able to filter products by category - Mobiles and then assert category', async ({ request }) => {\n        const response = await request.get('./products/?type=mobiles');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Assert if each product falls under the category - Mobiles and not any other\n        for (const product of productsFromResponse) {\n            expect(product.type.name === \"Controllers\").toBeFalsy();\n            expect(product.type.name === \"Desktops\").toBeFalsy();\n            expect(product.type.name === \"Laptops\").toBeFalsy();\n            expect(product.type.name === \"Mobiles\").toBeTruthy();\n            expect(product.type.name === \"Monitors\").toBeFalsy();\n        }\n    });\n\n    // Filter products by category - Monitors and then assert category\n    test('should be able to filter products by category - Monitors and then assert category', async ({ request }) => {\n        const response = await request.get('./products/?type=monitors');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Assert if each product falls under the category - Monitors and not any other\n        for (const product of productsFromResponse) {\n            expect(product.type.name === \"Controllers\").toBeFalsy();\n            expect(product.type.name === \"Desktops\").toBeFalsy();\n            expect(product.type.name === \"Laptops\").toBeFalsy();\n            expect(product.type.name === \"Mobiles\").toBeFalsy();\n            expect(product.type.name === \"Monitors\").toBeTruthy();\n        }\n    });\n});\n\ntest.describe('API data Assertions - count', () => {\n    // Get all products, verify status and get and assert count of each product\n    test('should be able to get all products, verify status and get and assert count of each product', async ({ request }) => {\n        // Initialize total no of each product\n        let totalNoOfControllers = 0;\n        let totalNoOfDesktops = 0;\n        let totalNoOfLaptops = 0;\n        let totalNoOfMobiles = 0;\n        let totalNoOfMonitors = 0;\n\n        const response = await request.get('./products');\n        await expect(response).toBeOK();\n\n        const productsFromResponse: Product[] = (await response.json()).products;\n\n        // Get count of each product\n        for (const product of productsFromResponse) {\n            if (product.type.name === \"Controllers\") ++totalNoOfControllers;\n            if (product.type.name === \"Desktops\") ++totalNoOfDesktops;\n            if (product.type.name === \"Laptops\") ++totalNoOfLaptops;\n            if (product.type.name === \"Mobiles\") ++totalNoOfMobiles;\n            if (product.type.name === \"Monitors\") ++totalNoOfMonitors;\n        }\n\n        // Assert total numbers of each product\n        expect(totalNoOfControllers).toBeGreaterThanOrEqual(0);\n        expect(totalNoOfDesktops).toBeGreaterThanOrEqual(0);\n        expect(totalNoOfLaptops).toBeGreaterThanOrEqual(0);\n        expect(totalNoOfMobiles).toBeGreaterThanOrEqual(0);\n        expect(totalNoOfMonitors).toBeGreaterThanOrEqual(0);\n    });\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/api/products.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\nlet _productid = 1;\n\ntest.skip(() => !process.env.REACT_APP_APIURL, 'requires REACT_APP_APIURL');\n\ntest.use({\n  baseURL: process.env.REACT_APP_APIURL + '/',\n});\n\ntest.describe('Products API', () => {\n  // Text search API\n  test('should be able to load search by text data', async ({ request }) => {\n    const response = await request.get('./Products/search/laptops');\n    await expect(response).toBeOK();\n  });\n\n  // Load products list\n  test('should be able to load products list', async ({ request }) => {\n    const response = await request.get('./Products/?type=laptops');\n    await expect(response).toBeOK();\n  });\n\n  // Load product details\n  test('should be able to load product details', async ({ request }) => {\n    const response = await request.get('./Products/' + _productid);\n    await expect(response).toBeOK();\n  });\n\n  // Filter products by brands\n  test('should be able to filter products by brands', async ({ request }) => {\n    const response = await request.get('./Products/?type=laptops&brand=1');\n    await expect(response).toBeOK();\n  });\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/auth.setup.ts",
    "content": "import { test as setup } from '@playwright/test';\n\nconst authFile = '.auth/user.json';\n\nsetup('authenticate', async ({ page }) => {\n    // skip setup if environment variables for AAD creds are not set\n    setup.skip(!process.env.REACT_APP_AADUSERNAME || !process.env.REACT_APP_AADPASSWORD, 'AADUSERNAME and AADPASSWORD environment variables must be set');\n\n    await page.goto('');\n    // Sign in using creds from env variables\n    const dialogPromise = page.waitForEvent('popup');\n    await page.getByText('LOGIN').click();\n    const dialog = await dialogPromise;\n    await dialog.getByPlaceholder('Email, phone, or Skype').fill(process.env.REACT_APP_AADUSERNAME!);\n    await dialog.getByRole('button', { name: 'Next' }).click();\n    await dialog.getByPlaceholder('Password').fill(process.env.REACT_APP_AADPASSWORD!);\n    await dialog.getByRole('button', { name: 'Sign in' }).click();\n    // Do not stay signed in\n    await dialog.getByRole('button', { name: 'No' }).click();\n    // Use try catch block to handle the case where the consent dialog is shown\n    try {\n        await dialog.waitForURL('**/Consent/**');\n        await dialog.getByRole('button', { name: 'Yes' }).click();\n    } catch (e) {\n        // Consent dialog was not shown       \n    }\n    // Save auth state to file (.gitignore'd)\n    await page.context().storageState({ path: authFile });\n}); "
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/cart.spec.ts",
    "content": "import { test, expect, } from '@playwright/test';\n\n// SETUP: Get first 5 product ID's and add them all to cart\ntest.beforeEach(async ({ page, request }) => {\n    const response = await request.get(process.env.REACT_APP_APIURL + '/products')\n    await expect(response).toBeOK();\n    const productsFromResponse = (await response.json()).products.slice(0, 5);\n    for (const product of productsFromResponse) {\n        await page.goto(`/product/detail/${product.id}`);\n        await page.getByRole('button', { name: 'Add To Bag' }).click();\n    }\n    await page.getByRole('button', { name: 'cart' }).click();\n});\n\ntest.describe('Shopping Cart', () => {\n    test('should be able to view cart', async ({ page }) => {\n        await expect(page.getByRole('heading', { name: 'My Cart' })).toBeVisible();\n        await expect(page.getByText('Order Summary')).toBeVisible();\n        await expect(page.getByText('Product Name')).toBeVisible();\n        await expect(page.getByText('Price', { exact: true })).toBeVisible();\n        await expect(page.getByText('Qty', { exact: true })).toBeVisible();\n        await expect(page.getByText('Subtotal', { exact: true })).toBeVisible();\n    });\n\n    test('should be able to increase and decrease test quantity', async ({ page }) => {\n        const subtotal = async () => Number((await page.getByTestId('subtotal').innerText()).replace('$', ''));\n        const clickButton = async (name: string) => await page.getByRole('button', { name }).first().click();\n\n        const subtotalBefore = await subtotal();\n        await clickButton('+');\n        await expect(async () => {\n            expect(await subtotal()).toBeGreaterThan(subtotalBefore);\n        }).toPass();\n\n        const subtotalAfter = await subtotal();\n        await clickButton('-');\n        await expect(async () => {\n            expect(await subtotal()).toBeLessThan(subtotalAfter);\n        }).toPass();\n    });\n\n    test('should be able to remove items from cart', async ({ page }) => {\n        while (!(await page.getByRole('heading', { name: 'Your Cart is empty' }).isVisible())) {\n            await page.getByRole('link', { name: 'Remove' }).first().click();\n        }\n    });\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/darkmode.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto('/');\n});\n\ntest.describe.skip('Dark Mode', () => {\n  test.skip(({ browserName }) => browserName !== 'chromium', 'Chromium only!');\n  test('should be able to toggle dark mode', async ({ page }) => {\n    await page.getByLabel('Dark Mode').check();\n    await expect(page.locator('.App')).toHaveAttribute('class', 'App dark')\n    await expect(page.locator('.App')).toHaveCSS('background-color', 'rgb(34, 34, 34)')\n    await page.getByLabel('Dark Mode').uncheck();\n    await expect(page.locator('.App')).toHaveAttribute('class', 'App light')\n    await expect(page.locator('.App')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)')\n  })\n  test('verify dark mode is pixel perfect - on', async ({ page }) => {\n    await page.getByLabel('Dark Mode').check();\n    await expect(page.getByRole('banner').filter({ hasText: '0Dark Mode' })).toHaveScreenshot();\n  })\n  test('verify dark mode is pixel perfect - off', async ({ page }) => {\n    await expect(page.getByRole('banner').filter({ hasText: '0Dark Mode' })).toHaveScreenshot();\n  })\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/discounts.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\nconst productId = 1;\nconst validDiscountCodes = ['DISCOUNT15', 'DISCOUNT10'];\nconst invalidDiscountCodes = ['DISCOUNT20', 'DISCOUNT30'];\n\n// SETUP: Add product to cart, view cart\ntest.beforeEach(async ({ page }) => {\n    await page.goto(`/product/detail/${productId}`);\n    await page.getByRole('button', { name: 'Add To Bag' }).click();\n    await page.getByRole('button', { name: 'cart' }).click();\n})\n\ntest.describe('Discount Codes', () => {\n    for (const code of validDiscountCodes) {\n        test(`should be able to use VALID discount code ${code}`, async ({ page }) => {\n            await page.getByPlaceholder('Enter coupon code').fill(code);\n            await page.getByRole('button', { name: 'CHECK' }).click();\n            await expect(page.getByRole('button', { name: code })).toBeVisible();\n            // check that correct discount is applied\n            await expect(page.getByTestId('discount')).toHaveText(`-$${code.replace('DISCOUNT', '')}.00`);\n        });\n    }\n\n    for (const code of invalidDiscountCodes) {\n        test(`should not be able to use INVALID discount code ${code}`, async ({ page }) => {\n            await page.getByPlaceholder('Enter coupon code').fill(code);\n            await page.getByRole('button', { name: 'CHECK' }).click();\n            await expect(page.getByText('This coupon is invalid')).toBeVisible();\n        });\n    }\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/fileupload.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\nimport path from 'path';\n\nconst imgPath = path.join(__dirname, '../src/assets/images/original/Contoso_Assets/Grid_Products_Collection/product_image.png');\n\ntest.describe.skip('File Upload', () => {\n  test('should be able to upload a file', async ({ page }) => {\n    await page.goto(\"/\");\n    await page.getByRole('button', { name: 'iconimage' }).click();\n    await page.locator('input[type=\"file\"]').setInputFiles(imgPath);\n    await expect(page).toHaveURL('/suggested-products-list');\n  });\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/map.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\n// Emulate geolocation, language and timezone\ntest.use({\n  geolocation: {\n    latitude: 41.890221,\n    longitude: 12.492348\n  },\n  locale: 'it-IT',\n  permissions: ['geolocation'],\n  timezoneId: 'Europe/Rome'\n});\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto('/');\n  await page.mouse.wheel(0, 4000); // scroll down to map\n});\n\ntest.describe('Map', () => {\n  test.skip(({ browserName }) => browserName !== 'chromium', 'Chromium only!');\n  test('should display bing maps iframe', async ({ page, geolocation }) => {\n    await expect.poll(() => page.locator('input#latitude').inputValue()).toEqual(geolocation?.latitude.toString());\n    await expect.poll(() => page.locator('input#longitude').inputValue()).toEqual(geolocation?.longitude.toString());\n    await expect(page.locator('#current-location')).toBeVisible();\n    await expect(async () => {\n      const boundingBox = await page.frameLocator('iframe[title=\"geolocation\"]').locator('canvas[aria-label=\"Interactive Map\"]').boundingBox();\n      if (!boundingBox || boundingBox?.width < 100 || boundingBox?.height < 100) {\n        throw new Error('Map is too small');\n      }\n    }).toPass();\n  });\n\n  test('should zoom in on bing maps iframe', async ({ page }) => {\n    await page.frameLocator('iframe[title=\"geolocation\"]').getByRole('button', { name: 'Zoom avanti' }).click();\n  });\n\n  test('should zoom out on bing maps iframe', async ({ page }) => {\n    await page.frameLocator('iframe[title=\"geolocation\"]').getByRole('button', { name: 'Zoom indietro' }).click();\n  });\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/mocks.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\ntest.describe('Mocks', () => {\n    // Mock home page\n    test('should be able to load mock home page', async ({ page }) => {\n        await page.route('/', async route => {\n            await route.fulfill({ status: 200, body: \"<html>Test Content</html>\" });\n        });\n\n        // Load home page\n        await page.goto('/');\n        \n        // Assert content of mock page\n        await expect(page.locator('html').first()).toContainText(\"Test Content\");\n    });\n\n    // Mock API - load product details\n    test('should be able to load product details', async ({ page }) => {\n      await page.route('/Products/1', async route => {\n          const jsonResponse = \n          {\n            \"id\": 1,\n            \"name\": \"Test Product 01\",\n            \"price\": 10\n          };\n          await route.fulfill({ status: 200, json: jsonResponse });\n      });\n\n      await page.goto('/Products/1');\n      await expect(async () => {\n        const data = await page.locator('pre').first().allInnerTexts();\n        const product = JSON.parse(data[0]);\n        expect(product.name).toBe(\"Test Product 01\");\n      }).toPass();\n    });\n});"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/pages.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\nlet _productid = 1;\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto('/');\n})\n\ntest.describe('Header', () => {\n  test('should be able to search by text', async ({ page }) => {\n    await page.getByPlaceholder('Search by product name or search by image').fill('laptops');\n    await page.getByPlaceholder('Search by product name or search by image').press('Enter');\n    await expect(page).toHaveURL('/suggested-products-list');\n  });\n\n  test('should be able to select category', async ({ page }) => {\n    await page.getByRole('button', { name: 'All Categories' }).click();\n    await page.getByRole('menuitem', { name: 'Laptops' }).click();\n    await expect(page).toHaveURL('/list/laptops');\n  });\n\n  test('should be able to hover over header menus', async ({ page }) => {\n    await page.getByRole('navigation').getByRole('link', { name: 'All Products' }).hover();\n    await page.getByRole('navigation').getByRole('link', { name: 'Laptops' }).hover();\n    await page.getByRole('navigation').getByRole('link', { name: 'Controllers' }).hover();\n    await page.getByRole('navigation').getByRole('link', { name: 'Mobiles' }).hover();\n    await page.getByRole('navigation').getByRole('link', { name: 'Monitors' }).hover();\n  });\n\n  test('should be able to select header menu', async ({ page }) => {\n    await page.getByRole('navigation').getByRole('link', { name: 'All Products' }).click();\n    await expect(page).toHaveURL('/list/all-products');\n  });\n});\n\ntest.describe('Carousel', () => {\n  test('prev and next buttons change slider', async ({ page }) => {\n    const carousel = page.getByTestId('carousel');\n    await expect(carousel.getByText('The Fastest, Most Powerful Xbox Ever.')).toBeVisible();\n    await carousel.getByRole('button', { name: 'Next' }).click();\n    await expect(carousel.getByText('Xbox Wireless Controller - Mineral Camo Special Edition')).toBeVisible();\n    await carousel.getByRole('button', { name: 'Previous' }).click();\n  })\n\n  test('carousel buttons change slider', async ({ page }) => {\n    const carousel = page.getByTestId('carousel');\n    await carousel.getByRole('button', { name: 'carousel indicator 2' }).click();\n    await expect(carousel.getByText('Xbox Wireless Controller - Mineral Camo Special Edition')).toBeVisible();\n    await carousel.getByRole('button', { name: 'carousel indicator 1' }).click();\n    await expect(carousel.getByText('The Fastest, Most Powerful Xbox Ever.')).toBeVisible();\n  })\n\n  test('buy now button links to product page', async ({ page }) => {\n    await page.getByTestId('carousel').getByRole('button', { name: 'Buy Now' }).first().click();\n    await expect(page).toHaveURL('/product/detail/1');\n  })\n\n  test('more details links to list page', async ({ page }) => {\n    await page.getByTestId('carousel').getByRole('button', { name: 'More Details' }).first().click();\n    await expect(page).toHaveURL('/list/controllers');\n  })\n});\n\ntest.describe('CarouselVRT', () => {  \n  test.skip(({ browserName }) => browserName !== 'chromium', 'Chromium only!');\n  test('verify carousel is pixel perfect - slide 1', async ({ page }) => {\n    await expect(page.getByTestId('carousel')).toHaveScreenshot();\n  })\n\n  test('verify carousel is pixel perfect - slide 2', async ({ page }) => {\n    const carousel = page.getByTestId('carousel');\n    await carousel.getByRole('button', { name: 'Next' }).click();\n    await expect(carousel).toHaveScreenshot();\n  })\n});\n\ntest.describe('Product Listing', () => {\n  test('should be able to select product to view details', async ({ page }) => {\n    await page.goto('/list/all-products');\n    await page.getByRole('img', { name: 'Xbox Wireless Controller Lunar Shift Special Edition' }).click();\n    await expect(page).toHaveURL('/product/detail/' + _productid);\n  });\n\n  test('should be able to filter product by brands', async ({ page }) => {\n    await page.goto('/list/all-products');\n    await page.locator('[id=\"\\\\32 \"]').check();\n  });\n});\n\ntest.describe('Product Details', () => {\n  test('Image does not break UI', async ({ page }) => {\n    // Navigate to the page with the image\n    await page.getByRole('link', { name: 'Mobiles' }).click();\n    await page.getByRole('img', { name: 'Asus Zenfone 5Z' }).click();\n\n    // Get the dimensions of the image\n    const image = page.locator('.productdetailsimage')\n    const imageSize = await image.boundingBox();\n\n    let containerSize = { height: 600 };\n\n    // Assert that the image fits within the container\n    expect(imageSize?.height).toBeLessThanOrEqual(containerSize?.height);\n  });\n});\n\ntest.describe('Footer', () => {\n  test('should be able to select footer menu', async ({ page }) => {\n    await page.getByRole('listitem').filter({ hasText: 'Monitors' }).getByRole('link', { name: 'Monitors' }).click();\n    await expect(page).toHaveURL('/list/monitors');\n  });\n});\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tests/test-data.csv",
    "content": "﻿test,firstName,lastName,email,mobile,dob,currentpassword,newpassword,confirmpassword\nTest,Test1,Test1,notarealemail@outlook.com,2029182132,2022-01-01,NotarealPassword!!,NotarealPassword!!,NotarealPassword!!\nTest,Test2,Test2,notarealemail@microsoft.com,2029182132,2023-02-02,N0tarealP@ssword,N0tarealP@ssword,N0tarealP@ssword\n"
  },
  {
    "path": "src/ContosoTraders.Ui.Website/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"module\": \"esnext\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react\"\n  },\n}"
  },
  {
    "path": "src/ContosoTraders.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31903.59\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"ContosoTraders.Api.Core\", \"ContosoTraders.Api.Core\\ContosoTraders.Api.Core.csproj\", \"{07A99FA1-A7CF-4797-9120-937994D30FF7}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"ContosoTraders.Api.Products\", \"ContosoTraders.Api.Products\\ContosoTraders.Api.Products.csproj\", \"{F867B7B7-2B68-401A-A072-ADFEB61F75D8}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"ContosoTraders.Api.Profiles\", \"ContosoTraders.Api.Profiles\\ContosoTraders.Api.Profiles.csproj\", \"{470EC658-DAAD-47FC-9041-342F744C2D2F}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"ContosoTraders.Api.Carts\", \"ContosoTraders.Api.Carts\\ContosoTraders.Api.Carts.csproj\", \"{8B124EFF-94AA-4A17-B15B-EBD601452D68}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{07A99FA1-A7CF-4797-9120-937994D30FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{07A99FA1-A7CF-4797-9120-937994D30FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{07A99FA1-A7CF-4797-9120-937994D30FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{07A99FA1-A7CF-4797-9120-937994D30FF7}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F867B7B7-2B68-401A-A072-ADFEB61F75D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F867B7B7-2B68-401A-A072-ADFEB61F75D8}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F867B7B7-2B68-401A-A072-ADFEB61F75D8}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F867B7B7-2B68-401A-A072-ADFEB61F75D8}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{470EC658-DAAD-47FC-9041-342F744C2D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{470EC658-DAAD-47FC-9041-342F744C2D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{470EC658-DAAD-47FC-9041-342F744C2D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{470EC658-DAAD-47FC-9041-342F744C2D2F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8B124EFF-94AA-4A17-B15B-EBD601452D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8B124EFF-94AA-4A17-B15B-EBD601452D68}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8B124EFF-94AA-4A17-B15B-EBD601452D68}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8B124EFF-94AA-4A17-B15B-EBD601452D68}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {0F32E022-174B-4EDC-9D7B-58A6FD7C213D}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "src/ContosoTraders.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Contoso/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=cosmosdb/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Daos/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=dbcontext/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=dbcontexts/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=dotnetcore/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Dtos/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=eastus/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=imageclassifier/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=keyvault/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=mediatr/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Upsert/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>"
  }
]