@strapi/admin 4.6.1 → 4.7.0-beta.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.
Files changed (93) hide show
  1. package/admin/src/assets/images/onboarding-preview.png +0 -0
  2. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +0 -2
  3. package/admin/src/hooks/useRegenerate/index.js +2 -2
  4. package/admin/src/hooks/useSettingsMenu/utils/defaultGlobalLinks.js +7 -0
  5. package/admin/src/pages/Admin/Onboarding/constants.js +46 -0
  6. package/admin/src/pages/Admin/Onboarding/index.js +161 -89
  7. package/admin/src/pages/Admin/index.js +5 -2
  8. package/admin/src/pages/SettingsPage/{pages/ApiTokens/EditView/components → components/Tokens}/FormHead/index.js +36 -19
  9. package/admin/src/pages/SettingsPage/components/Tokens/FormiTokenContainer/LifeSpanInput.js +96 -0
  10. package/admin/src/pages/SettingsPage/components/Tokens/LifeSpanInput/index.js +98 -0
  11. package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +73 -0
  12. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/DefaultButton/index.js +1 -1
  13. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/DeleteButton/index.js +1 -1
  14. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/ReadButton/index.js +0 -0
  15. package/admin/src/pages/SettingsPage/{pages/ApiTokens/ListView/DynamicTable → components/Tokens/Table}/UpdateButton/index.js +0 -0
  16. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +135 -0
  17. package/admin/src/pages/SettingsPage/{pages/ApiTokens/EditView/components/ContentBox → components/Tokens/TokenBox}/index.js +17 -17
  18. package/admin/src/pages/SettingsPage/components/Tokens/TokenDescription/index.js +51 -0
  19. package/admin/src/pages/SettingsPage/components/Tokens/TokenName/index.js +46 -0
  20. package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +69 -0
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +5 -3
  22. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +52 -142
  23. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +5 -1
  24. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +37 -14
  25. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +5 -13
  26. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +105 -0
  27. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/LoadingView/index.js +50 -0
  28. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +201 -0
  29. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/getDateOfExpiration.js +16 -0
  30. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/index.js +4 -0
  31. package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +10 -0
  32. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +182 -0
  33. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/utils/tableHeaders.js +48 -0
  34. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedCreateView/index.js +14 -0
  35. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedEditView/index.js +14 -0
  36. package/admin/src/pages/SettingsPage/pages/TransferTokens/ProtectedListView/index.js +12 -0
  37. package/admin/src/pages/SettingsPage/utils/defaultRoutes.js +33 -0
  38. package/admin/src/permissions/defaultPermissions.js +8 -0
  39. package/admin/src/translations/en.json +18 -1
  40. package/build/19eb2dfcf2603eb55733.png +0 -0
  41. package/build/4649.15cc0afe.chunk.js +30 -0
  42. package/build/7259.aa68d808.chunk.js +1 -0
  43. package/build/7407.883fb1f5.chunk.js +1 -0
  44. package/build/Admin-authenticatedApp.f29f6021.chunk.js +79 -0
  45. package/build/{Admin_settingsPage.d1493824.chunk.js → Admin_settingsPage.178dc6e3.chunk.js} +25 -25
  46. package/build/{admin-app.25934eaa.chunk.js → admin-app.77a50e1f.chunk.js} +19 -19
  47. package/build/{api-tokens-create-page.d248362d.chunk.js → api-tokens-create-page.0db3aec1.chunk.js} +1 -1
  48. package/build/{api-tokens-edit-page.8516fa20.chunk.js → api-tokens-edit-page.671e0e26.chunk.js} +1 -1
  49. package/build/api-tokens-list-page.7387102c.chunk.js +16 -0
  50. package/build/{content-manager.35ff9726.chunk.js → content-manager.42b24d46.chunk.js} +77 -77
  51. package/build/en-json.b0748970.chunk.js +1 -0
  52. package/build/index.html +1 -1
  53. package/build/main.1022ed01.js +4393 -0
  54. package/build/runtime~main.84941a97.js +2 -0
  55. package/build/transfer-tokens-create-page.16e23791.chunk.js +1 -0
  56. package/build/transfer-tokens-edit-page.3886c973.chunk.js +1 -0
  57. package/build/transfer-tokens-list-page.e8010a89.chunk.js +16 -0
  58. package/package.json +12 -12
  59. package/server/bootstrap.js +2 -0
  60. package/server/config/admin-actions.js +48 -0
  61. package/server/content-types/index.js +2 -0
  62. package/server/content-types/transfer-token-permission.js +36 -0
  63. package/server/content-types/transfer-token.js +66 -0
  64. package/server/controllers/api-token.js +4 -5
  65. package/server/controllers/index.js +1 -0
  66. package/server/controllers/transfer/index.js +13 -0
  67. package/server/controllers/transfer/runner.js +24 -0
  68. package/server/controllers/transfer/token.js +131 -0
  69. package/server/register.js +2 -9
  70. package/server/routes/index.js +2 -0
  71. package/server/routes/transfer.js +95 -0
  72. package/server/services/api-token.js +2 -3
  73. package/server/services/constants.js +6 -0
  74. package/server/services/index.js +1 -0
  75. package/server/services/transfer/index.js +6 -0
  76. package/server/services/transfer/permission.js +22 -0
  77. package/server/services/transfer/token.js +409 -0
  78. package/server/strategies/api-token.js +4 -2
  79. package/server/strategies/data-transfer.js +107 -0
  80. package/server/strategies/index.js +1 -0
  81. package/server/utils/index.d.ts +2 -0
  82. package/server/validation/api-tokens.js +1 -6
  83. package/server/validation/transfer/index.js +5 -0
  84. package/server/validation/transfer/token.js +34 -0
  85. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +0 -78
  86. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +0 -112
  87. package/build/4318.f96a9d4d.chunk.js +0 -30
  88. package/build/8633.00ccd382.chunk.js +0 -1
  89. package/build/Admin-authenticatedApp.ce646f66.chunk.js +0 -75
  90. package/build/api-tokens-list-page.44a79fda.chunk.js +0 -16
  91. package/build/en-json.1f137a90.chunk.js +0 -1
  92. package/build/main.7b151630.js +0 -4377
  93. package/build/runtime~main.a20d633b.js +0 -2
