├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── LICENSE-CODE ├── README.md ├── SECURITY.md ├── angular-app ├── .browserslistrc ├── .gitignore ├── .prettierrc ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── about.component.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── build-specific │ │ │ ├── index.prod.ts │ │ │ └── index.ts │ │ ├── core │ │ │ ├── components │ │ │ │ ├── header-bar-brand.component.ts │ │ │ │ ├── header-bar.component.ts │ │ │ │ ├── index.ts │ │ │ │ ├── nav.component.ts │ │ │ │ └── not-found.component.ts │ │ │ ├── index.ts │ │ │ └── model │ │ │ │ ├── index.ts │ │ │ │ └── product.ts │ │ ├── products │ │ │ ├── product-detail.component.ts │ │ │ ├── product-list.component.ts │ │ │ ├── product.service.ts │ │ │ ├── products.component.ts │ │ │ └── products.module.ts │ │ ├── router.ts │ │ ├── shared │ │ │ ├── button-footer.component.ts │ │ │ ├── card-content.component.ts │ │ │ ├── list-header.component.ts │ │ │ ├── modal.component.ts │ │ │ └── shared.module.ts │ │ └── store │ │ │ ├── config.ts │ │ │ ├── entity-metadata.ts │ │ │ └── store.module.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── staticwebapp.config.json ├── tsconfig.base.json ├── tsconfig.json └── tslint.json ├── api ├── .funcignore ├── .gitignore ├── README.md ├── host.json ├── local.settings.json.sample ├── package-lock.json ├── package.json ├── products-delete │ ├── function.json │ └── index.js ├── products-get │ ├── function.json │ ├── index.js │ └── sample.dat ├── products-post │ ├── function.json │ └── index.js ├── products-put │ ├── function.json │ └── index.js ├── proxies.json └── shared │ └── product-data.js ├── react-app-new ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── src │ ├── About.js │ ├── App.css │ ├── App.js │ ├── components │ │ ├── ButtonFooter.js │ │ ├── CardContent.js │ │ ├── HeaderBar.js │ │ ├── HeaderBarBrand.js │ │ ├── InputDetail.js │ │ ├── ListHeader.js │ │ ├── Modal.js │ │ ├── ModalYesNo.js │ │ ├── NavBar.js │ │ ├── NotFound.js │ │ └── index.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── products │ │ ├── ProductDetail.js │ │ ├── ProductList.js │ │ ├── Products.js │ │ └── useProducts.js │ ├── reportWebVitals.js │ ├── serviceWorker.js │ ├── store │ │ ├── action-utils.js │ │ ├── config.js │ │ ├── index.js │ │ ├── product.actions.js │ │ ├── product.api.js │ │ ├── product.reducer.js │ │ └── product.saga.js │ └── styles.scss └── staticwebapp.config.json ├── react-app ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── About.js │ ├── App.css │ ├── App.js │ ├── components │ │ ├── ButtonFooter.js │ │ ├── CardContent.js │ │ ├── HeaderBar.js │ │ ├── HeaderBarBrand.js │ │ ├── InputDetail.js │ │ ├── ListHeader.js │ │ ├── Modal.js │ │ ├── ModalYesNo.js │ │ ├── NavBar.js │ │ ├── NotFound.js │ │ └── index.js │ ├── globe.png │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── products │ │ ├── ProductDetail.js │ │ ├── ProductList.js │ │ ├── Products.js │ │ └── useProducts.js │ ├── serviceWorker.js │ ├── store │ │ ├── action-utils.js │ │ ├── config.js │ │ ├── index.js │ │ ├── product.actions.js │ │ ├── product.api.js │ │ ├── product.reducer.js │ │ └── product.saga.js │ └── styles.scss └── staticwebapp.config.json ├── resources └── img │ └── publish-swa-learn-path.png ├── svelte-app ├── .gitignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.png │ ├── global.scss │ ├── index.html │ └── svelte-icon.png ├── rollup.config.js ├── src │ ├── About.svelte │ ├── App.svelte │ ├── components │ │ ├── ButtonFooter.svelte │ │ ├── CardContent.svelte │ │ ├── HeaderBar.svelte │ │ ├── HeaderBarBrand.svelte │ │ ├── ListHeader.svelte │ │ ├── Modal.svelte │ │ ├── NavBar.svelte │ │ ├── PageNotFound.svelte │ │ ├── Redirect.svelte │ │ └── index.js │ ├── main.js │ ├── products │ │ ├── ProductDetail.svelte │ │ ├── ProductList.svelte │ │ └── Products.svelte │ ├── store │ │ ├── config.js │ │ ├── http-utils.js │ │ ├── index.js │ │ ├── product-data.js │ │ └── store.js │ └── styles.scss └── staticwebapp.config.json └── vue-app ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── app.vue ├── assets │ └── logo.png ├── components │ ├── button-footer.vue │ ├── card-content.vue │ ├── header-bar-brand.vue │ ├── header-bar.vue │ ├── list-header.vue │ ├── modal.vue │ ├── nav-bar.vue │ └── page-not-found.vue ├── main.js ├── router.js ├── store │ ├── config.js │ ├── index.js │ └── modules │ │ ├── action-utils.js │ │ ├── mutation-types.js │ │ └── products.js ├── styles.scss └── views │ ├── about.vue │ └── products │ ├── product-detail.vue │ ├── product-list.vue │ └── products.vue ├── staticwebapp.config.json └── vue.config.js /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": "api", 3 | "azureFunctions.postDeployTask": "npm install", 4 | "azureFunctions.projectLanguage": "JavaScript", 5 | "azureFunctions.projectRuntime": "~3", 6 | "debug.internalConsoleOptions": "neverOpen", 7 | "azureFunctions.preDeployTask": "npm prune" 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "command": "host start", 7 | "problemMatcher": "$func-watch", 8 | "isBackground": true, 9 | "dependsOn": "npm install", 10 | "options": { 11 | "cwd": "${workspaceFolder}/api" 12 | } 13 | }, 14 | { 15 | "type": "shell", 16 | "label": "npm install", 17 | "command": "npm install", 18 | "options": { 19 | "cwd": "${workspaceFolder}/api" 20 | } 21 | }, 22 | { 23 | "type": "shell", 24 | "label": "npm prune", 25 | "command": "npm prune --production", 26 | "problemMatcher": [], 27 | "options": { 28 | "cwd": "${workspaceFolder}/api" 29 | } 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE-CODE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft 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/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If 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://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You 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://www.microsoft.com/msrc). 18 | 19 | Please 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: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If 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://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /angular-app/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /angular-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /angular-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /angular-app/README.md: -------------------------------------------------------------------------------- 1 | # Static Web App 2 | 3 | This project was created to help represent a fundamental app written with Angular. The shopping theme is used throughout the app. 4 | 5 | ## Getting Started 6 | 7 | 1. Create a repository from this template repository 8 | 9 | 1. Enter the name of your new repository as _my-static-web-app_ 10 | 11 | 1. Clone your new repository 12 | 13 | ```bash 14 | git clone https://github.com/your-github-organization/my-static-web-app 15 | cd my-static-web-app/angular-app 16 | ``` 17 | 18 | 1. Install the npm packages 19 | 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | 1. Run the app 25 | 26 | ```bash 27 | npm start 28 | ``` 29 | 30 | ## Resources 31 | 32 | ### Azure Static Web Apps 33 | 34 | - Learn how to [Publish an Angular, React, Svelte, or Vue JavaScript app and API with Azure Static Web Apps](https://docs.microsoft.com/learn/modules/publish-app-service-static-web-app-api?wt.mc_id=mslearn_staticwebapp-github-jopapa) 35 | - [API support in Azure Static Web Apps](https://docs.microsoft.com/azure/static-web-apps/apis?wt.mc_id=mslearn_staticwebapp-github-jopapa) 36 | - [Add an API to Azure Static Web Apps](https://docs.microsoft.com/azure/static-web-apps/add-api?wt.mc_id=mslearn_staticwebapp-github-jopapa) 37 | - [Authentication and authorization](https://docs.microsoft.com/azure/static-web-apps/authentication-authorization?wt.mc_id=mslearn_staticwebapp-github-jopapa) 38 | - [Routes](https://docs.microsoft.com/azure/static-web-apps/routes?wt.mc_id=mslearn_staticwebapp-github-jopapa) 39 | - [Review pre-production environments](https://docs.microsoft.com/azure/static-web-apps/review-publish-pull-requests?wt.mc_id=mslearn_staticwebapp-github-jopapa) 40 | 41 | ### Azure Functions 42 | 43 | - Learn how to [Refactor Node.js and Express APIs to Serverless APIs with Azure Functions](https://docs.microsoft.com/learn/modules/shift-nodejs-express-apis-serverless/?wt.mc_id=mslearn_staticwebapp-github-jopapa) 44 | - Learn about the Azure Functions [local.settings.json](https://docs.microsoft.com/azure/azure-functions/functions-run-local#local-settings-file?wt.mc_id=mslearn_staticwebapp-github-jopapa) file 45 | - Learn how to [Deploy to Azure Using Azure Functions](https://code.visualstudio.com/tutorials/functions-extension/getting-started?wt.mc_id=mslearn_staticwebapp-github-jopapa) 46 | - Sign up for a [Free Trial of Azure](https://azure.microsoft.com/free/?wt.mc_id=mslearn_staticwebapp-github-jopapa) 47 | 48 | ### Visual Studio Code 49 | 50 | - [Azure Free Trial](https://azure.microsoft.com/free/?wt.mc_id=mslearn_staticwebapp-github-jopapa) 51 | - [VS Code](https://code.visualstudio.com?wt.mc_id=mslearn_staticwebapp-github-jopapa) 52 | - [VS Code Extension for Node on Azure](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack&WT.mc_id=mslearn_staticwebapp-github-jopapa) 53 | - Azure Functions [local.settings.json](https://docs.microsoft.com/azure/azure-functions/functions-run-local#local-settings-file?WT.mc_id=mslearn_staticwebapp-github-jopapa) file 54 | 55 | ### Debugging Resources 56 | 57 | - [Debugging Angular in VS Code](https://code.visualstudio.com/docs/nodejs/angular-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 58 | - [Debugging React in VS Code](https://code.visualstudio.com/docs/nodejs/reactjs-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 59 | - [Debugging Vue in VS Code](https://code.visualstudio.com/docs/nodejs/vuejs-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 60 | -------------------------------------------------------------------------------- /angular-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --proxy-config proxy.conf.json --open", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^15.0.0", 15 | "@angular/common": "^15.0.0", 16 | "@angular/compiler": "^15.0.0", 17 | "@angular/core": "^15.0.0", 18 | "@angular/forms": "^15.0.0", 19 | "@angular/platform-browser": "^15.0.0", 20 | "@angular/platform-browser-dynamic": "^15.0.0", 21 | "@angular/router": "^15.0.0", 22 | "@ngrx/data": "^12.1.0", 23 | "@ngrx/effects": "^12.1.0", 24 | "@ngrx/entity": "^12.1.0", 25 | "@ngrx/store": "^12.1.0", 26 | "@ngrx/store-devtools": "^12.1.0", 27 | "bulma": "^0.9.2", 28 | "core-js": "^3.6.4", 29 | "font-awesome": "^4.7.0", 30 | "minimist": "^1.2.5", 31 | "rxjs": "~6.6.2", 32 | "tslib": "^2.0.0", 33 | "zone.js": "~0.11.4" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "^15.0.0", 37 | "@angular/cli": "~15.0.0", 38 | "@angular/compiler-cli": "^15.0.0", 39 | "@angular/language-service": "^12.0.4", 40 | "@types/jasmine": "~4.3.0", 41 | "@types/jasminewd2": "^2.0.8", 42 | "@types/node": "^14.14.31", 43 | "codelyzer": "^0.0.28", 44 | "jasmine-core": "~4.5.0", 45 | "jasmine-spec-reporter": "~5.0.0", 46 | "karma": "~6.4.0", 47 | "karma-chrome-launcher": "~3.1.0", 48 | "karma-coverage-istanbul-reporter": "~3.0.2", 49 | "karma-jasmine": "~4.0.0", 50 | "karma-jasmine-html-reporter": "^1.5.0", 51 | "ts-node": "^8.6.2", 52 | "tslint": "~6.1.0", 53 | "typescript": "4.8.4" 54 | } 55 | } -------------------------------------------------------------------------------- /angular-app/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:7071", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /angular-app/src/app/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-about', 5 | template: ` 6 |
7 |
8 |

