├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── ask-a-question.md │ └── bug_report.md ├── dependabot.yml ├── policies │ └── resourceManagement.yml └── workflows │ ├── auto-merge-dependabot.yml │ └── node.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── graph-tutorial ├── .gitignore ├── package.json ├── public │ ├── favicon.ico │ ├── images │ │ └── no-profile-photo.png │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── AppContext.tsx │ ├── Calendar.css │ ├── Calendar.tsx │ ├── CalendarDayRow.tsx │ ├── Config.example.ts │ ├── ErrorMessage.tsx │ ├── GraphService.ts │ ├── NavBar.tsx │ ├── NewEvent.tsx │ ├── Welcome.tsx │ ├── __mocks__ │ │ └── mockCss.js │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts ├── tsconfig.json └── yarn.lock └── qs.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Denote all files that are truly binary and should not be modified. 5 | *.png binary 6 | *.jpg binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Ask a question about Graph, adding features to this sample, etc. 4 | title: '' 5 | labels: question, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | Thank you for taking an interest in Microsoft Graph development! Please feel free to ask a question here, but keep in mind the following: 11 | 12 | - This is not an official Microsoft support channel, and our ability to respond to questions here is limited. Questions about Graph, or questions about adding a new feature to the sample, will be answered on a best-effort basis. 13 | - Questions should be asked on [Microsoft Q&A](https://docs.microsoft.com/answers/products/graph). 14 | - Issues with Microsoft Graph itself should be handled through [support](https://developer.microsoft.com/graph/support). 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug report, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Dependency versions** 32 | - Authentication library (MSAL, etc.) version: 33 | - Graph library (Graph SDK, REST library, etc.) version: 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" # See documentation for possible values 4 | directory: "/graph-tutorial/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/policies/resourceManagement.yml: -------------------------------------------------------------------------------- 1 | id: 2 | name: GitOps.PullRequestIssueManagement 3 | description: GitOps.PullRequestIssueManagement primitive 4 | owner: 5 | resource: repository 6 | disabled: false 7 | where: 8 | configuration: 9 | resourceManagementConfiguration: 10 | scheduledSearches: 11 | - description: 12 | frequencies: 13 | - hourly: 14 | hour: 6 15 | filters: 16 | - isIssue 17 | - isOpen 18 | - hasLabel: 19 | label: needs author feedback 20 | - hasLabel: 21 | label: no recent activity 22 | - noActivitySince: 23 | days: 3 24 | actions: 25 | - closeIssue 26 | - description: 27 | frequencies: 28 | - hourly: 29 | hour: 6 30 | filters: 31 | - isIssue 32 | - isOpen 33 | - hasLabel: 34 | label: needs author feedback 35 | - noActivitySince: 36 | days: 4 37 | - isNotLabeledWith: 38 | label: no recent activity 39 | actions: 40 | - addLabel: 41 | label: no recent activity 42 | - addReply: 43 | reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**. 44 | - description: 45 | frequencies: 46 | - hourly: 47 | hour: 6 48 | filters: 49 | - isIssue 50 | - isOpen 51 | - hasLabel: 52 | label: duplicate 53 | - noActivitySince: 54 | days: 1 55 | actions: 56 | - addReply: 57 | reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. 58 | - closeIssue 59 | - description: 60 | frequencies: 61 | - hourly: 62 | hour: 3 63 | filters: 64 | - isOpen 65 | - isIssue 66 | - hasLabel: 67 | label: graph question 68 | actions: 69 | - removeLabel: 70 | label: 'needs attention :wave:' 71 | - removeLabel: 72 | label: needs author feedback 73 | - removeLabel: 74 | label: 'needs triage :mag:' 75 | - removeLabel: 76 | label: no recent activity 77 | - addLabel: 78 | label: out of scope 79 | - addReply: 80 | reply: >- 81 | It looks like you are asking a question about using Microsoft Graph or one of the Microsoft Graph SDKs that is not directly related to this sample. Unfortunately we are not set up to answer general questions in this repository, so this issue will be closed. 82 | 83 | 84 | Please try asking your question on [Microsoft Q&A](https://learn.microsoft.com/answers/tags/161/ms-graph), tagging your question with `Microsoft Graph`. 85 | - closeIssue 86 | - description: 87 | frequencies: 88 | - hourly: 89 | hour: 3 90 | filters: 91 | - isOpen 92 | - isIssue 93 | - hasLabel: 94 | label: graph issue 95 | actions: 96 | - removeLabel: 97 | label: 'needs attention :wave:' 98 | - removeLabel: 99 | label: needs author feedback 100 | - removeLabel: 101 | label: 'needs triage :mag:' 102 | - removeLabel: 103 | label: no recent activity 104 | - addLabel: 105 | label: out of scope 106 | - addReply: 107 | reply: >- 108 | It looks like you are reporting an issue with Microsoft Graph or one of the Microsoft Graph SDKs that is not fixable by changing code in this sample. Unfortunately we are not set up to provide product support in this repository, so this issue will be closed. 109 | 110 | 111 | Please visit one of the following links to report your issue. 112 | 113 | 114 | - Issue with Microsoft Graph service: [Microsoft Graph support](https://developer.microsoft.com/graph/support#report-issues-with-the-service), choose one of the options under **Report issues with the service** 115 | 116 | - Issue with a Microsoft Graph SDK: Open an issue in the SDK's GitHub repository. See [microsoftgraph on GitHub](https://github.com/microsoftgraph?q=sdk+in%3Aname&type=public&language=) for a list of SDK repositories. 117 | - closeIssue 118 | eventResponderTasks: 119 | - if: 120 | - payloadType: Issue_Comment 121 | - isAction: 122 | action: Created 123 | - isActivitySender: 124 | issueAuthor: True 125 | - hasLabel: 126 | label: needs author feedback 127 | - isOpen 128 | then: 129 | - addLabel: 130 | label: 'needs attention :wave:' 131 | - removeLabel: 132 | label: needs author feedback 133 | description: 134 | - if: 135 | - payloadType: Issues 136 | - not: 137 | isAction: 138 | action: Closed 139 | - hasLabel: 140 | label: no recent activity 141 | then: 142 | - removeLabel: 143 | label: no recent activity 144 | description: 145 | - if: 146 | - payloadType: Issue_Comment 147 | - hasLabel: 148 | label: no recent activity 149 | then: 150 | - removeLabel: 151 | label: no recent activity 152 | description: 153 | - if: 154 | - payloadType: Pull_Request 155 | then: 156 | - inPrLabel: 157 | label: in pr 158 | description: 159 | onFailure: 160 | onSuccess: 161 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-merge dependabot updates 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | 11 | jobs: 12 | 13 | dependabot-merge: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | if: ${{ github.actor == 'dependabot[bot]' }} 18 | 19 | steps: 20 | - name: Dependabot metadata 21 | id: metadata 22 | uses: dependabot/fetch-metadata@v2.3.0 23 | with: 24 | github-token: "${{ secrets.GITHUB_TOKEN }}" 25 | 26 | - name: Enable auto-merge for Dependabot PRs 27 | # Only if version bump is not a major version change 28 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} 29 | run: gh pr merge --auto --merge "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - demo/graph-tutorial/** 9 | pull_request: 10 | branches: 11 | - main 12 | - live 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | defaults: 18 | run: 19 | working-directory: graph-tutorial 20 | 21 | name: Build and test 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | matrix: 26 | node-version: [18.x, 20.x] 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Copy settings file 31 | run: | 32 | cp ./src/Config.example.ts ./src/Config.ts 33 | - name: Verify npm all runs and build output matches 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | - run: yarn install 38 | - run: yarn run build 39 | - run: yarn run test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # VS Code settings 64 | .vscode/ 65 | 66 | Config.ts 67 | -------------------------------------------------------------------------------- /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 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Microsoft Graph 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | description: This sample demonstrates how to use the Microsoft Graph JavaScript SDK to access data in Office 365 from React browser apps. 4 | products: 5 | - ms-graph 6 | - office-exchange-online 7 | languages: 8 | - typescript 9 | - nodejs 10 | --- 11 | 12 | # Microsoft Graph sample React single-page app 13 | 14 | This sample demonstrates how to use the Microsoft Graph JavaScript SDK to access data in Office 365 from React browser apps. 15 | 16 | ## Prerequisites 17 | 18 | Before you start this tutorial, you should have [Node.js](https://nodejs.org) and [Yarn](https://classic.yarnpkg.com/) installed on your development machine. If you do not have Node.js or Yarn, visit the previous links for download options. 19 | 20 | You should also have either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account. If you don't have a Microsoft account, there are a couple of options to get a free account: 21 | 22 | - You can [sign up for a new personal Microsoft account](https://signup.live.com/signup?wa=wsignin1.0&rpsnv=12&ct=1454618383&rver=6.4.6456.0&wp=MBI_SSL_SHARED&wreply=https://mail.live.com/default.aspx&id=64855&cbcxt=mai&bk=1454618383&uiflavor=web&uaid=b213a65b4fdc484382b6622b3ecaa547&mkt=E-US&lc=1033&lic=1). 23 | - You can [sign up for the Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program) to get a free Microsoft 365 subscription. 24 | 25 | ## Register a web application with the Azure Active Directory admin center 26 | 27 | 1. Open a browser and navigate to the Azure Active Directory admin center. Login using a **personal account** (aka: Microsoft Account) or **Work or School Account**. 28 | 29 | 1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**. 30 | 31 | > **Note:** Azure AD B2C users may only see **App registrations (legacy)**. In this case, please go directly to [https://aka.ms/appregistrations](https://aka.ms/appregistrations). 32 | 33 | 1. Select **New registration**. On the **Register an application** page, set the values as follows. 34 | 35 | - Set **Name** to `React Graph Tutorial`. 36 | - Set **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts**. 37 | - Under **Redirect URI**, set the first drop-down to `Single-page application (SPA)` and set the value to `http://localhost:3000`. 38 | 39 | 1. Choose **Register**. On the **React Graph Tutorial** page, copy the value of the **Application (client) ID** and save it, you will need it in the next step. 40 | 41 | ## Configure the sample 42 | 43 | 1. Rename the `./graph-tutorial/src/Config.example.ts` file to `./graph-tutorial/src/Config.ts`. 44 | 1. Edit the `./graph-tutorial/src/Config.ts` file and make the following changes. 45 | 1. Replace `YOUR_APP_ID_HERE` with the **Application Id** you got from the App Registration Portal. 46 | 1. In your command-line interface (CLI), navigate to the `graph-tutorial` directory and run the following command to install requirements. 47 | 48 | ```Shell 49 | yarn install 50 | ``` 51 | 52 | ## Run the sample 53 | 54 | 1. Run the following command in your CLI to start the application. 55 | 56 | ```Shell 57 | yarn start 58 | ``` 59 | 60 | 1. Open a browser and browse to `http://localhost:3000`. 61 | 62 | ## Code of conduct 63 | 64 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 65 | 66 | ## Disclaimer 67 | 68 | **THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** 69 | -------------------------------------------------------------------------------- /graph-tutorial/.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 | -------------------------------------------------------------------------------- /graph-tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graph-tutorial", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@azure/msal-browser": "^3.24.0", 7 | "@azure/msal-react": "^2.0.22", 8 | "@microsoft/mgt-element": "^3.1.3", 9 | "@microsoft/microsoft-graph-client": "^3.0.7", 10 | "bootstrap": "^5.3.3", 11 | "date-fns": "4.1.0", 12 | "date-fns-tz": "3.2.0", 13 | "react": "^18.3.1", 14 | "react-bootstrap": "^2.10.4", 15 | "react-dom": "^18.3.1", 16 | "react-router-dom": "^6.26.1", 17 | "react-scripts": "5.0.1", 18 | "web-vitals": "^3.5.2", 19 | "windows-iana": "^5.1.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 47 | "@microsoft/microsoft-graph-types": "^2.40.0", 48 | "@testing-library/jest-dom": "^6.5.0", 49 | "@testing-library/react": "^14.3.0", 50 | "@testing-library/user-event": "^14.5.2", 51 | "@types/jest": "^29.5.11", 52 | "@types/node": "^20.14.2", 53 | "@types/react": "^18.3.6", 54 | "@types/react-dom": "^18.3.0", 55 | "@types/react-router-dom": "^5.3.3", 56 | "typescript": "^5.5.2" 57 | }, 58 | "jest": { 59 | "transformIgnorePatterns": [], 60 | "moduleNameMapper": { 61 | "\\.(css)$": "/src/__mocks__/mockCss.js" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /graph-tutorial/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-reactspa/4604c53c6b01da30218bb51e73f917d909426cd2/graph-tutorial/public/favicon.ico -------------------------------------------------------------------------------- /graph-tutorial/public/images/no-profile-photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-reactspa/4604c53c6b01da30218bb51e73f917d909426cd2/graph-tutorial/public/images/no-profile-photo.png -------------------------------------------------------------------------------- /graph-tutorial/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /graph-tutorial/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-reactspa/4604c53c6b01da30218bb51e73f917d909426cd2/graph-tutorial/public/logo192.png -------------------------------------------------------------------------------- /graph-tutorial/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/msgraph-sample-reactspa/4604c53c6b01da30218bb51e73f917d909426cd2/graph-tutorial/public/logo512.png -------------------------------------------------------------------------------- /graph-tutorial/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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /graph-tutorial/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /graph-tutorial/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /graph-tutorial/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import React from 'react'; 5 | import { act, render, screen } from '@testing-library/react'; 6 | import '@testing-library/jest-dom'; 7 | import { PublicClientApplication } from '@azure/msal-browser'; 8 | import App from './App'; 9 | 10 | describe('Basic render tests', () => { 11 | let pca: PublicClientApplication; 12 | 13 | beforeEach(() => { 14 | pca = new PublicClientApplication({ 15 | auth: { 16 | clientId: '' 17 | } 18 | }); 19 | 20 | jest.spyOn(pca, 'addEventCallback').mockImplementation((fn) => { 21 | return ''; 22 | }); 23 | }); 24 | 25 | afterEach(() => { 26 | jest.clearAllMocks(); 27 | }); 28 | 29 | test('renders welcome page', async () => { 30 | await act(async () => { // eslint-disable-line 31 | render(); 32 | }); 33 | 34 | const titleElement = screen.getByRole('heading'); 35 | expect(titleElement).toBeInTheDocument(); 36 | expect(titleElement).toHaveTextContent(/React Graph Tutorial/i); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /graph-tutorial/src/App.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 5 | import { Container } from 'react-bootstrap'; 6 | import { MsalProvider } from '@azure/msal-react' 7 | import { IPublicClientApplication } from '@azure/msal-browser'; 8 | 9 | import ProvideAppContext from './AppContext'; 10 | import ErrorMessage from './ErrorMessage'; 11 | import NavBar from './NavBar'; 12 | import Welcome from './Welcome'; 13 | import Calendar from './Calendar'; 14 | import NewEvent from './NewEvent'; 15 | import 'bootstrap/dist/css/bootstrap.css'; 16 | 17 | // 18 | type AppProps = { 19 | pca: IPublicClientApplication 20 | }; 21 | // 22 | 23 | export default function App({ pca }: AppProps): JSX.Element { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | } /> 36 | 39 | } /> 40 | 43 | } /> 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /graph-tutorial/src/AppContext.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import React, { 5 | useContext, 6 | createContext, 7 | useState, 8 | MouseEventHandler, 9 | useEffect 10 | } from 'react'; 11 | import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser'; 12 | import { InteractionType, PublicClientApplication } from '@azure/msal-browser'; 13 | import { useMsal } from '@azure/msal-react'; 14 | 15 | import { getUser } from './GraphService'; 16 | import config from './Config'; 17 | 18 | // 19 | export interface AppUser { 20 | displayName?: string, 21 | email?: string, 22 | avatar?: string, 23 | timeZone?: string, 24 | timeFormat?: string 25 | }; 26 | 27 | export interface AppError { 28 | message: string, 29 | debug?: string 30 | }; 31 | 32 | type AppContext = { 33 | user?: AppUser; 34 | error?: AppError; 35 | signIn?: MouseEventHandler; 36 | signOut?: MouseEventHandler; 37 | displayError?: Function; 38 | clearError?: Function; 39 | authProvider?: AuthCodeMSALBrowserAuthenticationProvider; 40 | } 41 | 42 | const appContext = createContext({ 43 | user: undefined, 44 | error: undefined, 45 | signIn: undefined, 46 | signOut: undefined, 47 | displayError: undefined, 48 | clearError: undefined, 49 | authProvider: undefined 50 | }); 51 | 52 | export function useAppContext(): AppContext { 53 | return useContext(appContext); 54 | } 55 | 56 | interface ProvideAppContextProps { 57 | children: React.ReactNode; 58 | } 59 | 60 | export default function ProvideAppContext({ children }: ProvideAppContextProps) { 61 | const auth = useProvideAppContext(); 62 | return ( 63 | 64 | {children} 65 | 66 | ); 67 | } 68 | // 69 | 70 | function useProvideAppContext() { 71 | const msal = useMsal(); 72 | const [user, setUser] = useState(undefined); 73 | const [error, setError] = useState(undefined); 74 | 75 | const displayError = (message: string, debug?: string) => { 76 | setError({ message, debug }); 77 | } 78 | 79 | const clearError = () => { 80 | setError(undefined); 81 | } 82 | 83 | // 84 | // Used by the Graph SDK to authenticate API calls 85 | const authProvider = new AuthCodeMSALBrowserAuthenticationProvider( 86 | msal.instance as PublicClientApplication, 87 | { 88 | account: msal.instance.getActiveAccount()!, 89 | scopes: config.scopes, 90 | interactionType: InteractionType.Popup 91 | } 92 | ); 93 | // 94 | 95 | // 96 | useEffect(() => { 97 | const checkUser = async () => { 98 | if (!user) { 99 | try { 100 | // Check if user is already signed in 101 | const account = msal.instance.getActiveAccount(); 102 | if (account) { 103 | // Get the user from Microsoft Graph 104 | const user = await getUser(authProvider); 105 | 106 | setUser({ 107 | displayName: user.displayName || '', 108 | email: user.mail || user.userPrincipalName || '', 109 | timeFormat: user.mailboxSettings?.timeFormat || 'h:mm a', 110 | timeZone: user.mailboxSettings?.timeZone || 'UTC' 111 | }); 112 | } 113 | } catch (err: any) { 114 | displayError(err.message); 115 | } 116 | } 117 | }; 118 | checkUser(); 119 | }); 120 | // 121 | 122 | // 123 | const signIn = async () => { 124 | await msal.instance.loginPopup({ 125 | scopes: config.scopes, 126 | prompt: 'select_account' 127 | }); 128 | 129 | // Get the user from Microsoft Graph 130 | const user = await getUser(authProvider); 131 | 132 | setUser({ 133 | displayName: user.displayName || '', 134 | email: user.mail || user.userPrincipalName || '', 135 | timeFormat: user.mailboxSettings?.timeFormat || '', 136 | timeZone: user.mailboxSettings?.timeZone || 'UTC' 137 | }); 138 | }; 139 | // 140 | 141 | // 142 | const signOut = async () => { 143 | await msal.instance.logoutPopup(); 144 | setUser(undefined); 145 | }; 146 | // 147 | 148 | return { 149 | user, 150 | error, 151 | signIn, 152 | signOut, 153 | displayError, 154 | clearError, 155 | authProvider 156 | }; 157 | } 158 | -------------------------------------------------------------------------------- /graph-tutorial/src/Calendar.css: -------------------------------------------------------------------------------- 1 | .calendar-view-date-cell { 2 | width: 150px; 3 | } 4 | 5 | .calendar-view-date { 6 | width: 40px; 7 | font-size: 36px; 8 | line-height: 36px; 9 | margin-right: 10px; 10 | } 11 | 12 | .calendar-view-month { 13 | font-size: 0.75em; 14 | } 15 | 16 | .calendar-view-timespan { 17 | width: 200px; 18 | } 19 | 20 | .calendar-view-subject { 21 | font-size: 1.25em; 22 | } 23 | 24 | .calendar-view-organizer { 25 | font-size: .75em; 26 | } 27 | -------------------------------------------------------------------------------- /graph-tutorial/src/Calendar.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { useEffect, useState } from 'react'; 5 | import { NavLink as RouterNavLink } from 'react-router-dom'; 6 | import { Table } from 'react-bootstrap'; 7 | import { findIana } from 'windows-iana'; 8 | import { Event } from '@microsoft/microsoft-graph-types'; 9 | import { AuthenticatedTemplate } from '@azure/msal-react'; 10 | import { add, endOfWeek, format, getDay, parseISO, startOfWeek } from 'date-fns'; 11 | 12 | import { getUserWeekCalendar } from './GraphService'; 13 | import { useAppContext } from './AppContext'; 14 | import CalendarDayRow from './CalendarDayRow'; 15 | import './Calendar.css'; 16 | 17 | export default function Calendar() { 18 | const app = useAppContext(); 19 | 20 | const [events, setEvents] = useState(); 21 | 22 | useEffect(() => { 23 | const loadEvents = async () => { 24 | if (app.user && !events) { 25 | try { 26 | const ianaTimeZones = findIana(app.user?.timeZone!); 27 | const events = await getUserWeekCalendar(app.authProvider!, ianaTimeZones[0].valueOf()); 28 | setEvents(events); 29 | } catch (err) { 30 | const error = err as Error; 31 | app.displayError!(error.message); 32 | } 33 | } 34 | }; 35 | 36 | loadEvents(); 37 | }); 38 | 39 | // 40 | const weekStart = startOfWeek(new Date()); 41 | const weekEnd = endOfWeek(weekStart); 42 | 43 | return ( 44 | 45 |
46 |

{format(weekStart, 'MMMM d, yyyy')} - {format(weekEnd, 'MMMM d, yyyy')}

47 | New event 48 |
49 |
50 |
51 | {events && 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | getDay(parseISO(event.start?.dateTime!)) === 0)} /> 64 | getDay(parseISO(event.start?.dateTime!)) === 1)} /> 68 | getDay(parseISO(event.start?.dateTime!)) === 2)} /> 72 | getDay(parseISO(event.start?.dateTime!)) === 3)} /> 76 | getDay(parseISO(event.start?.dateTime!)) === 4)} /> 80 | getDay(parseISO(event.start?.dateTime!)) === 5)} /> 84 | getDay(parseISO(event.start?.dateTime!)) === 6)} /> 88 | 89 |
DateTimeEvent
} 90 |
91 |
92 |
93 | ); 94 | //
95 | } 96 | -------------------------------------------------------------------------------- /graph-tutorial/src/CalendarDayRow.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import React from 'react'; 6 | import { Event } from '@microsoft/microsoft-graph-types'; 7 | import { format, parseISO } from 'date-fns'; 8 | 9 | type CalendarDayRowProps = { 10 | date: Date, 11 | timeFormat: string, 12 | events: Event[] 13 | }; 14 | 15 | interface FormatMap { 16 | [key: string]: string; 17 | } 18 | 19 | // date-fns format strings are slightly 20 | // different than the ones returned by Graph 21 | const formatMap: FormatMap = { 22 | "h:mm tt": "h:mm a", 23 | "hh:mm tt": "hh:mm a" 24 | }; 25 | 26 | // Helper function to format Graph date/time in the user's 27 | // preferred format 28 | function formatDateTime(dateTime: string | undefined, timeFormat: string) { 29 | if (dateTime !== undefined) { 30 | const parsedDate = parseISO(dateTime); 31 | return format(parsedDate, formatMap[timeFormat] || timeFormat); 32 | } 33 | } 34 | 35 | function DateCell(props: CalendarDayRowProps) { 36 | return ( 37 | 38 |
{format(props.date, 'dd')}
39 |
{format(props.date, 'EEEE')}
40 |
{format(props.date, 'MMMM, yyyy')}
41 | 42 | ); 43 | } 44 | 45 | export default function CalendarDayRow(props: CalendarDayRowProps) { 46 | const today = new Date(); 47 | const rowClass = today.getDay() === props.date.getDay() ? 'table-warning' : ''; 48 | 49 | if (props.events.length <= 0) { 50 | return ( 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | 59 | return ( 60 | 61 | {props.events.map( 62 | function (event: Event, index: Number) { 63 | return ( 64 | 65 | {index === 0 && } 66 | 67 |
{formatDateTime(event.start?.dateTime, props.timeFormat)} - {formatDateTime(event.end?.dateTime, props.timeFormat)}
68 | 69 | 70 |
{event.subject}
71 |
{event.organizer?.emailAddress?.name}
72 | 73 | 74 | ) 75 | } 76 | )} 77 |
78 | ); 79 | } 80 | //
81 | -------------------------------------------------------------------------------- /graph-tutorial/src/Config.example.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const config = { 5 | appId: 'YOUR_APP_ID_HERE', 6 | redirectUri: 'http://localhost:3000', 7 | scopes: [ 8 | 'user.read', 9 | 'mailboxsettings.read', 10 | 'calendars.readwrite' 11 | ] 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /graph-tutorial/src/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { Alert } from 'react-bootstrap'; 6 | import { useAppContext } from './AppContext'; 7 | 8 | export default function ErrorMessage() { 9 | const app = useAppContext(); 10 | 11 | if (app.error) { 12 | return ( 13 | app.clearError!()}> 14 |

{app.error.message}

15 | {app.error.debug ? 16 |
{app.error.debug}
17 | : null 18 | } 19 |
20 | ); 21 | } 22 | 23 | return null; 24 | } 25 | //
26 | -------------------------------------------------------------------------------- /graph-tutorial/src/GraphService.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { Client, GraphRequestOptions, PageCollection, PageIterator } from '@microsoft/microsoft-graph-client'; 6 | import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser'; 7 | import { endOfWeek, startOfWeek } from 'date-fns'; 8 | import { fromZonedTime } from 'date-fns-tz'; 9 | import { User, Event } from '@microsoft/microsoft-graph-types'; 10 | 11 | let graphClient: Client | undefined = undefined; 12 | 13 | function ensureClient(authProvider: AuthCodeMSALBrowserAuthenticationProvider) { 14 | if (!graphClient) { 15 | graphClient = Client.initWithMiddleware({ 16 | authProvider: authProvider 17 | }); 18 | } 19 | 20 | return graphClient; 21 | } 22 | 23 | export async function getUser(authProvider: AuthCodeMSALBrowserAuthenticationProvider): Promise { 24 | ensureClient(authProvider); 25 | 26 | // Return the /me API endpoint result as a User object 27 | const user: User = await graphClient!.api('/me') 28 | // Only retrieve the specific fields needed 29 | .select('displayName,mail,mailboxSettings,userPrincipalName') 30 | .get(); 31 | 32 | return user; 33 | } 34 | // 35 | 36 | // 37 | export async function getUserWeekCalendar(authProvider: AuthCodeMSALBrowserAuthenticationProvider, 38 | timeZone: string): Promise { 39 | ensureClient(authProvider); 40 | 41 | // Generate startDateTime and endDateTime query params 42 | // to display a 7-day window 43 | const now = new Date(); 44 | const startDateTime = fromZonedTime(startOfWeek(now), timeZone).toISOString(); 45 | const endDateTime = fromZonedTime(endOfWeek(now), timeZone).toISOString(); 46 | 47 | // GET /me/calendarview?startDateTime=''&endDateTime='' 48 | // &$select=subject,organizer,start,end 49 | // &$orderby=start/dateTime 50 | // &$top=50 51 | var response: PageCollection = await graphClient! 52 | .api('/me/calendarview') 53 | .header('Prefer', `outlook.timezone="${timeZone}"`) 54 | .query({ startDateTime: startDateTime, endDateTime: endDateTime }) 55 | .select('subject,organizer,start,end') 56 | .orderby('start/dateTime') 57 | .top(25) 58 | .get(); 59 | 60 | if (response["@odata.nextLink"]) { 61 | // Presence of the nextLink property indicates more results are available 62 | // Use a page iterator to get all results 63 | var events: Event[] = []; 64 | 65 | // Must include the time zone header in page 66 | // requests too 67 | var options: GraphRequestOptions = { 68 | headers: { 'Prefer': `outlook.timezone="${timeZone}"` } 69 | }; 70 | 71 | var pageIterator = new PageIterator(graphClient!, response, (event) => { 72 | events.push(event); 73 | return true; 74 | }, options); 75 | 76 | await pageIterator.iterate(); 77 | 78 | return events; 79 | } else { 80 | 81 | return response.value; 82 | } 83 | } 84 | // 85 | 86 | // 87 | export async function createEvent(authProvider: AuthCodeMSALBrowserAuthenticationProvider, 88 | newEvent: Event): Promise { 89 | ensureClient(authProvider); 90 | 91 | // POST /me/events 92 | // JSON representation of the new event is sent in the 93 | // request body 94 | return await graphClient! 95 | .api('/me/events') 96 | .post(newEvent); 97 | } 98 | // 99 | -------------------------------------------------------------------------------- /graph-tutorial/src/NavBar.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { NavLink as RouterNavLink } from 'react-router-dom'; 6 | import { 7 | Container, 8 | Dropdown, 9 | Navbar, 10 | Nav, 11 | NavDropdown, 12 | NavItem 13 | } from 'react-bootstrap'; 14 | import { AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react'; 15 | 16 | import { AppUser, useAppContext } from './AppContext'; 17 | 18 | interface UserAvatarProps { 19 | user: AppUser 20 | }; 21 | 22 | function UserAvatar(props: UserAvatarProps) { 23 | // If a user avatar is available, return an img tag with the pic 24 | return user; 28 | } 29 | 30 | export default function NavBar() { 31 | const app = useAppContext(); 32 | const user = app.user || { displayName: '', email: '' }; 33 | 34 | return ( 35 | 36 | 37 | React Graph Tutorial 38 | 39 | 40 | 50 | 71 | 72 | 73 | 74 | ); 75 | } 76 | // 77 | -------------------------------------------------------------------------------- /graph-tutorial/src/NewEvent.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { useEffect, useState } from 'react'; 6 | import { NavLink as RouterNavLink, Navigate } from 'react-router-dom'; 7 | import { Button, Col, Form, Row } from 'react-bootstrap'; 8 | import { Attendee, Event } from '@microsoft/microsoft-graph-types'; 9 | 10 | import { createEvent } from './GraphService'; 11 | import { useAppContext } from './AppContext'; 12 | 13 | 14 | export default function NewEvent() { 15 | const app = useAppContext(); 16 | 17 | const [subject, setSubject] = useState(''); 18 | const [attendees, setAttendees] = useState(''); 19 | const [start, setStart] = useState(''); 20 | const [end, setEnd] = useState(''); 21 | const [body, setBody] = useState(''); 22 | const [formDisabled, setFormDisabled] = useState(true); 23 | const [redirect, setRedirect] = useState(false); 24 | 25 | useEffect(() => { 26 | setFormDisabled( 27 | subject.length === 0 || 28 | start.length === 0 || 29 | end.length === 0); 30 | }, [subject, start, end]); 31 | 32 | const doCreate = async () => { 33 | const attendeeEmails = attendees.split(';'); 34 | const attendeeArray: Attendee[] = []; 35 | 36 | attendeeEmails.forEach((email) => { 37 | if (email.length > 0) { 38 | attendeeArray.push({ 39 | emailAddress: { 40 | address: email 41 | } 42 | }); 43 | } 44 | }); 45 | 46 | const newEvent: Event = { 47 | subject: subject, 48 | // Only add if there are attendees 49 | attendees: attendeeArray.length > 0 ? attendeeArray : undefined, 50 | // Specify the user's time zone so 51 | // the start and end are set correctly 52 | start: { 53 | dateTime: start, 54 | timeZone: app.user?.timeZone 55 | }, 56 | end: { 57 | dateTime: end, 58 | timeZone: app.user?.timeZone 59 | }, 60 | // Only add if a body was given 61 | body: body.length > 0 ? { 62 | contentType: 'text', 63 | content: body 64 | } : undefined 65 | }; 66 | 67 | try { 68 | await createEvent(app.authProvider!, newEvent); 69 | setRedirect(true); 70 | } catch (err) { 71 | app.displayError!('Error creating event', JSON.stringify(err)); 72 | } 73 | }; 74 | 75 | if (redirect) { 76 | return 77 | } 78 | return ( 79 |
80 | 81 | Subject 82 | setSubject(ev.target.value)} /> 88 | 89 | 90 | Attendees 91 | setAttendees(ev.target.value)} /> 98 | 99 | 100 | 101 | 102 | Start 103 | setStart(ev.target.value)} /> 108 | 109 | 110 | 111 | 112 | End 113 | setEnd(ev.target.value)} /> 118 | 119 | 120 | 121 | 122 | Body 123 | setBody(ev.target.value)} /> 130 | 131 | 135 | Cancel 138 |
139 | ); 140 | } 141 | //
142 | -------------------------------------------------------------------------------- /graph-tutorial/src/Welcome.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { 6 | Button, 7 | Container 8 | } from 'react-bootstrap'; 9 | import { AuthenticatedTemplate, UnauthenticatedTemplate } from '@azure/msal-react'; 10 | import { useAppContext } from './AppContext'; 11 | 12 | export default function Welcome() { 13 | const app = useAppContext(); 14 | 15 | return ( 16 |
17 | 18 |

React Graph Tutorial

19 |

20 | This sample app shows how to use the Microsoft Graph API to access a user's data from React 21 |

22 | 23 |
24 |

Welcome {app.user?.displayName || ''}!

25 |

Use the navigation bar at the top of the page to get started.

26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | ); 34 | } 35 | //
36 | -------------------------------------------------------------------------------- /graph-tutorial/src/__mocks__/mockCss.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /graph-tutorial/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 4.5rem; 3 | } 4 | 5 | .alert-pre { 6 | word-wrap: break-word; 7 | word-break: break-all; 8 | white-space: pre-wrap; 9 | } 10 | -------------------------------------------------------------------------------- /graph-tutorial/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom/client'; 6 | 7 | import { 8 | PublicClientApplication, 9 | EventType, 10 | EventMessage, 11 | AuthenticationResult 12 | } from '@azure/msal-browser'; 13 | 14 | import config from './Config'; 15 | import './index.css'; 16 | import App from './App'; 17 | import reportWebVitals from './reportWebVitals'; 18 | 19 | // 20 | const msalInstance = new PublicClientApplication({ 21 | auth: { 22 | clientId: config.appId, 23 | redirectUri: config.redirectUri 24 | }, 25 | cache: { 26 | cacheLocation: 'sessionStorage', 27 | storeAuthStateInCookie: true 28 | } 29 | }); 30 | 31 | // Check if there are already accounts in the browser session 32 | // If so, set the first account as the active account 33 | const accounts = msalInstance.getAllAccounts(); 34 | if (accounts && accounts.length > 0) { 35 | msalInstance.setActiveAccount(accounts[0]); 36 | } 37 | 38 | msalInstance.addEventCallback((event: EventMessage) => { 39 | if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { 40 | // Set the active account - this simplifies token acquisition 41 | const authResult = event.payload as AuthenticationResult; 42 | msalInstance.setActiveAccount(authResult.account); 43 | } 44 | }); 45 | // 46 | 47 | const root = ReactDOM.createRoot( 48 | document.getElementById('root') as HTMLElement 49 | ); 50 | 51 | root.render( 52 | 53 | 54 | 55 | ); 56 | 57 | // If you want to start measuring performance in your app, pass a function 58 | // to log results (for example: reportWebVitals(console.log)) 59 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 60 | reportWebVitals(); 61 | -------------------------------------------------------------------------------- /graph-tutorial/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /graph-tutorial/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /graph-tutorial/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /graph-tutorial/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | 7 | global.crypto = require('crypto'); 8 | -------------------------------------------------------------------------------- /graph-tutorial/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /qs.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceDirectory": "./demo", 3 | "exampleConfigFile": "./graph-tutorial/src/Config.example.ts", 4 | "configFile": "Config.ts", 5 | "archiveFile": "ReactQuickStart.zip", 6 | "zipReadMe": "./README.md" 7 | } 8 | --------------------------------------------------------------------------------