Private
Public Access
1
0

feat: integrate Microsoft Power Apps SDK and enhance email handling

- Added dependency for @microsoft/power-apps to package.json.
- Updated power.config.json to include appId, environmentId, and connection references.
- Implemented sanitisation function for HTML content in App.tsx to prevent XSS.
- Enhanced error handling in email loading and marking as read functionalities.
- Updated email display logic to handle HTML content and previews.
- Refactored OutlookService to use auto-generated service from @microsoft/power-apps.
- Added new methods for sending, marking as read, and deleting emails in OutlookService.
- Updated types for Email to include bodyPreview, isHtml, hasAttachments, and importance.
- Configured Vite to exclude @microsoft/power-apps/data from the build.
- Created .gitignore to exclude build artifacts and environment files.
- Added local development shim for @microsoft/power-apps/data to support local testing.
- Defined type stubs for @microsoft/power-apps/data to facilitate local development.
This commit is contained in:
Lago
2026-04-16 22:42:40 +02:00
parent 96f76e21a7
commit 0a1d96a40f
35 changed files with 2558 additions and 57689 deletions

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# dependencies
node_modules/
# build output
dist/
# auto-generated Power Platform schemas (contain environment-specific URLs)
.power/
# editor
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# env
.env
.env.*

238
README.md
View File