Product Wish List

9 |

10 | This project was created to help represent a fundamental app written 11 | with Angular. The shopping theme is used throughout the app. 12 |

13 |
14 |

Resources

15 | 22 |
23 |
24 | `, 25 | }) 26 | export class AboutComponent {} 27 | -------------------------------------------------------------------------------- /angular-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /angular-app/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrosoftDocs/mslearn-staticwebapp-api/fc95ea8ea0a8cdff93b346bc57a0c65fb4bd48be/angular-app/src/app/app.component.scss -------------------------------------------------------------------------------- /angular-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | export class Customer { 3 | public id: number; 4 | public name: string; 5 | } 6 | @Component({ 7 | selector: 'app-root', 8 | templateUrl: './app.component.html', 9 | styleUrls: ['./app.component.scss'] 10 | }) 11 | export class AppComponent { 12 | customers: Customer[] = [{ id: 1, name: 'john' }]; 13 | } 14 | -------------------------------------------------------------------------------- /angular-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | import { routes } from './router'; 6 | import { AppComponent } from './app.component'; 7 | import { AppStoreModule } from './store/store.module'; 8 | import { AboutComponent } from './about.component'; 9 | import { RouterModule } from '@angular/router'; 10 | import { externalModules } from './build-specific'; 11 | import { declarations } from './core'; 12 | 13 | @NgModule({ 14 | declarations: [AppComponent, AboutComponent, declarations], 15 | imports: [ 16 | BrowserModule, 17 | HttpClientModule, 18 | RouterModule.forRoot(routes), 19 | AppStoreModule, 20 | externalModules 21 | ], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule {} 25 | -------------------------------------------------------------------------------- /angular-app/src/app/build-specific/index.prod.ts: -------------------------------------------------------------------------------- 1 | export const externalModules = []; 2 | -------------------------------------------------------------------------------- /angular-app/src/app/build-specific/index.ts: -------------------------------------------------------------------------------- 1 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 2 | 3 | /** 4 | * Put dev specific code here, and prod specific code in index.prod.ts 5 | * https://ngrx.io/guide/store-devtools/recipes/exclude 6 | */ 7 | export const externalModules = [ 8 | StoreDevtoolsModule.instrument({ 9 | maxAge: 25 10 | }) 11 | ]; 12 | -------------------------------------------------------------------------------- /angular-app/src/app/core/components/header-bar-brand.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header-bar-brand', 5 | template: ` 6 | 21 | `, 22 | }) 23 | export class HeaderBarBrandComponent {} 24 | -------------------------------------------------------------------------------- /angular-app/src/app/core/components/header-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header-bar', 5 | template: ` 6 |
7 | 14 |
15 | `, 16 | }) 17 | export class HeaderBarComponent {} 18 | -------------------------------------------------------------------------------- /angular-app/src/app/core/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './header-bar.component'; 2 | export * from './header-bar-brand.component'; 3 | export * from './nav.component'; 4 | export * from './not-found.component'; 5 | 6 | import { HeaderBarBrandComponent } from './header-bar-brand.component'; 7 | import { HeaderBarComponent } from './header-bar.component'; 8 | import { NavComponent } from './nav.component'; 9 | import { NotFoundComponent } from './not-found.component'; 10 | 11 | export const declarations = [ 12 | NavComponent, 13 | HeaderBarComponent, 14 | HeaderBarBrandComponent, 15 | NotFoundComponent, 16 | ]; 17 | -------------------------------------------------------------------------------- /angular-app/src/app/core/components/nav.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-nav', 5 | template: ` 6 | 17 | `, 18 | }) 19 | export class NavComponent {} 20 | -------------------------------------------------------------------------------- /angular-app/src/app/core/components/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | template: ` 6 |
7 |
8 |   9 | These aren't the bits you're looking for 10 |
11 |
12 | `, 13 | }) 14 | export class NotFoundComponent {} 15 | -------------------------------------------------------------------------------- /angular-app/src/app/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './model'; 3 | -------------------------------------------------------------------------------- /angular-app/src/app/core/model/index.ts: -------------------------------------------------------------------------------- 1 | export * from './product'; 2 | -------------------------------------------------------------------------------- /angular-app/src/app/core/model/product.ts: -------------------------------------------------------------------------------- 1 | export class Product { 2 | id: number; 3 | name: string; 4 | description: string; 5 | quantity: number; 6 | } 7 | -------------------------------------------------------------------------------- /angular-app/src/app/products/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | EventEmitter, 4 | Input, 5 | Output, 6 | ChangeDetectionStrategy, 7 | } from '@angular/core'; 8 | import { Product } from '../core'; 9 | 10 | @Component({ 11 | selector: 'app-product-list', 12 | template: ` 13 |
14 | Loading data ... 15 |
16 | 47 | `, 48 | changeDetection: ChangeDetectionStrategy.OnPush, 49 | }) 50 | export class ProductListComponent { 51 | @Input() products: Product[]; 52 | @Output() deleted = new EventEmitter(); 53 | @Output() selected = new EventEmitter(); 54 | 55 | trackByProduct(index: number, product: Product): number { 56 | return product.id; 57 | } 58 | 59 | selectProduct(product: Product) { 60 | this.selected.emit(product); 61 | } 62 | 63 | deleteProduct(product: Product) { 64 | this.deleted.emit(product); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /angular-app/src/app/products/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | EntityCollectionServiceBase, 4 | EntityCollectionServiceElementsFactory, 5 | } from '@ngrx/data'; 6 | import { Product } from '../core'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class ProductService extends EntityCollectionServiceBase { 10 | constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) { 11 | super('Product', serviceElementsFactory); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /angular-app/src/app/products/products.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { Product } from '../core'; 4 | import { ProductService } from './product.service'; 5 | 6 | @Component({ 7 | selector: 'app-products', 8 | template: ` 9 |
10 | 15 |
16 |
17 | 23 | 29 |
30 |
31 | 32 | 39 |
40 | `, 41 | }) 42 | export class ProductsComponent implements OnInit { 43 | selected: Product; 44 | products$: Observable; 45 | message = '?'; 46 | productToDelete: Product; 47 | showModal = false; 48 | 49 | constructor(private productService: ProductService) { 50 | this.products$ = productService.entities$; 51 | } 52 | 53 | ngOnInit() { 54 | this.getProducts(); 55 | } 56 | 57 | add(product: Product) { 58 | this.productService.add(product, {isOptimistic: false}); 59 | } 60 | 61 | askToDelete(product: Product) { 62 | this.productToDelete = product; 63 | this.showModal = true; 64 | if (this.productToDelete.name) { 65 | this.message = `Would you like to delete ${this.productToDelete.name}?`; 66 | } 67 | } 68 | 69 | clear() { 70 | this.selected = null; 71 | } 72 | 73 | closeModal() { 74 | this.showModal = false; 75 | } 76 | 77 | deleteProduct() { 78 | this.closeModal(); 79 | if (this.productToDelete) { 80 | this.productService 81 | .delete(this.productToDelete.id) 82 | .subscribe(() => (this.productToDelete = null)); 83 | } 84 | this.clear(); 85 | } 86 | 87 | enableAddMode() { 88 | this.selected = {}; 89 | } 90 | 91 | getProducts() { 92 | this.productService.getAll(); 93 | this.clear(); 94 | } 95 | 96 | save(product: Product) { 97 | if (this.selected && this.selected.name) { 98 | this.update(product); 99 | } else { 100 | this.add(product); 101 | } 102 | } 103 | 104 | select(product: Product) { 105 | this.selected = product; 106 | } 107 | 108 | update(product: Product) { 109 | this.productService.update(product); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /angular-app/src/app/products/products.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { SharedModule } from '../shared/shared.module'; 5 | import { ProductDetailComponent } from './product-detail.component'; 6 | import { ProductListComponent } from './product-list.component'; 7 | import { ProductsComponent } from './products.component'; 8 | 9 | const routes: Routes = [ 10 | { 11 | path: '', 12 | component: ProductsComponent, 13 | }, 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [CommonModule, RouterModule.forChild(routes), SharedModule], 18 | exports: [RouterModule, ProductsComponent], 19 | declarations: [ 20 | ProductsComponent, 21 | ProductListComponent, 22 | ProductDetailComponent, 23 | ], 24 | }) 25 | export class ProductsModule {} 26 | -------------------------------------------------------------------------------- /angular-app/src/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { AboutComponent } from './about.component'; 3 | import { NotFoundComponent } from './core'; 4 | 5 | export const routes: Routes = [ 6 | { path: '', pathMatch: 'full', redirectTo: 'products' }, 7 | { 8 | path: 'products', 9 | loadChildren: () => 10 | import('./products/products.module').then((m) => m.ProductsModule), 11 | }, 12 | { path: 'about', component: AboutComponent }, 13 | { path: '**', component: NotFoundComponent }, 14 | ]; 15 | -------------------------------------------------------------------------------- /angular-app/src/app/shared/button-footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-button-footer', 5 | template: ` 6 | 16 | ` 17 | }) 18 | export class ButtonFooterComponent implements OnInit { 19 | @Input() label; 20 | @Input() className; 21 | @Input() iconClasses; 22 | @Input() item; 23 | @Input() dataId; 24 | 25 | @Output() clicked = new EventEmitter(); 26 | 27 | ngOnInit() {} 28 | 29 | handleClick() { 30 | this.clicked.emit(this.item); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /angular-app/src/app/shared/card-content.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-card-content', 5 | template: ` 6 |
7 |
8 |
{{ name }}
9 |
{{ description }}
10 |
11 |
12 | ` 13 | }) 14 | export class CardContentComponent implements OnInit { 15 | @Input() name; 16 | @Input() description; 17 | 18 | ngOnInit() {} 19 | } 20 | -------------------------------------------------------------------------------- /angular-app/src/app/shared/list-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-list-header', 5 | template: ` 6 |
7 | 8 |

