@strapi/admin 4.6.2 → 4.7.0-exp.3d6a31eb083e9d44afcf98f68c107fb7567e5720

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 (97) hide show
  1. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +0 -2
  2. package/admin/src/hooks/useRegenerate/index.js +2 -2
  3. package/admin/src/hooks/useSettingsMenu/utils/defaultGlobalLinks.js +7 -0
  4. package/admin/src/pages/HomePage/CloudBox.js +83 -0
  5. package/admin/src/pages/HomePage/ContentBlocks.js +2 -0
  6. package/admin/src/pages/HomePage/assets/strapi-cloud-background.png +0 -0
  7. package/admin/src/pages/HomePage/assets/strapi-cloud-flags.svg +1 -0
  8. package/admin/src/pages/HomePage/assets/strapi-cloud-icon.svg +1 -0
  9. package/admin/src/pages/SettingsPage/{pages/ApiTokens/EditView/components → components/Tokens}/FormHead/index.js +36 -19
  10. package/admin/src/pages/SettingsPage/components/Tokens/FormiTokenContainer/LifeSpanInput.js +95 -0
  11. package/admin/src/pages/SettingsPage/components/Tokens/LifeSpanInput/index.js +97 -0
  12. package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +73 -0
  13. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/DeleteButton/index.js +19 -6
  14. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +145 -0
  15. package/admin/src/pages/SettingsPage/{pages/ApiTokens/EditView/components/ContentBox → components/Tokens/TokenBox}/index.js +19 -16
  16. package/admin/src/pages/SettingsPage/components/Tokens/TokenDescription/index.js +51 -0
  17. package/admin/src/pages/SettingsPage/components/Tokens/TokenName/index.js +46 -0
  18. package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +69 -0
  19. package/admin/src/pages/SettingsPage/components/Tokens/constants.js +2 -0
  20. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +3 -1
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +53 -150
  22. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +5 -1
  23. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +46 -17
  24. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +16 -16
  25. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +101 -0
  26. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/LoadingView/index.js +48 -0
  27. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +219 -0
  28. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/getDateOfExpiration.js +16 -0
  29. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/index.js +4 -0
  30. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +10 -0
  31. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +194 -0
  32. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/utils/tableHeaders.js +48 -0
  33. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedCreateView/index.js +14 -0
  34. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedEditView/index.js +14 -0
  35. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedListView/index.js +12 -0
  36. package/admin/src/pages/SettingsPage/utils/defaultRoutes.js +33 -0
  37. package/admin/src/permissions/defaultPermissions.js +8 -0
  38. package/admin/src/translations/en.json +15 -0
  39. package/build/27d16aefee06412db90a.png +0 -0
  40. package/build/4049.16583eee.chunk.js +1 -0
  41. package/build/4649.b7e84a29.chunk.js +30 -0
  42. package/build/7259.3f04094f.chunk.js +1 -0
  43. package/build/{Admin-authenticatedApp.dd16edad.chunk.js → Admin-authenticatedApp.368164a1.chunk.js} +6 -6
  44. package/build/Admin_homePage.1f10437f.chunk.js +78 -0
  45. package/build/{Admin_settingsPage.3cd54156.chunk.js → Admin_settingsPage.5a329b58.chunk.js} +25 -25
  46. package/build/{admin-app.3a084127.chunk.js → admin-app.df9adf93.chunk.js} +26 -26
  47. package/build/{api-tokens-create-page.a31c7fba.chunk.js → api-tokens-create-page.4328b852.chunk.js} +1 -1
  48. package/build/{api-tokens-edit-page.64fef287.chunk.js → api-tokens-edit-page.bce5050f.chunk.js} +1 -1
  49. package/build/api-tokens-list-page.149903c8.chunk.js +16 -0
  50. package/build/bb3108f7fd1e6179bde1.svg +1 -0
  51. package/build/bb4d0d527bdfb161bc5a.svg +1 -0
  52. package/build/{content-manager.d04b738f.chunk.js → content-manager.6ed87531.chunk.js} +1 -1
  53. package/build/en-json.8e5451b1.chunk.js +1 -0
  54. package/build/index.html +1 -1
  55. package/build/{main.9c01de7f.js → main.8009bfe8.js} +1 -0
  56. package/build/runtime~main.725b20df.js +2 -0
  57. package/build/transfer-tokens-create-page.a1f14bb1.chunk.js +1 -0
  58. package/build/transfer-tokens-edit-page.00ee1c74.chunk.js +1 -0
  59. package/build/transfer-tokens-list-page.1e15926d.chunk.js +16 -0
  60. package/package.json +9 -9
  61. package/server/bootstrap.js +2 -0
  62. package/server/config/admin-actions.js +48 -0
  63. package/server/content-types/index.js +2 -0
  64. package/server/content-types/transfer-token-permission.js +36 -0
  65. package/server/content-types/transfer-token.js +66 -0
  66. package/server/controllers/api-token.js +4 -5
  67. package/server/controllers/index.js +1 -0
  68. package/server/controllers/transfer/index.js +13 -0
  69. package/server/controllers/transfer/runner.js +24 -0
  70. package/server/controllers/transfer/token.js +131 -0
  71. package/server/register.js +2 -9
  72. package/server/routes/index.js +2 -0
  73. package/server/routes/transfer.js +95 -0
  74. package/server/services/api-token.js +2 -3
  75. package/server/services/constants.js +6 -0
  76. package/server/services/index.js +1 -0
  77. package/server/services/transfer/index.js +6 -0
  78. package/server/services/transfer/permission.js +22 -0
  79. package/server/services/transfer/token.js +409 -0
  80. package/server/strategies/api-token.js +4 -2
  81. package/server/strategies/data-transfer.js +107 -0
  82. package/server/strategies/index.js +1 -0
  83. package/server/utils/index.d.ts +2 -0
  84. package/server/validation/api-tokens.js +1 -6
  85. package/server/validation/transfer/index.js +5 -0
  86. package/server/validation/transfer/token.js +34 -0
  87. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +0 -77
  88. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +0 -110
  89. package/build/1341.5d48c79b.chunk.js +0 -1
  90. package/build/4318.9b1ac9bc.chunk.js +0 -30
  91. package/build/Admin_homePage.4b878f04.chunk.js +0 -72
  92. package/build/api-tokens-list-page.370459ba.chunk.js +0 -16
  93. package/build/en-json.9cada7f3.chunk.js +0 -1
  94. package/build/runtime~main.bb1389c9.js +0 -2
  95. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/DefaultButton/index.js +1 -1
  96. /package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/ReadButton/index.js +0 -0
  97. /package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/UpdateButton/index.js +0 -0
