@qlover/create-app 0.3.1 → 0.3.3

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/configs/_common/.github/workflows/general-check.yml +17 -29
  3. package/configs/_common/.github/workflows/{release.yml.template → release.yml} +16 -51
  4. package/package.json +3 -3
  5. package/templates/pack-app/fe-config.json +30 -1
  6. package/templates/pack-app/package.json +45 -32
  7. package/templates/pack-app/tsconfig.test.json +1 -1
  8. package/templates/react-app/config/Identifier.I18n.ts +878 -0
  9. package/templates/react-app/config/app.router.json +7 -7
  10. package/templates/react-app/config/common.ts +7 -4
  11. package/templates/react-app/config/theme.json +7 -88
  12. package/templates/react-app/package.json +9 -4
  13. package/templates/react-app/postcss.config.js +1 -2
  14. package/templates/react-app/public/locales/en/common.json +118 -1
  15. package/templates/react-app/public/locales/zh/common.json +118 -1
  16. package/templates/react-app/src/App.tsx +14 -2
  17. package/templates/react-app/src/base/cases/RequestLogger.ts +3 -4
  18. package/templates/react-app/src/base/cases/RequestStatusCatcher.ts +3 -2
  19. package/templates/react-app/src/base/services/I18nService.ts +31 -3
  20. package/templates/react-app/src/base/services/ProcesserService.ts +3 -2
  21. package/templates/react-app/src/core/IOC.ts +15 -9
  22. package/templates/react-app/src/core/bootstrap.ts +42 -53
  23. package/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +14 -0
  24. package/templates/react-app/src/core/bootstraps/index.ts +36 -7
  25. package/templates/react-app/src/core/globals.ts +4 -6
  26. package/templates/react-app/src/core/registers/RegisterApi.ts +2 -5
  27. package/templates/react-app/src/core/registers/RegisterCommon.ts +38 -28
  28. package/templates/react-app/src/core/registers/RegisterControllers.ts +5 -10
  29. package/templates/react-app/src/core/registers/RegisterGlobals.ts +15 -14
  30. package/templates/react-app/src/core/registers/index.ts +27 -12
  31. package/templates/react-app/src/main.tsx +1 -1
  32. package/templates/react-app/src/pages/404.tsx +1 -1
  33. package/templates/react-app/src/pages/500.tsx +1 -1
  34. package/templates/react-app/src/pages/auth/Login.tsx +128 -36
  35. package/templates/react-app/src/pages/base/About.tsx +5 -2
  36. package/templates/react-app/src/pages/base/ErrorIdentifier.tsx +38 -19
  37. package/templates/react-app/src/pages/base/Executor.tsx +447 -29
  38. package/templates/react-app/src/pages/base/Home.tsx +99 -93
  39. package/templates/react-app/src/pages/base/JSONStorage.tsx +47 -38
  40. package/templates/react-app/src/pages/base/Layout.tsx +5 -2
  41. package/templates/react-app/src/pages/base/Request.tsx +90 -208
  42. package/templates/react-app/src/pages/base/components/BaseHeader.tsx +13 -5
  43. package/templates/react-app/src/styles/css/page.css +11 -0
  44. package/templates/react-app/src/styles/css/tailwind.css +5 -0
  45. package/templates/react-app/src/styles/css/themes/_default.css +200 -0
  46. package/templates/react-app/src/styles/css/themes/dark.css +154 -0
  47. package/templates/react-app/src/styles/css/themes/index.css +3 -0
  48. package/templates/react-app/src/styles/css/themes/pink.css +160 -0
  49. package/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +56 -0
  50. package/templates/react-app/src/uikit/components/Loading.tsx +27 -21
  51. package/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +63 -13
  52. package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +1 -1
  53. package/templates/react-app/src/uikit/controllers/RouterController.ts +3 -3
  54. package/templates/react-app/src/uikit/controllers/UserController.ts +1 -1
  55. package/templates/react-app/tailwind.config.js +1 -15
  56. package/templates/react-app/vite.config.ts +9 -3
  57. package/templates/react-app/lib/tailwind/root10px.js +0 -178
  58. package/templates/react-app/lib/tailwind/theme-generator.js +0 -238
  59. package/templates/react-app/public/locales/en/about.json +0 -3
  60. package/templates/react-app/public/locales/en/executor.json +0 -6
  61. package/templates/react-app/public/locales/en/home.json +0 -10
  62. package/templates/react-app/public/locales/en/jsonStorage.json +0 -11
  63. package/templates/react-app/public/locales/en/login.json +0 -7
  64. package/templates/react-app/public/locales/en/request.json +0 -15
  65. package/templates/react-app/public/locales/zh/about.json +0 -3
  66. package/templates/react-app/public/locales/zh/executor.json +0 -7
  67. package/templates/react-app/public/locales/zh/home.json +0 -10
  68. package/templates/react-app/public/locales/zh/jsonStorage.json +0 -11
  69. package/templates/react-app/public/locales/zh/login.json +0 -8
  70. package/templates/react-app/public/locales/zh/request.json +0 -15
  71. package/templates/react-app/src/base/port/InversifyIocInterface.ts +0 -9
  72. package/templates/react-app/src/uikit/styles/css/page.css +0 -3
  73. package/templates/react-app/src/uikit/styles/css/tailwind.css +0 -3
  74. /package/templates/react-app/config/{ErrorIdentifier.ts → Identifier.Error.ts} +0 -0
  75. /package/templates/react-app/src/{uikit/styles → styles}/css/index.css +0 -0