{{title}}

9 |
10 | 17 | 24 |
25 | ` 26 | }) 27 | export class ListHeaderComponent implements OnInit { 28 | @Input() title: string; 29 | @Output() add = new EventEmitter(); 30 | @Output() refresh = new EventEmitter(); 31 | 32 | ngOnInit() {} 33 | 34 | handleAdd() { 35 | this.add.emit(); 36 | } 37 | handleRefresh() { 38 | this.refresh.emit(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /angular-app/src/app/shared/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, OnInit, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-modal', 5 | template: ` 6 | 21 | ` 22 | }) 23 | export class ModalComponent implements OnInit { 24 | @Input() message; 25 | @Input() isOpen = false; 26 | @Output() handleYes = new EventEmitter(); 27 | @Output() handleNo = new EventEmitter(); 28 | 29 | ngOnInit() {} 30 | 31 | onNo = () => { 32 | this.handleNo.emit(); 33 | } 34 | 35 | onYes = () => { 36 | this.handleYes.emit(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /angular-app/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { ListHeaderComponent } from './list-header.component'; 5 | import { CardContentComponent } from './card-content.component'; 6 | import { ButtonFooterComponent } from './button-footer.component'; 7 | import { ModalComponent } from './modal.component'; 8 | 9 | const components = [ 10 | ButtonFooterComponent, 11 | CardContentComponent, 12 | ListHeaderComponent, 13 | ModalComponent 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [CommonModule, FormsModule, ReactiveFormsModule], 18 | declarations: [components], 19 | exports: [components, FormsModule, ReactiveFormsModule] 20 | }) 21 | export class SharedModule {} 22 | -------------------------------------------------------------------------------- /angular-app/src/app/store/config.ts: -------------------------------------------------------------------------------- 1 | import { DefaultDataServiceConfig } from '@ngrx/data'; 2 | import { environment } from './../../environments/environment'; 3 | 4 | const root = environment.API || 'api'; 5 | 6 | export const defaultDataServiceConfig: DefaultDataServiceConfig = { 7 | root, // default root path to the server's web api 8 | 9 | entityHttpResourceUrls: { 10 | Product: { 11 | // You must specify the root as part of the resource URL. 12 | entityResourceUrl: `${root}/products/`, 13 | collectionResourceUrl: `${root}/products/`, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /angular-app/src/app/store/entity-metadata.ts: -------------------------------------------------------------------------------- 1 | import { EntityMetadataMap } from '@ngrx/data'; 2 | 3 | const entityMetadata: EntityMetadataMap = { 4 | Product: {}, 5 | }; 6 | 7 | export const entityConfig = { 8 | entityMetadata, 9 | }; 10 | -------------------------------------------------------------------------------- /angular-app/src/app/store/store.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { EffectsModule } from '@ngrx/effects'; 3 | import { StoreModule } from '@ngrx/store'; 4 | import { DefaultDataServiceConfig, EntityDataModule } from '@ngrx/data'; 5 | import { defaultDataServiceConfig } from './config'; 6 | import { entityConfig } from './entity-metadata'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | StoreModule.forRoot({}), 11 | EffectsModule.forRoot([]), 12 | EntityDataModule.forRoot(entityConfig) 13 | ], 14 | providers: [ 15 | { provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig } 16 | ] 17 | }) 18 | export class AppStoreModule {} 19 | -------------------------------------------------------------------------------- /angular-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrosoftDocs/mslearn-staticwebapp-api/fc95ea8ea0a8cdff93b346bc57a0c65fb4bd48be/angular-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular-app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | API: 'api', 4 | }; 5 | -------------------------------------------------------------------------------- /angular-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | API: 'api' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /angular-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrosoftDocs/mslearn-staticwebapp-api/fc95ea8ea0a8cdff93b346bc57a0c65fb4bd48be/angular-app/src/favicon.ico -------------------------------------------------------------------------------- /angular-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /angular-app/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /angular-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /angular-app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /angular-app/src/styles.scss: -------------------------------------------------------------------------------- 1 | $vue: #42b883; 2 | $vue-light: #42b883; 3 | $angular: #b52e31; 4 | $angular-light: #eb7a7c; 5 | $react: #00b3e6; 6 | $react-light: #61dafb; 7 | $primary: $angular; 8 | $primary-light: $angular-light; 9 | $link: $primary; // #00b3e6; // #ff4081; 10 | 11 | $shade-light: #fafafa; 12 | 13 | @import 'bulma/bulma.sass'; 14 | 15 | .menu-list .router-link-active { 16 | color: #fff; 17 | background-color: $link; 18 | } 19 | 20 | .not-found { 21 | i { 22 | font-size: 20px; 23 | margin-right: 8px; 24 | } 25 | .title { 26 | letter-spacing: 0px; 27 | font-weight: normal; 28 | font-size: 24px; 29 | text-transform: none; 30 | } 31 | } 32 | 33 | header { 34 | font-weight: bold; 35 | font-family: Arial; 36 | span { 37 | letter-spacing: 0px; 38 | &.brand-first { 39 | color: #fff; 40 | } 41 | &.brand-second { 42 | color: #ccc; 43 | } 44 | &.brand-third { 45 | color: $primary-light; 46 | } 47 | } 48 | .navbar-item.nav-home { 49 | border: 3px solid transparent; 50 | border-radius: 0%; 51 | &:hover { 52 | border-right: 3px solid $primary-light; 53 | border-left: 3px solid $primary-light; 54 | } 55 | } 56 | .fab { 57 | font-size: 24px; 58 | &.js-logo { 59 | color: $primary-light; 60 | } 61 | } 62 | .buttons { 63 | i.fab { 64 | color: #fff; 65 | margin-left: 20px; 66 | margin-right: 10px; 67 | &:hover { 68 | color: $primary-light; 69 | } 70 | } 71 | } 72 | } 73 | 74 | .edit-detail { 75 | .input[readonly] { 76 | background-color: $shade-light; 77 | } 78 | .input::placeholder { 79 | color: #ccc; 80 | font-style: italic; 81 | } 82 | } 83 | 84 | .content-title-group { 85 | margin-bottom: 16px; 86 | h2 { 87 | border-left: 16px solid $primary; 88 | border-bottom: 2px solid $primary; 89 | padding-left: 8px; 90 | padding-right: 16px; 91 | display: inline-block; 92 | text-transform: uppercase; 93 | color: #555; 94 | letter-spacing: 0px; 95 | &:hover { 96 | color: $link; 97 | } 98 | } 99 | button.button { 100 | border: 0; 101 | color: #999; 102 | &:hover { 103 | color: $link; 104 | } 105 | } 106 | } 107 | ul.list { 108 | box-shadow: none; 109 | } 110 | div.card-content { 111 | background-color: $shade-light; 112 | .name { 113 | font-size: 28px; 114 | color: #000; 115 | } 116 | .description { 117 | font-size: 20px; 118 | color: #999; 119 | } 120 | background-color: $shade-light; 121 | } 122 | .card { 123 | margin-bottom: 2em; 124 | } 125 | 126 | label.label { 127 | font-weight: normal; 128 | } 129 | 130 | p.card-header-title { 131 | background-color: $primary; 132 | text-transform: uppercase; 133 | letter-spacing: 4px; 134 | color: #fff; 135 | display: block; 136 | padding-left: 24px; 137 | } 138 | .card-footer button { 139 | font-size: 16px; 140 | i { 141 | margin-right: 10px; 142 | } 143 | color: #888; 144 | &:hover { 145 | color: $link; 146 | } 147 | } 148 | 149 | .modal-card-foot button { 150 | display: inline-block; 151 | width: 80px; 152 | } 153 | 154 | .modal-card-head, 155 | .modal-card-body { 156 | text-align: center; 157 | } 158 | 159 | .field { 160 | margin-bottom: 0.75rem; 161 | } 162 | 163 | .navbar-burger { 164 | margin-left: auto; 165 | } 166 | 167 | button.link { 168 | background: none; 169 | border: none; 170 | cursor: pointer; 171 | } 172 | -------------------------------------------------------------------------------- /angular-app/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /angular-app/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /angular-app/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /angular-app/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /angular-app/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationFallback": { 3 | "rewrite": "/index.html", 4 | "exclude": ["*.{css,scss,js,png,gif,ico,jpg,svg}"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /angular-app/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "angularCompilerOptions": { 4 | "strictTemplates": true, 5 | "strictInjectionParameters": true 6 | }, 7 | "compilerOptions": { 8 | "baseUrl": "./", 9 | "importHelpers": true, 10 | "outDir": "./dist/out-tsc", 11 | "sourceMap": true, 12 | "declaration": false, 13 | "module": "es2020", 14 | "moduleResolution": "node", 15 | "experimentalDecorators": true, 16 | "target": "es2015", 17 | "typeRoots": ["node_modules/@types"], 18 | "lib": ["es2018", "dom"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /angular-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./src/tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./src/tsconfig.spec.json" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /angular-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "curly": true, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "eofline": true, 13 | "forin": true, 14 | "import-blacklist": [true], 15 | "import-spacing": true, 16 | "indent": [true, "spaces"], 17 | "interface-over-type-literal": true, 18 | "label-position": true, 19 | "max-line-length": [true, 140], 20 | "member-access": false, 21 | "member-ordering": [ 22 | true, 23 | { 24 | "order": [ 25 | "static-field", 26 | "instance-field", 27 | "static-method", 28 | "instance-method" 29 | ] 30 | } 31 | ], 32 | "no-arg": true, 33 | "no-bitwise": true, 34 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 35 | "no-construct": true, 36 | "no-debugger": true, 37 | "no-duplicate-super": true, 38 | "no-empty": false, 39 | "no-empty-interface": true, 40 | "no-eval": true, 41 | "no-inferrable-types": [true, "ignore-params"], 42 | "no-misused-new": true, 43 | "no-non-null-assertion": true, 44 | "no-redundant-jsdoc": true, 45 | "no-shadowed-variable": true, 46 | "no-string-literal": false, 47 | "no-string-throw": true, 48 | "no-switch-case-fall-through": true, 49 | "no-trailing-whitespace": true, 50 | "no-unnecessary-initializer": true, 51 | "no-unused-expression": true, 52 | "no-var-keyword": true, 53 | "object-literal-sort-keys": false, 54 | "one-line": [ 55 | true, 56 | "check-open-brace", 57 | "check-catch", 58 | "check-else", 59 | "check-whitespace" 60 | ], 61 | "prefer-const": true, 62 | "quotemark": [true, "single"], 63 | "radix": true, 64 | "semicolon": [true, "always"], 65 | "triple-equals": [true, "allow-null-check"], 66 | "typedef-whitespace": [ 67 | true, 68 | { 69 | "call-signature": "nospace", 70 | "index-signature": "nospace", 71 | "parameter": "nospace", 72 | "property-declaration": "nospace", 73 | "variable-declaration": "nospace" 74 | } 75 | ], 76 | "unified-signatures": true, 77 | "variable-name": false, 78 | "whitespace": [ 79 | true, 80 | "check-branch", 81 | "check-decl", 82 | "check-operator", 83 | "check-separator", 84 | "check-type" 85 | ], 86 | "no-output-on-prefix": true, 87 | "no-inputs-metadata-property": true, 88 | "no-outputs-metadata-property": true, 89 | "no-host-metadata-property": true, 90 | "no-input-rename": true, 91 | "no-output-rename": true, 92 | "use-lifecycle-interface": true, 93 | "use-pipe-transform-interface": true, 94 | "component-class-suffix": true, 95 | "directive-class-suffix": true 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /api/.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | # TypeScript output 87 | dist 88 | out 89 | 90 | # Azure Functions artifacts 91 | bin 92 | obj 93 | appsettings.json 94 | local.settings.json -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions API 2 | 3 | This project is an Azure Functions app, that responds to GET, POST, PUT, and DELETE endpoints for products. 4 | 5 | ## Prerequisites 6 | 7 | - A GitHub account 8 | - [Node.js and Git](https://nodejs.org/) 9 | - [Visual Studio Code](https://code.visualstudio.com/?WT.mc_id=mslearn_staticwebapp-github-jopapa) installed 10 | - The [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions?WT.mc_id=mslearn_staticwebapp-github-jopapa) installed 11 | - The [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=mslearn_staticwebapp-github-jopapa) installed 12 | 13 | ## Getting Started 14 | 15 | 1. Create a repository from this template repository 16 | 17 | 1. Enter the name of your new repository as _mslearn-staticwebapp_ 18 | 19 | 1. Clone your new repository 20 | 21 | ```bash 22 | git clone https://github.com/your-github-organization/mslearn-staticwebapp 23 | cd mslearn-staticwebapp/api 24 | ``` 25 | 26 | 1. Create the file `api/local.setting.json` and modify its contents as follows: 27 | 28 | ```json 29 | { 30 | "IsEncrypted": false, 31 | "Values": { 32 | "AzureWebJobsStorage": "", 33 | "FUNCTIONS_WORKER_RUNTIME": "node" 34 | }, 35 | "Host": { 36 | "CORS": "http://localhost:3000,http://localhost:4200,http://localhost:5000,http://localhost:8080" 37 | } 38 | } 39 | ``` 40 | 41 | 1. Run the app 42 | 43 | ```bash 44 | npm start 45 | ``` 46 | 47 | ## Resources 48 | 49 | - [Azure Free Trial](https://azure.microsoft.com/en-us/free/?wt.mc_id=mslearn_staticwebapp-github-jopapa) 50 | - [VS Code](https://code.visualstudio.com?wt.mc_id=mslearn_staticwebapp-github-jopapa) 51 | - [VS Code Extension for Node on Azure](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack&WT.mc_id=mslearn_staticwebapp-github-jopapa) 52 | - Azure Functions [local.settings.json](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file?WT.mc_id=mslearn_staticwebapp-github-jopapa) file 53 | 54 | ### Debugging Resources 55 | 56 | - [Debugging Angular in VS Code](https://code.visualstudio.com/docs/nodejs/angular-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 57 | - [Debugging React in VS Code](https://code.visualstudio.com/docs/nodejs/reactjs-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 58 | - [Debugging Vue in VS Code](https://code.visualstudio.com/docs/nodejs/vuejs-tutorial?wt.mc_id=mslearn_staticwebapp-github-jopapa) 59 | -------------------------------------------------------------------------------- /api/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "extensionBundle": { 4 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 5 | "version": "[1.*, 2.0.0)" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /api/local.settings.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "FUNCTIONS_WORKER_RUNTIME": "node", 5 | "AzureWebJobsStorage": "" 6 | }, 7 | "Host": { 8 | "LocalHttpPort": 7071, 9 | "CORS": "*" 10 | } 11 | } -------------------------------------------------------------------------------- /api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "functions", 9 | "version": "1.0.0", 10 | "devDependencies": { 11 | "@azure/functions": "^4.3.0" 12 | } 13 | }, 14 | "node_modules/@azure/functions": { 15 | "version": "4.3.0", 16 | "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.3.0.tgz", 17 | "integrity": "sha512-l7iAuSyyBCOgwkDZmV6UUagwkFoqMOVfq01oJ+rJlFhN7Mb8/kkUAZLffCPUxBy2Wwah741BhJGizwaCP9G2/A==", 18 | "dev": true, 19 | "dependencies": { 20 | "cookie": "^0.6.0", 21 | "long": "^4.0.0", 22 | "undici": "^5.13.0" 23 | }, 24 | "engines": { 25 | "node": ">=18.0" 26 | } 27 | }, 28 | "node_modules/@fastify/busboy": { 29 | "version": "2.1.1", 30 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 31 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 32 | "dev": true, 33 | "engines": { 34 | "node": ">=14" 35 | } 36 | }, 37 | "node_modules/cookie": { 38 | "version": "0.6.0", 39 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 40 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 41 | "dev": true, 42 | "engines": { 43 | "node": ">= 0.6" 44 | } 45 | }, 46 | "node_modules/long": { 47 | "version": "4.0.0", 48 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 49 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", 50 | "dev": true 51 | }, 52 | "node_modules/undici": { 53 | "version": "5.28.3", 54 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", 55 | "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", 56 | "dev": true, 57 | "dependencies": { 58 | "@fastify/busboy": "^2.0.0" 59 | }, 60 | "engines": { 61 | "node": ">=14.0" 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "func start", 7 | "test": "echo \"No tests yet...\"" 8 | }, 9 | "devDependencies": { 10 | "@azure/functions": "^4.3.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/products-delete/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["delete"], 9 | "route": "products/{id}" 10 | }, 11 | { 12 | "type": "http", 13 | "direction": "out", 14 | "name": "res" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/products-delete/index.js: -------------------------------------------------------------------------------- 1 | const data = require('../shared/product-data'); 2 | 3 | module.exports = async function (context, req) { 4 | const id = parseInt(req.params.id, 10); 5 | 6 | try { 7 | data.deleteProduct(id); 8 | context.res.status(200).json({}); 9 | } catch (error) { 10 | context.res.status(500).send(error); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /api/products-get/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["get"], 9 | "route": "products" 10 | }, 11 | { 12 | "type": "http", 13 | "direction": "out", 14 | "name": "res" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/products-get/index.js: -------------------------------------------------------------------------------- 1 | const data = require('../shared/product-data'); 2 | 3 | module.exports = async function (context, req) { 4 | try { 5 | const products = data.getProducts(); 6 | context.res.status(200).json(products); 7 | } catch (error) { 8 | context.res.status(500).send(error); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /api/products-get/sample.dat: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure" 3 | } -------------------------------------------------------------------------------- /api/products-post/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["post"], 9 | "route": "products" 10 | }, 11 | { 12 | "type": "http", 13 | "direction": "out", 14 | "name": "res" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/products-post/index.js: -------------------------------------------------------------------------------- 1 | const data = require('../shared/product-data'); 2 | 3 | module.exports = async function (context, req) { 4 | const product = { 5 | id: undefined, 6 | name: req.body.name, 7 | description: req.body.description, 8 | }; 9 | 10 | try { 11 | const newProduct = data.addProduct(product); 12 | context.res.status(201).json(newProduct); 13 | } catch (error) { 14 | context.res.status(500).send(error); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /api/products-put/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req", 8 | "methods": ["put"], 9 | "route": "products/{id}" 10 | }, 11 | { 12 | "type": "http", 13 | "direction": "out", 14 | "name": "res" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/products-put/index.js: -------------------------------------------------------------------------------- 1 | const data = require('../shared/product-data'); 2 | 3 | module.exports = async function (context, req) { 4 | const product = { 5 | id: parseInt(req.params.id, 10), 6 | name: req.body.name, 7 | description: req.body.description, 8 | quantity: req.body.quantity, 9 | }; 10 | 11 | try { 12 | const updatedProduct = data.updateProduct(product); 13 | context.res.status(200).json(updatedProduct); 14 | } catch (error) { 15 | context.res.status(500).send(error); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /api/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /api/shared/product-data.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | products: [ 3 | { 4 | id: 10, 5 | name: 'Strawberries', 6 | description: '16oz package of fresh organic strawberries', 7 | quantity: '1', 8 | }, 9 | { 10 | id: 20, 11 | name: 'Sliced bread', 12 | description: 'Loaf of fresh sliced wheat bread', 13 | quantity: 1, 14 | }, 15 | { 16 | id: 30, 17 | name: 'Apples', 18 | description: 'Bag of 7 fresh McIntosh apples', 19 | quantity: 1, 20 | }, 21 | ], 22 | }; 23 | 24 | const getRandomInt = () => { 25 | const max = 1000; 26 | const min = 100; 27 | return Math.floor(Math.random() * Math.floor(max) + min); 28 | }; 29 | 30 | const addProduct = (product) => { 31 | product.id = getRandomInt(); 32 | data.products.push(product); 33 | return product; 34 | }; 35 | 36 | const updateProduct = (product) => { 37 | const index = data.products.findIndex((v) => v.id === product.id); 38 | console.log(product); 39 | data.products.splice(index, 1, product); 40 | return product; 41 | }; 42 | 43 | const deleteProduct = (id) => { 44 | const value = parseInt(id, 10); 45 | data.products = data.products.filter((v) => v.id !== value); 46 | return true; 47 | }; 48 | 49 | const getProducts = () => { 50 | return data.products; 51 | }; 52 | 53 | module.exports = { addProduct, updateProduct, deleteProduct, getProducts }; 54 | -------------------------------------------------------------------------------- /react-app-new/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended", 7 | "prettier" 8 | ], 9 | "rules": { 10 | "react/prop-types": 0, 11 | "jsx-a11y/label-has-for": 0, 12 | "jsx-a11y/anchor-is-valid": 0, 13 | "jsx-a11y/click-events-have-key-events": 0, 14 | "no-console": 1, 15 | "quotes": [ 16 | 2, 17 | "single", 18 | { 19 | "avoidEscape": true, 20 | "allowTemplateLiterals": true 21 | } 22 | ] 23 | }, 24 | "plugins": ["react", "import", "jsx-a11y"], 25 | "parserOptions": { 26 | "ecmaVersion": 2018, 27 | "sourceType": "module", 28 | "ecmaFeatures": { 29 | "jsx": true 30 | } 31 | }, 32 | "env": { 33 | "es6": true, 34 | "browser": true, 35 | "node": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /react-app-new/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react-app-new/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /react-app-new/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If 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. 37 | 38 | Instead, 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. 39 | 40 | You 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This 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) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This 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) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This 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) 71 | -------------------------------------------------------------------------------- /react-app-new/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-new", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "export WDS_SOCKET_PORT=443 && react-scripts start", 7 | "build": "react-scripts build", 8 | "test": "react-scripts test", 9 | "eject": "react-scripts eject", 10 | "format": "prettier --write \"src/**/*.{js,jsx}\"", 11 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet" 12 | }, 13 | "eslintConfig": { 14 | "extends": [ 15 | "react-app", 16 | "react-app/jest" 17 | ] 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "proxy": "http://localhost:7071/", 32 | "dependencies": { 33 | "@fortawesome/fontawesome-free": "^6.5.1", 34 | "@testing-library/jest-dom": "^5.17.0", 35 | "@testing-library/react": "^13.4.0", 36 | "@testing-library/user-event": "^13.5.0", 37 | "axios": "^1.6.8", 38 | "bulma": "^0.9.4", 39 | "react": "^18.2.0", 40 | "react-dom": "^18.2.0", 41 | "react-redux": "^9.1.0", 42 | "react-router-dom": "^6.22.3", 43 | "react-scripts": "5.0.1", 44 | "redux": "^5.0.1", 45 | "redux-saga": "^1.3.0", 46 | "redux-thunk": "^3.1.0", 47 | "sass": "^1.72.0", 48 | "web-vitals": "^2.1.4" 49 | }, 50 | "devDependencies": { 51 | "eslint-config-airbnb": "^19.0.4", 52 | "eslint-config-prettier": "^9.1.0", 53 | "eslint-plugin-import": "^2.29.1", 54 | "eslint-plugin-jsx-a11y": "^6.8.0", 55 | "eslint-plugin-prettier": "^5.1.3", 56 | "eslint-plugin-react": "^7.34.1", 57 | "prettier": "^3.2.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /react-app-new/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrosoftDocs/mslearn-staticwebapp-api/fc95ea8ea0a8cdff93b346bc57a0c65fb4bd48be/react-app-new/public/favicon.ico -------------------------------------------------------------------------------- /react-app-new/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 22 | 31 | React App 32 | 33 | 34 | 35 | 38 | 39 |
40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /react-app-new/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /react-app-new/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-app-new/src/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = () => ( 4 |
5 |
6 |

Product Wish List

7 |

8 | This project was created to help represent a fundamental app written 9 | with React. The shopping theme is used throughout the app. 10 |

11 |
12 |

Resources

13 | 20 |
21 |
22 | ); 23 | 24 | export default About; 25 | -------------------------------------------------------------------------------- /react-app-new/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrosoftDocs/mslearn-staticwebapp-api/fc95ea8ea0a8cdff93b346bc57a0c65fb4bd48be/react-app-new/src/App.css -------------------------------------------------------------------------------- /react-app-new/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, lazy, Suspense } from 'react'; 2 | import 'bulma/css/bulma.css'; 3 | import './styles.scss'; 4 | import { Routes, Route, Navigate } from 'react-router-dom'; 5 | import { HeaderBar, NavBar, NotFound } from './components'; 6 | import About from './About'; 7 | 8 | const Products = lazy(() => import(/* webpackChunkName: "products" */ './products/Products')); 9 | 10 | class App extends Component { 11 | render() { 12 | return ( 13 |
14 | 15 |
16 | 17 |
18 | Loading...
}> 19 | 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | 25 | 26 | 27 |
28 | 29 | ); 30 | } 31 | } 32 | export default App; 33 | -------------------------------------------------------------------------------- /react-app-new/src/components/ButtonFooter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ButtonFooter = ({ 4 | label, 5 | className, 6 | iconClasses, 7 | onClick, 8 | dataIndex, 9 | dataId, 10 | }) => { 11 | return ( 12 | 23 | ); 24 | }; 25 | 26 | export default ButtonFooter; 27 | -------------------------------------------------------------------------------- /react-app-new/src/components/CardContent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CardContent = ({ name, description }) => ( 4 |
5 |
6 |
{name}
7 |
{description}
8 |
9 |
10 | ); 11 | 12 | export default CardContent; 13 | -------------------------------------------------------------------------------- /react-app-new/src/components/HeaderBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeaderBarBrand from './HeaderBarBrand'; 3 | 4 | const HeaderBar = () => ( 5 |
6 | 13 |
14 | ); 15 | 16 | export default HeaderBar; 17 | -------------------------------------------------------------------------------- /react-app-new/src/components/HeaderBarBrand.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | 4 | const HeaderBarBrand = () => ( 5 |
6 | 12 |