@@ -11,19 +11,22 @@ import {
11
11
  useRBAC,
12
12
  useFetchClient,
13
13
  } from '@strapi/helper-plugin';
14
- import { Main } from '@strapi/design-system';
14
+ import { Main, ContentLayout, Stack } from '@strapi/design-system';
15
15
  import { Formik } from 'formik';
16
16
  import { useRouteMatch, useHistory } from 'react-router-dom';
17
17
  import { useQuery } from 'react-query';
18
18
  import { formatAPIErrors } from '../../../../../utils';
19
19
  import { schema } from './utils';
20
20
  import LoadingView from './components/LoadingView';
21
- import FormHead from './components/FormHead';
22
- import FormBody from './components/FormBody';
23
21
  import adminPermissions from '../../../../../permissions';
24
22
  import { ApiTokenPermissionsContextProvider } from '../../../../../contexts/ApiTokenPermissions';
25
23
  import init from './init';
26
24
  import reducer, { initialState } from './reducer';
25
+ import Permissions from './components/Permissions';
26
+ import FormApiTokenContainer from './components/FormApiTokenContainer';
27
+ import TokenBox from '../../../components/Tokens/TokenBox';
28
+ import FormHead from '../../../components/Tokens/FormHead';
29
+ import { API_TOKEN_TYPE } from '../../../components/Tokens/constants';
27
30
 
28
31
  const MSG_ERROR_NAME_TAKEN = 'Name already taken';
29
32
 
@@ -105,7 +108,9 @@ const ApiTokenCreateView = () => {
105
108
  );
106
109
 