@@ -1,10 +1,36 @@
1
- import { IOC } from '@/core/IOC';
1
+ import {
2
+ Button,
3
+ Progress,
4
+ Tag,
5
+ Space,
6
+ Card,
7
+ message,
8
+ Input,
9
+ Select
10
+ } from 'antd';
2
11
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
12
+ import { useState, useEffect } from 'react';
13
+ import { IOC } from '@/core/IOC';
3
14
  import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
4
15
  import { ExecutorController } from '@/uikit/controllers/ExecutorController';
5
16
  import { useSliceStore } from '@qlover/slice-store-react';
17
+ import * as i18nKeys from '@config/Identifier.I18n';
18
+
19
+ interface Task {
20
+ id: string;
21
+ name: string;
22
+ status: 'pending' | 'running' | 'completed' | 'failed';
23
+ progress: number;
24
+ type: 'data-sync' | 'report-generation' | 'system-maintenance' | 'backup';
25
+ startTime?: Date;
26
+ endTime?: Date;
27
+ responseType?: 'text' | 'json' | 'blob';
28
+ url?: string;
29
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
30
+ }
6
31
 
7
32
  export default function Executor() {
33
+ const { t } = useBaseRoutePage();
8
34
  const executorController = IOC(ExecutorController);
9
35
  const jSONStorageController = IOC(JSONStorageController);
10
36
  const requestTimeout = useSliceStore(
@@ -17,46 +43,438 @@ export default function Executor() {
17
43
  executorController.selector.helloState
18
44
  );
19
45
 
20
- const { t } = useBaseRoutePage();
46
+ const [tasks, setTasks] = useState<Task[]>([
47
+ {
48
+ id: '1',
49
+ name: t(i18nKeys.PAGE_EXECUTOR_TEST_PLUGIN_TITLE),
50
+ status: 'pending',
51
+ progress: 0,
52
+ type: 'data-sync',
53
+ responseType: 'text',
54
+ url: '/api/v1/executor/test',
55
+ method: 'GET'
56
+ },
57
+ {
58
+ id: '2',
59
+ name: t(i18nKeys.PAGE_EXECUTOR_TASK_TYPE_DATA_SYNC),
60
+ status: 'pending',
61
+ progress: 0,
62
+ type: 'data-sync',
63
+ responseType: 'json',
64
+ url: '/api/v1/data/sync',
65
+ method: 'POST'
66
+ },
67
+ {
68
+ id: '3',
69
+ name: t(i18nKeys.PAGE_EXECUTOR_TASK_TYPE_MAINTENANCE),
70
+ status: 'pending',
71
+ progress: 0,
72
+ type: 'system-maintenance',
73
+ responseType: 'json',
74
+ url: '/api/v1/system/maintenance',
75
+ method: 'PUT'
76
+ },
77
+ {
78
+ id: '4',
79
+ name: t(i18nKeys.PAGE_EXECUTOR_TASK_TYPE_BACKUP),
80
+ status: 'pending',
81
+ progress: 0,
82
+ type: 'backup',
83
+ responseType: 'blob',
84
+ url: '/api/v1/data/backup',
85
+ method: 'GET'
86
+ }
87
+ ]);
88
+
89
+ const [customUrl, setCustomUrl] = useState('');
90
+ const [customMethod, setCustomMethod] = useState<
91
+ 'GET' | 'POST' | 'PUT' | 'DELETE'
92
+ >('GET');
93
+ const [customResponseType, setCustomResponseType] = useState<
94
+ 'text' | 'json' | 'blob'
95
+ >('text');
96
+
97
+ // 监听 helloState 变化,更新任务状态
98
+ useEffect(() => {
99
+ if (helloState.result) {
100
+ message.success(t(i18nKeys.PAGE_EXECUTOR_PLUGIN_TEST_SUCCESS));
101
+ // 更新任务状态
102
+ setTasks((prevTasks) =>
103
+ prevTasks.map((task) =>
104
+ task.id === '1'
105
+ ? {
106
+ ...task,
107
+ status: 'completed',
108
+ progress: 100,
109
+ endTime: new Date()
110
+ }
111
+ : task
112
+ )
113
+ );
114
+ } else if (helloState.error) {
115
+ message.error(t(i18nKeys.PAGE_EXECUTOR_PLUGIN_TEST_FAILURE));
116
+ // 更新任务状态
117
+ setTasks((prevTasks) =>
118
+ prevTasks.map((task) =>
119
+ task.id === '1'
120
+ ? { ...task, status: 'failed', endTime: new Date() }
121
+ : task
122
+ )
123
+ );
124
+ }
125
+ }, [helloState.result, helloState.error, t]);
126
+
127
+ const getStatusColor = (status: Task['status']) => {
128
+ switch (status) {
129
+ case 'running':
130
+ return 'active';
131
+ case 'completed':
132
+ return 'success';
133
+ case 'failed':
134
+ return 'exception';
135
+ default:
136
+ return 'normal';
137
+ }
138
+ };
139
+
140
+ const getTypeColor = (type: Task['type']) => {
141
+ switch (type) {
142
+ case 'data-sync':
143
+ return 'blue';
144
+ case 'report-generation':
145
+ return 'green';
146
+ case 'system-maintenance':
147
+ return 'orange';
148
+ case 'backup':
149
+ return 'purple';
150
+ default:
151
+ return 'default';
152
+ }
153
+ };
154
+
155
+ const formatDuration = (startTime?: Date, endTime?: Date) => {
156
+ if (!startTime) return '-';
157
+ const end = endTime || new Date();
158
+ const duration = end.getTime() - startTime.getTime();
159
+ const minutes = Math.floor(duration / (1000 * 60));
160
+ return `${minutes} ${t(i18nKeys.PAGE_EXECUTOR_TASK_DURATION_UNIT)}`;
161
+ };
162
+
163
+ const handleStartTask = async (taskId: string) => {
164
+ const task = tasks.find((t) => t.id === taskId);
165
+ if (!task) return;
166
+
167
+ setTasks((prevTasks) =>
168
+ prevTasks.map((t) =>
169
+ t.id === taskId ? { ...t, status: 'running', startTime: new Date() } : t
170
+ )
171
+ );
172
+
173
+ try {
174
+ await executorController.onTestPlugins();
175
+
176
+ setTasks((prevTasks) =>
177
+ prevTasks.map((t) =>
178
+ t.id === taskId
179
+ ? { ...t, status: 'completed', progress: 100, endTime: new Date() }
180
+ : t
181
+ )
182
+ );
183
+
184
+ message.success(
185
+ t(i18nKeys.PAGE_EXECUTOR_TASK_SUCCESS, { name: task.name })
186
+ );
187
+ } catch {
188
+ setTasks((prevTasks) =>
189
+ prevTasks.map((t) =>
190
+ t.id === taskId ? { ...t, status: 'failed', endTime: new Date() } : t
191
+ )
192
+ );
193
+
194
+ message.error(
195
+ t(i18nKeys.PAGE_EXECUTOR_TASK_FAILURE, { name: task.name })
196
+ );
197
+ }
198
+ };
199
+
200
+ const handleStopTask = (taskId: string) => {
201
+ setTasks((prevTasks) =>
202
+ prevTasks.map((task) =>
203
+ task.id === taskId
204
+ ? { ...task, status: 'failed', endTime: new Date() }
205
+ : task
206
+ )
207
+ );
208
+ };
209
+
210
+ const handleCreateTask = () => {
211
+ if (!customUrl) {
212
+ message.error(t(i18nKeys.PAGE_EXECUTOR_CUSTOM_TASK_URL_REQUIRED));
213
+ return;
214
+ }
215
+
216
+ const newTask: Task = {
217
+ id: Date.now().toString(),
218
+ name: t(i18nKeys.PAGE_EXECUTOR_CUSTOM_TASK_NAME, {
219
+ method: customMethod,
220
+ url: customUrl
221
+ }),
222
+ status: 'pending',
223
+ progress: 0,
224
+ type: 'data-sync',
225
+ responseType: customResponseType,
226
+ url: customUrl,
227
+ method: customMethod
228
+ };
229
+
230
+ setTasks((prev) => [...prev, newTask]);
231
+ setCustomUrl('');
232
+ setCustomMethod('GET');
233
+ setCustomResponseType('text');
234
+ };
21
235
 
22
236
  return (
23
- <div className="min-h-screen bg-gray-100 py-6 flex flex-col justify-center sm:py-12">
24
- <div className="relative py-3 sm:max-w-xl sm:mx-auto">
25
- <div className="bg-white shadow-lg rounded-lg px-8 py-6">
26
- <h1 className="text-3xl font-bold text-center text-gray-800 mb-8">
27
- {t('executorDemo')}
28
- </h1>
29
- </div>
30
-
31
- <div className="bg-white shadow-lg rounded-lg px-8 py-6">
32
- {t('requestTimeout')}: {requestTimeout}
33
- </div>
34
- <div className="space-y-6">
35
- <div className="p-6 bg-gray-50 rounded-lg">
36
- <h2 className="text-xl font-semibold text-gray-800 mb-4">
37
- {t('executorTestPlugin')}
38
- </h2>
39
-
40
- <div className="text-gray-800">
237
+ <div className="min-h-screen bg-primary py-8 px-4 sm:px-6 lg:px-8">
238
+ <div className="max-w-6xl mx-auto space-y-6">
239
+ {/* Header Section */}
240
+ <section className="py-8">
241
+ <div className="text-center">
242
+ <h1 className="text-4xl md:text-5xl font-bold mb-6 text-text">
243
+ {t(i18nKeys.PAGE_EXECUTOR_MAIN_TITLE)}
244
+ </h1>
245
+ <p className="text-xl text-text-secondary mb-8">
246
+ {t(i18nKeys.PAGE_EXECUTOR_DESCRIPTION)}
247
+ </p>
248
+ </div>
249
+ </section>
250
+
251
+ {/* Test Plugin Section */}
252
+ <section className="bg-secondary shadow sm:rounded-lg p-6 border border-border">
253
+ <h2 className="text-xl font-medium text-text mb-4">
254
+ {t(i18nKeys.PAGE_EXECUTOR_TEST_PLUGIN_TITLE)}
255
+ </h2>
256
+ <div className="space-y-4">
257
+ <div className="text-text-secondary">
258
+ {t(i18nKeys.PAGE_REQUEST_TIMEOUT)}: {requestTimeout}
259
+ </div>
260
+ <div>
41
261
  {helloState.loading ? (
42
- <div>Loading...</div>
262
+ <div className="text-text-secondary">Loading...</div>
43
263
  ) : (
44
- <button
45
- className="bg-blue-500 px-4 py-2 rounded-md"
264
+ <Button
265
+ type="primary"
46
266
  onClick={executorController.onTestPlugins}
47
267
  >
48
- {t('testPlugin')}
49
- </button>
268
+ {t(i18nKeys.PAGE_EXECUTOR_TEST_PLUGIN_TITLE)}
269
+ </Button>
50
270
  )}
51
-
271
+ </div>
272
+ <div className="bg-elevated p-4 rounded-lg">
52
273
  {helloState.error instanceof Error ? (
53
- <div>{helloState.error.message}</div>
274
+ <div className="text-red-500">{helloState.error.message}</div>
54
275
  ) : (
55
- <div>{IOC('JSON').stringify(helloState.result?.data)}</div>
276
+ <pre className="text-text-secondary">
277
+ {IOC('JSON').stringify(helloState.result?.data)}
278
+ </pre>
56
279
  )}
57
280
  </div>
58
281
  </div>
59
- </div>
282
+ </section>
283
+
284
+ {/* Create Task Section */}
285
+ <section className="bg-secondary shadow sm:rounded-lg p-6 border border-border">
286
+ <h2 className="text-xl font-medium text-text mb-4">
287
+ {t(i18nKeys.PAGE_EXECUTOR_CREATE_TASK_TITLE)}
288
+ </h2>
289
+ <div className="space-y-4">
290
+ <div className="flex items-center space-x-4">
291
+ <Input
292
+ placeholder={t(i18nKeys.PAGE_EXECUTOR_ENTER_URL)}
293
+ value={customUrl}
294
+ onChange={(e) => setCustomUrl(e.target.value)}
295
+ className="flex-1"
296
+ />
297
+ <Select
298
+ value={customMethod}
299
+ onChange={setCustomMethod}
300
+ style={{ width: 120 }}
301
+ >
302
+ <Select.Option value="GET">GET</Select.Option>
303
+ <Select.Option value="POST">POST</Select.Option>
304
+ <Select.Option value="PUT">PUT</Select.Option>
305
+ <Select.Option value="DELETE">DELETE</Select.Option>
306
+ </Select>
307
+ <Select
308
+ value={customResponseType}
309
+ onChange={setCustomResponseType}
310
+ style={{ width: 120 }}
311
+ >
312
+ <Select.Option value="text">Text</Select.Option>
313
+ <Select.Option value="json">JSON</Select.Option>
314
+ <Select.Option value="blob">Blob</Select.Option>
315
+ </Select>
316
+ <Button type="primary" onClick={handleCreateTask}>
317
+ {t(i18nKeys.PAGE_EXECUTOR_CREATE_BUTTON)}
318
+ </Button>
319
+ </div>
320
+ </div>
321
+ </section>
322
+
323
+ {/* Task Statistics */}
324
+ <section className="grid grid-cols-1 md:grid-cols-4 gap-4">
325
+ <Card className="bg-elevated border-border">
326
+ <div className="text-center">
327
+ <div className="text-2xl font-bold text-text">{tasks.length}</div>
328
+ <div className="text-sm text-text-secondary">
329
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_STATS_TOTAL)}
330
+ </div>
331
+ </div>
332
+ </Card>
333
+ <Card className="bg-elevated border-border">
334
+ <div className="text-center">
335
+ <div className="text-2xl font-bold text-text">
336
+ {tasks.filter((t) => t.status === 'running').length}
337
+ </div>
338
+ <div className="text-sm text-text-secondary">
339
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_STATS_RUNNING)}
340
+ </div>
341
+ </div>
342
+ </Card>
343
+ <Card className="bg-elevated border-border">
344
+ <div className="text-center">
345
+ <div className="text-2xl font-bold text-text">
346
+ {tasks.filter((t) => t.status === 'completed').length}
347
+ </div>
348
+ <div className="text-sm text-text-secondary">
349
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_STATS_COMPLETED)}
350
+ </div>
351
+ </div>
352
+ </Card>
353
+ <Card className="bg-elevated border-border">
354
+ <div className="text-center">
355
+ <div className="text-2xl font-bold text-text">
356
+ {tasks.filter((t) => t.status === 'failed').length}
357
+ </div>
358
+ <div className="text-sm text-text-secondary">
359
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_STATS_FAILED)}
360
+ </div>
361
+ </div>
362
+ </Card>
363
+ </section>
364
+
365
+ {/* Task List Section */}
366
+ <section className="bg-secondary shadow sm:rounded-lg p-6 border border-border">
367
+ <h2 className="text-xl font-medium text-text mb-4">
368
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_LIST_TITLE)}
369
+ </h2>
370
+ <div className="space-y-4">
371
+ {tasks.map((task) => (
372
+ <Card
373
+ key={task.id}
374
+ className="bg-elevated border-border"
375
+ title={
376
+ <div className="flex items-center justify-between">
377
+ <span className="text-text">{task.name}</span>
378
+ <Space>
379
+ <Tag color={getTypeColor(task.type)}>{task.type}</Tag>
380
+ <Tag color={getStatusColor(task.status)}>
381
+ {task.status}
382
+ </Tag>
383
+ </Space>
384
+ </div>
385
+ }
386
+ >
387
+ <div className="space-y-4">
388
+ <div className="grid grid-cols-2 gap-4 text-text-secondary">
389
+ <div>
390
+ <span className="font-medium">URL:</span> {task.url}
391
+ </div>
392
+ <div>
393
+ <span className="font-medium">Method:</span> {task.method}
394
+ </div>
395
+ <div>
396
+ <span className="font-medium">Response Type:</span>{' '}
397
+ {task.responseType}
398
+ </div>
399
+ <div>
400
+ <span className="font-medium">Duration:</span>{' '}
401
+ {formatDuration(task.startTime, task.endTime)}
402
+ </div>
403
+ </div>
404
+ <Progress
405
+ percent={task.progress}
406
+ status={getStatusColor(task.status)}
407
+ />
408
+ <div className="flex justify-end space-x-2">
409
+ {task.status === 'pending' && (
410
+ <Button
411
+ type="primary"
412
+ onClick={() => handleStartTask(task.id)}
413
+ >
414
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_START)}
415
+ </Button>
416
+ )}
417
+ {task.status === 'running' && (
418
+ <Button
419
+ type="primary"
420
+ danger
421
+ onClick={() => handleStopTask(task.id)}
422
+ >
423
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_STOP)}
424
+ </Button>
425
+ )}
426
+ </div>
427
+ </div>
428
+ </Card>
429
+ ))}
430
+ </div>
431
+ </section>
432
+
433
+ {/* Task History */}
434
+ <section className="bg-secondary shadow sm:rounded-lg p-6 border border-border">
435
+ <h2 className="text-xl font-medium text-text mb-4">
436
+ {t(i18nKeys.PAGE_EXECUTOR_TASK_HISTORY_TITLE)}
437
+ </h2>
438
+ <div className="space-y-2">
439
+ {tasks
440
+ .filter(
441
+ (task) =>
442
+ task.status === 'completed' || task.status === 'failed'
443
+ )
444
+ .map((task) => (
445
+ <div
446
+ key={task.id}
447
+ className="flex items-center justify-between p-2 bg-elevated rounded"
448
+ >
449
+ <div className="flex items-center space-x-2">
450
+ <Tag color={getStatusColor(task.status)}>{task.status}</Tag>
451
+ <span className="text-text">{task.name}</span>
452
+ </div>
453
+ <span className="text-sm text-text-secondary">
454
+ {task.endTime?.toLocaleString()}
455
+ </span>
456
+ </div>
457
+ ))}
458
+ </div>
459
+ </section>
460
+
461
+ {/* Call to Action Section */}
462
+ <section className="py-8 text-center">
463
+ <h2 className="text-2xl font-bold mb-4 text-text">
464
+ {t(i18nKeys.PAGE_EXECUTOR_HELP_TITLE)}
465
+ </h2>
466
+ <p className="text-lg text-text-secondary mb-6">
467
+ {t(i18nKeys.PAGE_EXECUTOR_HELP_DESCRIPTION)}
468
+ </p>
469
+ <Space>
470
+ <Button type="primary" size="large">
471
+ {t(i18nKeys.PAGE_EXECUTOR_VIEW_GUIDE)}
472
+ </Button>
473
+ <Button size="large">
474
+ {t(i18nKeys.PAGE_EXECUTOR_CONTACT_SUPPORT)}
475
+ </Button>
476
+ </Space>
477
+ </section>
60
478
  </div>
61
479
  </div>
62
480
  );