@seeka-labs/cli-apps 3.5.2 → 3.5.4
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/dist/index.js +47183 -0
- package/dist/index.js.map +7 -0
- package/dist/init-template/.gitlab-ci.yml +67 -0
- package/dist/init-template/.nvmrc +1 -0
- package/dist/init-template/README.md +27 -0
- package/dist/init-template/app/.eslintrc.cjs +13 -0
- package/dist/init-template/app/browser/README.md +1 -0
- package/dist/init-template/app/browser/package.json +37 -0
- package/dist/init-template/app/browser/scripts/esbuild/build-browser-plugin.mjs +130 -0
- package/dist/init-template/app/browser/scripts/esbuild/plugins/importAsGlobals.mjs +39 -0
- package/dist/init-template/app/browser/src/browser.ts +12 -0
- package/dist/init-template/app/browser/src/plugin/index.ts +61 -0
- package/dist/init-template/app/browser/tsconfig.json +34 -0
- package/dist/init-template/app/lib/package.json +46 -0
- package/dist/init-template/app/lib/src/index.ts +4 -0
- package/dist/init-template/app/lib/src/models/index.ts +29 -0
- package/dist/init-template/app/lib/src/validation/index.ts +14 -0
- package/dist/init-template/app/lib/tsconfig.json +22 -0
- package/dist/init-template/app/server-azurefunc/.eslintrc.cjs +4 -0
- package/dist/init-template/app/server-azurefunc/.funcignore +19 -0
- package/dist/init-template/app/server-azurefunc/README.md +105 -0
- package/dist/init-template/app/server-azurefunc/host.json +31 -0
- package/dist/init-template/app/server-azurefunc/local.settings.template.json +34 -0
- package/dist/init-template/app/server-azurefunc/package.json +67 -0
- package/dist/init-template/app/server-azurefunc/scripts/dev-queue-setup.js +55 -0
- package/dist/init-template/app/server-azurefunc/src/app/api/router.ts +14 -0
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/getInstallationSettings.ts +13 -0
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/setInstallationSettings.ts +35 -0
- package/dist/init-template/app/server-azurefunc/src/app/jobs/index.ts +61 -0
- package/dist/init-template/app/server-azurefunc/src/app/logging/index.ts +4 -0
- package/dist/init-template/app/server-azurefunc/src/app/models/index.ts +12 -0
- package/dist/init-template/app/server-azurefunc/src/app/services/activites.ts +8 -0
- package/dist/init-template/app/server-azurefunc/src/functions/healthCheck.ts +19 -0
- package/dist/init-template/app/server-azurefunc/src/functions/seekaAppWebhook.ts +202 -0
- package/dist/init-template/app/server-azurefunc/src/functions/trackActivityQueueHandler.ts +49 -0
- package/dist/init-template/app/server-azurefunc/src/functions/ui.ts +51 -0
- package/dist/init-template/app/server-azurefunc/tsconfig.json +24 -0
- package/dist/init-template/app/ui/README.md +40 -0
- package/dist/init-template/app/ui/index.html +21 -0
- package/dist/init-template/app/ui/package.json +72 -0
- package/dist/init-template/app/ui/public/favicon.ico +0 -0
- package/dist/init-template/app/ui/scripts/copy-output.mjs +30 -0
- package/dist/init-template/app/ui/src/App.tsx +72 -0
- package/dist/init-template/app/ui/src/assets/app-icon.svg +1 -0
- package/dist/init-template/app/ui/src/components/setup/steps/complete/index.tsx +32 -0
- package/dist/init-template/app/ui/src/components/setup/steps/first/index.tsx +27 -0
- package/dist/init-template/app/ui/src/components/setup/steps/index.tsx +22 -0
- package/dist/init-template/app/ui/src/components/setup/steps/second/index.tsx +38 -0
- package/dist/init-template/app/ui/src/index.tsx +45 -0
- package/dist/init-template/app/ui/src/routes/home/index.tsx +21 -0
- package/dist/init-template/app/ui/src/vite-env.d.ts +13 -0
- package/dist/init-template/app/ui/tsconfig.json +35 -0
- package/dist/init-template/app/ui/tsconfig.node.json +10 -0
- package/dist/init-template/app/ui/vite.config.mts +48 -0
- package/dist/init-template/package.json +44 -0
- package/dist/init-template/tsconfig.json +24 -0
- package/package.json +1 -1
- package/README.md +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
import '../app/logging'
|
|
3
|
+
|
|
4
|
+
import {app, HttpRequest, HttpResponseInit, InvocationContext} from '@azure/functions';
|
|
5
|
+
import {
|
|
6
|
+
SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
|
|
7
|
+
SeekaAppUninstalledWebhookPayload,
|
|
8
|
+
SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
|
|
9
|
+
throwOnInvalidWebhookSignature
|
|
10
|
+
} from '@seeka-labs/sdk-apps-server';
|
|
11
|
+
|
|
12
|
+
import {tryGetInstallation,
|
|
13
|
+
createOrUpdateInstallation, deleteInstallation, getSeekaBrowserPlugin, startServices,
|
|
14
|
+
webhookLogger,
|
|
15
|
+
childLogger,
|
|
16
|
+
} from '@seeka-labs/sdk-apps-server-host';
|
|
17
|
+
|
|
18
|
+
import { validateInstallationSettings, ExampleAppAppBrowserSdkPluginConfig, ExampleAppAppInstallContext} from '@example-org-name/example-app-name-lib'
|
|
19
|
+
|
|
20
|
+
import type {Logger} from 'winston';
|
|
21
|
+
import {queueNames, triggerBackgroundJob} from '../app/jobs';
|
|
22
|
+
import {TrackExampleAppActivityQueueItem} from "../app/models";
|
|
23
|
+
|
|
24
|
+
app.http('seekaAppWebhook', {
|
|
25
|
+
methods: ['POST'],
|
|
26
|
+
authLevel: 'anonymous',
|
|
27
|
+
route: 'api/webhook/seeka/app',
|
|
28
|
+
handler: seekaAppWebhook
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export async function seekaAppWebhook(req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
|
32
|
+
const bodyStr = (await req.text()) as string;
|
|
33
|
+
if (!bodyStr) {
|
|
34
|
+
return {
|
|
35
|
+
status: 400,
|
|
36
|
+
jsonBody: {error: "Body missing. Ensure body is present and either specify the Content-Length request header OR set Transfer-Encoding request header to 'chunked'"}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const body = JSON.parse(bodyStr) as SeekaWebhookPayload;
|
|
40
|
+
|
|
41
|
+
let logger = webhookLogger(body, context);
|
|
42
|
+
logger.profile('http.seeka.webhook.app')
|
|
43
|
+
logger.verbose('Received webhook from Seeka', {body});
|
|
44
|
+
|
|
45
|
+
// Handle probe
|
|
46
|
+
if (body.type === SeekaWebhookCallType.Probe) {
|
|
47
|
+
return {
|
|
48
|
+
status: 204
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate webhook
|
|
53
|
+
try {
|
|
54
|
+
throwOnInvalidWebhookSignature(process.env.SEEKA_APP_SECRET as string, req.headers as Headers, bodyStr);
|
|
55
|
+
logger.debug('Webhook signature validated', {body});
|
|
56
|
+
} catch {
|
|
57
|
+
logger.warn('Webhook signature invalid', {body});
|
|
58
|
+
return {
|
|
59
|
+
status: 401,
|
|
60
|
+
jsonBody: {error: "Webhook call invalid"}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (body.isTest) {
|
|
65
|
+
// This is a test webhook call
|
|
66
|
+
return {
|
|
67
|
+
status: 204
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await startServices(logger);
|
|
72
|
+
|
|
73
|
+
// Check if the webhook is for an app we have installed
|
|
74
|
+
let installation: ExampleAppAppInstallContext | null = null;
|
|
75
|
+
if (body.type != SeekaWebhookCallType.AppInstalled) {
|
|
76
|
+
installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
|
|
77
|
+
if (installation == null) {
|
|
78
|
+
logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', {body});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
status: 422,
|
|
82
|
+
jsonBody: {error: "App not installed"}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
logger = childLogger(null, installation, logger, context);
|
|
88
|
+
|
|
89
|
+
// Do something
|
|
90
|
+
let errorMessage: string | null = null;
|
|
91
|
+
try {
|
|
92
|
+
switch (body.type) {
|
|
93
|
+
case SeekaWebhookCallType.AppInstalled: {
|
|
94
|
+
errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case SeekaWebhookCallType.AppUninstalled: {
|
|
98
|
+
if (!body.isTest) {
|
|
99
|
+
const payload = body as SeekaAppUninstalledWebhookPayload;
|
|
100
|
+
await deleteInstallation(payload.context?.applicationInstallId as string, logger)
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case SeekaWebhookCallType.ActivityAccepted: {
|
|
105
|
+
const payload = body as SeekaActivityAcceptedWebhookPayload;
|
|
106
|
+
await handleSeekaActivity(payload, installation, context, logger);
|
|
107
|
+
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case SeekaWebhookCallType.IdentityChanged: {
|
|
111
|
+
const payload = body as SeekaIdentityChangedWebhookPayload;
|
|
112
|
+
|
|
113
|
+
logger.debug('Identity changed webhook received', {personId: payload.content.personId });
|
|
114
|
+
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case SeekaWebhookCallType.BrowserSdkPlugin: {
|
|
119
|
+
// Configuration object passed to the Seeka browser plugin
|
|
120
|
+
// Under no circumstances should internal or private api keys be exposed to the client/browser via the below object
|
|
121
|
+
const pluginConfig = {
|
|
122
|
+
appId: process.env.SEEKA_APP_ID,
|
|
123
|
+
appInstallId: installation.applicationInstallId,
|
|
124
|
+
appUrl: process.env.SELF_HOST_BASEURL,
|
|
125
|
+
installationSettings: {
|
|
126
|
+
browserPluginAppSetting1: installation.installationState?.installationSettings?.browserPluginAppSetting1
|
|
127
|
+
}
|
|
128
|
+
} as ExampleAppAppBrowserSdkPluginConfig;
|
|
129
|
+
const plugin = await getSeekaBrowserPlugin<ExampleAppAppBrowserSdkPluginConfig>('ExampleAppAppConvergeSdkPlugin', pluginConfig, logger);
|
|
130
|
+
|
|
131
|
+
logger.profile('http.seeka.webhook.app')
|
|
132
|
+
return {
|
|
133
|
+
status: 200,
|
|
134
|
+
body: JSON.stringify(plugin),
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.error('Failed to handle webhook', {ex: winston.exceptions.getAllInfo(err)});
|
|
143
|
+
return {
|
|
144
|
+
status: 500,
|
|
145
|
+
jsonBody: {error: "Request failed"}
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
logger.profile('http.seeka.webhook.app')
|
|
149
|
+
logger.verbose('Seeka webhook handled');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (errorMessage) {
|
|
153
|
+
logger.warn('Webhook call failed', {errorMessage});
|
|
154
|
+
return {
|
|
155
|
+
status: 400,
|
|
156
|
+
jsonBody: {error: {message: errorMessage}}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
status: 204
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
166
|
+
if (payload.isTest) return null;
|
|
167
|
+
|
|
168
|
+
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
169
|
+
if (errorMessage) return errorMessage;
|
|
170
|
+
|
|
171
|
+
const installation = await createOrUpdateInstallation({
|
|
172
|
+
...payload.context,
|
|
173
|
+
installationState: {
|
|
174
|
+
grantedPermissions: payload.content?.grantedPermissions || [],
|
|
175
|
+
installationSettings: payload.content?.installationSettings || {}
|
|
176
|
+
},
|
|
177
|
+
applicationInstallId: payload.context?.applicationInstallId as string,
|
|
178
|
+
organisationBrandId: payload.context?.organisationBrandId as string,
|
|
179
|
+
organisationId: payload.context?.organisationId as string,
|
|
180
|
+
installedAt: new Date().toISOString(),
|
|
181
|
+
}, logger)
|
|
182
|
+
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, installation: ExampleAppAppInstallContext, context: InvocationContext, logger: Logger) => {
|
|
187
|
+
logger = childLogger({
|
|
188
|
+
'Activity_ActivityIdentifier': activity?.content?.activity?.activityId,
|
|
189
|
+
'Tracking_Source_Url': activity?.content?.source?.loc,
|
|
190
|
+
'Activity_Name': activity?.content?.activity?.activityNameCustom || activity?.content?.activity?.activityName?.toString()
|
|
191
|
+
}, installation, logger, context);
|
|
192
|
+
|
|
193
|
+
await triggerBackgroundJob(queueNames.trackActivity, {
|
|
194
|
+
payload: activity.content,
|
|
195
|
+
organisationBrandId: installation.organisationBrandId,
|
|
196
|
+
organisationId: installation.organisationId,
|
|
197
|
+
applicationInstallId: installation.applicationInstallId,
|
|
198
|
+
causationId: context.invocationId,
|
|
199
|
+
correlationId: context.invocationId
|
|
200
|
+
} as TrackExampleAppActivityQueueItem, logger)
|
|
201
|
+
}
|
|
202
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
import '../app/logging'
|
|
3
|
+
|
|
4
|
+
import {app, InvocationContext} from '@azure/functions';
|
|
5
|
+
|
|
6
|
+
import {deserialiseQueuePayload, queueNames, sendQueueMessageToPoisonQueue} from '../app/jobs';
|
|
7
|
+
import {sendActivityToAnotherSoftware} from "../app/services/activites";
|
|
8
|
+
import {TrackExampleAppActivityQueueItem} from "../app/models";
|
|
9
|
+
import {backgroundJobLogger, childLogger, startServices, tryGetInstallation} from "@seeka-labs/sdk-apps-server-host";
|
|
10
|
+
|
|
11
|
+
app.storageQueue('trackActivityQueueHandler', {
|
|
12
|
+
queueName: queueNames.trackActivity,
|
|
13
|
+
connection: 'AzureWebJobsStorage',
|
|
14
|
+
handler: trackActivityQueueHandler
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export async function trackActivityQueueHandler(queueItem: unknown, context: InvocationContext): Promise<void> {
|
|
18
|
+
const body = typeof queueItem === 'string' ? deserialiseQueuePayload<TrackExampleAppActivityQueueItem>(queueItem) : queueItem as TrackExampleAppActivityQueueItem;
|
|
19
|
+
let logger = backgroundJobLogger(queueNames.trackActivity, body, context);
|
|
20
|
+
logger.profile(`queue.${queueNames.trackActivity}`)
|
|
21
|
+
|
|
22
|
+
await startServices(logger);
|
|
23
|
+
|
|
24
|
+
const applicationInstallId = body.applicationInstallId as string || (body.payload as any)?.applicationInstallId as string; // Support legacy payload / previous bug
|
|
25
|
+
|
|
26
|
+
const installation = await tryGetInstallation(applicationInstallId as string, true, logger);
|
|
27
|
+
|
|
28
|
+
logger = childLogger({
|
|
29
|
+
'Activity_ActivityIdentifier': body?.payload?.seekaActivity?.activity?.activityId,
|
|
30
|
+
'Tracking_Source_Url': body?.payload?.seekaActivity?.source?.loc,
|
|
31
|
+
'Activity_Name': body?.payload?.seekaActivity?.activity?.activityNameCustom || body?.payload?.seekaActivity?.activity?.activityName?.toString()
|
|
32
|
+
}, installation, logger, context)
|
|
33
|
+
|
|
34
|
+
logger.info('Received request to track activity for ExampleApp', {body});
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await sendActivityToAnotherSoftware(body.payload, installation, context.invocationId, context.invocationId, logger);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
const ex: any = winston.exceptions.getAllInfo(err);
|
|
40
|
+
logger.error('Error handling track ExampleApp activity queue item', {ex});
|
|
41
|
+
|
|
42
|
+
await sendQueueMessageToPoisonQueue(queueNames.trackActivity, {
|
|
43
|
+
...body,
|
|
44
|
+
correlationId: context.invocationId,
|
|
45
|
+
} as TrackExampleAppActivityQueueItem, logger);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logger.profile(`queue.${queueNames.trackActivity}`)
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {app, HttpRequest, HttpResponseInit, InvocationContext} from '@azure/functions';
|
|
2
|
+
import '../app/logging'
|
|
3
|
+
import {
|
|
4
|
+
ExampleAppAppInstallContext,
|
|
5
|
+
ExampleAppAppInstallState,
|
|
6
|
+
ExampleAppAppUiClientInitState
|
|
7
|
+
} from "@example-org-name/example-app-name-lib";
|
|
8
|
+
import { SeekaAppInstallContext } from "@seeka-labs/sdk-apps-core";
|
|
9
|
+
|
|
10
|
+
import { generateAppUiHttpRequestResponse, HttpMethod} from '@seeka-labs/sdk-apps-server-host';
|
|
11
|
+
import {Logger} from "winston";
|
|
12
|
+
import {apiRouteHandler} from "../app/api/router";
|
|
13
|
+
|
|
14
|
+
// Define your route and handler for the /ui path
|
|
15
|
+
app.http('ui', {
|
|
16
|
+
methods: ['GET', 'HEAD', 'POST', 'DELETE'],
|
|
17
|
+
authLevel: 'anonymous',
|
|
18
|
+
route: 'app/{*route}', // Wildcard route under /ui
|
|
19
|
+
handler: ui
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Function to serve the React app under /app
|
|
23
|
+
export async function ui(req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
|
24
|
+
const createBrowserInitState = async (installation: ExampleAppAppInstallContext, logger: Logger ): Promise<ExampleAppAppUiClientInitState> => {
|
|
25
|
+
// Under no circumstances should internal or private api keys be exposed to the client/browser via the below object
|
|
26
|
+
// Below state will be passed in plain text to the UI app, important to be selective (no spread operators on installation state)
|
|
27
|
+
// and not expose any internal or private api keys to the client/browser
|
|
28
|
+
return {
|
|
29
|
+
exampleInstallSetting1: installation?.installationState?.installationSettings?.exampleInstallSetting1
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const res = await generateAppUiHttpRequestResponse(
|
|
34
|
+
req.url,
|
|
35
|
+
req.method as HttpMethod,
|
|
36
|
+
req.headers as Headers,
|
|
37
|
+
context,
|
|
38
|
+
createBrowserInitState,
|
|
39
|
+
async(...args) => {
|
|
40
|
+
return await apiRouteHandler(req, context, ...args)
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
status: res.status,
|
|
46
|
+
headers: res.headers as HeadersInit,
|
|
47
|
+
cookies: res.cookies,
|
|
48
|
+
body: res.body as HttpResponseInit['body'],
|
|
49
|
+
jsonBody: res.jsonBody
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
4
|
+
"skipLibCheck": true,
|
|
5
|
+
"strict": false,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"module": "CommonJS",
|
|
11
|
+
"target": "ES6",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"isolatedModules": false
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*.ts",
|
|
19
|
+
"src/definitions.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Material UI - Create React App example in TypeScript
|
|
2
|
+
|
|
3
|
+
## How to use
|
|
4
|
+
|
|
5
|
+
Download the example [or clone the repo](https://github.com/mui/material-ui):
|
|
6
|
+
|
|
7
|
+
<!-- #default-branch-switch -->
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-ui-cra-ts
|
|
11
|
+
cd material-ui-cra-ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install it and run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
npm start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
or:
|
|
22
|
+
|
|
23
|
+
<!-- #default-branch-switch -->
|
|
24
|
+
|
|
25
|
+
[](https://codesandbox.io/p/sandbox/github/mui/material-ui/tree/master/examples/material-ui-cra-ts)
|
|
26
|
+
|
|
27
|
+
[](https://stackblitz.com/github/mui/material-ui/tree/master/examples/material-ui-cra-ts)
|
|
28
|
+
|
|
29
|
+
## The idea behind the example
|
|
30
|
+
|
|
31
|
+
This example demonstrates how you can use Material UI with [Create React App](https://github.com/facebookincubator/create-react-app) in [TypeScript](https://github.com/Microsoft/TypeScript).
|
|
32
|
+
It includes `@mui/material` and its peer dependencies, including [Emotion](https://emotion.sh/docs/introduction), the default style engine in Material UI v6.
|
|
33
|
+
If you prefer, you can [use styled-components instead](https://mui.com/material-ui/integrations/interoperability/#styled-components).
|
|
34
|
+
|
|
35
|
+
## What's next?
|
|
36
|
+
|
|
37
|
+
<!-- #default-branch-switch -->
|
|
38
|
+
|
|
39
|
+
You now have a working example project.
|
|
40
|
+
You can head back to the documentation and continue by browsing the [templates](https://mui.com/material-ui/getting-started/templates/) section.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8"/>
|
|
6
|
+
<link rel="icon" href="/favicon.ico"/>
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
8
|
+
<title>Seeka ExampleApp</title>
|
|
9
|
+
<!-- Fonts to support Material Design -->
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
|
13
|
+
<script vite-ignore src="%VITE_BASE_URL%config.js?c=%VITE_RELEASE_TAG%"></script>
|
|
14
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
18
|
+
<div id="root"></div>
|
|
19
|
+
</body>
|
|
20
|
+
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@example-org-name/example-app-name-ui",
|
|
3
|
+
"version": "3.5.4",
|
|
4
|
+
"description": "Seeka app UI for example-app-name",
|
|
5
|
+
"author": "Seeka <administrator@seeka.co>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "/app",
|
|
8
|
+
"private": true,
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@emotion/react": "^11",
|
|
11
|
+
"@emotion/styled": "^11",
|
|
12
|
+
"@example-org-name/example-app-name-lib": "workspace:*",
|
|
13
|
+
"@hookform/resolvers": "^5",
|
|
14
|
+
"@mui/icons-material": "7",
|
|
15
|
+
"@mui/lab": "7.0.1-beta.20",
|
|
16
|
+
"@mui/material": "^7",
|
|
17
|
+
"@mui/styles": "^6",
|
|
18
|
+
"@mui/system": "^7",
|
|
19
|
+
"@reduxjs/toolkit": "^2",
|
|
20
|
+
"@seeka-labs/converge": "^1",
|
|
21
|
+
"@seeka-labs/sdk-apps-core": "../../../workspace:* || ^3",
|
|
22
|
+
"@seeka-labs/sdk-apps-react": "../../../workspace:* || ^3",
|
|
23
|
+
"lodash-es": "^4",
|
|
24
|
+
"material-ui-confirm": "^4",
|
|
25
|
+
"notistack": "^3",
|
|
26
|
+
"query-string": "^9",
|
|
27
|
+
"react": "^18",
|
|
28
|
+
"react-dom": "^18",
|
|
29
|
+
"react-hook-form": "^7",
|
|
30
|
+
"react-redux": "^9",
|
|
31
|
+
"react-router-dom": "^7",
|
|
32
|
+
"redux": "^5",
|
|
33
|
+
"redux-persist": "^6",
|
|
34
|
+
"uuid": "^13",
|
|
35
|
+
"vite-plugin-svgr": "^4",
|
|
36
|
+
"yup": "^1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/fs-extra": "^11",
|
|
40
|
+
"@types/lodash-es": "^4",
|
|
41
|
+
"@types/react": "^18",
|
|
42
|
+
"@types/react-dom": "^18",
|
|
43
|
+
"@vitejs/plugin-react": "^5",
|
|
44
|
+
"cross-env": "^10",
|
|
45
|
+
"fs-extra": "^11",
|
|
46
|
+
"rimraf": "^6",
|
|
47
|
+
"typescript": "^5",
|
|
48
|
+
"vite": "^7"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"start": "vite",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"clean": "rimraf build package dist",
|
|
54
|
+
"copy-output": "node ./scripts/copy-output.mjs",
|
|
55
|
+
"dev": "cross-env NODE_ENV=development cross-env VITE_RELEASE_TAG=$(date +%Y%m%d%H%M%S) cross-env VITE_BASE_URL=/app/ vite",
|
|
56
|
+
"build": "vite build && yarn copy-output",
|
|
57
|
+
"build:dev": "cross-env NODE_ENV=development VITE_RELEASE_TAG=$(date +%Y%m%d%H%M%S) VITE_BASE_URL=/app/ vite build --watch",
|
|
58
|
+
"preview": "vite preview"
|
|
59
|
+
},
|
|
60
|
+
"browserslist": {
|
|
61
|
+
"production": [
|
|
62
|
+
">0.2%",
|
|
63
|
+
"not dead",
|
|
64
|
+
"not op_mini all"
|
|
65
|
+
],
|
|
66
|
+
"development": [
|
|
67
|
+
"last 1 chrome version",
|
|
68
|
+
"last 1 firefox version",
|
|
69
|
+
"last 1 safari version"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import { existsSync, mkdirSync, cpSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import {rmSync} from "node:fs";
|
|
5
|
+
|
|
6
|
+
const postBuild = () => {
|
|
7
|
+
const serverDirNames = ['server-azurefunc']
|
|
8
|
+
|
|
9
|
+
serverDirNames.forEach(dirName => {
|
|
10
|
+
if (existsSync(resolve(`../${dirName}`))) {
|
|
11
|
+
|
|
12
|
+
if (!existsSync(resolve(`../${dirName}/dist/src/ui`))) {
|
|
13
|
+
mkdirSync(resolve(`../${dirName}/dist/src/ui`), {
|
|
14
|
+
recursive: true,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
rmSync(resolve(`../${dirName}/dist/src/ui`), { recursive: true });
|
|
18
|
+
mkdirSync(resolve(`../${dirName}/dist/src/ui`), {
|
|
19
|
+
recursive: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Copy all files in current working dir ../build dir and all files to `../${dirName}/dist/src/ui
|
|
23
|
+
cpSync(resolve(`./build`), resolve(`../${dirName}/dist/src/ui/build`), {
|
|
24
|
+
recursive: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
postBuild();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Container, Paper } from '@mui/material';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import { ConfirmProvider } from 'material-ui-confirm';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
createBrowserRouter,
|
|
7
|
+
Outlet,
|
|
8
|
+
RouterProvider,
|
|
9
|
+
} from "react-router-dom";
|
|
10
|
+
import { Index } from './routes/home';
|
|
11
|
+
import {TokenProvider, SeekaCoreApiReduxProvider} from "@seeka-labs/sdk-apps-react";
|
|
12
|
+
import {ExampleAppAppUiClientInitConfig} from "@example-org-name/example-app-name-lib";
|
|
13
|
+
|
|
14
|
+
let basename = (import.meta as any).env.BASE_URL
|
|
15
|
+
// trim last /
|
|
16
|
+
basename = basename.substring(0, basename.length - 1);
|
|
17
|
+
|
|
18
|
+
function AppLayout() {
|
|
19
|
+
return (
|
|
20
|
+
<TokenProvider>
|
|
21
|
+
<SeekaCoreApiReduxProvider>
|
|
22
|
+
<ConfirmProvider>
|
|
23
|
+
<Outlet />
|
|
24
|
+
</ConfirmProvider>
|
|
25
|
+
</SeekaCoreApiReduxProvider>
|
|
26
|
+
</TokenProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const router = createBrowserRouter([
|
|
31
|
+
{
|
|
32
|
+
element: <AppLayout />,
|
|
33
|
+
children: [
|
|
34
|
+
{ path: '/', element: <Index /> },
|
|
35
|
+
// other routes…
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
], { basename });
|
|
39
|
+
|
|
40
|
+
declare global {
|
|
41
|
+
interface Window {
|
|
42
|
+
seekaAppConfig: ExampleAppAppUiClientInitConfig
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function App() {
|
|
47
|
+
const [loaded, setLoaded] = React.useState(Boolean(window.seekaAppConfig));
|
|
48
|
+
|
|
49
|
+
let interval: any = null;
|
|
50
|
+
if (!loaded && !interval) {
|
|
51
|
+
interval = setInterval(() => {
|
|
52
|
+
setLoaded(Boolean(window.seekaAppConfig || process.env.NODE_ENV === 'development'));
|
|
53
|
+
}, 200);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (loaded && interval) {
|
|
58
|
+
clearInterval(interval);
|
|
59
|
+
}
|
|
60
|
+
}, [loaded]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
!loaded ? <></> : (
|
|
64
|
+
<Box sx={{ my: 4, mx: 2 }}>
|
|
65
|
+
<Container maxWidth='lg'>
|
|
66
|
+
<Paper sx={{ bgcolor: 'white', p: 3 }} elevation={0}>
|
|
67
|
+
<RouterProvider router={router} />
|
|
68
|
+
</Paper></Container>
|
|
69
|
+
</Box>
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='utf-8'?><!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 268.4 84.7' style='enable-background:new 0 0 268.4 84.7;' xml:space='preserve'><style type='text/css'> .st0{fill:#722ED1;} .st1{enable-background:new ;} </style><path d='M0,72.5c0,6.7,5.5,12.2,12.2,12.2l0,0l0,0l0,0c3.2,0,6.3-1.3,8.6-3.6l12.5-12.6c-0.4,0-0.7,0-1.1,0l0,0 c-4.2,0-8.4-1.7-11.4-4.7l-8.6-8.6l-8.6,8.6C1.3,66.1,0,69.3,0,72.5z'/><g><path d='M40.9,3.6C38.6,1.3,35.6,0,32.3,0S26,1.3,23.7,3.6L11.1,16.1c0.4,0,0.7,0,1.1,0c4.1,0,8.3,1.6,11.5,4.7l8.6,8.6l8.6-8.6 C45.7,16.1,45.7,8.3,40.9,3.6z'/><path d='M2.2,22.2L2.2,22.2L2.2,22.2z'/></g><path class='st0' d='M3.7,41L3.7,41c2.3,2.3,5.3,3.5,8.5,3.5c0,0,0,0,0.1,0c3.2,0,6.3-1.3,8.6-3.6l8.6-8.6l-8.6-8.6 c-2.4-2.4-5.5-3.6-8.6-3.6S6,21.3,3.6,23.7C-1.2,28.4-1.2,36.2,3.7,41L3.7,41z'/><g class='st1'><path d='M76.3,62.6C66,62.6,59.7,57.2,59.6,49h12.5c0,2.8,1.9,4,4.4,4c1.9,0,3.7-1,3.7-2.9c0-2.2-2.9-2.8-6.5-3.4 c-5.9-1-13.6-2.7-13.6-11.9c0-7.6,6.5-12.5,16.1-12.5s15.9,5.1,16,12.8H80.1c0-2.4-1.6-3.5-4-3.5c-2.1,0-3.5,1-3.5,2.8 c0,2.2,2.9,2.7,6.5,3.3C85,38.7,93,40,93,49.5C93,57.4,86.3,62.6,76.3,62.6L76.3,62.6z'/></g><g class='st1'><path d='M138,42.1c0,1.1-0.1,2.3-0.3,3.4h-28.1c0.9,4.3,3.6,6.6,7.7,6.6c3,0,5.6-1.3,6.8-3.5h13c-2.7,8.6-10.3,14-19.8,14 c-11.8,0-20.5-8.6-20.5-20.2s8.7-20.1,20.5-20.1C129.6,22.3,138,31,138,42.1L138,42.1z M109.9,38.3h15.3c-1.1-3.8-3.9-5.8-7.8-5.8 C113.6,32.5,111,34.6,109.9,38.3z'/></g><g class='st1'><path d='M183.6,42.1c0,1.1-0.1,2.3-0.3,3.4h-28.1c0.9,4.3,3.6,6.6,7.7,6.6c3,0,5.6-1.3,6.8-3.5h13c-2.7,8.6-10.3,14-19.8,14 c-11.8,0-20.5-8.6-20.5-20.2s8.7-20.1,20.5-20.1C175.1,22.3,183.6,31,183.6,42.1L183.6,42.1z M155.5,38.3h15.3 c-1.1-3.8-3.9-5.8-7.8-5.8C159.2,32.5,156.5,34.6,155.5,38.3z'/></g><g class='st1'><path d='M211.1,61.4l-9.5-16v16h-13V9.9h13v28.5l9-14.8h14.6l-11.8,18l13,19.8H211.1z'/><path d='M268.4,23.5v37.8h-10.1l-1.1-2.7c-3.1,2.5-7,4-11.4,4c-11.6,0-19.8-8.4-19.8-20.2c0-11.7,8.3-20,19.8-20 c4.5,0,8.4,1.5,11.6,4.1l1.3-2.9L268.4,23.5z M256,42.5c0-4.9-3.6-8.6-8.5-8.6s-8.5,3.7-8.5,8.6s3.6,8.6,8.5,8.6S256,47.4,256,42.5 z'/></g><path d='M12.2,48.5c-0.3,0-0.7,0-1,0L23.7,61c2.3,2.3,5.4,3.6,8.6,3.6l0,0l0,0l0,0c3.3,0,6.3-1.3,8.6-3.6c4.8-4.8,4.7-12.5,0-17.3 l-8.6-8.6l-8.6,8.6C20.7,46.8,16.5,48.5,12.2,48.5L12.2,48.5z'/></svg>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Button, Typography } from "@mui/material"
|
|
2
|
+
import { Stack } from "@mui/system"
|
|
3
|
+
import {SeekaAppWizardStep, SeekaAppWizardHeading, SeekaAppWizardStepContent, SeekaBotHappy, SeekaAppWizardStepActions, sendSeekaCoreAppCommand} from "@seeka-labs/sdk-apps-react";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
onBack?: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AppSetupCompletedStep = ({ onBack }: Props) => {
|
|
10
|
+
return (
|
|
11
|
+
<SeekaAppWizardStep>
|
|
12
|
+
<Stack direction={"row"} sx={{ width: '100%' }} gap={12} alignItems="center" justifyContent={"center"}>
|
|
13
|
+
<SeekaBotHappy style={{ width: '260px' }} />
|
|
14
|
+
<Stack gap={6} alignItems="flex-start" justifyContent={"center"}>
|
|
15
|
+
<Stack gap={3}>
|
|
16
|
+
<SeekaAppWizardHeading title="Success!" />_b
|
|
17
|
+
|
|
18
|
+
<Typography>ExampleApp™ is now connected to Seeka</Typography>
|
|
19
|
+
</Stack>
|
|
20
|
+
|
|
21
|
+
<SeekaAppWizardStepActions StackProps={{ gap: 3 }}>
|
|
22
|
+
<Button variant='text' color="primary" onClick={onBack}>Back</Button>
|
|
23
|
+
<Button variant='contained' color="primary" onClick={() => sendSeekaCoreAppCommand('dashboard')}>Back to dashboard</Button>
|
|
24
|
+
<Button variant='outlined' color="primary" onClick={() => sendSeekaCoreAppCommand('appsList')}>Connect another app</Button>
|
|
25
|
+
</SeekaAppWizardStepActions>
|
|
26
|
+
</Stack>
|
|
27
|
+
|
|
28
|
+
</Stack>
|
|
29
|
+
|
|
30
|
+
</SeekaAppWizardStep>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Button } from "@mui/material"
|
|
2
|
+
import seekaAppIcon from '../../../../assets/app-icon.svg'
|
|
3
|
+
import {SeekaAppWizardStep, SeekaAppWizardHeading, SeekaAppWizardStepContent, SeekaAppWizardStepActions} from "@seeka-labs/sdk-apps-react";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
onNext?: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const AppSetupFirstStep = ({ onNext }: Props) => { return (
|
|
10
|
+
<SeekaAppWizardStep>
|
|
11
|
+
<SeekaAppWizardHeading title="Connect to ExampleApp™" image={seekaAppIcon}>
|
|
12
|
+
Connecting your ExampleApp™ to Seeka is super easy and can be done in around 3 minutes by anyone with Admin access to ExampleApp™.
|
|
13
|
+
</SeekaAppWizardHeading>
|
|
14
|
+
<SeekaAppWizardStepContent title="Instructions" subtitle="Authenticate ExampleApp">
|
|
15
|
+
<ol>
|
|
16
|
+
<li>Click the Authenticate ExampleApp button</li>
|
|
17
|
+
<li>Select the account that is authorised to your ExampleApp™ account</li>
|
|
18
|
+
<li>Follow the guided prompts to authorise Seeka.</li>
|
|
19
|
+
</ol>
|
|
20
|
+
</SeekaAppWizardStepContent>
|
|
21
|
+
|
|
22
|
+
<SeekaAppWizardStepActions>
|
|
23
|
+
{onNext && <Button variant='contained' color="primary" onClick={onNext}>Next</Button>}
|
|
24
|
+
</SeekaAppWizardStepActions>
|
|
25
|
+
</SeekaAppWizardStep>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
import { Box } from "@mui/material";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AppSetupFirstStep } from "./first";
|
|
5
|
+
import { AppSetupSecondStep } from "./second";
|
|
6
|
+
import { AppSetupCompletedStep } from "./complete";
|
|
7
|
+
import {SeekaAppLoadingSpinner} from "@seeka-labs/sdk-apps-react";
|
|
8
|
+
|
|
9
|
+
export const SetupSteps = () => {
|
|
10
|
+
const [activeStep, setActiveStep] = useState<number | null>(0)
|
|
11
|
+
const [loading, setLoading] = useState(true)
|
|
12
|
+
|
|
13
|
+
if (activeStep === null || loading) return <Box><SeekaAppLoadingSpinner /></Box>
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Box>
|
|
17
|
+
{activeStep === 0 && <AppSetupFirstStep onNext={() => setActiveStep(1)} />}
|
|
18
|
+
{activeStep === 0 && <AppSetupSecondStep onNext={() => setActiveStep(2)} onBack={() => setActiveStep(1)} />}
|
|
19
|
+
{activeStep === 3 && <AppSetupCompletedStep onBack={() => setActiveStep(1)} />}
|
|
20
|
+
</Box>
|
|
21
|
+
)
|
|
22
|
+
}
|