107
110
  useEffect(() => {
108
- trackUsageRef.current(isCreating ? 'didAddTokenFromList' : 'didEditTokenFromList');
111
+ trackUsageRef.current(isCreating ? 'didAddTokenFromList' : 'didEditTokenFromList', {
112
+ tokenType: API_TOKEN_TYPE,
113
+ });
109
114
  }, [isCreating]);
110
115
 
111
116
  const { status } = useQuery(
@@ -150,7 +155,9 @@ const ApiTokenCreateView = () => {
150
155
  );
151
156
 
152
157
  const handleSubmit = async (body, actions) => {
153
- trackUsageRef.current(isCreating ? 'willCreateToken' : 'willEditToken');
158
+ trackUsageRef.current(isCreating ? 'willCreateToken' : 'willEditToken', {
159
+ tokenType: API_TOKEN_TYPE,
160
+ });
154
161
  lockApp();
155
162
  const lifespanVal =
156
163
  body.lifespan && parseInt(body.lifespan, 10) && body.lifespan !== '0'
@@ -197,6 +204,7 @@ const ApiTokenCreateView = () => {
197
204
 
198
205
  trackUsageRef.current(isCreating ? 'didCreateToken' : 'didEditToken', {
199
206
  type: apiToken.type,
207
+ tokenType: API_TOKEN_TYPE,
200
208
  });
201
209
  } catch (err) {
202
210
  const errors = formatAPIErrors(err.response.data);
@@ -280,22 +288,43 @@ const ApiTokenCreateView = () => {
280
288
  return (
281
289
  <Form>
282
290
  <FormHead
283
- apiToken={apiToken}
284
- setApiToken={setApiToken}
291
+ backUrl="/settings/api-tokens"
292
+ title={{
293
+ id: 'Settings.apiTokens.createPage.title',
294
+ defaultMessage: 'Create API Token',
295
+ }}
296
+ token={apiToken}
297
+ setToken={setApiToken}
285
298
  canEditInputs={canEditInputs}
286
299
  canRegenerate={canRegenerate}
287
300
  isSubmitting={isSubmitting}
301
+ regenerateUrl="/admin/api-tokens/"
288
302
  />
289
- <FormBody
290
- apiToken={apiToken}
291
- errors={errors}
292
- onChange={handleChange}
293
- canEditInputs={canEditInputs}
294
- isCreating={isCreating}
295
- values={values}
296
- onDispatch={dispatch}
297
- setHasChangedPermissions={setHasChangedPermissions}
298
- />
303
+
304
+ <ContentLayout>
305
+ <Stack spacing={6}>
306
+ {Boolean(apiToken?.name) && (
307
+ <TokenBox token={apiToken?.accessKey} tokenType={API_TOKEN_TYPE} />
308
+ )}
309
+ <FormApiTokenContainer
310
+ errors={errors}
311
+ onChange={handleChange}
312
+ canEditInputs={canEditInputs}
313
+ isCreating={isCreating}
314
+ values={values}
315
+ apiToken={apiToken}
316
+ onDispatch={dispatch}
317
+ setHasChangedPermissions={setHasChangedPermissions}
318
+ />
319
+ <Permissions
320
+ disabled={
321
+ !canEditInputs ||
322
+ values?.type === 'read-only' ||
323
+ values?.type === 'full-access'
324
+ }
325
+ />
326
+ </Stack>
327
+ </ContentLayout>
299
328
  </Form>
300
329
  );
301
330
  }}
@@ -11,7 +11,6 @@ import {
11
11
  NoPermissions,
12
12
  useRBAC,
13
13
  NoContent,
14
- DynamicTable,
15
14
  useTracking,
16
15
  useGuidedTour,
17
16
  useFetchClient,
@@ -22,7 +21,8 @@ import { Plus } from '@strapi/icons';
22
21
 
23
22
  import adminPermissions from '../../../../../permissions';
24
23
  import tableHeaders from './utils/tableHeaders';
25
- import TableRows from './DynamicTable';
24
+ import Table from '../../../components/Tokens/Table';
25
+ import { API_TOKEN_TYPE } from '../../../components/Tokens/constants';
26
26
 
27
27
  const ApiTokenListView = () => {
28
28
  useFocusWhenNavigate();
@@ -63,12 +63,14 @@ const ApiTokenListView = () => {
63
63
  } = useQuery(
64
64
  ['api-tokens'],
65
65
  async () => {
66
- trackUsage('willAccessTokenList');
66
+ trackUsage('willAccessTokenList', {
67
+ tokenType: API_TOKEN_TYPE,
68
+ });
67
69
  const {
68
70
  data: { data },
69
71
  } = await get(`/admin/api-tokens`);
70
72
 
71
- trackUsage('didAccessTokenList', { number: data.length });
73
+ trackUsage('didAccessTokenList', { number: data.length, tokenType: API_TOKEN_TYPE });
72
74
 
73
75
  return data;
74
76
  },
@@ -128,7 +130,11 @@ const ApiTokenListView = () => {
128
130
  data-testid="create-api-token-button"
129
131
  startIcon={<Plus />}
130
132
  size="S"
131
- onClick={() => trackUsage('willAddTokenFromList')}
133
+ onClick={() =>
134
+ trackUsage('willAddTokenFromList', {
135
+ tokenType: API_TOKEN_TYPE,
136
+ })
137
+ }
132
138
  to="/settings/api-tokens/create"
133
139
  >
134
140
  {formatMessage({
@@ -142,22 +148,16 @@ const ApiTokenListView = () => {
142
148
  <ContentLayout>
143
149
  {!canRead && <NoPermissions />}
144
150
  {shouldDisplayDynamicTable && (
145
- <DynamicTable
151
+ <Table
152
+ permissions={{ canRead, canDelete, canUpdate }}
146
153
  headers={headers}
147
154
  contentType="api-tokens"
148
155
  rows={apiTokens}
149
- withBulkActions={canDelete || canUpdate || canRead}
150
156
  isLoading={isLoading}
151
157
  onConfirmDelete={(id) => deleteMutation.mutateAsync(id)}
152
- >
153
- <TableRows
154
- canRead={canRead}
155
- canDelete={canDelete}
156
- canUpdate={canUpdate}
157
- rows={apiTokens}
158
- withBulkActions={canDelete || canUpdate || canRead}
159
- />
160
- </DynamicTable>
158
+ tokens={apiTokens}
159
+ tokenType={API_TOKEN_TYPE}
160
+ />
161
161
  )}
162
162
  {shouldDisplayNoContentWithCreationButton && (
163
163
  <NoContent
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import PropTypes from 'prop-types';
4
+ import { Box, Grid, GridItem, Stack, Typography } from '@strapi/design-system';
5
+ import LifeSpanInput from '../../../../../components/Tokens/LifeSpanInput';
6
+ import TokenName from '../../../../../components/Tokens/TokenName';
7
+ import TokenDescription from '../../../../../components/Tokens/TokenDescription';
8
+
9
+ const FormTransferTokenContainer = ({
10
+ errors,
11
+ onChange,
12
+ canEditInputs,
13
+ isCreating,
14
+ values,
15
+ transferToken,
16
+ }) => {
17
+ const { formatMessage } = useIntl();
18
+
19
+ return (
20
+ <Box
21
+ background="neutral0"
22
+ hasRadius
23
+ shadow="filterShadow"
24
+ paddingTop={6}
25
+ paddingBottom={6}
26
+ paddingLeft={7}
27
+ paddingRight={7}
28
+ >
29
+ <Stack spacing={4}>
30
+ <Typography variant="delta" as="h2">
31
+ {formatMessage({
32
+ id: 'global.details',
33
+ defaultMessage: 'Details',
34
+ })}
35
+ </Typography>
36
+ <Grid gap={5}>
37
+ <GridItem key="name" col={6} xs={12}>
38
+ <TokenName
39
+ errors={errors}
40
+ values={values}
41
+ canEditInputs={canEditInputs}
42
+ onChange={onChange}
43
+ />
44
+ </GridItem>
45
+ <GridItem key="description" col={6} xs={12}>
46
+ <TokenDescription
47
+ errors={errors}
48
+ values={values}
49
+ canEditInputs={canEditInputs}
50
+ onChange={onChange}
51
+ />
52
+ </GridItem>
53
+ <GridItem key="lifespan" col={6} xs={12}>
54
+ <LifeSpanInput
55
+ isCreating={isCreating}
56
+ errors={errors}
57
+ values={values}
58
+ onChange={onChange}
59
+ token={transferToken}
60
+ />
61
+ </GridItem>
62
+ </Grid>
63
+ </Stack>
64
+ </Box>
65
+ );
66
+ };
67
+
68
+ FormTransferTokenContainer.propTypes = {
69
+ errors: PropTypes.shape({
70
+ name: PropTypes.string,
71
+ description: PropTypes.string,
72
+ lifespan: PropTypes.string,
73
+ type: PropTypes.string,
74
+ }),
75
+ onChange: PropTypes.func.isRequired,
76
+ canEditInputs: PropTypes.bool.isRequired,
77
+ values: PropTypes.shape({
78
+ name: PropTypes.string,
79
+ description: PropTypes.string,
80
+ lifespan: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
81
+ type: PropTypes.string,
82
+ }).isRequired,
83
+ isCreating: PropTypes.bool.isRequired,
84
+ transferToken: PropTypes.shape({
85
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
86
+ type: PropTypes.string,
87
+ lifespan: PropTypes.string,
88
+ name: PropTypes.string,
89
+ accessKey: PropTypes.string,
90
+ permissions: PropTypes.array,
91
+ description: PropTypes.string,
92
+ createdAt: PropTypes.string,
93
+ }),
94
+ };
95
+
96
+ FormTransferTokenContainer.defaultProps = {
97
+ errors: {},
98
+ transferToken: {},
99
+ };
100
+
101
+ export default FormTransferTokenContainer;
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import {
3
+ SettingsPageTitle,
4
+ LoadingIndicatorPage,
5
+ useFocusWhenNavigate,
6
+ } from '@strapi/helper-plugin';
7
+ import { HeaderLayout, ContentLayout, Main, Button } from '@strapi/design-system';
8
+ import { Check } from '@strapi/icons';
9
+ import { useIntl } from 'react-intl';
10
+ import PropTypes from 'prop-types';
11
+
12
+ const LoadingView = ({ transferTokenName }) => {
13
+ const { formatMessage } = useIntl();
14
+ useFocusWhenNavigate();
15
+
16
+ return (
17
+ <Main aria-busy="true">
18
+ <SettingsPageTitle name="Transfer Tokens" />
19
+ <HeaderLayout
20
+ primaryAction={
21
+ <Button disabled startIcon={<Check />} type="button" size="L">
22
+ {formatMessage({ id: 'global.save', defaultMessage: 'Save' })}
23
+ </Button>
24
+ }
25
+ title={
26
+ transferTokenName ||
27
+ formatMessage({
28
+ id: 'Settings.transferTokens.createPage.title',
29
+ defaultMessage: 'Create Transfer Token',
30
+ })
31
+ }
32
+ />
33
+ <ContentLayout>
34
+ <LoadingIndicatorPage />
35
+ </ContentLayout>
36
+ </Main>
37
+ );
38
+ };
39
+
40
+ LoadingView.defaultProps = {
41
+ transferTokenName: null,
42
+ };
43
+
44
+ LoadingView.propTypes = {
45
+ transferTokenName: PropTypes.string,
46
+ };
47
+
48
+ export default LoadingView;
@@ -0,0 +1,219 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { Formik } from 'formik';
4
+ import { useRouteMatch, useHistory } from 'react-router-dom';
5
+ import { useQuery } from 'react-query';
6
+ import {
7
+ SettingsPageTitle,
8
+ useFocusWhenNavigate,
9
+ Form,
10
+ useOverlayBlocker,
11
+ useNotification,
12
+ useTracking,
13
+ useGuidedTour,
14
+ useRBAC,
15
+ useFetchClient,
16
+ } from '@strapi/helper-plugin';
17
+ import { ContentLayout, Main, Stack } from '@strapi/design-system';
18
+ import { formatAPIErrors } from '../../../../../utils';
19
+ import { schema } from './utils';
20
+ import LoadingView from './components/LoadingView';
21
+ import adminPermissions from '../../../../../permissions';
22
+ import FormTransferTokenContainer from './components/FormTransferTokenContainer';
23
+ import TokenBox from '../../../components/Tokens/TokenBox';
24
+ import FormHead from '../../../components/Tokens/FormHead';
25
+ import { TRANSFER_TOKEN_TYPE } from '../../../components/Tokens/constants';
26
+
27
+ const MSG_ERROR_NAME_TAKEN = 'Name already taken';
28
+
29
+ const TransferTokenCreateView = () => {
30
+ useFocusWhenNavigate();
31
+ const { formatMessage } = useIntl();
32
+ const { lockApp, unlockApp } = useOverlayBlocker();
33
+ const toggleNotification = useNotification();
34
+ const history = useHistory();
35
+ const [transferToken, setTransferToken] = useState(
36
+ history.location.state?.transferToken.accessKey
37
+ ? {
38
+ ...history.location.state.transferToken,
39
+ }
40
+ : null
41
+ );
42
+ const { trackUsage } = useTracking();
43
+ const trackUsageRef = useRef(trackUsage);
44
+ const { setCurrentStep } = useGuidedTour();
45
+ const {
46
+ allowedActions: { canCreate, canUpdate, canRegenerate },
47
+ } = useRBAC(adminPermissions.settings['transfer-tokens']);
48
+ const {
49
+ params: { id },
50
+ } = useRouteMatch('/settings/transfer-tokens/:id');
51
+ const { get, post, put } = useFetchClient();
52
+
53
+ const isCreating = id === 'create';
54
+
55
+ useEffect(() => {
56
+ trackUsageRef.current(isCreating ? 'didAddTokenFromList' : 'didEditTokenFromList', {
57
+ tokenType: TRANSFER_TOKEN_TYPE,
58
+ });
59
+ }, [isCreating]);
60
+
61
+ const { status } = useQuery(
62
+ ['transfer-token', id],
63
+ async () => {
64
+ const {
65
+ data: { data },
66
+ } = await get(`/admin/transfer/tokens/${id}`);
67
+
68
+ setTransferToken({
69
+ ...data,
70
+ });
71
+
72
+ return data;
73
+ },
74
+ {
75
+ enabled: !isCreating && !transferToken,
76
+ onError() {
77
+ toggleNotification({
78
+ type: 'warning',
79
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
80
+ });
81
+ },
82
+ }
83
+ );
84
+
85
+ const handleSubmit = async (body, actions) => {
86
+ trackUsageRef.current(isCreating ? 'willCreateToken' : 'willEditToken', {
87
+ tokenType: TRANSFER_TOKEN_TYPE,
88
+ });
89
+ lockApp();
90
+ const lifespanVal =
91
+ body.lifespan && parseInt(body.lifespan, 10) && body.lifespan !== '0'
92
+ ? parseInt(body.lifespan, 10)
93
+ : null;
94
+
95
+ try {
96
+ const {
97
+ data: { data: response },
98
+ } = isCreating
99
+ ? await post(`/admin/transfer/tokens`, {
100
+ ...body,
101
+ lifespan: lifespanVal,
102
+ permissions: ['push'],
103
+ })
104
+ : await put(`/admin/transfer/tokens/${id}`, {
105
+ name: body.name,
106
+ description: body.description,
107
+ type: body.type,
108
+ permissions: ['push'],
109
+ });
110
+
111
+ unlockApp();
112
+
113
+ if (isCreating) {
114
+ history.replace(`/settings/transfer-tokens/${response.id}`, { transferToken: response });
115
+ setCurrentStep('transferTokens.success');
116
+ }
117
+ setTransferToken({
118
+ ...response,
119
+ });
120
+
121
+ toggleNotification({
122
+ type: 'success',
123
+ message: isCreating
124
+ ? formatMessage({
125
+ id: 'notification.success.transfertokencreated',
126
+ defaultMessage: 'Transfer Token successfully created',
127
+ })
128
+ : formatMessage({
129
+ id: 'notification.success.transfertokenedited',
130
+ defaultMessage: 'Transfer Token successfully edited',
131
+ }),
132
+ });
133
+
134
+ trackUsageRef.current(isCreating ? 'didCreateToken' : 'didEditToken', {
135
+ type: transferToken?.type,
136
+ tokenType: TRANSFER_TOKEN_TYPE,
137
+ });
138
+ } catch (err) {
139
+ const errors = formatAPIErrors(err.response.data);
140
+ actions.setErrors(errors);
141
+
142
+ if (err?.response?.data?.error?.message === MSG_ERROR_NAME_TAKEN) {
143
+ toggleNotification({
144
+ type: 'warning',
145
+ message: err.response.data.message || 'notification.error.tokennamenotunique',
146
+ });
147
+ } else {
148
+ toggleNotification({
149
+ type: 'warning',
150
+ message: err?.response?.data?.message || 'notification.error',
151
+ });
152
+ }
153
+ unlockApp();
154
+ }
155
+ };
156
+
157
+ const canEditInputs = (canUpdate && !isCreating) || (canCreate && isCreating);
158
+ const isLoading = !isCreating && !transferToken && status !== 'success';
159
+
160
+ if (isLoading) {
161
+ return <LoadingView transferTokenName={transferToken?.name} />;
162
+ }
163
+
164
+ return (
165
+ <Main>
166
+ <SettingsPageTitle name="Transfer Tokens" />
167
+ <Formik
168
+ validationSchema={schema}
169
+ validateOnChange={false}
170
+ initialValues={{
171
+ name: transferToken?.name || '',
172
+ description: transferToken?.description || '',
173
+ lifespan: transferToken?.lifespan
174
+ ? transferToken.lifespan.toString()
175
+ : transferToken?.lifespan,
176
+ }}
177
+ enableReinitialize
178
+ onSubmit={(body, actions) => handleSubmit(body, actions)}
179
+ >
180
+ {({ errors, handleChange, isSubmitting, values }) => {
181
+ return (
182
+ <Form>
183
+ <FormHead
184
+ backUrl="/settings/transfer-tokens"
185
+ title={{
186
+ id: 'Settings.transferTokens.createPage.title',
187
+ defaultMessage: 'Create Transfer Token',
188
+ }}
189
+ token={transferToken}
190
+ setToken={setTransferToken}
191
+ canEditInputs={canEditInputs}
192
+ canRegenerate={canRegenerate}
193
+ isSubmitting={isSubmitting}
194
+ regenerateUrl="/admin/transfer/tokens/"
195
+ />
196
+ <ContentLayout>
197
+ <Stack spacing={6}>
198
+ {Boolean(transferToken?.name) && (
199
+ <TokenBox token={transferToken?.accessKey} tokenType={TRANSFER_TOKEN_TYPE} />
200
+ )}
201
+ <FormTransferTokenContainer
202
+ errors={errors}
203
+ onChange={handleChange}
204
+ canEditInputs={canEditInputs}
205
+ isCreating={isCreating}
206
+ values={values}
207
+ transferToken={transferToken}
208
+ />
209
+ </Stack>
210
+ </ContentLayout>
211
+ </Form>
212
+ );
213
+ }}
214
+ </Formik>
215
+ </Main>
216
+ );
217
+ };
218
+
219
+ export default TransferTokenCreateView;
@@ -0,0 +1,16 @@
1
+ import { addDays, format } from 'date-fns';
2
+ import * as locales from 'date-fns/locale';
3
+
4
+ const getDateOfExpiration = (createdAt, duration, language = 'en') => {
5
+ if (duration && typeof duration === 'number') {
6
+ const durationInDays = duration / 24 / 60 / 60 / 1000;
7
+
8
+ return format(addDays(new Date(createdAt), durationInDays), 'PPP', {
9
+ locale: locales[language],
10
+ });
11
+ }
12
+
13
+ return 'Unlimited';
14
+ };
15
+
16
+ export default getDateOfExpiration;
@@ -0,0 +1,4 @@
1
+ import getDateOfExpiration from './getDateOfExpiration';
2
+ import schema from './schema';
3
+
4
+ export { getDateOfExpiration, schema };
@@ -0,0 +1,10 @@
1
+ import * as yup from 'yup';
2
+ import { translatedErrors } from '@strapi/helper-plugin';
3
+
4
+ const schema = yup.object().shape({
5
+ name: yup.string(translatedErrors.string).required(translatedErrors.required),
6
+ description: yup.string().nullable(),
7
+ lifespan: yup.number().integer().min(0).nullable().defined(translatedErrors.required),
8
+ });
9
+
10
+ export default schema;