@khester/create-dynamics-app 1.0.8 → 1.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/bin/create-dynamics-app.js +1 -1
- package/dist/index.js +140 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/consultingHelpers.d.ts +13 -0
- package/dist/utils/consultingHelpers.d.ts.map +1 -0
- package/dist/utils/consultingHelpers.js +569 -0
- package/dist/utils/consultingHelpers.js.map +1 -0
- package/dist/utils/copyTemplate.d.ts.map +1 -1
- package/dist/utils/copyTemplate.js.map +1 -1
- package/dist/utils/initGit.d.ts.map +1 -1
- package/dist/utils/initGit.js.map +1 -1
- package/dist/utils/installDependencies.d.ts.map +1 -1
- package/dist/utils/installDependencies.js +3 -2
- package/dist/utils/installDependencies.js.map +1 -1
- package/dist/utils/updatePackageJson.d.ts +1 -1
- package/dist/utils/updatePackageJson.d.ts.map +1 -1
- package/dist/utils/updatePackageJson.js +11 -1
- package/dist/utils/updatePackageJson.js.map +1 -1
- package/package.json +1 -1
- package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
- package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
- package/templates/dynamics-365-starter/README.md +566 -137
- package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
- package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
- package/templates/dynamics-365-starter/deployment/README.md +484 -0
- package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
- package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
- package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
- package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
- package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
- package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
- package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
- package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
- package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
- package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
- package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
- package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
- package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
- package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
- package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
- package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
- package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
- package/templates/dynamics-365-starter/package.json +22 -1
- package/templates/dynamics-365-starter/public/index.html +8 -11
- package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
- package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
- package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
- package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
- package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
- package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
- package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
- package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
- package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
- package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
- package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
- package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
- package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
- package/templates/dynamics-365-starter/src/examples/README.md +52 -0
- package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
- package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
- package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
- package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
- package/templates/dynamics-365-starter/src/index.tsx +107 -19
- package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
- package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
- package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
- package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
- package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
- package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
- package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
- package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
- package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
- package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
- package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
- package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
- package/templates/dynamics-365-starter/src/styles/index.css +74 -7
- package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
- package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
- package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
- package/templates/dynamics-365-starter/tsconfig.json +11 -8
- package/templates/dynamics-365-starter/webpack.config.js +8 -9
- package/templates/power-pages-starter/README.md +7 -1
- package/templates/power-pages-starter/public/index.html +8 -11
- package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
- package/templates/power-pages-starter/src/index.tsx +3 -3
- package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
- package/templates/power-pages-starter/tsconfig.json +3 -9
- package/templates/power-pages-starter/webpack.config.js +8 -3
|
@@ -3,7 +3,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
3
3
|
|
|
4
4
|
module.exports = (env, argv) => {
|
|
5
5
|
const isProduction = argv.mode === 'production';
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
return {
|
|
8
8
|
entry: './src/index.tsx',
|
|
9
9
|
output: {
|
|
@@ -11,8 +11,6 @@ module.exports = (env, argv) => {
|
|
|
11
11
|
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
|
|
12
12
|
clean: true,
|
|
13
13
|
publicPath: '/',
|
|
14
|
-
library: 'DynamicsApp',
|
|
15
|
-
libraryTarget: 'umd',
|
|
16
14
|
},
|
|
17
15
|
resolve: {
|
|
18
16
|
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
|
@@ -21,7 +19,12 @@ module.exports = (env, argv) => {
|
|
|
21
19
|
rules: [
|
|
22
20
|
{
|
|
23
21
|
test: /\.tsx?$/,
|
|
24
|
-
use:
|
|
22
|
+
use: {
|
|
23
|
+
loader: 'ts-loader',
|
|
24
|
+
options: {
|
|
25
|
+
transpileOnly: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
25
28
|
exclude: /node_modules/,
|
|
26
29
|
},
|
|
27
30
|
{
|
|
@@ -45,14 +48,10 @@ module.exports = (env, argv) => {
|
|
|
45
48
|
hot: true,
|
|
46
49
|
historyApiFallback: true,
|
|
47
50
|
},
|
|
48
|
-
externals: isProduction ? {
|
|
49
|
-
'react': 'React',
|
|
50
|
-
'react-dom': 'ReactDOM'
|
|
51
|
-
} : {},
|
|
52
51
|
optimization: {
|
|
53
52
|
splitChunks: {
|
|
54
53
|
chunks: 'all',
|
|
55
54
|
},
|
|
56
55
|
},
|
|
57
56
|
};
|
|
58
|
-
};
|
|
57
|
+
};
|
|
@@ -5,20 +5,25 @@ This is a Power Pages application built with Dynamics UI Kit components.
|
|
|
5
5
|
## Getting Started
|
|
6
6
|
|
|
7
7
|
1. **Environment Setup**
|
|
8
|
+
|
|
8
9
|
```bash
|
|
9
10
|
cp .env.example .env
|
|
10
11
|
```
|
|
12
|
+
|
|
11
13
|
Edit `.env` and set your `PORTAL_URL` to your Power Pages portal URL.
|
|
12
14
|
|
|
13
15
|
2. **Install Dependencies**
|
|
16
|
+
|
|
14
17
|
```bash
|
|
15
18
|
npm install
|
|
16
19
|
```
|
|
17
20
|
|
|
18
21
|
3. **Development**
|
|
22
|
+
|
|
19
23
|
```bash
|
|
20
24
|
npm run dev
|
|
21
25
|
```
|
|
26
|
+
|
|
22
27
|
This starts the development server at http://localhost:3000
|
|
23
28
|
|
|
24
29
|
4. **Build for Production**
|
|
@@ -52,6 +57,7 @@ src/
|
|
|
52
57
|
To deploy your application to Power Pages:
|
|
53
58
|
|
|
54
59
|
1. Build the application:
|
|
60
|
+
|
|
55
61
|
```bash
|
|
56
62
|
npm run build
|
|
57
63
|
```
|
|
@@ -86,4 +92,4 @@ To deploy your application to Power Pages:
|
|
|
86
92
|
|
|
87
93
|
- [Dynamics UI Kit Documentation](https://github.com/your-org/dynamics-ui-kit)
|
|
88
94
|
- [Power Pages Documentation](https://docs.microsoft.com/power-pages/)
|
|
89
|
-
- [React Documentation](https://reactjs.org/)
|
|
95
|
+
- [React Documentation](https://reactjs.org/)
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
3
|
+
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<link rel="icon" href="
|
|
5
|
+
<link rel="icon" href="data:," />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<meta name="theme-color" content="#
|
|
8
|
-
<meta
|
|
9
|
-
name="description"
|
|
10
|
-
content="Power Pages application built with Dynamics UI Kit"
|
|
11
|
-
/>
|
|
7
|
+
<meta name="theme-color" content="#000000" />
|
|
8
|
+
<meta name="description" content="Power Pages Application built with Dynamics UI Kit" />
|
|
12
9
|
<title>Power Pages App - Dynamics UI Kit</title>
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
15
12
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
16
13
|
<div id="root"></div>
|
|
17
|
-
|
|
18
|
-
</html>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
TextField,
|
|
5
|
-
Dropdown,
|
|
6
|
-
DatePicker
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
TextField,
|
|
5
|
+
Dropdown,
|
|
6
|
+
DatePicker,
|
|
7
7
|
} from '@khester/dynamics-ui-components';
|
|
8
8
|
import { usePowerPagesApi } from '../providers/PowerPagesProvider';
|
|
9
9
|
import './ContactForm.css';
|
|
@@ -20,19 +20,19 @@ interface ContactFormData {
|
|
|
20
20
|
const preferredContactOptions = [
|
|
21
21
|
{ key: 'email', text: 'Email' },
|
|
22
22
|
{ key: 'phone', text: 'Phone' },
|
|
23
|
-
{ key: 'mail', text: 'Mail' }
|
|
23
|
+
{ key: 'mail', text: 'Mail' },
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
export const ContactForm: React.FC = () => {
|
|
27
27
|
const { createRecord, isAuthenticated } = usePowerPagesApi();
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
const [formData, setFormData] = useState<ContactFormData>({
|
|
30
30
|
firstname: '',
|
|
31
31
|
lastname: '',
|
|
32
32
|
emailaddress1: '',
|
|
33
33
|
telephone1: '',
|
|
34
34
|
preferredcontactmethodcode: 1,
|
|
35
|
-
birthdate: null
|
|
35
|
+
birthdate: null,
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
const [errors, setErrors] = useState<Partial<ContactFormData>>({});
|
|
@@ -57,7 +57,10 @@ export const ContactForm: React.FC = () => {
|
|
|
57
57
|
newErrors.emailaddress1 = 'Please enter a valid email address';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
if (
|
|
60
|
+
if (
|
|
61
|
+
formData.telephone1 &&
|
|
62
|
+
!/^\+?[\d\s\-\(\)]+$/.test(formData.telephone1)
|
|
63
|
+
) {
|
|
61
64
|
newErrors.telephone1 = 'Please enter a valid phone number';
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -65,16 +68,19 @@ export const ContactForm: React.FC = () => {
|
|
|
65
68
|
return Object.keys(newErrors).length === 0;
|
|
66
69
|
}, [formData]);
|
|
67
70
|
|
|
68
|
-
const handleInputChange = useCallback(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
);
|
|
78
84
|
|
|
79
85
|
const handleSubmit = useCallback(async () => {
|
|
80
86
|
if (!isAuthenticated) {
|
|
@@ -97,11 +103,11 @@ export const ContactForm: React.FC = () => {
|
|
|
97
103
|
emailaddress1: formData.emailaddress1.trim(),
|
|
98
104
|
telephone1: formData.telephone1.trim() || null,
|
|
99
105
|
preferredcontactmethodcode: formData.preferredcontactmethodcode,
|
|
100
|
-
birthdate: formData.birthdate ? formData.birthdate.toISOString() : null
|
|
106
|
+
birthdate: formData.birthdate ? formData.birthdate.toISOString() : null,
|
|
101
107
|
};
|
|
102
108
|
|
|
103
109
|
await createRecord('contacts', contactData);
|
|
104
|
-
|
|
110
|
+
|
|
105
111
|
setSubmitSuccess(true);
|
|
106
112
|
// Reset form
|
|
107
113
|
setFormData({
|
|
@@ -110,24 +116,26 @@ export const ContactForm: React.FC = () => {
|
|
|
110
116
|
emailaddress1: '',
|
|
111
117
|
telephone1: '',
|
|
112
118
|
preferredcontactmethodcode: 1,
|
|
113
|
-
birthdate: null
|
|
119
|
+
birthdate: null,
|
|
114
120
|
});
|
|
115
121
|
} catch (error) {
|
|
116
|
-
setSubmitError(
|
|
122
|
+
setSubmitError(
|
|
123
|
+
error instanceof Error
|
|
124
|
+
? error.message
|
|
125
|
+
: 'An error occurred while saving'
|
|
126
|
+
);
|
|
117
127
|
} finally {
|
|
118
128
|
setIsSubmitting(false);
|
|
119
129
|
}
|
|
120
|
-
}, [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
handleInputChange('preferredcontactmethodcode', code);
|
|
130
|
-
}, [handleInputChange]);
|
|
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
|
+
);
|
|
131
139
|
|
|
132
140
|
return (
|
|
133
141
|
<div className="contact-form">
|
|
@@ -154,7 +162,9 @@ export const ContactForm: React.FC = () => {
|
|
|
154
162
|
label="First Name"
|
|
155
163
|
required
|
|
156
164
|
value={formData.firstname}
|
|
157
|
-
onChange={(_, value) =>
|
|
165
|
+
onChange={(_, value) =>
|
|
166
|
+
handleInputChange('firstname', value || '')
|
|
167
|
+
}
|
|
158
168
|
errorMessage={errors.firstname}
|
|
159
169
|
disabled={isSubmitting}
|
|
160
170
|
/>
|
|
@@ -164,7 +174,9 @@ export const ContactForm: React.FC = () => {
|
|
|
164
174
|
label="Last Name"
|
|
165
175
|
required
|
|
166
176
|
value={formData.lastname}
|
|
167
|
-
onChange={(_, value) =>
|
|
177
|
+
onChange={(_, value) =>
|
|
178
|
+
handleInputChange('lastname', value || '')
|
|
179
|
+
}
|
|
168
180
|
errorMessage={errors.lastname}
|
|
169
181
|
disabled={isSubmitting}
|
|
170
182
|
/>
|
|
@@ -178,7 +190,9 @@ export const ContactForm: React.FC = () => {
|
|
|
178
190
|
type="email"
|
|
179
191
|
required
|
|
180
192
|
value={formData.emailaddress1}
|
|
181
|
-
onChange={(_, value) =>
|
|
193
|
+
onChange={(_, value) =>
|
|
194
|
+
handleInputChange('emailaddress1', value || '')
|
|
195
|
+
}
|
|
182
196
|
errorMessage={errors.emailaddress1}
|
|
183
197
|
disabled={isSubmitting}
|
|
184
198
|
/>
|
|
@@ -188,7 +202,9 @@ export const ContactForm: React.FC = () => {
|
|
|
188
202
|
label="Phone Number"
|
|
189
203
|
type="tel"
|
|
190
204
|
value={formData.telephone1}
|
|
191
|
-
onChange={(_, value) =>
|
|
205
|
+
onChange={(_, value) =>
|
|
206
|
+
handleInputChange('telephone1', value || '')
|
|
207
|
+
}
|
|
192
208
|
errorMessage={errors.telephone1}
|
|
193
209
|
disabled={isSubmitting}
|
|
194
210
|
/>
|
|
@@ -201,8 +217,11 @@ export const ContactForm: React.FC = () => {
|
|
|
201
217
|
label="Preferred Contact Method"
|
|
202
218
|
options={preferredContactOptions}
|
|
203
219
|
selectedKey={
|
|
204
|
-
formData.preferredcontactmethodcode === 1
|
|
205
|
-
|
|
220
|
+
formData.preferredcontactmethodcode === 1
|
|
221
|
+
? 'email'
|
|
222
|
+
: formData.preferredcontactmethodcode === 2
|
|
223
|
+
? 'phone'
|
|
224
|
+
: 'mail'
|
|
206
225
|
}
|
|
207
226
|
onChange={handlePreferredContactChange}
|
|
208
227
|
disabled={isSubmitting}
|
|
@@ -236,4 +255,4 @@ export const ContactForm: React.FC = () => {
|
|
|
236
255
|
)}
|
|
237
256
|
</div>
|
|
238
257
|
);
|
|
239
|
-
};
|
|
258
|
+
};
|
|
@@ -12,11 +12,11 @@ const App: React.FC = () => {
|
|
|
12
12
|
<h1>Power Pages Contact Management</h1>
|
|
13
13
|
<p>Built with Dynamics UI Kit</p>
|
|
14
14
|
</header>
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
<main className="app-main">
|
|
17
17
|
<ContactForm />
|
|
18
18
|
</main>
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
<footer className="app-footer">
|
|
21
21
|
<p>© 2024 Your Organization. Powered by Dynamics UI Kit.</p>
|
|
22
22
|
</footer>
|
|
@@ -29,4 +29,4 @@ const container = document.getElementById('root');
|
|
|
29
29
|
if (!container) throw new Error('Failed to find the root element');
|
|
30
30
|
|
|
31
31
|
const root = createRoot(container);
|
|
32
|
-
root.render(<App />);
|
|
32
|
+
root.render(<App />);
|
|
@@ -5,35 +5,45 @@ interface PowerPagesContextType {
|
|
|
5
5
|
apiService: PowerPagesApiService | null;
|
|
6
6
|
isAuthenticated: boolean;
|
|
7
7
|
createRecord: (entityName: string, data: any) => Promise<any>;
|
|
8
|
-
retrieveRecord: (
|
|
8
|
+
retrieveRecord: (
|
|
9
|
+
entityName: string,
|
|
10
|
+
id: string,
|
|
11
|
+
select?: string
|
|
12
|
+
) => Promise<any>;
|
|
9
13
|
updateRecord: (entityName: string, id: string, data: any) => Promise<any>;
|
|
10
14
|
deleteRecord: (entityName: string, id: string) => Promise<void>;
|
|
11
15
|
retrieveMultiple: (entityName: string, query?: string) => Promise<any>;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
const PowerPagesContext = createContext<PowerPagesContextType | undefined>(
|
|
18
|
+
const PowerPagesContext = createContext<PowerPagesContextType | undefined>(
|
|
19
|
+
undefined
|
|
20
|
+
);
|
|
15
21
|
|
|
16
22
|
interface PowerPagesProviderProps {
|
|
17
23
|
children: React.ReactNode;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({
|
|
21
|
-
|
|
26
|
+
export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({
|
|
27
|
+
children,
|
|
28
|
+
}) => {
|
|
29
|
+
const [apiService, setApiService] = useState<PowerPagesApiService | null>(
|
|
30
|
+
null
|
|
31
|
+
);
|
|
22
32
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
23
33
|
|
|
24
34
|
useEffect(() => {
|
|
25
35
|
// Initialize the API service
|
|
26
|
-
const portalUrl =
|
|
27
|
-
|
|
36
|
+
const portalUrl = window.location.origin;
|
|
37
|
+
|
|
28
38
|
// CSRF token function for Power Pages authentication
|
|
29
39
|
const getCsrfToken = async (): Promise<string> => {
|
|
30
40
|
// In a real Power Pages implementation, you would fetch the CSRF token
|
|
31
41
|
// For demo purposes, return empty string
|
|
32
42
|
return '';
|
|
33
43
|
};
|
|
34
|
-
|
|
44
|
+
|
|
35
45
|
const service = new PowerPagesApiService(portalUrl, getCsrfToken);
|
|
36
|
-
|
|
46
|
+
|
|
37
47
|
setApiService(service);
|
|
38
48
|
|
|
39
49
|
// Check authentication status
|
|
@@ -47,11 +57,19 @@ export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({ children
|
|
|
47
57
|
return await apiService.createRecord(entityName, data);
|
|
48
58
|
};
|
|
49
59
|
|
|
50
|
-
const retrieveRecord = async (
|
|
60
|
+
const retrieveRecord = async (
|
|
61
|
+
entityName: string,
|
|
62
|
+
id: string,
|
|
63
|
+
select?: string
|
|
64
|
+
) => {
|
|
51
65
|
if (!apiService) throw new Error('API service not initialized');
|
|
52
66
|
// Use retrieveMultipleRecords with FetchXML to get a single record
|
|
53
|
-
const selectAttributes = select
|
|
54
|
-
|
|
67
|
+
const selectAttributes = select
|
|
68
|
+
? select.split(',').map((attr) => attr.trim())
|
|
69
|
+
: ['*'];
|
|
70
|
+
const attributes = selectAttributes
|
|
71
|
+
.map((attr) => (attr === '*' ? '' : `<attribute name="${attr}" />`))
|
|
72
|
+
.join('');
|
|
55
73
|
const fetchXml = `
|
|
56
74
|
<fetch top="1">
|
|
57
75
|
<entity name="${entityName}">
|
|
@@ -62,7 +80,10 @@ export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({ children
|
|
|
62
80
|
</entity>
|
|
63
81
|
</fetch>
|
|
64
82
|
`;
|
|
65
|
-
const result = await apiService.retrieveMultipleRecords(
|
|
83
|
+
const result = await apiService.retrieveMultipleRecords(
|
|
84
|
+
entityName,
|
|
85
|
+
fetchXml
|
|
86
|
+
);
|
|
66
87
|
return result.entities.length > 0 ? result.entities[0] : null;
|
|
67
88
|
};
|
|
68
89
|
|
|
@@ -80,36 +101,36 @@ export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({ children
|
|
|
80
101
|
if (!apiService) throw new Error('API service not initialized');
|
|
81
102
|
// Convert OData-style query to FetchXML
|
|
82
103
|
let fetchXml = `<fetch>`;
|
|
83
|
-
|
|
104
|
+
|
|
84
105
|
if (query) {
|
|
85
106
|
// Parse basic OData query parameters
|
|
86
107
|
const selectMatch = query.match(/\$select=([^&]*)/i);
|
|
87
108
|
const orderByMatch = query.match(/\$orderby=([^&]*)/i);
|
|
88
109
|
const topMatch = query.match(/\$top=(\d+)/i);
|
|
89
|
-
|
|
110
|
+
|
|
90
111
|
if (topMatch) {
|
|
91
112
|
fetchXml = `<fetch top="${topMatch[1]}">`;
|
|
92
113
|
}
|
|
93
|
-
|
|
114
|
+
|
|
94
115
|
fetchXml += `<entity name="${entityName}">`;
|
|
95
|
-
|
|
116
|
+
|
|
96
117
|
if (selectMatch) {
|
|
97
|
-
const attributes = selectMatch[1].split(',').map(attr => attr.trim());
|
|
98
|
-
attributes.forEach(attr => {
|
|
118
|
+
const attributes = selectMatch[1].split(',').map((attr) => attr.trim());
|
|
119
|
+
attributes.forEach((attr) => {
|
|
99
120
|
fetchXml += `<attribute name="${attr}" />`;
|
|
100
121
|
});
|
|
101
122
|
}
|
|
102
|
-
|
|
123
|
+
|
|
103
124
|
if (orderByMatch) {
|
|
104
125
|
const [field, direction] = orderByMatch[1].split(' ');
|
|
105
126
|
fetchXml += `<order attribute="${field.trim()}" descending="${direction?.toLowerCase() === 'desc'}" />`;
|
|
106
127
|
}
|
|
107
|
-
|
|
128
|
+
|
|
108
129
|
fetchXml += `</entity></fetch>`;
|
|
109
130
|
} else {
|
|
110
131
|
fetchXml = `<fetch><entity name="${entityName}"></entity></fetch>`;
|
|
111
132
|
}
|
|
112
|
-
|
|
133
|
+
|
|
113
134
|
return await apiService.retrieveMultipleRecords(entityName, fetchXml);
|
|
114
135
|
};
|
|
115
136
|
|
|
@@ -133,7 +154,9 @@ export const PowerPagesProvider: React.FC<PowerPagesProviderProps> = ({ children
|
|
|
133
154
|
export const usePowerPagesApi = (): PowerPagesContextType => {
|
|
134
155
|
const context = useContext(PowerPagesContext);
|
|
135
156
|
if (context === undefined) {
|
|
136
|
-
throw new Error(
|
|
157
|
+
throw new Error(
|
|
158
|
+
'usePowerPagesApi must be used within a PowerPagesProvider'
|
|
159
|
+
);
|
|
137
160
|
}
|
|
138
161
|
return context;
|
|
139
|
-
};
|
|
162
|
+
};
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "es5",
|
|
4
|
-
"lib": [
|
|
5
|
-
"dom",
|
|
6
|
-
"dom.iterable",
|
|
7
|
-
"es6"
|
|
8
|
-
],
|
|
4
|
+
"lib": ["dom", "dom.iterable", "es6"],
|
|
9
5
|
"allowJs": true,
|
|
10
6
|
"skipLibCheck": true,
|
|
11
7
|
"esModuleInterop": true,
|
|
@@ -20,7 +16,5 @@
|
|
|
20
16
|
"noEmit": true,
|
|
21
17
|
"jsx": "react-jsx"
|
|
22
18
|
},
|
|
23
|
-
"include": [
|
|
24
|
-
|
|
25
|
-
]
|
|
26
|
-
}
|
|
19
|
+
"include": ["src"]
|
|
20
|
+
}
|
|
@@ -3,7 +3,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
3
3
|
|
|
4
4
|
module.exports = (env, argv) => {
|
|
5
5
|
const isProduction = argv.mode === 'production';
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
return {
|
|
8
8
|
entry: './src/index.tsx',
|
|
9
9
|
output: {
|
|
@@ -19,7 +19,12 @@ module.exports = (env, argv) => {
|
|
|
19
19
|
rules: [
|
|
20
20
|
{
|
|
21
21
|
test: /\.tsx?$/,
|
|
22
|
-
use:
|
|
22
|
+
use: {
|
|
23
|
+
loader: 'ts-loader',
|
|
24
|
+
options: {
|
|
25
|
+
transpileOnly: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
23
28
|
exclude: /node_modules/,
|
|
24
29
|
},
|
|
25
30
|
{
|
|
@@ -49,4 +54,4 @@ module.exports = (env, argv) => {
|
|
|
49
54
|
},
|
|
50
55
|
},
|
|
51
56
|
};
|
|
52
|
-
};
|
|
57
|
+
};
|