@khester/create-dynamics-app 2.10.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -3
- package/dist/artifacts/registry.d.ts +7 -5
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +82 -27
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +15 -8
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +9 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/templatePlan.d.ts.map +1 -1
- package/dist/templatePlan.js +20 -3
- package/dist/templatePlan.js.map +1 -1
- package/dist/utils/copyTemplate.d.ts +1 -1
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js +5 -1
- package/dist/utils/copyTemplate.js.map +1 -1
- package/package.json +1 -1
- package/templates/_shared/dataverse-seam/XrmApiService.ts +26 -5
- package/templates/host-swap/README.md +57 -2
- package/templates/host-swap/_variants/host.custom-page/src/App.tsx +9 -5
- package/templates/host-swap/_variants/host.pcf/ControlManifest.Input.xml +26 -2
- package/templates/host-swap/_variants/host.pcf/gitignore +7 -0
- package/templates/host-swap/_variants/host.pcf/index.ts +21 -3
- package/templates/host-swap/_variants/host.pcf/package.json +20 -4
- package/templates/host-swap/_variants/host.pcf/{{componentName}}.pcfproj +67 -0
- package/templates/host-swap/_variants/host.web-resource/src/App.tsx +5 -1
- package/templates/host-swap/src/Component.tsx +54 -7
- package/templates/host-swap/src/useHostWidth.ts +53 -0
- package/templates/pcf-dataset/dev/harness/dataverse.ts +82 -0
- package/templates/pcf-dataset/dev/index.html +20 -0
- package/templates/pcf-dataset/dev/main.tsx +190 -0
- package/templates/pcf-dataset/dev/vite-env.d.ts +1 -0
- package/templates/pcf-dataset/env.local.example +24 -0
- package/templates/pcf-dataset/eslintrc.json +11 -0
- package/templates/pcf-dataset/gitignore +4 -0
- package/templates/pcf-dataset/package.json +18 -8
- package/templates/pcf-dataset/pcfconfig.json +3 -0
- package/templates/pcf-dataset/tsconfig.dev.json +15 -0
- package/templates/pcf-dataset/tsconfig.json +11 -2
- package/templates/pcf-dataset/vite.config.ts +63 -0
- package/templates/pcf-dataset/{{componentName}}/ControlManifest.Input.xml +45 -0
- package/templates/pcf-dataset/{{componentName}}/{{componentName}}App.tsx +64 -0
- package/templates/pcf-dataset/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +9 -46
- package/templates/pcf-field/eslintrc.json +11 -0
- package/templates/pcf-field/package.json +1 -0
- package/templates/pcf-field/pcfconfig.json +3 -0
- package/templates/pcf-field/{{componentName}}/ControlManifest.Input.xml +42 -0
- package/templates/power-pages-island/README.md +93 -0
- package/templates/power-pages-island/package.json +27 -0
- package/templates/power-pages-island/public/index.html +13 -0
- package/templates/power-pages-island/src/App.tsx +37 -0
- package/templates/power-pages-island/src/Component.tsx +136 -0
- package/templates/power-pages-island/src/core/services/MockApiService.ts +58 -0
- package/templates/power-pages-island/src/core/services/portalSeam.assert.ts +14 -0
- package/templates/power-pages-island/src/island-env.d.ts +12 -0
- package/templates/power-pages-island/src/islands.tsx +38 -0
- package/templates/power-pages-island/src/useHostWidth.ts +53 -0
- package/templates/power-pages-island/tsconfig.json +15 -0
- package/templates/power-pages-island/webpack.config.js +22 -0
- package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +8 -2
- package/templates/pcf-dataset/ControlManifest.Input.xml +0 -16
- package/templates/pcf-field/ControlManifest.Input.xml +0 -17
- package/templates/power-pages-starter/.env.example +0 -6
- package/templates/power-pages-starter/README.md +0 -95
- package/templates/power-pages-starter/package.json +0 -43
- package/templates/power-pages-starter/public/index.html +0 -15
- package/templates/power-pages-starter/src/components/ContactForm.css +0 -84
- package/templates/power-pages-starter/src/components/ContactForm.tsx +0 -258
- package/templates/power-pages-starter/src/index.tsx +0 -32
- package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +0 -162
- package/templates/power-pages-starter/src/styles/index.css +0 -76
- package/templates/power-pages-starter/tsconfig.json +0 -20
- package/templates/power-pages-starter/webpack.config.js +0 -57
- package/templates/pcf-dataset/{index.ts → {{componentName}}/index.ts} +0 -0
- package/templates/pcf-dataset/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
- package/templates/pcf-field/{index.ts → {{componentName}}/index.ts} +0 -0
- package/templates/pcf-field/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
- package/templates/pcf-field/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +0 -0
- package/templates/{power-pages-starter → power-pages-island}/gitignore +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
3
|
+
|
|
4
|
+
// A Power Pages ISLAND: one self-contained bundle, uploaded as a portal WEB FILE and
|
|
5
|
+
// referenced from a Liquid page (see README). The output filename is STABLE (no
|
|
6
|
+
// contenthash) so the Liquid `<script src>` URL stays fixed across rebuilds.
|
|
7
|
+
// HtmlWebpackPlugin exists only for the local `webpack serve` preview — on a real
|
|
8
|
+
// portal the Liquid page is the host, so the emitted index.html is never uploaded.
|
|
9
|
+
module.exports = {
|
|
10
|
+
entry: './src/islands.tsx',
|
|
11
|
+
output: {
|
|
12
|
+
path: path.resolve(__dirname, 'dist'),
|
|
13
|
+
filename: '{{projectName}}.js',
|
|
14
|
+
clean: true,
|
|
15
|
+
},
|
|
16
|
+
resolve: { extensions: ['.ts', '.tsx', '.js'] },
|
|
17
|
+
module: {
|
|
18
|
+
rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
|
|
19
|
+
},
|
|
20
|
+
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
|
|
21
|
+
devServer: { static: './dist', port: 8080, hot: true },
|
|
22
|
+
};
|
|
@@ -107,8 +107,14 @@ async function main() {
|
|
|
107
107
|
await req("POST", "PublishXml", { ParameterXml: paramXml });
|
|
108
108
|
console.log("✓ Published.");
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// Surfacing guidance. NOTE: this app reads window.parent.Xrm / Xrm.Page — a model-driven FORM
|
|
111
|
+
// context. It therefore needs a host that provides one. It CANNOT run in a Power Apps custom page
|
|
112
|
+
// (that is a canvasapp; Xrm.Page does not exist there), and opening the raw /WebResources/ URL
|
|
113
|
+
// standalone gives it no parent Xrm either, so ServiceFactory throws.
|
|
114
|
+
console.log(`\nSurface it via a sitemap SubArea: $webresource:${name}`);
|
|
115
|
+
console.log("Or embed it on a model-driven form, or open it with Xrm.Navigation.navigateTo.");
|
|
116
|
+
console.log(`\nDirect URL (dev only — no parent Xrm, so production mode will fail):`);
|
|
117
|
+
console.log(` ${url}/WebResources/${name}`);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
main().catch((e) => {
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
-
<manifest>
|
|
3
|
-
<control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="virtual">
|
|
4
|
-
<data-set name="dataset" display-name-key="Dataset_Display_Key">
|
|
5
|
-
</data-set>
|
|
6
|
-
<resources>
|
|
7
|
-
<code path="index.ts" order="1"/>
|
|
8
|
-
<platform-library name="React" version="18.2.0" />
|
|
9
|
-
<platform-library name="Fluent" version="8.115.6" />
|
|
10
|
-
<resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
|
|
11
|
-
</resources>
|
|
12
|
-
<feature-usage>
|
|
13
|
-
<uses-feature name="Utility" required="true" />
|
|
14
|
-
</feature-usage>
|
|
15
|
-
</control>
|
|
16
|
-
</manifest>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
-
<manifest>
|
|
3
|
-
<control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="standard">
|
|
4
|
-
<property name="value" display-name-key="Value" description-key="Value_Desc" of-type="{{propertyType}}" usage="bound" required="true" />
|
|
5
|
-
<property name="placeholder" display-name-key="Placeholder" description-key="Placeholder_Desc" of-type="SingleLine.Text" usage="input" required="false" />
|
|
6
|
-
<property name="disabled" display-name-key="Disabled" description-key="Disabled_Desc" of-type="TwoOptions" usage="input" required="false" default-value="false" />
|
|
7
|
-
<resources>
|
|
8
|
-
<code path="index.ts" order="1"/>
|
|
9
|
-
<platform-library name="React" version="16.8.6" />
|
|
10
|
-
<platform-library name="Fluent" version="8.29.0" />
|
|
11
|
-
<resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
|
|
12
|
-
</resources>
|
|
13
|
-
<feature-usage>
|
|
14
|
-
<uses-feature name="Utility" required="true" />
|
|
15
|
-
</feature-usage>
|
|
16
|
-
</control>
|
|
17
|
-
</manifest>
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Power Pages Application
|
|
2
|
-
|
|
3
|
-
This is a Power Pages application built with Dynamics UI Kit components.
|
|
4
|
-
|
|
5
|
-
## Getting Started
|
|
6
|
-
|
|
7
|
-
1. **Environment Setup**
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
cp .env.example .env
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Edit `.env` and set your `PORTAL_URL` to your Power Pages portal URL.
|
|
14
|
-
|
|
15
|
-
2. **Install Dependencies**
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
3. **Development**
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm run dev
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
This starts the development server at http://localhost:3000
|
|
28
|
-
|
|
29
|
-
4. **Build for Production**
|
|
30
|
-
```bash
|
|
31
|
-
npm run build
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Project Structure
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
src/
|
|
38
|
-
├── components/ # React components
|
|
39
|
-
│ ├── ContactForm.tsx # Main contact form component
|
|
40
|
-
│ └── ContactForm.css # Component styles
|
|
41
|
-
├── providers/ # Context providers
|
|
42
|
-
│ └── PowerPagesProvider.tsx # Power Pages API provider
|
|
43
|
-
├── styles/ # Global styles
|
|
44
|
-
│ └── index.css # Global CSS
|
|
45
|
-
└── index.tsx # Application entry point
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Features
|
|
49
|
-
|
|
50
|
-
- **Contact Management**: Create and manage contacts using Dynamics UI Kit components
|
|
51
|
-
- **Power Pages Integration**: Seamless integration with Power Pages API
|
|
52
|
-
- **Responsive Design**: Mobile-friendly responsive layout
|
|
53
|
-
- **Type Safety**: Full TypeScript support
|
|
54
|
-
|
|
55
|
-
## Deployment
|
|
56
|
-
|
|
57
|
-
To deploy your application to Power Pages:
|
|
58
|
-
|
|
59
|
-
1. Build the application:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
npm run build
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
2. Upload the `dist` folder contents to your Power Pages portal's web files
|
|
66
|
-
|
|
67
|
-
3. Create a new web page in Power Pages and reference your built JavaScript and CSS files
|
|
68
|
-
|
|
69
|
-
## Customization
|
|
70
|
-
|
|
71
|
-
### Adding New Components
|
|
72
|
-
|
|
73
|
-
1. Create your component in `src/components/`
|
|
74
|
-
2. Use Dynamics UI Kit components for consistent styling
|
|
75
|
-
3. Import and use the `usePowerPagesApi` hook for data operations
|
|
76
|
-
|
|
77
|
-
### Styling
|
|
78
|
-
|
|
79
|
-
- Global styles are in `src/styles/index.css`
|
|
80
|
-
- Component-specific styles should be co-located with components
|
|
81
|
-
- Follow the existing CSS naming conventions
|
|
82
|
-
|
|
83
|
-
## Available Scripts
|
|
84
|
-
|
|
85
|
-
- `npm run dev` - Start development server
|
|
86
|
-
- `npm run build` - Build for production
|
|
87
|
-
- `npm run typecheck` - Type check without building
|
|
88
|
-
- `npm run clean` - Clean build directory
|
|
89
|
-
- `npm run lint` - Lint code
|
|
90
|
-
|
|
91
|
-
## Learn More
|
|
92
|
-
|
|
93
|
-
- [Dynamics UI Kit Documentation](https://github.com/your-org/dynamics-ui-kit)
|
|
94
|
-
- [Power Pages Documentation](https://docs.microsoft.com/power-pages/)
|
|
95
|
-
- [React Documentation](https://reactjs.org/)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "power-pages-app",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Power Pages application built with Dynamics UI Kit",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"build": "webpack --mode=production",
|
|
8
|
-
"dev": "webpack serve --mode=development",
|
|
9
|
-
"typecheck": "tsc --noEmit",
|
|
10
|
-
"clean": "rimraf dist",
|
|
11
|
-
"lint": "eslint src --ext .ts,.tsx"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@khester/dynamics-ui-api-client": "^1.0.0",
|
|
15
|
-
"@khester/dynamics-ui-components": "^1.0.0",
|
|
16
|
-
"react": "^18.2.0",
|
|
17
|
-
"react-dom": "^18.2.0"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/react": "^18.2.0",
|
|
21
|
-
"@types/react-dom": "^18.2.0",
|
|
22
|
-
"css-loader": "^6.8.1",
|
|
23
|
-
"html-webpack-plugin": "^5.5.3",
|
|
24
|
-
"style-loader": "^3.3.3",
|
|
25
|
-
"ts-loader": "^9.5.1",
|
|
26
|
-
"typescript": "^5.3.3",
|
|
27
|
-
"webpack": "^5.89.0",
|
|
28
|
-
"webpack-cli": "^5.1.4",
|
|
29
|
-
"webpack-dev-server": "^4.15.1"
|
|
30
|
-
},
|
|
31
|
-
"browserslist": {
|
|
32
|
-
"production": [
|
|
33
|
-
">0.2%",
|
|
34
|
-
"not dead",
|
|
35
|
-
"not op_mini all"
|
|
36
|
-
],
|
|
37
|
-
"development": [
|
|
38
|
-
"last 1 chrome version",
|
|
39
|
-
"last 1 firefox version",
|
|
40
|
-
"last 1 safari version"
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<link rel="icon" href="data:," />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<meta name="theme-color" content="#000000" />
|
|
8
|
-
<meta name="description" content="Power Pages Application built with Dynamics UI Kit" />
|
|
9
|
-
<title>Power Pages App - Dynamics UI Kit</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
13
|
-
<div id="root"></div>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
.contact-form {
|
|
2
|
-
max-width: 800px;
|
|
3
|
-
margin: 0 auto;
|
|
4
|
-
padding: 24px;
|
|
5
|
-
background: #ffffff;
|
|
6
|
-
border-radius: 8px;
|
|
7
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.contact-form__header {
|
|
11
|
-
margin-bottom: 24px;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.contact-form__title {
|
|
15
|
-
margin: 0;
|
|
16
|
-
font-size: 24px;
|
|
17
|
-
font-weight: 600;
|
|
18
|
-
color: #323130;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.contact-form__content {
|
|
22
|
-
margin-bottom: 24px;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.contact-form__row {
|
|
26
|
-
display: grid;
|
|
27
|
-
grid-template-columns: 1fr 1fr;
|
|
28
|
-
gap: 16px;
|
|
29
|
-
margin-bottom: 16px;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.contact-form__field {
|
|
33
|
-
display: flex;
|
|
34
|
-
flex-direction: column;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.contact-form__actions {
|
|
38
|
-
display: flex;
|
|
39
|
-
gap: 12px;
|
|
40
|
-
justify-content: flex-start;
|
|
41
|
-
margin-top: 24px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.contact-form__success {
|
|
45
|
-
padding: 12px 16px;
|
|
46
|
-
margin-bottom: 16px;
|
|
47
|
-
background-color: #dff6dd;
|
|
48
|
-
border: 1px solid #107c10;
|
|
49
|
-
border-radius: 4px;
|
|
50
|
-
color: #107c10;
|
|
51
|
-
font-weight: 500;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.contact-form__error {
|
|
55
|
-
padding: 12px 16px;
|
|
56
|
-
margin-bottom: 16px;
|
|
57
|
-
background-color: #fef7f1;
|
|
58
|
-
border: 1px solid #d13438;
|
|
59
|
-
border-radius: 4px;
|
|
60
|
-
color: #d13438;
|
|
61
|
-
font-weight: 500;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.contact-form__auth-notice {
|
|
65
|
-
margin-top: 16px;
|
|
66
|
-
padding: 12px 16px;
|
|
67
|
-
background-color: #fff4ce;
|
|
68
|
-
border: 1px solid #ffb900;
|
|
69
|
-
border-radius: 4px;
|
|
70
|
-
color: #8a6d00;
|
|
71
|
-
text-align: center;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@media (max-width: 768px) {
|
|
75
|
-
.contact-form {
|
|
76
|
-
padding: 16px;
|
|
77
|
-
margin: 16px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.contact-form__row {
|
|
81
|
-
grid-template-columns: 1fr;
|
|
82
|
-
gap: 12px;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
TextField,
|
|
5
|
-
Dropdown,
|
|
6
|
-
DatePicker,
|
|
7
|
-
} from '@khester/dynamics-ui-components';
|
|
8
|
-
import { usePowerPagesApi } from '../providers/PowerPagesProvider';
|
|
9
|
-
import './ContactForm.css';
|
|
10
|
-
|
|
11
|
-
interface ContactFormData {
|
|
12
|
-
firstname: string;
|
|
13
|
-
lastname: string;
|
|
14
|
-
emailaddress1: string;
|
|
15
|
-
telephone1: string;
|
|
16
|
-
preferredcontactmethodcode: number;
|
|
17
|
-
birthdate: Date | null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const preferredContactOptions = [
|
|
21
|
-
{ key: 'email', text: 'Email' },
|
|
22
|
-
{ key: 'phone', text: 'Phone' },
|
|
23
|
-
{ key: 'mail', text: 'Mail' },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export const ContactForm: React.FC = () => {
|
|
27
|
-
const { createRecord, isAuthenticated } = usePowerPagesApi();
|
|
28
|
-
|
|
29
|
-
const [formData, setFormData] = useState<ContactFormData>({
|
|
30
|
-
firstname: '',
|
|
31
|
-
lastname: '',
|
|
32
|
-
emailaddress1: '',
|
|
33
|
-
telephone1: '',
|
|
34
|
-
preferredcontactmethodcode: 1,
|
|
35
|
-
birthdate: null,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const [errors, setErrors] = useState<Partial<ContactFormData>>({});
|
|
39
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
40
|
-
const [submitError, setSubmitError] = useState<string>('');
|
|
41
|
-
const [submitSuccess, setSubmitSuccess] = useState(false);
|
|
42
|
-
|
|
43
|
-
const validateForm = useCallback((): boolean => {
|
|
44
|
-
const newErrors: Partial<ContactFormData> = {};
|
|
45
|
-
|
|
46
|
-
if (!formData.firstname.trim()) {
|
|
47
|
-
newErrors.firstname = 'First name is required';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!formData.lastname.trim()) {
|
|
51
|
-
newErrors.lastname = 'Last name is required';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!formData.emailaddress1.trim()) {
|
|
55
|
-
newErrors.emailaddress1 = 'Email address is required';
|
|
56
|
-
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.emailaddress1)) {
|
|
57
|
-
newErrors.emailaddress1 = 'Please enter a valid email address';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
formData.telephone1 &&
|
|
62
|
-
!/^\+?[\d\s\-\(\)]+$/.test(formData.telephone1)
|
|
63
|
-
) {
|
|
64
|
-
newErrors.telephone1 = 'Please enter a valid phone number';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
setErrors(newErrors);
|
|
68
|
-
return Object.keys(newErrors).length === 0;
|
|
69
|
-
}, [formData]);
|
|
70
|
-
|
|
71
|
-
const handleInputChange = useCallback(
|
|
72
|
-
(field: keyof ContactFormData, value: any) => {
|
|
73
|
-
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
74
|
-
|
|
75
|
-
// Clear error for this field when user starts typing
|
|
76
|
-
if (errors[field]) {
|
|
77
|
-
setErrors((prev) => ({ ...prev, [field]: undefined }));
|
|
78
|
-
}
|
|
79
|
-
setSubmitError('');
|
|
80
|
-
setSubmitSuccess(false);
|
|
81
|
-
},
|
|
82
|
-
[errors]
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const handleSubmit = useCallback(async () => {
|
|
86
|
-
if (!isAuthenticated) {
|
|
87
|
-
setSubmitError('You must be signed in to save contact information');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!validateForm()) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
setIsSubmitting(true);
|
|
96
|
-
setSubmitError('');
|
|
97
|
-
setSubmitSuccess(false);
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const contactData = {
|
|
101
|
-
firstname: formData.firstname.trim(),
|
|
102
|
-
lastname: formData.lastname.trim(),
|
|
103
|
-
emailaddress1: formData.emailaddress1.trim(),
|
|
104
|
-
telephone1: formData.telephone1.trim() || null,
|
|
105
|
-
preferredcontactmethodcode: formData.preferredcontactmethodcode,
|
|
106
|
-
birthdate: formData.birthdate ? formData.birthdate.toISOString() : null,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
await createRecord('contacts', contactData);
|
|
110
|
-
|
|
111
|
-
setSubmitSuccess(true);
|
|
112
|
-
// Reset form
|
|
113
|
-
setFormData({
|
|
114
|
-
firstname: '',
|
|
115
|
-
lastname: '',
|
|
116
|
-
emailaddress1: '',
|
|
117
|
-
telephone1: '',
|
|
118
|
-
preferredcontactmethodcode: 1,
|
|
119
|
-
birthdate: null,
|
|
120
|
-
});
|
|
121
|
-
} catch (error) {
|
|
122
|
-
setSubmitError(
|
|
123
|
-
error instanceof Error
|
|
124
|
-
? error.message
|
|
125
|
-
: 'An error occurred while saving'
|
|
126
|
-
);
|
|
127
|
-
} finally {
|
|
128
|
-
setIsSubmitting(false);
|
|
129
|
-
}
|
|
130
|
-
}, [formData, isAuthenticated, validateForm, createRecord]);
|
|
131
|
-
|
|
132
|
-
const handlePreferredContactChange = useCallback(
|
|
133
|
-
(option: any) => {
|
|
134
|
-
const code = option.key === 'email' ? 1 : option.key === 'phone' ? 2 : 3;
|
|
135
|
-
handleInputChange('preferredcontactmethodcode', code);
|
|
136
|
-
},
|
|
137
|
-
[handleInputChange]
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className="contact-form">
|
|
142
|
-
<div className="contact-form__header">
|
|
143
|
-
<h2 className="contact-form__title">Create New Contact</h2>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<div className="contact-form__content">
|
|
147
|
-
{submitSuccess && (
|
|
148
|
-
<div className="contact-form__success" role="alert">
|
|
149
|
-
Contact created successfully!
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{submitError && (
|
|
154
|
-
<div className="contact-form__error" role="alert">
|
|
155
|
-
{submitError}
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
|
|
159
|
-
<div className="contact-form__row">
|
|
160
|
-
<div className="contact-form__field">
|
|
161
|
-
<TextField
|
|
162
|
-
label="First Name"
|
|
163
|
-
required
|
|
164
|
-
value={formData.firstname}
|
|
165
|
-
onChange={(_, value) =>
|
|
166
|
-
handleInputChange('firstname', value || '')
|
|
167
|
-
}
|
|
168
|
-
errorMessage={errors.firstname}
|
|
169
|
-
disabled={isSubmitting}
|
|
170
|
-
/>
|
|
171
|
-
</div>
|
|
172
|
-
<div className="contact-form__field">
|
|
173
|
-
<TextField
|
|
174
|
-
label="Last Name"
|
|
175
|
-
required
|
|
176
|
-
value={formData.lastname}
|
|
177
|
-
onChange={(_, value) =>
|
|
178
|
-
handleInputChange('lastname', value || '')
|
|
179
|
-
}
|
|
180
|
-
errorMessage={errors.lastname}
|
|
181
|
-
disabled={isSubmitting}
|
|
182
|
-
/>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
<div className="contact-form__row">
|
|
187
|
-
<div className="contact-form__field">
|
|
188
|
-
<TextField
|
|
189
|
-
label="Email Address"
|
|
190
|
-
type="email"
|
|
191
|
-
required
|
|
192
|
-
value={formData.emailaddress1}
|
|
193
|
-
onChange={(_, value) =>
|
|
194
|
-
handleInputChange('emailaddress1', value || '')
|
|
195
|
-
}
|
|
196
|
-
errorMessage={errors.emailaddress1}
|
|
197
|
-
disabled={isSubmitting}
|
|
198
|
-
/>
|
|
199
|
-
</div>
|
|
200
|
-
<div className="contact-form__field">
|
|
201
|
-
<TextField
|
|
202
|
-
label="Phone Number"
|
|
203
|
-
type="tel"
|
|
204
|
-
value={formData.telephone1}
|
|
205
|
-
onChange={(_, value) =>
|
|
206
|
-
handleInputChange('telephone1', value || '')
|
|
207
|
-
}
|
|
208
|
-
errorMessage={errors.telephone1}
|
|
209
|
-
disabled={isSubmitting}
|
|
210
|
-
/>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<div className="contact-form__row">
|
|
215
|
-
<div className="contact-form__field">
|
|
216
|
-
<Dropdown
|
|
217
|
-
label="Preferred Contact Method"
|
|
218
|
-
options={preferredContactOptions}
|
|
219
|
-
selectedKey={
|
|
220
|
-
formData.preferredcontactmethodcode === 1
|
|
221
|
-
? 'email'
|
|
222
|
-
: formData.preferredcontactmethodcode === 2
|
|
223
|
-
? 'phone'
|
|
224
|
-
: 'mail'
|
|
225
|
-
}
|
|
226
|
-
onChange={handlePreferredContactChange}
|
|
227
|
-
disabled={isSubmitting}
|
|
228
|
-
/>
|
|
229
|
-
</div>
|
|
230
|
-
<div className="contact-form__field">
|
|
231
|
-
<DatePicker
|
|
232
|
-
label="Birth Date"
|
|
233
|
-
value={formData.birthdate || undefined}
|
|
234
|
-
onSelectDate={(date) => handleInputChange('birthdate', date)}
|
|
235
|
-
disabled={isSubmitting}
|
|
236
|
-
placeholder="Select date..."
|
|
237
|
-
/>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<div className="contact-form__actions">
|
|
243
|
-
<Button
|
|
244
|
-
text="Save Contact"
|
|
245
|
-
variant="primary"
|
|
246
|
-
onClick={handleSubmit}
|
|
247
|
-
disabled={isSubmitting || !isAuthenticated}
|
|
248
|
-
/>
|
|
249
|
-
</div>
|
|
250
|
-
|
|
251
|
-
{!isAuthenticated && (
|
|
252
|
-
<div className="contact-form__auth-notice">
|
|
253
|
-
Please sign in to save contact information.
|
|
254
|
-
</div>
|
|
255
|
-
)}
|
|
256
|
-
</div>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { ContactForm } from './components/ContactForm';
|
|
4
|
-
import { PowerPagesProvider } from './providers/PowerPagesProvider';
|
|
5
|
-
import './styles/index.css';
|
|
6
|
-
|
|
7
|
-
const App: React.FC = () => {
|
|
8
|
-
return (
|
|
9
|
-
<PowerPagesProvider>
|
|
10
|
-
<div className="app">
|
|
11
|
-
<header className="app-header">
|
|
12
|
-
<h1>Power Pages Contact Management</h1>
|
|
13
|
-
<p>Built with Dynamics UI Kit</p>
|
|
14
|
-
</header>
|
|
15
|
-
|
|
16
|
-
<main className="app-main">
|
|
17
|
-
<ContactForm />
|
|
18
|
-
</main>
|
|
19
|
-
|
|
20
|
-
<footer className="app-footer">
|
|
21
|
-
<p>© 2024 Your Organization. Powered by Dynamics UI Kit.</p>
|
|
22
|
-
</footer>
|
|
23
|
-
</div>
|
|
24
|
-
</PowerPagesProvider>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const container = document.getElementById('root');
|
|
29
|
-
if (!container) throw new Error('Failed to find the root element');
|
|
30
|
-
|
|
31
|
-
const root = createRoot(container);
|
|
32
|
-
root.render(<App />);
|