@@ -220,8 +220,6 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
220
220
 
221
221
  const onDelete = useCallback(
222
222
  async (trackerProperty) => {
223
- console.log('onDelete');
224
-
225
223
  try {
226
224
  trackUsageRef.current('willDeleteEntry', trackerProperty);
227
225
 
@@ -2,7 +2,7 @@ import { useState } from 'react';
2
2
  import { get } from 'lodash';
3
3
  import { useFetchClient, useNotification } from '@strapi/helper-plugin';
4
4
 
5
- const useRegenerate = (id, onRegenerate) => {
5
+ const useRegenerate = (url, id, onRegenerate) => {
6
6
  const [isLoadingConfirmation, setIsLoadingConfirmation] = useState(false);
7
7
  const toggleNotification = useNotification();
8
8
  const { post } = useFetchClient();
@@ -13,7 +13,7 @@ const useRegenerate = (id, onRegenerate) => {
13
13
  data: {
14
14
  data: { accessKey },
15
15
  },
16
- } = await post(`/admin/api-tokens/${id}/regenerate`);
16
+ } = await post(`${url}${id}/regenerate`);
17
17
  setIsLoadingConfirmation(false);
18
18
  onRegenerate(accessKey);
19
19
  } catch (error) {
@@ -22,6 +22,13 @@ const defaultGlobalLinks = [
22
22
  isDisplayed: false,
23
23
  permissions: adminPermissions.settings['api-tokens'].main,
24
24
  },
25
+ {
26
+ intlLabel: { id: 'Settings.transferTokens.title', defaultMessage: 'Transfer Tokens' },
27
+ to: '/settings/transfer-tokens?sort=name:ASC',
28
+ id: 'transfer-tokens',
29
+ isDisplayed: false,
30
+ permissions: adminPermissions.settings['transfer-tokens'].main,
31
+ },
25
32
  ];
26
33
 
27
34
  export default defaultGlobalLinks;
@@ -0,0 +1,46 @@
1
+ import { Book, PaperPlane } from '@strapi/icons';
2
+
3
+ export const VIDEO_LINKS = [
4
+ {
5
+ label: {
6
+ id: 'app.components.Onboarding.link.build-content',
7
+ defaultMessage: 'Build a content architecture',
8
+ },
9
+ href: 'https://www.youtube.com/watch?v=G9GjN0RxhkE',
10
+ duration: '5:48',
11
+ },
12
+ {
13
+ label: {
14
+ id: 'app.components.Onboarding.link.manage-content',
15
+ defaultMessage: 'Add & manage content',
16
+ },
17
+ href: 'https://www.youtube.com/watch?v=DEZw4KbybAI',
18
+ duration: '3:18',
19
+ },
20
+ {
21
+ label: { id: 'app.components.Onboarding.link.manage-media', defaultMessage: 'Manage media' },
22
+ href: 'https://www.youtube.com/watch?v=-61MuiMQb38',
23
+ duration: '3:41',
24
+ },
25
+ ];
26
+
27
+ export const WATCH_MORE = {
28
+ href: 'https://www.youtube.com/playlist?list=PL7Q0DQYATmvidz6lEmwE5nIcOAYagxWqq',
29
+ label: {
30
+ id: 'app.components.Onboarding.link.more-videos',
31
+ defaultMessage: 'Watch more videos',
32
+ },
33
+ };
34
+
35
+ export const DOCUMENTATION_LINKS = [
36
+ {
37
+ label: { id: 'global.documentation', defaultMessage: 'documentation' },
38
+ href: 'https://docs.strapi.io',
39
+ icon: Book,
40
+ },
41
+ {
42
+ label: { id: 'app.static.links.cheatsheet', defaultMessage: 'cheatsheet' },
43
+ href: 'https://strapi-showcase.s3-us-west-2.amazonaws.com/CheatSheet.pdf',
44
+ icon: PaperPlane,
45
+ },
46
+ ];
@@ -1,92 +1,91 @@
1
- import React, { useState } from 'react';
1
+ import React, { useRef, useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { useIntl } from 'react-intl';
4
- import { Box, Flex, FocusTrap, Typography, Icon, Stack } from '@strapi/design-system';
5
- import { Book, Cross, Information, Question } from '@strapi/icons';
6
- import { pxToRem } from '@strapi/helper-plugin';
7
-
8
- import { useConfigurations } from '../../../hooks';
9
-
10
- const OnboardingWrapper = styled(Box)`
11
- position: fixed;
12
- bottom: ${({ theme }) => theme.spaces[2]};
13
- right: ${({ theme }) => theme.spaces[2]};
14
- `;
15
-
16
- const Button = styled(Box)`
17
- width: ${({ theme }) => theme.spaces[8]};
18
- height: ${({ theme }) => theme.spaces[8]};
19
- background: ${({ theme }) => theme.colors.primary600};
20
- box-shadow: ${({ theme }) => theme.shadows.tableShadow};
4
+ import {
5
+ Box,
6
+ Button,
7
+ Divider,
8
+ Flex,
9
+ FocusTrap,
10
+ Icon,
11
+ Portal,
12
+ PopoverPrimitives,
13
+ Stack,
14
+ Typography,
15
+ VisuallyHidden,
16
+ } from '@strapi/design-system';
17
+ import { Cross, Play, Question } from '@strapi/icons';
18
+
19
+ import onboardingPreview from '../../../assets/images/onboarding-preview.png';
20
+ import { VIDEO_LINKS, DOCUMENTATION_LINKS, WATCH_MORE } from './constants';
21
+
22
+ // TODO: use new Button props derived from Box props with next DS release
23
+ const HelperButton = styled(Button)`
21
24
  border-radius: 50%;
22
-
23
- svg path {
24
- fill: ${({ theme }) => theme.colors.buttonNeutral0};
25
- }
25
+ padding: ${({ theme }) => theme.spaces[3]};
26
+ /* Resetting 2rem height defined by Button component */
27
+ height: 100%;
26
28
  `;
27
29
 
28
- const LinksWrapper = styled(Box)`
29
- bottom: ${({ theme }) => `${theme.spaces[9]}`};
30
- min-width: ${200 / 16}rem;
31
- position: absolute;
32
- right: 0;
30
+ const IconWrapper = styled(Flex)`
31
+ transform: translate(-50%, -50%);
33
32
  `;
34
33
 
35
- const StyledLink = styled(Flex)`
34
+ const VideoLinkWrapper = styled(Flex)`
36
35
  text-decoration: none;
37
36
 
38
- svg path {
39
- fill: ${({ theme }) => theme.colors.neutral600};
37
+ :focus-visible {
38
+ outline-offset: ${({ theme }) => `-${theme.spaces[1]}`};
40
39
  }
41
40
 
42
- &:focus,
43
- &:hover {
44
- background: ${({ theme }) => theme.colors.neutral100};
41
+ :hover {
42
+ background: ${({ theme }) => theme.colors.primary100};
45
43
 
46
- svg path {
47
- fill: ${({ theme }) => theme.colors.neutral700};
44
+ /* Hover style for the number displayed */
45
+ ${Typography}:first-child {
46
+ color: ${({ theme }) => theme.colors.primary500};
48
47
  }
49
48
 
50
- ${[Typography]} {
51
- color: ${({ theme }) => theme.colors.neutral700};
49
+ /* Hover style for the label */
50
+ ${Typography}:nth-child(1) {
51
+ color: ${({ theme }) => theme.colors.primary600};
52
52
  }
53
53
  }
54
54
  `;
55
55
 
56
+ const Preview = styled.img`
57
+ width: ${({ theme }) => theme.spaces[10]};
58
+ height: ${({ theme }) => theme.spaces[8]};
59
+ /* Same overlay used in ModalLayout */
60
+ background: ${({ theme }) => `${theme.colors.neutral800}1F`};
61
+ border-radius: ${({ theme }) => theme.borderRadius};
62
+ `;
63
+
64
+ const TypographyLineHeight = styled(Typography)`
65
+ /* line height of label and watch more to 1 so they can be better aligned visually */
66
+ line-height: 1;
67
+ `;
68
+
69
+ const TextLink = styled(TypographyLineHeight)`
70
+ text-decoration: none;
71
+
72
+ :hover {
73
+ text-decoration: underline;
74
+ }
75
+ `;
76
+
56
77
  const Onboarding = () => {
78
+ const triggerRef = useRef();
57
79
  const [isOpen, setIsOpen] = useState(false);
58
80
  const { formatMessage } = useIntl();
59
- const { showTutorials } = useConfigurations();
60
-
61
- if (!showTutorials) {
62
- return null;
63
- }
64
81
 
65
- const STATIC_LINKS = [
66
- {
67
- Icon: <Book />,
68
- label: formatMessage({
69
- id: 'global.documentation',
70
- defaultMessage: 'Documentation',
71
- }),
72
- destination: 'https://docs.strapi.io',
73
- },
74
- {
75
- Icon: <Information />,
76
- label: formatMessage({ id: 'app.static.links.cheatsheet', defaultMessage: 'CheatSheet' }),
77
- destination: 'https://strapi-showcase.s3-us-west-2.amazonaws.com/CheatSheet.pdf',
78
- },
79
- ];
80
-
81
- const handleClick = () => {
82
+ const handlePopoverVisibility = () => {
82
83
  setIsOpen((prev) => !prev);
83
84
  };
84
85
 
85
86
  return (
86
- <OnboardingWrapper as="aside">
87
- <Button
88
- as="button"
89
- id="onboarding"
87
+ <Box as="aside" position="fixed" bottom={2} right={2}>
88
+ <HelperButton
90
89
  aria-label={formatMessage(
91
90
  isOpen
92
91
  ? {
@@ -98,37 +97,110 @@ const Onboarding = () => {
98
97
  defaultMessage: 'Open help menu',
99
98
  }
100
99
  )}
101
- onClick={handleClick}
100
+ onClick={handlePopoverVisibility}
101
+ ref={triggerRef}
102
102
  >
103
- <Icon as={isOpen ? Cross : Question} height={pxToRem(16)} width={pxToRem(16)} />
104
- </Button>
103
+ <Icon as={isOpen ? Cross : Question} color="buttonNeutral0" />
104
+ </HelperButton>
105
105
 
106
- {/* FIX ME - replace with popover when overflow popover is fixed
107
- + when v4 mockups for onboarding component are ready */}
108
106
  {isOpen && (
109
- <FocusTrap onEscape={handleClick}>
110
- <LinksWrapper background="neutral0" hasRadius shadow="tableShadow" padding={2}>
111
- {STATIC_LINKS.map((link) => (
112
- <StyledLink
113
- as="a"
114
- key={link.label}
115
- rel="nofollow noreferrer noopener"
116
- target="_blank"
117
- href={link.destination}
118
- padding={2}
119
- hasRadius
120
- alignItems="center"
107
+ <Portal>
108
+ <PopoverPrimitives.Content
109
+ padding={0}
110
+ source={triggerRef}
111
+ placement="top-end"
112
+ spacing={12}
113
+ >
114
+ <FocusTrap onEscape={handlePopoverVisibility}>
115
+ <Flex
116
+ justifyContent="space-between"
117
+ paddingBottom={5}
118
+ paddingRight={6}
119
+ paddingLeft={6}
120
+ paddingTop={6}
121
121
  >
122
- <Stack horizontal spacing={2}>
123
- {link.Icon}
124
- <Typography color="neutral600">{link.label}</Typography>
125
- </Stack>
126
- </StyledLink>
127
- ))}
128
- </LinksWrapper>
129
- </FocusTrap>
122
+ <TypographyLineHeight fontWeight="bold">
123
+ {formatMessage({
124
+ id: 'app.components.Onboarding.title',
125
+ defaultMessage: 'Get started videos',
126
+ })}
127
+ </TypographyLineHeight>
128
+ <TextLink
129
+ as="a"
130
+ href={WATCH_MORE.href}
131
+ target="_blank"
132
+ rel="noreferrer noopener"
133
+ variant="pi"
134
+ textColor="primary600"
135
+ >
136
+ {formatMessage(WATCH_MORE.label)}
137
+ </TextLink>
138
+ </Flex>
139
+ <Divider />
140
+ {VIDEO_LINKS.map(({ href, duration, label }, index) => (
141
+ <VideoLinkWrapper
142
+ as="a"
143
+ href={href}
144
+ target="_blank"
145
+ rel="noreferrer noopener"
146
+ key={href}
147
+ hasRadius
148
+ paddingTop={4}
149
+ paddingBottom={4}
150
+ paddingLeft={6}
151
+ paddingRight={11}
152
+ >
153
+ <Box paddingRight={5}>
154
+ <Typography textColor="neutral200" variant="alpha">
155
+ {index + 1}
156
+ </Typography>
157
+ </Box>
158
+ <Box position="relative">
159
+ <Preview src={onboardingPreview} alt="" />
160
+ <IconWrapper
161
+ position="absolute"
162
+ top="50%"
163
+ left="50%"
164
+ background="primary600"
165
+ borderRadius="50%"
166
+ justifyContent="center"
167
+ width={6}
168
+ height={6}
169
+ >
170
+ <Icon as={Play} color="buttonNeutral0" width={3} height={3} />
171
+ </IconWrapper>
172
+ </Box>
173
+ <Flex direction="column" alignItems="start" paddingLeft={4}>
174
+ <Typography fontWeight="bold">{formatMessage(label)}</Typography>
175
+ <VisuallyHidden>:</VisuallyHidden>
176
+ <Typography textColor="neutral600" variant="pi">
177
+ {duration}
178
+ </Typography>
179
+ </Flex>
180
+ </VideoLinkWrapper>
181
+ ))}
182
+ <Stack spacing={2} paddingLeft={5} paddingTop={2} paddingBottom={5}>
183
+ {DOCUMENTATION_LINKS.map(({ label, href, icon }) => (
184
+ <Stack horizontal spacing={3} key={href}>
185
+ <Icon as={icon} color="primary600" />
186
+ <TextLink
187
+ as="a"
188
+ href={href}
189
+ target="_blank"
190
+ rel="noreferrer noopener"
191
+ variant="sigma"
192
+ textColor="primary700"
193
+ >
194
+ {formatMessage(label)}
195
+ </TextLink>
196
+ </Stack>
197
+ ))}
198
+ </Stack>
199
+ </FocusTrap>
200
+ </PopoverPrimitives.Content>
201
+ </Portal>
130
202
  )}
131
- </OnboardingWrapper>
203
+ </Box>
132
204
  );
133
205
  };
134
206
 
@@ -10,10 +10,11 @@ import { useTracking, LoadingIndicatorPage, useStrapiApp } from '@strapi/helper-
10
10
  import { useDispatch, useSelector } from 'react-redux';
11
11
  import { DndProvider } from 'react-dnd';
12
12
  import { HTML5Backend } from 'react-dnd-html5-backend';
13
+
13
14
  import GuidedTourModal from '../../components/GuidedTour/Modal';
14
15
  import LeftMenu from '../../components/LeftMenu';
15
16
  import AppLayout from '../../layouts/AppLayout';
16
- import { useMenu } from '../../hooks';
17
+ import { useMenu, useConfigurations } from '../../hooks';
17
18
  import { createRoute } from '../../utils';
18
19
  import { SET_APP_RUNTIME_STATUS } from '../App/constants';
19
20
  import Onboarding from './Onboarding';
@@ -65,6 +66,7 @@ const Admin = () => {
65
66
  useTrackUsage();
66
67
  const { isLoading, generalSectionLinks, pluginsSectionLinks } = useMenu();
67
68
  const { menu } = useStrapiApp();
69
+ const { showTutorials } = useConfigurations();
68
70
 
69
71
  const routes = useMemo(() => {
70
72
  return menu
@@ -106,7 +108,8 @@ const Admin = () => {
106
108
  </Switch>
107
109
  </Suspense>
108
110
  <GuidedTourModal />
109
- <Onboarding />
111
+
112
+ {showTutorials && <Onboarding />}
110
113
  </AppLayout>
111
114
  </DndProvider>
112
115
  );
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { useIntl } from 'react-intl';
3
- import { Link } from '@strapi/helper-plugin';
4
3
  import PropTypes from 'prop-types';
4
+ import { Link } from '@strapi/helper-plugin';
5
5
  import ArrowLeft from '@strapi/icons/ArrowLeft';
6
6
  import Check from '@strapi/icons/Check';
7
7
  import { Button } from '@strapi/design-system/Button';
@@ -9,29 +9,36 @@ import { HeaderLayout } from '@strapi/design-system/Layout';
9
9
  import { Stack } from '@strapi/design-system/Stack';
10
10
  import Regenerate from '../Regenerate';
11
11
 
12
- const FormHead = ({ apiToken, setApiToken, canEditInputs, canRegenerate, isSubmitting }) => {
12
+ const FormHead = ({
13
+ title,
14
+ token,
15
+ setToken,
16
+ canEditInputs,
17
+ canRegenerate,
18
+ isSubmitting,
19
+ backUrl,
20
+ regenerateUrl,
21
+ }) => {
13
22
  const { formatMessage } = useIntl();
14
23
  const handleRegenerate = (newKey) => {
15
- setApiToken({
16
- ...apiToken,
24
+ setToken({
25
+ ...token,
17
26
  accessKey: newKey,
18
27
  });
19
28
  };
20
29
 
21
30
  return (
22
31
  <HeaderLayout
23
- title={
24
- apiToken?.name ||
25
- formatMessage({
26
- id: 'Settings.apiTokens.createPage.title',
27
- defaultMessage: 'Create API Token',
28
- })
29
- }
32
+ title={token?.name || formatMessage(title)}
30
33
  primaryAction={
31
34
  canEditInputs ? (
32
35
  <Stack horizontal spacing={2}>
33
- {canRegenerate && apiToken?.id && (
34
- <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
36
+ {canRegenerate && token?.id && (
37
+ <Regenerate
38
+ backUrl={regenerateUrl}
39
+ onRegenerate={handleRegenerate}
40
+ idToRegenerate={token?.id}
41
+ />
35
42
  )}
36
43
  <Button
37
44
  disabled={isSubmitting}
@@ -48,13 +55,17 @@ const FormHead = ({ apiToken, setApiToken, canEditInputs, canRegenerate, isSubmi
48
55
  </Stack>
49
56
  ) : (
50
57
  canRegenerate &&
51
- apiToken?.id && (
52
- <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
58
+ token?.id && (
59
+ <Regenerate
60
+ onRegenerate={handleRegenerate}
61
+ idToRegenerate={token?.id}
62
+ backUrl={regenerateUrl}
63
+ />
53
64
  )
54
65
  )
55
66
  }
56
67
  navigationAction={
57
- <Link startIcon={<ArrowLeft />} to="/settings/api-tokens">
68
+ <Link startIcon={<ArrowLeft />} to={backUrl}>
58
69
  {formatMessage({
59
70
  id: 'global.back',
60
71
  defaultMessage: 'Back',
@@ -66,7 +77,7 @@ const FormHead = ({ apiToken, setApiToken, canEditInputs, canRegenerate, isSubmi
66
77
  };
67
78
 
68
79
  FormHead.propTypes = {
69
- apiToken: PropTypes.shape({
80
+ token: PropTypes.shape({
70
81
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
71
82
  type: PropTypes.string,
72
83
  lifespan: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
@@ -78,12 +89,18 @@ FormHead.propTypes = {
78
89
  }),
79
90
  canEditInputs: PropTypes.bool.isRequired,
80
91
  canRegenerate: PropTypes.bool.isRequired,
81
- setApiToken: PropTypes.func.isRequired,
92
+ setToken: PropTypes.func.isRequired,
82
93
  isSubmitting: PropTypes.bool.isRequired,
94
+ backUrl: PropTypes.string.isRequired,
95
+ title: PropTypes.shape({
96
+ id: PropTypes.string,
97
+ label: PropTypes.string,
98
+ }).isRequired,
99
+ regenerateUrl: PropTypes.string.isRequired,
83
100
  };
84
101
 
85
102
  FormHead.defaultProps = {
86
- apiToken: undefined,
103
+ token: undefined,
87
104
  };
88
105
 
89
106
  export default FormHead;
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useIntl } from 'react-intl';
4
+ import { usePersistentState } from '@strapi/helper-plugin';
5
+ import { Select, Option } from '@strapi/design-system/Select';
6
+ import { Typography } from '@strapi/design-system/Typography';
7
+ import { getDateOfExpiration } from '../../../pages/ApiTokens/EditView/utils';
8
+
9
+ const LifeSpanInput = ({ token, errors, values, onChange, disabled }) => {
10
+ const { formatMessage } = useIntl();
11
+ const [lang] = usePersistentState('strapi-admin-language', 'en');
12
+
13
+ return (
14
+ <>
15
+ <Select
16
+ name="lifespan"
17
+ label={formatMessage({
18
+ id: 'Settings.apiTokens.form.duration',
19
+ defaultMessage: 'Token duration',
20
+ })}
21
+ value={values.lifespan !== null ? values.lifespan : '0'}
22
+ error={
23
+ errors.lifespan
24
+ ? formatMessage(
25
+ errors.lifespan?.id
26
+ ? errors.lifespan
27
+ : { id: errors.lifespan, defaultMessage: errors.lifespan }
28
+ )
29
+ : null
30
+ }
31
+ onChange={(value) => {
32
+ onChange({ target: { name: 'lifespan', value } });
33
+ }}
34
+ required
35
+ disabled={disabled}
36
+ placeholder="Select"
37
+ >
38
+ <Option value="604800000">
39
+ {formatMessage({
40
+ id: 'Settings.apiTokens.duration.7-days',
41
+ defaultMessage: '7 days',
42
+ })}
43
+ </Option>
44
+ <Option value="2592000000">
45
+ {formatMessage({
46
+ id: 'Settings.apiTokens.duration.30-days',
47
+ defaultMessage: '30 days',
48
+ })}
49
+ </Option>
50
+ <Option value="7776000000">
51
+ {formatMessage({
52
+ id: 'Settings.apiTokens.duration.90-days',
53
+ defaultMessage: '90 days',
54
+ })}
55
+ </Option>
56
+ <Option value="0">
57
+ {formatMessage({
58
+ id: 'Settings.apiTokens.duration.unlimited',
59
+ defaultMessage: 'Unlimited',
60
+ })}
61
+ </Option>
62
+ </Select>
63
+ <Typography variant="pi" textColor="neutral600">
64
+ {disabled &&
65
+ `${formatMessage({
66
+ id: 'Settings.apiTokens.duration.expiration-date',
67
+ defaultMessage: 'Expiration date',
68
+ })}: ${getDateOfExpiration(token?.createdAt, parseInt(values.lifespan, 10, lang))}`}
69
+ </Typography>
70
+ </>
71
+ );
72
+ };
73
+
74
+ LifeSpanInput.propTypes = {
75
+ errors: PropTypes.string,
76
+ onChange: PropTypes.func.isRequired,
77
+ values: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
78
+ disabled: PropTypes.bool.isRequired,
79
+ token: PropTypes.shape({
80
+ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
81
+ type: PropTypes.string,
82
+ lifespan: PropTypes.string,
83
+ name: PropTypes.string,
84
+ accessKey: PropTypes.string,
85
+ permissions: PropTypes.array,
86
+ description: PropTypes.string,
87
+ createdAt: PropTypes.string,
88
+ }),
89
+ };
90
+
91
+ LifeSpanInput.defaultProps = {
92
+ errors: {},
93
+ token: {},
94
+ };
95
+
96
+ export default LifeSpanInput;