@@ -2,22 +2,22 @@
> Full email client in a code app, built with React + Fluent UI v9, powered by the Outlook connector.
This is the companion repo for [Part 2 of the blog series](https://www.thatsagoodquestion.info/power-apps-code-apps). Make sure you've completed Part 1 before continuing.
This is the companion repo for [Part 2 of the blog series](https://www.thatsagoodquestion.info/power-apps-code-apps). Make sure you have completed Part 1 before continuing.
---
## What you'll build
## What you will build
An Outlook-like email client with:
- **Folder navigation** — Inbox, Sent, Drafts, Trash
- **Email list** — unread indicators, avatar colors, relative timestamps
- **Reading pane** — full email body with Reply / Forward / Delete
- **Reading pane** — full email body (HTML-safe) with Reply / Forward / Delete
- **Compose dialog** — Fluent UI v9 dialog with validation
- **Connections panel** — visual reference of connectors + PAC CLI commands
- **Real-time search** — filter messages instantly by sender or subject
- **Real-time search** — filter by sender or subject
All built with **Fluent UI v9** (`@fluentui/react-components`) and the **Outlook connector** — the same connector available to 1,400+ Power Platform integrations.
All built with **Fluent UI v9** (`@fluentui/react-components`) and the **Outlook connector**.
---
@@ -28,7 +28,7 @@ All built with **Fluent UI v9** (`@fluentui/react-components`) and the **Outlook
| **Node.js** | 18+ |
| **npm** | 9+ |
| **PAC CLI** | 2.6+ (`dotnet tool install -g Microsoft.PowerApps.CLI`) |
| **Power Apps environment** | Licensed user |
| **Power Apps environment** | Licensed user with code apps enabled |
| **Microsoft 365 account** | For the Outlook connector |
---
@@ -41,16 +41,18 @@ git clone <this-repo>
cd power-apps-codeapps-blog-part2
npm install
# 2. Run locally
# 2. Run locally (UI only, no connector data)
npm run dev
# → opens at http://localhost:5173
# 3. Build for production
npm run build
# 3. Run locally WITH real connector data
# Terminal 1:
npm run dev
# Terminal 2:
pac code run -a http://localhost:5173
# 4. Open the URL printed by pac code run in your browser
```
> The app uses mock data by default. To connect to real Outlook data, follow the connector setup below.
---
## Project structure
@@ -60,16 +62,26 @@ power-apps-codeapps-blog-part2/
├── index.html HTML shell
├── package.json Dependencies & scripts
├── tsconfig.json TypeScript config
├── vite.config.ts Vite dev server & build
├── vite.config.ts Vite dev server & build config
├── power.config.json Power Platform app manifest (required by PAC CLI)
├── .gitignore Excludes dist/, .power/, node_modules/
└── src/
├── main.tsx Entry point — FluentProvider + theme
├── main.tsx Entry point — FluentProvider + light/dark theme
├── App.tsx Main UI: nav rail, folders, email list, reading pane
├── index.css Minimal reset (Fluent handles all component styles)
├── index.css Minimal reset (Fluent UI handles component styles)
├── shims/
│ └── power-apps-data.ts Local dev shim for @microsoft/power-apps/data
├── types/
── index.ts Shared TypeScript types (Email, Folder, UserProfile)
└── services/
└── OutlookService.ts Mock connector (mirrors real generated service API)
── index.ts Shared types (Email, Folder, FOLDER_PATH_MAP)
│ └── power-apps.d.ts Type declarations for the Power Platform runtime
├── services/
│ └── OutlookService.ts Adapter wrapping the generated Outlook_comService
└── generated/ Auto-generated by pac code add-data-source
├── index.ts
├── models/
│ └── Outlook_comModel.ts Typed request/response interfaces
└── services/
└── Outlook_comService.ts All connector operations as static methods
```
---
@@ -80,21 +92,23 @@ power-apps-codeapps-blog-part2/
┌──────────────────────────────────────────────────────┐
│ Your Code App │
│ │
│ React + Fluent UI → Service Layer → Connector
│ React + Fluent UI → OutlookService → Generated
│ (adapter) Service │
│ ↕ │
│ ┌───────────────────────────────────┐ │
│ │ Power Platform Connectors │ │
│ │ ───────────────────────────────── │ │
│ │ Outlook │ Dataverse Custom │ │
│ │ Power Platform Runtime │ │
│ │ ───────────────────────────── │ │
│ │ @microsoft/power-apps/data │ │
│ │ → Outlook connector API │ │
│ └───────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
The app talks to connectors through a **typed service layer**. In development, `OutlookService.ts` returns mock data. In production, swap it for the auto-generated service from `pac code add-data-source`.
`src/services/OutlookService.ts` wraps the auto-generated `Outlook_comService` and maps its responses to the UI`s `Email` type. All real connector calls go through the Power Platform runtime SDK.
---
## Connecting to real data
## Setting up your own connections
### 1. Check your connections
@@ -105,70 +119,144 @@ pac connection list
Example output:
```
Connected as lago@powerplatform.top
Connected as user@contoso.com
Id Name API Id Status
4839c34829284206bf6a11d4ce577491 Outlook.com /providers/Microsoft.PowerApps/apis/shared_outlook Connected
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Outlook.com /providers/Microsoft.PowerApps/apis/shared_outlook Connected
```
### 2. Add the connector
### 2. Add the connector to your code app
```bash
pac code add-data-source \
-a shared_outlook \
-c 4839c34829284206bf6a11d4ce577491
-c <your-connection-id>
```
This auto-generates typed TypeScript files:
This auto-generates typed files in `src/generated/`:
```
src/connectors/shared_outlook/
├── OutlookModel.ts ← request/response types
└── OutlookService.ts ← typed service with all connector actions
src/generated/
├── models/Outlook_comModel.ts ← request/response types
└── services/Outlook_comService.ts ← typed service with all operations
```
### 3. (Optional) Add Dataverse
### 3. Update `power.config.json`
Replace the placeholder values with your real IDs:
```json
{
"environmentId": "<your-environment-id>",
"connectionReferences": {
"<your-connection-reference-id>": {
"id": "/providers/Microsoft.PowerApps/apis/shared_outlook",
...
}
}
}
```
### 4. Run with real connections
```bash
pac code add-data-source \
-a shared_commondataservice \
-c <connection-id> \
-t accounts \
-d default
# Terminal 1 — Vite dev server
npm run dev
# Terminal 2 — Power Platform connection proxy
pac code run -a http://localhost:5173
```
### 4. Swap mock for real service
Replace the import in `App.tsx`:
```diff
- import { OutlookService } from './services/OutlookService';
+ import { OutlookService } from './connectors/shared_outlook/OutlookService';
```
The method signatures are identical — no other changes needed.
Open the URL printed by `pac code run` in your browser.
---
## Connection references (theory)
## Removing an unused data source / connection
Code apps use **connection references** instead of binding directly to a user-specific connection. Benefits:
If you added a connection by mistake, or the schema changed and you need to refresh:
### Delete the data source
```bash
pac code delete-data-source -a <apiName> -ds <dataSourceName>
```
For example, to remove an Outlook data source named `outlook`:
```bash
pac code delete-data-source -a shared_outlook -ds outlook
```
This removes:
- The entry from `power.config.json` → `connectionReferences`
- The generated files under `src/generated/`
### Clean up manually (if needed)
If the CLI does not fully clean up:
1. **`power.config.json`** — Remove the connection reference entry from `connectionReferences`
2. **`src/generated/`** — Delete the corresponding `*Model.ts` and `*Service.ts` files
3. **`.power/schemas/`** — Delete the connector schema folder (e.g., `.power/schemas/outlook/`)
4. **`src/services/OutlookService.ts`** — Remove or update the adapter that imports from the deleted generated service
### Verify
```bash
npx tsc --noEmit # should compile without errors
npm run build # should produce a clean build
```
> **Important**: There is no `refresh` command yet. If a connector schema changes, delete the data source and re-add it.
---
## Available connector methods
After running `pac code add-data-source`, the generated `Outlook_comService.ts` exposes these static methods:
| Method | Description |
|---|---|
| `GetEmails(folderPath, ...)` | Get emails from a folder |
| `GetEmailsV2(folderPath, ...)` | V2 with more filter options |
| `GetEmail(messageId)` | Get a single email by ID |
| `SendEmail(message)` | Send email (plain text) |
| `SendEmailV2(message)` | Send email (HTML) |
| `MarkAsRead(messageId)` | Mark as read |
| `DeleteEmail(messageId)` | Delete email |
| `Move(messageId, folderPath)` | Move to another folder |
| `Flag(messageId)` | Flag an email |
| `ReplyTo / ReplyToV2 / ReplyToV3` | Reply (text/HTML) |
| `ForwardEmail(messageId, body)` | Forward |
All methods return `Promise<IOperationResult<T>>` — access the data via `result.data`.
---
## Connection references
Code apps use **connection references** instead of binding to user-specific connections:
| Benefit | Description |
|---|---|
| **Environment promotion** | Connections change per environment (devstagingprod) without code changes |
| **Managed identity** | Use system-assigned identities instead of user credentials |
| **Governance** | IT can audit which apps use which connectors via DLP policies |
| **Environment promotion** | Connections change per env (dev/staging/prod) without code changes |
| **Managed identity** | System-assigned identities instead of user credentials |
| **Governance** | IT audits via DLP policies |
---
## Fluent UI v9
## Deploy to Power Platform
This project uses [Fluent UI v9](https://react.fluentui.dev/) — the same design system that powers Microsoft 365:
```bash
# Build and push to your environment
npm run build
pac code push
```
- `FluentProvider` with `webLightTheme` / `webDarkTheme` (auto-detected)
- `makeStyles` for zero-runtime CSS-in-JS
- Design tokens (`tokens.colorBrandBackground`, etc.) for consistent theming
- All components: `Avatar`, `Badge`, `Button`, `Dialog`, `Input`, `Textarea`, `Spinner`, `Tooltip`, `Divider`, `ToolbarButton`
To target a specific solution:
```bash
pac code push --solution <solution-id>
```
---
@@ -182,16 +270,6 @@ This project uses [Fluent UI v9](https://react.fluentui.dev/) — the same desig
---
## Deploy to Power Platform
```bash
pac code deploy
```
This packages the app and registers it in your environment. Then add it to a solution and publish.
---
## Key differences from Canvas Apps
| Aspect | Canvas Apps | Code Apps |
@@ -200,35 +278,19 @@ This packages the app and registers it in your environment. Then add it to a sol
| **Connector access** | Built-in connector picker | PAC CLI + typed client library |
| **Data model** | Implicit, delegation-based | Explicit TypeScript types |
| **Styling** | Themed controls | Fluent UI / any CSS framework |
| **Deployment** | Packaged by Power Platform | `pac code deploy` + solution |
| **Deployment** | Packaged by Power Platform | `pac code push` |
| **Source control** | Limited | Full git-native workflow |
---
## Supported connectors
Code apps support **1,400+ connectors**:
| Category | Examples |
|---|---|
| **Microsoft 365** | Outlook, Teams, SharePoint, OneDrive, Planner |
| **Dataverse** | Common Data Service (Dataverse) |
| **Data** | SQL Server, OData |
| **SaaS** | Salesforce, SAP, ServiceNow, Slack |
| **Custom** | Any standard REST connector |
**Not yet supported:** Excel Online (Business), Excel Online (OneDrive).
---
## References
- [How to: Connect your code app to data](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/connect-to-data) — Microsoft Learn
- [Power Apps code apps overview](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/overview) — Microsoft Learn
- [Code apps architecture](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/architecture) — Microsoft Learn
- [Use CLI to discover, create, and wire connectors](https://learn.microsoft.com/en-us/power-platform/release-plan/2026wave1/power-apps/use-cli-discover-create-wire-connectors-code-apps) — Microsoft Learn
- [pac connection list](https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/connection#pac-connection-list) — PAC CLI reference
- [pac code add-data-source](https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/code#pac-code-add-data-source) — PAC CLI reference
- [pac code delete-data-source](https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/code#pac-code-delete-data-source) — PAC CLI reference
- [pac connection list](https://learn.microsoft.com/en-us/power-platform/developer/cli/reference/connection#pac-connection-list) — PAC CLI reference
- [Connector classification (DLP)](https://learn.microsoft.com/en-us/power-platform/admin/dlp-connector-classification) — Microsoft Learn
- [Fluent UI v9 — React components](https://react.fluentui.dev/) — Microsoft
- [Part 1: Power Apps code apps tutorial](https://www.thatsagoodquestion.info/power-apps-code-apps) — That's a good question

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Outlook Lite — Power Apps Code Apps Part 2</title>
<script type="module" crossorigin src="/assets/index-DQ2nyFq4.js"></script>
<script type="module" crossorigin src="/assets/index-DdvZTbE9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CmpLfbp9.css">
</head>
<body>

1035
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,6 @@
import {
require_jsx_runtime
} from "./chunk-GOUXSCEN.js";
import {
require_scheduler
} from "./chunk-SVR3SNXV.js";
import {
require_react_dom
} from "./chunk-TF4LBITK.js";
} from "./chunk-G5LRF5Q3.js";
import {
ArrowDownRegular,
ArrowUpRegular,
@@ -117,13 +111,21 @@ import {
renderToStyleElements,
shorthands,
useRenderer
} from "./chunk-L54BDX3M.js";
} from "./chunk-WF23Q3BR.js";
import {
require_scheduler
} from "./chunk-RYT6YT3P.js";
import {
require_react_dom
} from "./chunk-HJTH342H.js";
import {
require_react
} from "./chunk-KNNXW3SV.js";
import {
__commonJS,
__export,
__toESM,
require_react
} from "./chunk-WBF6APZF.js";
__toESM
} from "./chunk-DC5AMYBS.js";
// node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js
var require_use_sync_external_store_shim_development = __commonJS({

File diff suppressed because one or more lines are too long

View File

@@ -25897,8 +25897,9 @@ import {
useIconContext,
useIconState,
wrapIcon
} from "./chunk-L54BDX3M.js";
import "./chunk-WBF6APZF.js";
} from "./chunk-WF23Q3BR.js";
import "./chunk-KNNXW3SV.js";
import "./chunk-DC5AMYBS.js";
export {
AccessTime20Filled,
AccessTime20Regular,

View File

@@ -1,67 +1,76 @@
{
"hash": "2822248f",
"hash": "a2cf0ee3",
"configHash": "199e9b49",
"lockfileHash": "47a5815c",
"browserHash": "eeb8a9d2",
"lockfileHash": "7d0db4c4",
"browserHash": "5f7ecc69",
"optimized": {
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "18ec2fb8",
"fileHash": "d02ecc7a",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "3182c799",
"fileHash": "7dc29179",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "3aa6d3f3",
"fileHash": "5dbee3e4",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "387edc6c",
"fileHash": "68c460e2",
"needsInterop": true
},
"@fluentui/react-components": {
"src": "../../@fluentui/react-components/lib/index.js",
"file": "@fluentui_react-components.js",
"fileHash": "6b473cbd",
"fileHash": "4c21893b",
"needsInterop": false
},
"@fluentui/react-icons": {
"src": "../../@fluentui/react-icons/lib/index.js",
"file": "@fluentui_react-icons.js",
"fileHash": "1b0d1c61",
"fileHash": "d07e302b",
"needsInterop": false
},
"@microsoft/power-apps/data": {
"src": "../../@microsoft/power-apps/dist/data/index.js",
"file": "@microsoft_power-apps_data.js",
"fileHash": "93d66341",
"needsInterop": false
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "484d4a24",
"fileHash": "1ccb8538",
"needsInterop": true
}
},
"chunks": {
"chunk-GOUXSCEN": {
"file": "chunk-GOUXSCEN.js"
"chunk-G5LRF5Q3": {
"file": "chunk-G5LRF5Q3.js"
},
"chunk-SVR3SNXV": {
"file": "chunk-SVR3SNXV.js"
"chunk-WF23Q3BR": {
"file": "chunk-WF23Q3BR.js"
},
"chunk-TF4LBITK": {
"file": "chunk-TF4LBITK.js"
"chunk-RYT6YT3P": {
"file": "chunk-RYT6YT3P.js"
},
"chunk-L54BDX3M": {
"file": "chunk-L54BDX3M.js"
"chunk-HJTH342H": {
"file": "chunk-HJTH342H.js"
},
"chunk-WBF6APZF": {
"file": "chunk-WBF6APZF.js"
"chunk-KNNXW3SV": {
"file": "chunk-KNNXW3SV.js"
},
"chunk-DC5AMYBS": {
"file": "chunk-DC5AMYBS.js"
}
}
}

View File

@@ -1,290 +0,0 @@
import {
__commonJS,
require_react
} from "./chunk-WBF6APZF.js";
// node_modules/react/cjs/react-jsx-runtime.development.js
var require_react_jsx_runtime_development = __commonJS({
"node_modules/react/cjs/react-jsx-runtime.development.js"(exports) {
"use strict";
(function() {
function getComponentNameFromType(type) {
if (null == type) return null;
if ("function" === typeof type)
return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null;
if ("string" === typeof type) return type;
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
case REACT_ACTIVITY_TYPE:
return "Activity";
}
if ("object" === typeof type)
switch ("number" === typeof type.tag && console.error(
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
), type.$$typeof) {
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_CONTEXT_TYPE:
return type.displayName || "Context";
case REACT_CONSUMER_TYPE:
return (type._context.displayName || "Context") + ".Consumer";
case REACT_FORWARD_REF_TYPE:
var innerType = type.render;
type = type.displayName;
type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef");
return type;
case REACT_MEMO_TYPE:
return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE:
innerType = type._payload;
type = type._init;
try {
return getComponentNameFromType(type(innerType));
} catch (x) {
}
}
return null;
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
try {
testStringCoercion(value);
var JSCompiler_inline_result = false;
} catch (e) {
JSCompiler_inline_result = true;
}
if (JSCompiler_inline_result) {
JSCompiler_inline_result = console;
var JSCompiler_temp_const = JSCompiler_inline_result.error;
var JSCompiler_inline_result$jscomp$0 = "function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
JSCompiler_temp_const.call(
JSCompiler_inline_result,
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
JSCompiler_inline_result$jscomp$0
);
return testStringCoercion(value);
}
}
function getTaskName(type) {
if (type === REACT_FRAGMENT_TYPE) return "<>";
if ("object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE)
return "<...>";
try {
var name = getComponentNameFromType(type);
return name ? "<" + name + ">" : "<...>";
} catch (x) {
return "<...>";
}
}
function getOwner() {
var dispatcher = ReactSharedInternals.A;
return null === dispatcher ? null : dispatcher.getOwner();
}
function UnknownOwner() {
return Error("react-stack-top-frame");
}
function hasValidKey(config) {
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) return false;
}
return void 0 !== config.key;
}
function defineKeyPropWarningGetter(props, displayName) {
function warnAboutAccessingKey() {
specialPropKeyWarningShown || (specialPropKeyWarningShown = true, console.error(
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
displayName
));
}
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
function elementRefGetterWithDeprecationWarning() {
var componentName = getComponentNameFromType(this.type);
didWarnAboutElementRef[componentName] || (didWarnAboutElementRef[componentName] = true, console.error(
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
));
componentName = this.props.ref;
return void 0 !== componentName ? componentName : null;
}
function ReactElement(type, key, props, owner, debugStack, debugTask) {
var refProp = props.ref;
type = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props,
_owner: owner
};
null !== (void 0 !== refProp ? refProp : null) ? Object.defineProperty(type, "ref", {
enumerable: false,
get: elementRefGetterWithDeprecationWarning
}) : Object.defineProperty(type, "ref", { enumerable: false, value: null });
type._store = {};
Object.defineProperty(type._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: 0
});
Object.defineProperty(type, "_debugInfo", {
configurable: false,
enumerable: false,
writable: true,
value: null
});
Object.defineProperty(type, "_debugStack", {
configurable: false,
enumerable: false,
writable: true,
value: debugStack
});
Object.defineProperty(type, "_debugTask", {
configurable: false,
enumerable: false,
writable: true,
value: debugTask
});
Object.freeze && (Object.freeze(type.props), Object.freeze(type));
return type;
}
function jsxDEVImpl(type, config, maybeKey, isStaticChildren, debugStack, debugTask) {
var children = config.children;
if (void 0 !== children)
if (isStaticChildren)
if (isArrayImpl(children)) {
for (isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++)
validateChildKeys(children[isStaticChildren]);
Object.freeze && Object.freeze(children);
} else
console.error(
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
);
else validateChildKeys(children);
if (hasOwnProperty.call(config, "key")) {
children = getComponentNameFromType(type);
var keys = Object.keys(config).filter(function(k) {
return "key" !== k;
});
isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
didWarnAboutKeySpread[children + isStaticChildren] || (keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}", console.error(
'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />',
isStaticChildren,
children,
keys,
children
), didWarnAboutKeySpread[children + isStaticChildren] = true);
}
children = null;
void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), children = "" + maybeKey);
hasValidKey(config) && (checkKeyStringCoercion(config.key), children = "" + config.key);
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
children && defineKeyPropWarningGetter(
maybeKey,
"function" === typeof type ? type.displayName || type.name || "Unknown" : type
);
return ReactElement(
type,
children,
maybeKey,
getOwner(),
debugStack,
debugTask
);
}
function validateChildKeys(node) {
isValidElement(node) ? node._store && (node._store.validated = 1) : "object" === typeof node && null !== node && node.$$typeof === REACT_LAZY_TYPE && ("fulfilled" === node._payload.status ? isValidElement(node._payload.value) && node._payload.value._store && (node._payload.value._store.validated = 1) : node._store && (node._store.validated = 1));
}
function isValidElement(object) {
return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
}
var React = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
return null;
};
React = {
react_stack_bottom_frame: function(callStackForError) {
return callStackForError();
}
};
var specialPropKeyWarningShown;
var didWarnAboutElementRef = {};
var unknownOwnerDebugStack = React.react_stack_bottom_frame.bind(
React,
UnknownOwner
)();
var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
var didWarnAboutKeySpread = {};
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.jsx = function(type, config, maybeKey) {
var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
false,
trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
exports.jsxs = function(type, config, maybeKey) {
var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
true,
trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
})();
}
});
// node_modules/react/jsx-runtime.js
var require_jsx_runtime = __commonJS({
"node_modules/react/jsx-runtime.js"(exports, module) {
if (false) {
module.exports = null;
} else {
module.exports = require_react_jsx_runtime_development();
}
}
});
export {
require_jsx_runtime
};
/*! Bundled license information:
react/cjs/react-jsx-runtime.development.js:
(**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=chunk-GOUXSCEN.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,292 +0,0 @@
import {
__commonJS
} from "./chunk-WBF6APZF.js";
// node_modules/scheduler/cjs/scheduler.development.js
var require_scheduler_development = __commonJS({
"node_modules/scheduler/cjs/scheduler.development.js"(exports) {
"use strict";
(function() {
function performWorkUntilDeadline() {
needsPaint = false;
if (isMessageLoopRunning) {
var currentTime = exports.unstable_now();
startTime = currentTime;
var hasMoreWork = true;
try {
a: {
isHostCallbackScheduled = false;
isHostTimeoutScheduled && (isHostTimeoutScheduled = false, localClearTimeout(taskTimeoutID), taskTimeoutID = -1);
isPerformingWork = true;
var previousPriorityLevel = currentPriorityLevel;
try {
b: {
advanceTimers(currentTime);
for (currentTask = peek(taskQueue); null !== currentTask && !(currentTask.expirationTime > currentTime && shouldYieldToHost()); ) {
var callback = currentTask.callback;
if ("function" === typeof callback) {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
var continuationCallback = callback(
currentTask.expirationTime <= currentTime
);
currentTime = exports.unstable_now();
if ("function" === typeof continuationCallback) {
currentTask.callback = continuationCallback;
advanceTimers(currentTime);
hasMoreWork = true;
break b;
}
currentTask === peek(taskQueue) && pop(taskQueue);
advanceTimers(currentTime);
} else pop(taskQueue);
currentTask = peek(taskQueue);
}
if (null !== currentTask) hasMoreWork = true;
else {
var firstTimer = peek(timerQueue);
null !== firstTimer && requestHostTimeout(
handleTimeout,
firstTimer.startTime - currentTime
);
hasMoreWork = false;
}
}
break a;
} finally {
currentTask = null, currentPriorityLevel = previousPriorityLevel, isPerformingWork = false;
}
hasMoreWork = void 0;
}
} finally {
hasMoreWork ? schedulePerformWorkUntilDeadline() : isMessageLoopRunning = false;
}
}
}
function push(heap, node) {
var index = heap.length;
heap.push(node);
a: for (; 0 < index; ) {
var parentIndex = index - 1 >>> 1, parent = heap[parentIndex];
if (0 < compare(parent, node))
heap[parentIndex] = node, heap[index] = parent, index = parentIndex;
else break a;
}
}
function peek(heap) {
return 0 === heap.length ? null : heap[0];
}
function pop(heap) {
if (0 === heap.length) return null;
var first = heap[0], last = heap.pop();
if (last !== first) {
heap[0] = last;
a: for (var index = 0, length = heap.length, halfLength = length >>> 1; index < halfLength; ) {
var leftIndex = 2 * (index + 1) - 1, left = heap[leftIndex], rightIndex = leftIndex + 1, right = heap[rightIndex];
if (0 > compare(left, last))
rightIndex < length && 0 > compare(right, left) ? (heap[index] = right, heap[rightIndex] = last, index = rightIndex) : (heap[index] = left, heap[leftIndex] = last, index = leftIndex);
else if (rightIndex < length && 0 > compare(right, last))
heap[index] = right, heap[rightIndex] = last, index = rightIndex;
else break a;
}
}
return first;
}
function compare(a, b) {
var diff = a.sortIndex - b.sortIndex;
return 0 !== diff ? diff : a.id - b.id;
}
function advanceTimers(currentTime) {
for (var timer = peek(timerQueue); null !== timer; ) {
if (null === timer.callback) pop(timerQueue);
else if (timer.startTime <= currentTime)
pop(timerQueue), timer.sortIndex = timer.expirationTime, push(taskQueue, timer);
else break;
timer = peek(timerQueue);
}
}
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled)
if (null !== peek(taskQueue))
isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline());
else {
var firstTimer = peek(timerQueue);
null !== firstTimer && requestHostTimeout(
handleTimeout,
firstTimer.startTime - currentTime
);
}
}
function shouldYieldToHost() {
return needsPaint ? true : exports.unstable_now() - startTime < frameInterval ? false : true;
}
function requestHostTimeout(callback, ms) {
taskTimeoutID = localSetTimeout(function() {
callback(exports.unstable_now());
}, ms);
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
exports.unstable_now = void 0;
if ("object" === typeof performance && "function" === typeof performance.now) {
var localPerformance = performance;
exports.unstable_now = function() {
return localPerformance.now();
};
} else {
var localDate = Date, initialTime = localDate.now();
exports.unstable_now = function() {
return localDate.now() - initialTime;
};
}
var taskQueue = [], timerQueue = [], taskIdCounter = 1, currentTask = null, currentPriorityLevel = 3, isPerformingWork = false, isHostCallbackScheduled = false, isHostTimeoutScheduled = false, needsPaint = false, localSetTimeout = "function" === typeof setTimeout ? setTimeout : null, localClearTimeout = "function" === typeof clearTimeout ? clearTimeout : null, localSetImmediate = "undefined" !== typeof setImmediate ? setImmediate : null, isMessageLoopRunning = false, taskTimeoutID = -1, frameInterval = 5, startTime = -1;
if ("function" === typeof localSetImmediate)
var schedulePerformWorkUntilDeadline = function() {
localSetImmediate(performWorkUntilDeadline);
};
else if ("undefined" !== typeof MessageChannel) {
var channel = new MessageChannel(), port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = function() {
port.postMessage(null);
};
} else
schedulePerformWorkUntilDeadline = function() {
localSetTimeout(performWorkUntilDeadline, 0);
};
exports.unstable_IdlePriority = 5;
exports.unstable_ImmediatePriority = 1;
exports.unstable_LowPriority = 4;
exports.unstable_NormalPriority = 3;
exports.unstable_Profiling = null;
exports.unstable_UserBlockingPriority = 2;
exports.unstable_cancelCallback = function(task) {
task.callback = null;
};
exports.unstable_forceFrameRate = function(fps) {
0 > fps || 125 < fps ? console.error(
"forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"
) : frameInterval = 0 < fps ? Math.floor(1e3 / fps) : 5;
};
exports.unstable_getCurrentPriorityLevel = function() {
return currentPriorityLevel;
};
exports.unstable_next = function(eventHandler) {
switch (currentPriorityLevel) {
case 1:
case 2:
case 3:
var priorityLevel = 3;
break;
default:
priorityLevel = currentPriorityLevel;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
exports.unstable_requestPaint = function() {
needsPaint = true;
};
exports.unstable_runWithPriority = function(priorityLevel, eventHandler) {
switch (priorityLevel) {
case 1:
case 2:
case 3:
case 4:
case 5:
break;
default:
priorityLevel = 3;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
exports.unstable_scheduleCallback = function(priorityLevel, callback, options) {
var currentTime = exports.unstable_now();
"object" === typeof options && null !== options ? (options = options.delay, options = "number" === typeof options && 0 < options ? currentTime + options : currentTime) : options = currentTime;
switch (priorityLevel) {
case 1:
var timeout = -1;
break;
case 2:
timeout = 250;
break;
case 5:
timeout = 1073741823;
break;
case 4:
timeout = 1e4;
break;
default:
timeout = 5e3;
}
timeout = options + timeout;
priorityLevel = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime: options,
expirationTime: timeout,
sortIndex: -1
};
options > currentTime ? (priorityLevel.sortIndex = options, push(timerQueue, priorityLevel), null === peek(taskQueue) && priorityLevel === peek(timerQueue) && (isHostTimeoutScheduled ? (localClearTimeout(taskTimeoutID), taskTimeoutID = -1) : isHostTimeoutScheduled = true, requestHostTimeout(handleTimeout, options - currentTime))) : (priorityLevel.sortIndex = timeout, push(taskQueue, priorityLevel), isHostCallbackScheduled || isPerformingWork || (isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline())));
return priorityLevel;
};
exports.unstable_shouldYield = shouldYieldToHost;
exports.unstable_wrapCallback = function(callback) {
var parentPriorityLevel = currentPriorityLevel;
return function() {
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = parentPriorityLevel;
try {
return callback.apply(this, arguments);
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
};
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// node_modules/scheduler/index.js
var require_scheduler = __commonJS({
"node_modules/scheduler/index.js"(exports, module) {
"use strict";
if (false) {
module.exports = null;
} else {
module.exports = require_scheduler_development();
}
}
});
export {
require_scheduler
};
/*! Bundled license information:
scheduler/cjs/scheduler.development.js:
(**
* @license React
* scheduler.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=chunk-SVR3SNXV.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,278 +0,0 @@
import {
__commonJS,
require_react
} from "./chunk-WBF6APZF.js";
// node_modules/react-dom/cjs/react-dom.development.js
var require_react_dom_development = __commonJS({
"node_modules/react-dom/cjs/react-dom.development.js"(exports) {
"use strict";
(function() {
function noop() {
}
function testStringCoercion(value) {
return "" + value;
}
function createPortal$1(children, containerInfo, implementation) {
var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
try {
testStringCoercion(key);
var JSCompiler_inline_result = false;
} catch (e) {
JSCompiler_inline_result = true;
}
JSCompiler_inline_result && (console.error(
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
"function" === typeof Symbol && Symbol.toStringTag && key[Symbol.toStringTag] || key.constructor.name || "Object"
), testStringCoercion(key));
return {
$$typeof: REACT_PORTAL_TYPE,
key: null == key ? null : "" + key,
children,
containerInfo,
implementation
};
}
function getCrossOriginStringAs(as, input) {
if ("font" === as) return "";
if ("string" === typeof input)
return "use-credentials" === input ? input : "";
}
function getValueDescriptorExpectingObjectForWarning(thing) {
return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : 'something with type "' + typeof thing + '"';
}
function getValueDescriptorExpectingEnumForWarning(thing) {
return null === thing ? "`null`" : void 0 === thing ? "`undefined`" : "" === thing ? "an empty string" : "string" === typeof thing ? JSON.stringify(thing) : "number" === typeof thing ? "`" + thing + "`" : 'something with type "' + typeof thing + '"';
}
function resolveDispatcher() {
var dispatcher = ReactSharedInternals.H;
null === dispatcher && console.error(
"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem."
);
return dispatcher;
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React = require_react(), Internals = {
d: {
f: noop,
r: function() {
throw Error(
"Invalid form element. requestFormReset must be passed a form that was rendered by React."
);
},
D: noop,
C: noop,
L: noop,
m: noop,
X: noop,
S: noop,
M: noop
},
p: 0,
findDOMNode: null
}, REACT_PORTAL_TYPE = Symbol.for("react.portal"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
"function" === typeof Map && null != Map.prototype && "function" === typeof Map.prototype.forEach && "function" === typeof Set && null != Set.prototype && "function" === typeof Set.prototype.clear && "function" === typeof Set.prototype.forEach || console.error(
"React depends on Map and Set built-in types. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"
);
exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;
exports.createPortal = function(children, container) {
var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;
if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType)
throw Error("Target container is not a DOM element.");
return createPortal$1(children, container, null, key);
};
exports.flushSync = function(fn) {
var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;
try {
if (ReactSharedInternals.T = null, Internals.p = 2, fn)
return fn();
} finally {
ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f() && console.error(
"flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task."
);
}
};
exports.preconnect = function(href, options) {
"string" === typeof href && href ? null != options && "object" !== typeof options ? console.error(
"ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.",
getValueDescriptorExpectingEnumForWarning(options)
) : null != options && "string" !== typeof options.crossOrigin && console.error(
"ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.",
getValueDescriptorExpectingObjectForWarning(options.crossOrigin)
) : console.error(
"ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
getValueDescriptorExpectingObjectForWarning(href)
);
"string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options));
};
exports.prefetchDNS = function(href) {
if ("string" !== typeof href || !href)
console.error(
"ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
getValueDescriptorExpectingObjectForWarning(href)
);
else if (1 < arguments.length) {
var options = arguments[1];
"object" === typeof options && options.hasOwnProperty("crossOrigin") ? console.error(
"ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.",
getValueDescriptorExpectingEnumForWarning(options)
) : console.error(
"ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.",
getValueDescriptorExpectingEnumForWarning(options)
);
}
"string" === typeof href && Internals.d.D(href);
};
exports.preinit = function(href, options) {
"string" === typeof href && href ? null == options || "object" !== typeof options ? console.error(
"ReactDOM.preinit(): Expected the `options` argument (second) to be an object with an `as` property describing the type of resource to be preinitialized but encountered %s instead.",
getValueDescriptorExpectingEnumForWarning(options)
) : "style" !== options.as && "script" !== options.as && console.error(
'ReactDOM.preinit(): Expected the `as` property in the `options` argument (second) to contain a valid value describing the type of resource to be preinitialized but encountered %s instead. Valid values for `as` are "style" and "script".',
getValueDescriptorExpectingEnumForWarning(options.as)
) : console.error(
"ReactDOM.preinit(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.",
getValueDescriptorExpectingObjectForWarning(href)
);
if ("string" === typeof href && options && "string" === typeof options.as) {
var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0;
"style" === as ? Internals.d.S(
href,
"string" === typeof options.precedence ? options.precedence : void 0,
{
crossOrigin,
integrity,
fetchPriority
}
) : "script" === as && Internals.d.X(href, {
crossOrigin,
integrity,
fetchPriority,
nonce: "string" === typeof options.nonce ? options.nonce : void 0
});
}
};
exports.preinitModule = function(href, options) {
var encountered = "";
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "script" !== options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingEnumForWarning(options.as) + ".");
if (encountered)
console.error(
"ReactDOM.preinitModule(): Expected up to two arguments, a non-empty `href` string and, optionally, an `options` object with a valid `as` property.%s",
encountered
);
else
switch (encountered = options && "string" === typeof options.as ? options.as : "script", encountered) {
case "script":
break;
default:
encountered = getValueDescriptorExpectingEnumForWarning(encountered), console.error(
'ReactDOM.preinitModule(): Currently the only supported "as" type for this function is "script" but received "%s" instead. This warning was generated for `href` "%s". In the future other module types will be supported, aligning with the import-attributes proposal. Learn more here: (https://github.com/tc39/proposal-import-attributes)',
encountered,
href
);
}
if ("string" === typeof href)
if ("object" === typeof options && null !== options) {
if (null == options.as || "script" === options.as)
encountered = getCrossOriginStringAs(
options.as,
options.crossOrigin
), Internals.d.M(href, {
crossOrigin: encountered,
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
nonce: "string" === typeof options.nonce ? options.nonce : void 0
});
} else null == options && Internals.d.M(href);
};
exports.preload = function(href, options) {
var encountered = "";
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
null == options || "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : "string" === typeof options.as && options.as || (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
encountered && console.error(
'ReactDOM.preload(): Expected two arguments, a non-empty `href` string and an `options` object with an `as` property valid for a `<link rel="preload" as="..." />` tag.%s',
encountered
);
if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) {
encountered = options.as;
var crossOrigin = getCrossOriginStringAs(
encountered,
options.crossOrigin
);
Internals.d.L(href, encountered, {
crossOrigin,
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
nonce: "string" === typeof options.nonce ? options.nonce : void 0,
type: "string" === typeof options.type ? options.type : void 0,
fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0,
referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0,
imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0,
imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0,
media: "string" === typeof options.media ? options.media : void 0
});
}
};
exports.preloadModule = function(href, options) {
var encountered = "";
"string" === typeof href && href || (encountered += " The `href` argument encountered was " + getValueDescriptorExpectingObjectForWarning(href) + ".");
void 0 !== options && "object" !== typeof options ? encountered += " The `options` argument encountered was " + getValueDescriptorExpectingObjectForWarning(options) + "." : options && "as" in options && "string" !== typeof options.as && (encountered += " The `as` option encountered was " + getValueDescriptorExpectingObjectForWarning(options.as) + ".");
encountered && console.error(
'ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `<link rel="modulepreload" as="..." />` tag.%s',
encountered
);
"string" === typeof href && (options ? (encountered = getCrossOriginStringAs(
options.as,
options.crossOrigin
), Internals.d.m(href, {
as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0,
crossOrigin: encountered,
integrity: "string" === typeof options.integrity ? options.integrity : void 0
})) : Internals.d.m(href));
};
exports.requestFormReset = function(form) {
Internals.d.r(form);
};
exports.unstable_batchedUpdates = function(fn, a) {
return fn(a);
};
exports.useFormState = function(action, initialState, permalink) {
return resolveDispatcher().useFormState(action, initialState, permalink);
};
exports.useFormStatus = function() {
return resolveDispatcher().useHostTransitionStatus();
};
exports.version = "19.2.5";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
}
});
// node_modules/react-dom/index.js
var require_react_dom = __commonJS({
"node_modules/react-dom/index.js"(exports, module) {
if (false) {
checkDCE();
module.exports = null;
} else {
module.exports = require_react_dom_development();
}
}
});
export {
require_react_dom
};
/*! Bundled license information:
react-dom/cjs/react-dom.development.js:
(**
* @license React
* react-dom.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
*/
//# sourceMappingURL=chunk-TF4LBITK.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
import {
require_react_dom
} from "./chunk-TF4LBITK.js";
import "./chunk-WBF6APZF.js";
} from "./chunk-HJTH342H.js";
import "./chunk-KNNXW3SV.js";
import "./chunk-DC5AMYBS.js";
export default require_react_dom();

View File

@@ -1,13 +1,15 @@
import {
require_scheduler
} from "./chunk-SVR3SNXV.js";
} from "./chunk-RYT6YT3P.js";
import {
require_react_dom
} from "./chunk-TF4LBITK.js";
} from "./chunk-HJTH342H.js";
import {
__commonJS,
require_react
} from "./chunk-WBF6APZF.js";
} from "./chunk-KNNXW3SV.js";
import {
__commonJS
} from "./chunk-DC5AMYBS.js";
// node_modules/react-dom/cjs/react-dom-client.development.js
var require_react_dom_client_development = __commonJS({

File diff suppressed because one or more lines are too long

3
node_modules/.vite/deps/react.js generated vendored
View File

@@ -1,4 +1,5 @@
import {
require_react
} from "./chunk-WBF6APZF.js";
} from "./chunk-KNNXW3SV.js";
import "./chunk-DC5AMYBS.js";
export default require_react();

View File

@@ -1,7 +1,9 @@
import {
__commonJS,
require_react
} from "./chunk-WBF6APZF.js";
} from "./chunk-KNNXW3SV.js";
import {
__commonJS
} from "./chunk-DC5AMYBS.js";
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
var require_react_jsx_dev_runtime_development = __commonJS({

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
import {
require_jsx_runtime
} from "./chunk-GOUXSCEN.js";
import "./chunk-WBF6APZF.js";
} from "./chunk-G5LRF5Q3.js";
import "./chunk-KNNXW3SV.js";
import "./chunk-DC5AMYBS.js";
export default require_jsx_runtime();

1036
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@fluentui/react-components": "^9.73.0",
"@fluentui/react-icons": "^2.0.324",
"@microsoft/power-apps": "^1.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},

View File

@@ -1,14 +1,16 @@
{
"$schema": "https://aka.ms/power-apps/code/power.config.schema.json",
"version": "1.0",
"app": {
"name": "outlook-lite",
"displayName": "Outlook Lite — Code Apps Demo",
"description": "Email client built with React + Fluent UI, powered by Power Platform connectors"
},
"dataSources": [],
"appId": "<your-app-id>",
"appDisplayName": "Outlook Lite",
"region": "prod",
"environmentId": "<your-environment-id>",
"description": "Email client built with React + Fluent UI, powered by Power Platform connectors",
"buildPath": "dist",
"buildEntryPoint": "index.html",
"localAppUrl": "http://localhost:5173",
"logoPath": "Default",
"connectionReferences": {
"e9a483eb-1c10-473e-8d5c-a71f3f1f79a1": {
"<your-connection-reference-id>": {
"id": "/providers/Microsoft.PowerApps/apis/shared_outlook",
"displayName": "Outlook.com",
"dataSources": [
@@ -16,5 +18,6 @@
],
"dataSets": {}
}
}
}
},
"databaseReferences": {}
}

View File

@@ -48,6 +48,14 @@ import { OutlookService } from './services/OutlookService';
const outlookService = new OutlookService();
/** Sanitise HTML content - strip script tags for basic XSS prevention */
function sanitiseHtml(html: string): string {
return html
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/on\w+="[^"]*"/gi, '')
.replace(/on\w+='[^']*'/gi, '');
}
/* ─── styles ─── */
const useStyles = makeStyles({
root: {
@@ -426,14 +434,20 @@ export default function App() {
const [composeSubject, setComposeSubject] = useState('');
const [composeBody, setComposeBody] = useState('');
const [sending, setSending] = useState(false);
const [error, setError] = useState<string | null>(null);
/* data */
const loadEmails = useCallback(async () => {
setLoading(true);
setError(null);
try {
const msgs = await outlookService.getMessages(activeFolder);
setEmails(msgs);
setSelectedEmail(null);
} catch (err) {
console.error('Error loading emails:', err);
setError('No se pudieron cargar los correos. Comprueba la conexión.');
setEmails([]);
} finally {
setLoading(false);
}
@@ -446,8 +460,22 @@ export default function App() {
const selectEmail = async (email: Email) => {
setSelectedEmail(email);
if (!email.isRead) {
await outlookService.markAsRead(email.id);
setEmails(prev => prev.map(e => (e.id === email.id ? { ...e, isRead: true } : e)));
try {
await outlookService.markAsRead(email.id);
setEmails(prev => prev.map(e => (e.id === email.id ? { ...e, isRead: true } : e)));
} catch (err) {
console.error('Error marking as read:', err);
}
}
};
const handleDelete = async (email: Email) => {
try {
await outlookService.deleteEmail(email.id);
setEmails(prev => prev.filter(e => e.id !== email.id));
if (selectedEmail?.id === email.id) setSelectedEmail(null);
} catch (err) {
console.error('Error deleting email:', err);
}
};
@@ -524,12 +552,12 @@ export default function App() {
<aside className={s.folderPane}>
<div className={s.folderHeader}>
<Avatar
name="Lago"
initials="LA"
name="User"
initials="US"
size={28}
color="brand"
/>
<Text weight="semibold" size={300}>lago@powerplatform.top</Text>
<Text weight="semibold" size={300}>user@contoso.com</Text>
</div>
<Divider style={{ margin: '6px 0' }} />
@@ -601,6 +629,14 @@ export default function App() {
<div className={s.emptyState} style={{ padding: '40px 0' }}>
<Spinner size="small" label="Cargando…" />
</div>
) : error ? (
<div className={s.emptyState} style={{ padding: '60px 16px' }}>
<Mail24Regular fontSize={40} style={{ opacity: 0.3 }} />
<Text size={300} style={{ textAlign: 'center' }}>{error}</Text>
<Button appearance="subtle" size="small" icon={<ArrowSync24Regular />} onClick={loadEmails}>
Reintentar
</Button>
</div>
) : filtered.length === 0 ? (
<div className={s.emptyState} style={{ padding: '60px 16px' }}>
<Mail24Regular fontSize={40} style={{ opacity: 0.3 }} />
@@ -632,7 +668,7 @@ export default function App() {
<span className={s.messageTime}>{formatTime(email.receivedAt)}</span>
</div>
<div className={s.messageSubject}>{email.subject}</div>
<div className={s.messagePreview}>{email.body.substring(0, 80)}</div>
<div className={s.messagePreview}>{email.bodyPreview || email.body.substring(0, 80)}</div>
</div>
</div>
))
@@ -672,7 +708,14 @@ export default function App() {
<Divider style={{ margin: '0 28px' }} />
<div className={s.readingBody}>{selectedEmail.body}</div>
{selectedEmail.isHtml ? (
<div
className={s.readingBody}
dangerouslySetInnerHTML={{ __html: sanitiseHtml(selectedEmail.body) }}
/>
) : (
<div className={s.readingBody}>{selectedEmail.body}</div>
)}
<Divider style={{ margin: '0 28px' }} />
@@ -697,6 +740,7 @@ export default function App() {
appearance="subtle"
icon={<Delete24Regular />}
size="small"
onClick={() => handleDelete(selectedEmail)}
>
Eliminar
</Button>
@@ -806,11 +850,9 @@ function ComposeDialog({
function ConnectionsPanel() {
const s = useStyles();
// Connections from power.config.json — replace with your own
const connections = [
{ id: '1', name: 'shared_outlook', displayName: 'Outlook', connector: 'Microsoft Outlook', status: 'connected' as const, color: '#0078D4' },
{ id: '2', name: 'shared_office365', displayName: 'Office 365 Users', connector: 'Office 365 Users', status: 'connected' as const, color: '#D83B01' },
{ id: '3', name: 'shared_dataverse', displayName: 'Dataverse', connector: 'Microsoft Dataverse', status: 'connected' as const, color: '#7719BA' },
{ id: '4', name: 'shared_azuresql', displayName: 'Azure SQL', connector: 'Azure SQL Database', status: 'disconnected' as const, color: '#0078D4' },
{ id: 'your-connection-ref-id', name: 'shared_outlook', displayName: 'Outlook.com', connector: 'Microsoft Outlook', status: 'connected' as const, color: '#0078D4' },
];
const archBoxes = [
@@ -818,7 +860,7 @@ function ConnectionsPanel() {
{ label: 'PAC Client', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
{ label: 'Connection Ref', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
{ label: 'Connection', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
{ label: 'Outlook / Dataverse', bg: '#7719BA', fg: '#fff' },
{ label: 'Outlook', bg: '#0078D4', fg: '#fff' },
];
return (
@@ -887,7 +929,7 @@ function ConnectionsPanel() {
<br />
<div style={{ color: tokens.colorNeutralForeground3 }}># Añadir Outlook como data source</div>
<div>pac code add-data-source -a shared_outlook \</div>
<div style={{ paddingLeft: 24 }}>-c 4839c34829284206bf6a11d4ce577491</div>
<div style={{ paddingLeft: 24 }}>-c &lt;your-connection-id&gt;</div>
<br />
<div style={{ color: tokens.colorNeutralForeground3 }}># Añadir Dataverse (tabular)</div>
<div>pac code add-data-source -a shared_commondataservice \</div>

View File

@@ -1,148 +1,139 @@
import type { Email, UserProfile } from '../types';
import { Outlook_comService } from '../generated';
import type { Outlook_comModel } from '../generated';
import type { Email } from '../types';
import { FOLDER_PATH_MAP } from '../types';
// NOTE: In a real code app, these would be injected via the Power Platform
// client library (@microsoft/power-apps-code-solutions or similar).
// The client exposes typed services like Office365UsersService and
// OutlookService that wrap the connector calls.
//
// For this demo, we simulate the connector responses with mock data.
// To wire up real connectors:
// 1. Create connections in make.powerapps.com (e.g. "shared_office365", "shared_outlook")
// 2. Run: pac code add-data-source -a <apiName> -c <connectionId>
// 3. The generator creates typed Model and Service files automatically
//
// Real service usage would look like:
//
// import { OutlookService } from './services/OutlookService';
// const outlook = new OutlookService();
// const emails = await outlook.getMessages('inbox');
/**
* Adapter around the auto-generated Outlook_comService.
* Maps the Power Platform connector responses to the UI's Email type.
*/
const MOCK_USER: UserProfile = {
displayName: 'Lago',
email: 'jose@lago.dev',
};
const REVERSE_FOLDER_MAP: Record<string, Email['folder']> = Object.fromEntries(
Object.entries(FOLDER_PATH_MAP).map(([k, v]) => [v, k as Email['folder']])
) as Record<string, Email['folder']>;
const MOCK_EMAILS: Email[] = [
{
id: '1',
from: 'jose@lago.dev',
fromName: 'Lago',
to: 'lago@example.com',
subject: 'Sprint planning — Thursday 10:00',
body: 'Hola,\n\nVamos a planificar el sprint del mes que viene el jueves a las 10:00. Necesito que reviseis los temas pendientes del backlog.\n\nUn saludo',
receivedAt: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
isRead: false,
folder: 'inbox',
},
{
id: '2',
from: 'maria@partner.com',
fromName: 'María García',
to: 'lago@example.com',
subject: 'RE: Presupuesto proyecto digital 2026',
body: 'Hola Lago,\n\nGracias por la información. He revisado el presupuesto y parece correcto. Adjunto la propuesta final.\n\n¿Podemos hablar mañana por teléfono?\n\nSaludos',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(),
isRead: false,
folder: 'inbox',
},
{
id: '3',
from: 'devops@company.com',
fromName: 'DevOps Team',
to: 'lago@example.com',
subject: '[Alert] Production deployment failed — pipeline #4821',
body: 'Pipeline #4821 failed at stage: build\n\nError: npm ERR! code ETARGET\nnpm ERR! notarget No valid target for react@19.0.1\n\nView logs: https://devops.company.com/pipelines/4821',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(),
isRead: true,
folder: 'inbox',
},
{
id: '4',
from: 'lago@example.com',
fromName: 'Lago',
to: 'equipo@company.com',
subject: 'Resumen semanal — Power Platform',
body: 'Equipo,\n\nAquí va el resumen de lo hecho esta semana:\n\n• Demo de code apps publicada en el blog\n• Connector de Outlook integrado\n• Migración del backlog a Dataverse completada\n\nPara la semana que viene:\n• Terminar la integración con Teams\n• Revisar los permisos de producción\n\nSaludos',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),
isRead: true,
folder: 'sent',
},
{
id: '5',
from: 'newsletter@techweekly.com',
fromName: 'Tech Weekly',
to: 'lago@example.com',
subject: 'This Week in AI & Power Platform — Issue #47',
body: 'Top stories this week:\n\n1. Microsoft announces GPT-5 integration for Copilot Studio\n2. Power Apps code apps reach 1,400+ connectors milestone\n3. Dataverse 2026 Wave 1 features now generally available\n\nRead more inside...',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
isRead: true,
folder: 'inbox',
},
{
id: '6',
from: 'noreply@linkedin.com',
fromName: 'LinkedIn',
to: 'lago@example.com',
subject: '5 people viewed your profile this week',
body: 'Your profile got noticed!\n\n5 people in Austria viewed your profile in the past week, including recruiters and hiring managers.\n\nView who they are →',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 72).toISOString(),
isRead: true,
folder: 'inbox',
},
{
id: '7',
from: 'lago@example.com',
fromName: 'Lago',
to: 'draft',
subject: 'Borrador: Propuesta reunión trimestral',
body: 'Borrador de la propuesta para la reunión trimestral con dirección...',
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 6).toISOString(),
isRead: true,
folder: 'drafts',
},
];
/** Extract a display name from "Name <email>" or just "email" */
function parseSender(raw?: string): { email: string; name: string } {
if (!raw) return { email: '', name: '(desconocido)' };
// Some connectors return "Display Name <user@example.com>"
const match = raw.match(/^(.+?)\s*<(.+)>$/);
if (match) return { name: match[1].trim(), email: match[2].trim() };
// Otherwise it's just an email address
const namePart = raw.split('@')[0].replace(/[._-]/g, ' ');
return { email: raw, name: namePart.charAt(0).toUpperCase() + namePart.slice(1) };
}
function mapMessage(
msg: Outlook_comModel.ClientReceiveMessageStringEnums,
folderHint: Email['folder'],
): Email {
const sender = parseSender(msg.From);
return {
id: msg.Id ?? '',
from: sender.email,
fromName: sender.name,
to: msg.To ?? '',
subject: msg.Subject ?? '(Sin asunto)',
body: msg.Body ?? '',
bodyPreview: msg.BodyPreview ?? msg.Body?.substring(0, 100) ?? '',
receivedAt: msg.DateTimeReceived ?? new Date().toISOString(),
isRead: msg.IsRead ?? true,
isHtml: msg.IsHtml ?? false,
hasAttachments: msg.HasAttachment ?? false,
importance: msg.Importance ?? 'Normal',
folder: folderHint,
};
}
export class OutlookService {
private delay(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
async getProfile(): Promise<UserProfile> {
await this.delay(300);
return MOCK_USER;
}
async getMessages(folder: string = 'inbox'): Promise<Email[]> {
await this.delay(600);
return MOCK_EMAILS.filter((e) => e.folder === folder);
/**
* Fetch emails from a given folder.
* Uses GetEmails (v1) which returns ClientReceiveMessageStringEnums[].
*/
async getMessages(folder: string = 'inbox', searchQuery?: string): Promise<Email[]> {
const folderPath = FOLDER_PATH_MAP[folder] ?? 'Inbox';
const uiFolder = (REVERSE_FOLDER_MAP[folderPath] ?? 'inbox') as Email['folder'];
const result = await Outlook_comService.GetEmails(
folderPath,
/* fetchOnlyUnread */ undefined,
/* includeAttachments */ false,
searchQuery,
/* top */ 50,
);
const messages = result.data ?? [];
return messages.map((m: Outlook_comModel.ClientReceiveMessageStringEnums) => mapMessage(m, uiFolder));
}
/**
* Get a single email by id.
*/
async getMessage(id: string): Promise<Email | undefined> {
await this.delay(200);
return MOCK_EMAILS.find((e) => e.id === id);
}
async sendMessage(email: Partial<Email>): Promise<Email> {
await this.delay(800);
const newEmail: Email = {
id: Date.now().toString(),
from: MOCK_USER.email,
fromName: MOCK_USER.displayName,
to: email.to || '',
subject: email.subject || '(Sin asunto)',
body: email.body || '',
receivedAt: new Date().toISOString(),
isRead: true,
folder: 'sent',
const result = await Outlook_comService.GetEmail(id, false);
if (!result.data) return undefined;
const msg = result.data;
const sender = parseSender(msg.From);
return {
id: msg.Id ?? '',
from: sender.email,
fromName: sender.name,
to: msg.To ?? '',
subject: msg.Subject ?? '(Sin asunto)',
body: msg.Body ?? '',
bodyPreview: msg.BodyPreview ?? '',
receivedAt: msg.DateTimeReceived ?? new Date().toISOString(),
isRead: msg.IsRead ?? true,
isHtml: msg.IsHtml ?? false,
hasAttachments: msg.HasAttachment ?? false,
importance: 'Normal',
folder: 'inbox',
};
MOCK_EMAILS.push(newEmail);
return newEmail;
}
/**
* Send an email via the V2 HTML endpoint.
*/
async sendMessage(params: { to: string; subject: string; body: string }): Promise<void> {
await Outlook_comService.SendEmailV2({
To: params.to,
Subject: params.subject,
Body: params.body,
});
}
/**
* Mark an email as read.
*/
async markAsRead(id: string): Promise<void> {
await this.delay(100);
const email = MOCK_EMAILS.find((e) => e.id === id);
if (email) email.isRead = true;
await Outlook_comService.MarkAsRead(id);
}
/**
* Delete an email.
*/
async deleteEmail(id: string): Promise<void> {
await Outlook_comService.DeleteEmail(id);
}
/**
* Reply to an email (HTML).
*/
async replyTo(messageId: string, body: string, replyAll: boolean = false): Promise<void> {
await Outlook_comService.ReplyToV3(messageId, {
Body: body,
ReplyAll: replyAll,
});
}
/**
* Forward an email.
*/
async forward(messageId: string, to: string, body: string): Promise<void> {
await Outlook_comService.ForwardEmail(messageId, {
ToRecipients: to,
Comment: body,
});
}
}

View File

@@ -0,0 +1,21 @@
/**
* Local development shim for @microsoft/power-apps/data.
*
* In production, the Power Platform runtime provides this module.
* During local dev, this shim logs calls and returns empty results
* so the app can render without the real connector backend.
*/
export function getClient(_dataSourcesInfo: unknown) {
return {
async executeAsync<_TParams, TResult>(request: {
connectorOperation: { operationName: string };
}): Promise<{ data?: TResult; error?: { message: string } }> {
console.warn(
`[power-apps shim] ${request.connectorOperation.operationName} called — ` +
`returning empty result (local dev mode, no Power Platform runtime).`
);
return { data: undefined };
},
};
}

View File

@@ -5,8 +5,12 @@ export interface Email {
to: string;
subject: string;
body: string;
bodyPreview: string;
receivedAt: string;
isRead: boolean;
isHtml: boolean;
hasAttachments: boolean;
importance: 'Low' | 'Normal' | 'High';
folder: 'inbox' | 'sent' | 'drafts' | 'trash';
}
@@ -17,8 +21,10 @@ export interface Folder {
unreadCount: number;
}
export interface UserProfile {
displayName: string;
email: string;
avatar?: string;
}
/** Maps UI folder ids to Outlook folderPath values */
export const FOLDER_PATH_MAP: Record<string, string> = {
inbox: 'Inbox',
sent: 'SentItems',
drafts: 'Drafts',
trash: 'DeletedItems',
};

29
src/types/power-apps.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
/**
* Type stub for @microsoft/power-apps/data.
*
* This module is provided by the Power Platform runtime at execution time.
* During local development / tsc type-checking it is not installed,
* so we declare just enough types for the generated service to compile.
*/
declare module '@microsoft/power-apps/data' {
export interface IOperationResult<T> {
data?: T;
error?: { message: string; code?: string };
}
export interface ConnectorOperation {
tableName: string;
operationName: string;
parameters?: unknown;
}
export interface ExecuteRequest {
connectorOperation: ConnectorOperation;
}
export interface IClient {
executeAsync<TParams, TResult>(request: ExecuteRequest): Promise<IOperationResult<TResult>>;
}
export function getClient(dataSourcesInfo: unknown): IClient;
}

View File

@@ -7,4 +7,9 @@ export default defineConfig({
host: true,
port: 5173,
},
build: {
rollupOptions: {
external: ['@microsoft/power-apps/data'],
},
},
});