slice-machine-ui 1.23.2-beta.1 → 1.23.2-beta.10

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 (122) hide show
  1. package/.depcheckrc +3 -0
  2. package/components/DeleteSliceModal/index.tsx +34 -24
  3. package/components/DeleteVariationModal/index.tsx +1 -0
  4. package/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +61 -38
  5. package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +2 -2
  6. package/components/Forms/RenameSliceModal/RenameSliceModal.tsx +33 -21
  7. package/components/Forms/RenameVariationModal/RenameVariationModal.tsx +0 -1
  8. package/components/ModalFormCard/index.tsx +0 -1
  9. package/components/Navigation/ChangesListItem.tsx +14 -32
  10. package/components/Navigation/index.tsx +29 -12
  11. package/components/ReviewModal/ReviewForm.tsx +0 -1
  12. package/lib/builders/common/EditModal/Form.jsx +0 -1
  13. package/lib/builders/common/Zone/Card/components/NewField.tsx +0 -1
  14. package/lib/models/common/widgets/StructuredText/index.ts +1 -1
  15. package/out/404.html +1 -1
  16. package/out/_next/static/Dz7wwTNVdXvoAjySXz7mE/_buildManifest.js +1 -0
  17. package/out/_next/static/chunks/18539003-5f78714a35fde4a1.js +97 -0
  18. package/out/_next/static/chunks/25-01f422cea8066349.js +1 -0
  19. package/out/_next/static/chunks/318-99b5914b60bf1d17.js +1 -0
  20. package/out/_next/static/chunks/727-f8fefdd3e50cab2a.js +1 -0
  21. package/out/_next/static/chunks/777-8c5507e3b119b5dc.js +8 -0
  22. package/out/_next/static/chunks/799-b616401fe0dafdeb.js +1 -0
  23. package/out/_next/static/chunks/904-4653b7a849f4db94.js +1 -0
  24. package/out/_next/static/chunks/pages/_app-5ec41fc53c1f6405.js +434 -0
  25. package/out/_next/static/chunks/pages/{changelog-afd18276fe018b24.js → changelog-02cd80dad6f40878.js} +1 -1
  26. package/out/_next/static/chunks/pages/changes-8e9da8fbb809aad3.js +1 -0
  27. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-3f862ad6111a1fc0.js → [customTypeId]-97ab3b90b85d645d.js} +1 -1
  28. package/out/_next/static/chunks/pages/{custom-types-94b8f8dbf7727412.js → custom-types-3c8e721ec6802127.js} +1 -1
  29. package/out/_next/static/chunks/pages/index-cf93d16e9c63b6db.js +1 -0
  30. package/out/_next/static/chunks/pages/{labs-ee206005948f8e87.js → labs-39b50541a4c370bf.js} +1 -1
  31. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-d1ffca11c42f2603.js → [pageTypeId]-84a22c5e480d3fa9.js} +1 -1
  32. package/out/_next/static/chunks/pages/settings-879686e3a8759dc3.js +1 -0
  33. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-897ca93c114a1435.js → simulator-c498e9c80cae8cce.js} +1 -1
  34. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-9b893c62b2f2fe4a.js +1 -0
  35. package/out/_next/static/chunks/pages/slices-4522d604117d8c66.js +1 -0
  36. package/out/_next/static/css/3c26dbbbd8f4be12.css +1 -0
  37. package/out/_next/static/css/{37153180c3b0dd66.css → 838fd8bb5a7c32b0.css} +1 -1
  38. package/out/_next/static/css/ca32882a5d2d8d88.css +1 -0
  39. package/out/changelog.html +1 -1
  40. package/out/changes.html +1 -1
  41. package/out/custom-types/[customTypeId].html +1 -1
  42. package/out/custom-types.html +1 -1
  43. package/out/index.html +1 -1
  44. package/out/labs.html +1 -1
  45. package/out/page-types/[pageTypeId].html +1 -1
  46. package/out/settings.html +1 -0
  47. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  48. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  49. package/out/slices.html +1 -1
  50. package/package.json +9 -6
  51. package/pages/settings.tsx +1 -0
  52. package/pages/slices.tsx +20 -8
  53. package/postcss.config.js +10 -0
  54. package/src/apiClient.ts +1 -22
  55. package/src/components/FieldSet/FieldSet.stories.tsx +20 -11
  56. package/src/components/RelativeTime/RelativeTime.stories.tsx +25 -0
  57. package/src/components/RelativeTime/RelativeTime.tsx +40 -0
  58. package/src/components/RelativeTime/index.ts +1 -0
  59. package/src/domain/__tests__/customType.test.ts +205 -19
  60. package/src/domain/customType.ts +132 -7
  61. package/src/domain/slice.ts +38 -0
  62. package/src/features/customTypes/PageTypeOnboarding.tsx +21 -26
  63. package/src/features/customTypes/actions/convertCustomToPageType.ts +14 -10
  64. package/src/features/customTypes/actions/createCustomType.ts +69 -0
  65. package/src/features/customTypes/actions/deleteCustomType.ts +13 -3
  66. package/src/features/customTypes/actions/renameCustomType.ts +10 -4
  67. package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +3 -1
  68. package/src/features/customTypes/customTypesTable/CustomTypesTable.tsx +8 -8
  69. package/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx +18 -17
  70. package/src/features/environments/actions/setEnvironment.ts +2 -2
  71. package/src/features/settings/SettingsPage.tsx +47 -0
  72. package/src/features/settings/git/ConnectGitRepository.tsx +126 -0
  73. package/src/features/settings/git/ConnectGitRepositoryBlankSlate.tsx +33 -0
  74. package/src/features/settings/git/GitOwnerSelect.tsx +71 -0
  75. package/src/features/settings/git/GitProvider.ts +40 -0
  76. package/src/features/settings/git/GitProviderConnectButtons.tsx +63 -0
  77. package/src/features/settings/git/GitRepositoriesList.tsx +76 -0
  78. package/src/features/settings/git/GitRepositoriesSearch.tsx +69 -0
  79. package/src/features/settings/git/GitRepositoryConnectDialog.tsx +85 -0
  80. package/src/features/settings/git/GitRepositoryDisconnectDialog.tsx +60 -0
  81. package/src/features/settings/git/useGitIntegrationExperiment.ts +8 -0
  82. package/src/features/settings/git/useGitOwners.ts +12 -0
  83. package/src/features/settings/git/useGitRepos.ts +24 -0
  84. package/src/features/settings/git/useLinkedGitRepos.ts +41 -0
  85. package/src/features/settings/git/useWriteAPIToken.ts +23 -0
  86. package/src/features/slices/actions/createSlice.ts +5 -29
  87. package/src/features/slices/actions/deleteSlice.ts +33 -0
  88. package/src/features/slices/actions/renameSlice.ts +37 -0
  89. package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx +0 -1
  90. package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +0 -1
  91. package/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx +0 -1
  92. package/src/hooks/useUser.ts +14 -0
  93. package/src/icons/SettingsIcon.tsx +19 -0
  94. package/src/modules/availableCustomTypes/index.ts +29 -135
  95. package/src/modules/loading/types.ts +0 -5
  96. package/src/modules/modal/types.ts +0 -3
  97. package/src/modules/slices/index.ts +27 -193
  98. package/src/modules/slices/selector.ts +0 -14
  99. package/src/modules/useSliceMachineActions.ts +70 -89
  100. package/src/modules/userContext/index.ts +3 -3
  101. package/src/redux/saga.ts +0 -4
  102. package/test/src/modules/__fixtures__/serverState.ts +1 -2
  103. package/test/src/modules/availableCustomTypes/index.test.ts +2 -243
  104. package/test/src/modules/slices.test.ts +1 -267
  105. package/out/_next/static/chunks/18539003-5cc559566b738915.js +0 -97
  106. package/out/_next/static/chunks/25-37d6fa7f064be04b.js +0 -1
  107. package/out/_next/static/chunks/318-ce7e74f18f2d5b22.js +0 -1
  108. package/out/_next/static/chunks/376-7e784d6069e01654.js +0 -8
  109. package/out/_next/static/chunks/727-3a3bc32518778317.js +0 -1
  110. package/out/_next/static/chunks/731-501990ebe6a71b32.js +0 -1
  111. package/out/_next/static/chunks/904-9a4c1264727ae89a.js +0 -1
  112. package/out/_next/static/chunks/pages/_app-cd9d4c76a4abc11a.js +0 -432
  113. package/out/_next/static/chunks/pages/changes-2a976f9efeb719aa.js +0 -1
  114. package/out/_next/static/chunks/pages/index-64cfb8b0948ba8ef.js +0 -1
  115. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-b2a9d773ce5d7739.js +0 -1
  116. package/out/_next/static/chunks/pages/slices-e9c70c082ca3e3f9.js +0 -1
  117. package/out/_next/static/css/200244e3e0463c39.css +0 -1
  118. package/out/_next/static/m6psnC5u1xT7Y6osWHKjp/_buildManifest.js +0 -1
  119. package/src/features/customTypes/__tests__/createCustomType.test.ts +0 -159
  120. package/src/features/customTypes/customTypesTable/createCustomType.ts +0 -107
  121. package/test/components/CreateCustomTypeModal.test.tsx +0 -73
  122. /package/out/_next/static/{m6psnC5u1xT7Y6osWHKjp → Dz7wwTNVdXvoAjySXz7mE}/_ssgManifest.js +0 -0
@@ -1,8 +1,10 @@
1
+ import { toast } from "react-toastify";
1
2
  import { CustomType } from "@prismicio/types-internal/lib/customtypes";
3
+
2
4
  import { managerClient } from "@src/managerClient";
3
- import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
4
5
  import { CustomTypeFormat } from "@slicemachine/manager";
5
- import { toast } from "react-toastify";
6
+
7
+ import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
6
8
 
7
9
  type RenameCustomTypeArgs = {
8
10
  model: CustomType;
@@ -23,10 +25,14 @@ export async function renameCustomType({
23
25
  label: newLabel,
24
26
  };
25
27
 
26
- await managerClient.customTypes.renameCustomType({
28
+ const { errors } = await managerClient.customTypes.renameCustomType({
27
29
  model: renamedCustomType,
28
30
  });
29
31
 
32
+ if (errors.length > 0) {
33
+ throw errors;
34
+ }
35
+
30
36
  onSuccess(renamedCustomType);
31
37
 
32
38
  toast.success(
@@ -40,8 +46,8 @@ export async function renameCustomType({
40
46
  start: true,
41
47
  plural: false,
42
48
  })} could not be renamed`;
43
- console.error(errorMessage, e);
44
49
 
50
+ console.error(errorMessage, e);
45
51
  toast.error(errorMessage);
46
52
  }
47
53
  }
@@ -39,7 +39,9 @@ export const CustomTypesBuilderPage: FC = () => {
39
39
  );
40
40
 
41
41
  useEffect(() => {
42
- // TODO(DT-1801): When creating a custom type, don't redirect to the builder page until the custom type is created
42
+ // This is a fallback in case the user try to directly access a custom type
43
+ // that don't exist
44
+ // TODO(DT-1801): implement a 404 page.
43
45
  if (!selectedCustomType || !hasLocal(selectedCustomType)) {
44
46
  void router.replace("/");
45
47
  }
@@ -11,16 +11,9 @@ import {
11
11
  } from "@src/components/Table";
12
12
  import { ReusableIcon } from "@src/icons/ReusableIcon";
13
13
  import { UniqueIcon } from "@src/icons/UniqueIcon";
14
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
15
14
  import { type CustomType } from "@prismicio/types-internal/lib/customtypes";
16
15
  import { type CustomTypeFormat } from "@slicemachine/manager";
17
16
  import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages";
18
- import { CUSTOM_TYPES_CONFIG } from "../customTypesConfig";
19
- import {
20
- useCustomTypes,
21
- useCustomTypesAutoRevalidation,
22
- } from "./useCustomTypes";
23
-
24
17
  import {
25
18
  BlankSlate,
26
19
  BlankSlateImage,
@@ -29,18 +22,25 @@ import {
29
22
  BlankSlateActions,
30
23
  BlankSlateContent,
31
24
  } from "@src/components/BlankSlate";
25
+
26
+ import { CUSTOM_TYPES_CONFIG } from "../customTypesConfig";
32
27
  import { EditDropdown } from "../EditDropdown";
28
+ import {
29
+ useCustomTypes,
30
+ useCustomTypesAutoRevalidation,
31
+ } from "./useCustomTypes";
33
32
 
34
33
  type CustomTypesTableProps = {
35
34
  format: CustomTypeFormat;
36
35
  isCreatingCustomType: boolean;
36
+ openCreateCustomTypeModal: () => void;
37
37
  };
38
38
 
39
39
  export const CustomTypesTable: FC<CustomTypesTableProps> = ({
40
40
  format,
41
41
  isCreatingCustomType,
42
+ openCreateCustomTypeModal,
42
43
  }) => {
43
- const { openCreateCustomTypeModal } = useSliceMachineActions();
44
44
  const router = useRouter();
45
45
  const { customTypes, updateCustomTypes } = useCustomTypes(format);
46
46
  const sortedCustomTypes = customTypes.sort(
@@ -1,4 +1,4 @@
1
- import { type FC, Suspense } from "react";
1
+ import { type FC, Suspense, useState } from "react";
2
2
  import {
3
3
  Button,
4
4
  ErrorBoundary,
@@ -7,7 +7,6 @@ import {
7
7
  DefaultErrorMessage,
8
8
  } from "@prismicio/editor-ui";
9
9
  import Head from "next/head";
10
- import { useSelector } from "react-redux";
11
10
 
12
11
  import {
13
12
  AppLayout,
@@ -16,13 +15,10 @@ import {
16
15
  AppLayoutContent,
17
16
  AppLayoutHeader,
18
17
  } from "@components/AppLayout";
19
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
20
- import { isLoading } from "@src/modules/loading";
21
- import { type SliceMachineStoreType } from "@src/redux/type";
22
- import { LoadingKeysEnum } from "@src/modules/loading/types";
23
18
  import { CreateCustomTypeModal } from "@components/Forms/CreateCustomTypeModal";
24
19
  import { CUSTOM_TYPES_MESSAGES } from "@src/features/customTypes/customTypesMessages";
25
20
  import type { CustomTypeFormat } from "@slicemachine/manager";
21
+
26
22
  import { CustomTypesTable } from "./CustomTypesTable";
27
23
 
28
24
  type CustomTypesTablePageProps = {
@@ -33,15 +29,9 @@ export const CustomTypesTablePage: FC<CustomTypesTablePageProps> = ({
33
29
  format,
34
30
  }) => {
35
31
  const customTypesMessages = CUSTOM_TYPES_MESSAGES[format];
36
- const { openCreateCustomTypeModal } = useSliceMachineActions();
37
- const { isCreatingCustomType } = useSelector(
38
- (store: SliceMachineStoreType) => ({
39
- isCreatingCustomType: isLoading(
40
- store,
41
- LoadingKeysEnum.CREATE_CUSTOM_TYPE,
42
- ),
43
- }),
44
- );
32
+ const [isCreatingCustomType, setIsCreatingCustomType] = useState(false);
33
+ const [isCreateCustomTypeModalOpen, setIsCreateCustomTypeModalOpen] =
34
+ useState(false);
45
35
 
46
36
  return (
47
37
  <>
@@ -101,7 +91,9 @@ export const CustomTypesTablePage: FC<CustomTypesTablePageProps> = ({
101
91
  <Button
102
92
  data-cy="create-ct"
103
93
  loading={isCreatingCustomType}
104
- onClick={openCreateCustomTypeModal}
94
+ onClick={() => {
95
+ setIsCreateCustomTypeModalOpen(true);
96
+ }}
105
97
  startIcon="add"
106
98
  >
107
99
  Create
@@ -113,8 +105,17 @@ export const CustomTypesTablePage: FC<CustomTypesTablePageProps> = ({
113
105
  <CustomTypesTable
114
106
  format={format}
115
107
  isCreatingCustomType={isCreatingCustomType}
108
+ openCreateCustomTypeModal={() => {
109
+ setIsCreateCustomTypeModalOpen(true);
110
+ }}
111
+ />
112
+ <CreateCustomTypeModal
113
+ format={format}
114
+ isCreating={isCreatingCustomType}
115
+ isOpen={isCreateCustomTypeModalOpen}
116
+ onCreateChange={setIsCreatingCustomType}
117
+ onOpenChange={setIsCreateCustomTypeModalOpen}
116
118
  />
117
- <CreateCustomTypeModal format={format} />
118
119
  </Box>
119
120
  </AppLayoutContent>
120
121
  </AppLayout>
@@ -13,6 +13,6 @@ export async function setEnvironment(
13
13
  environment: environment.domain,
14
14
  });
15
15
 
16
- revalidateData(getActiveEnvironment, []);
17
- revalidateData(getLegacySliceMachineState, []);
16
+ void revalidateData(getActiveEnvironment, []);
17
+ void revalidateData(getLegacySliceMachineState, []);
18
18
  }
@@ -0,0 +1,47 @@
1
+ import { Box } from "@prismicio/editor-ui";
2
+ import Head from "next/head";
3
+ import { useRouter } from "next/router";
4
+ import { type FC, useEffect } from "react";
5
+
6
+ import {
7
+ AppLayout,
8
+ AppLayoutBreadcrumb,
9
+ AppLayoutContent,
10
+ AppLayoutHeader,
11
+ } from "@components/AppLayout";
12
+ import { ConnectGitRepository } from "@src/features/settings/git/ConnectGitRepository";
13
+ import { useGitIntegrationExperiment } from "@src/features/settings/git/useGitIntegrationExperiment";
14
+
15
+ export const SettingsPage: FC = () => {
16
+ const gitIntegrationExperiment = useGitIntegrationExperiment();
17
+ const router = useRouter();
18
+
19
+ // TODO(DT-1801): implement a 404 page.
20
+ useEffect(() => {
21
+ if (!gitIntegrationExperiment.eligible) {
22
+ void router.replace("/");
23
+ }
24
+ }, [gitIntegrationExperiment.eligible, router]);
25
+
26
+ if (!gitIntegrationExperiment.eligible) {
27
+ return null;
28
+ }
29
+
30
+ return (
31
+ <>
32
+ <Head>
33
+ <title>Settings - Slice Machine</title>
34
+ </Head>
35
+ <AppLayout>
36
+ <AppLayoutHeader>
37
+ <AppLayoutBreadcrumb folder="Settings" />
38
+ </AppLayoutHeader>
39
+ <AppLayoutContent>
40
+ <Box flexDirection="column" maxWidth={600}>
41
+ <ConnectGitRepository />
42
+ </Box>
43
+ </AppLayoutContent>
44
+ </AppLayout>
45
+ </>
46
+ );
47
+ };
@@ -0,0 +1,126 @@
1
+ import {
2
+ Button,
3
+ ErrorBoundary,
4
+ IconButton,
5
+ Tooltip,
6
+ } from "@prismicio/editor-ui";
7
+ import {
8
+ // isUnauthenticatedError,
9
+ isUnauthorizedError,
10
+ } from "@slicemachine/manager/client";
11
+ import { type FC, type ReactNode, Suspense } from "react";
12
+
13
+ import {
14
+ FieldSet,
15
+ FieldSetContent,
16
+ FieldSetFooter,
17
+ FieldSetLegend,
18
+ } from "@src/components/FieldSet";
19
+ import { ConnectGitRepositoryBlankSlate } from "@src/features/settings/git/ConnectGitRepositoryBlankSlate";
20
+ import { GitProviderConnectButtons } from "@src/features/settings/git/GitProviderConnectButtons";
21
+ import { GitRepositoriesList } from "@src/features/settings/git/GitRepositoriesList";
22
+ import {
23
+ GitRepositoriesSearch,
24
+ GitRepositoriesSearchSkeleton,
25
+ } from "@src/features/settings/git/GitRepositoriesSearch";
26
+ import { useGitOwners } from "@src/features/settings/git/useGitOwners";
27
+ import { useLinkedGitRepos } from "@src/features/settings/git/useLinkedGitRepos";
28
+ import { useUser } from "@src/hooks/useUser";
29
+ import useSliceMachineActions from "@src/modules/useSliceMachineActions";
30
+
31
+ export const ConnectGitRepository: FC = () => (
32
+ <FieldSet>
33
+ <FieldSetLegend>Connected Git Repository</FieldSetLegend>
34
+ <ErrorBoundary renderError={renderError}>
35
+ <Suspense fallback={<GitRepositoriesSearchSkeleton gitOwnerSelect />}>
36
+ <Content />
37
+ </Suspense>
38
+ </ErrorBoundary>
39
+ <FieldSetFooter
40
+ action={
41
+ <Tooltip content="Documentation is coming soon." side="bottom">
42
+ <IconButton disabled icon="openInNew" />
43
+ </Tooltip>
44
+ }
45
+ >
46
+ Learn more about Prismic for Git
47
+ </FieldSetFooter>
48
+ </FieldSet>
49
+ );
50
+
51
+ const Content: FC = () => {
52
+ // TODO(TD-1952): the `useUser` hook should be replaced with a mechanism that
53
+ // automatically revalidates the `Suspense` cache whenever the Redux
54
+ // authentication status changes.
55
+ const { isLoggedIn } = useUser();
56
+ return isLoggedIn ? <LoggedInContent /> : <UnauthenticatedErrorContent />;
57
+ };
58
+
59
+ const LoggedInContent: FC = () => {
60
+ const { linkedGitRepos } = useLinkedGitRepos();
61
+ return linkedGitRepos.length > 0 ? (
62
+ <GitRepositoriesList mode="unlink" repos={linkedGitRepos} />
63
+ ) : (
64
+ <UnlinkedRepositoryContent />
65
+ );
66
+ };
67
+
68
+ const UnlinkedRepositoryContent: FC = () => {
69
+ const owners = useGitOwners();
70
+ return owners.length > 0 ? (
71
+ <GitRepositoriesSearch owners={owners} />
72
+ ) : (
73
+ <FieldSetContent>
74
+ <GitProviderConnectButtons />
75
+ </FieldSetContent>
76
+ );
77
+ };
78
+
79
+ function renderError(error: unknown, reset: () => void): ReactNode {
80
+ // if (isUnauthenticatedError(error)) {
81
+ // return <UnauthenticatedErrorContent />;
82
+ // } else
83
+ if (isUnauthorizedError(error)) {
84
+ return <UnauthorizedErrorContent />;
85
+ } else {
86
+ return <UnknownErrorContent reset={reset} />;
87
+ }
88
+ }
89
+
90
+ const UnauthenticatedErrorContent: FC = () => {
91
+ const { openLoginModal } = useSliceMachineActions();
92
+ return (
93
+ <FieldSetContent>
94
+ <ConnectGitRepositoryBlankSlate
95
+ title="It seems like you are logged out"
96
+ description="Log in to connect a Git repository."
97
+ action={<Button onClick={openLoginModal}>Log in to Prismic</Button>}
98
+ />
99
+ </FieldSetContent>
100
+ );
101
+ };
102
+
103
+ const UnauthorizedErrorContent: FC = () => (
104
+ <FieldSetContent>
105
+ <ConnectGitRepositoryBlankSlate
106
+ title="It seems like you do not have permission"
107
+ description="An owner or admin is required to connect a Git repository."
108
+ />
109
+ </FieldSetContent>
110
+ );
111
+
112
+ type UnknownErrorContentProps = { reset: () => void };
113
+
114
+ const UnknownErrorContent: FC<UnknownErrorContentProps> = ({ reset }) => (
115
+ <FieldSetContent>
116
+ <ConnectGitRepositoryBlankSlate
117
+ title="Unable to fetch data"
118
+ description="An error occurred while fetching the list of connected Git repositories."
119
+ action={
120
+ <Button color="grey" onClick={reset}>
121
+ Retry
122
+ </Button>
123
+ }
124
+ />
125
+ </FieldSetContent>
126
+ );
@@ -0,0 +1,33 @@
1
+ import { Box, ButtonGroup, Text } from "@prismicio/editor-ui";
2
+ import type { FC, ReactNode } from "react";
3
+
4
+ type ConnectGitRepositoryBlankSlateProps = {
5
+ title: string;
6
+ description: string;
7
+ action?: ReactNode;
8
+ };
9
+
10
+ export const ConnectGitRepositoryBlankSlate: FC<
11
+ ConnectGitRepositoryBlankSlateProps
12
+ > = ({ title, description, action }) => (
13
+ <Box
14
+ flexDirection="column"
15
+ /*
16
+ * TODO: these `padding` values actually don't match Figma, but they are the
17
+ * closest allowed by the `Box` component.
18
+ */
19
+ padding={{ block: 72, inline: 100 }}
20
+ >
21
+ <Text align="center" variant="emphasized">
22
+ {title}
23
+ </Text>
24
+ <Text align="center" color="grey11">
25
+ {description}
26
+ </Text>
27
+ {Boolean(action) ? (
28
+ <ButtonGroup sx={{ alignSelf: "center", marginTop: 8 }}>
29
+ {action}
30
+ </ButtonGroup>
31
+ ) : undefined}
32
+ </Box>
33
+ );
@@ -0,0 +1,71 @@
1
+ import { Select, SelectItem, tokens } from "@prismicio/editor-ui";
2
+ import type { GitOwner } from "@slicemachine/manager";
3
+ import type { ComponentProps, FC } from "react";
4
+
5
+ import { gitProviderToConfig } from "@src/features/settings/git/GitProvider";
6
+
7
+ type GitOwnerSelectProps = {
8
+ disabled?: boolean;
9
+ owners?: GitOwner[];
10
+ selectedOwner?: GitOwner;
11
+ onSelectedOwnerChange?: (selectedOwner: GitOwner) => void;
12
+ sx?: SX;
13
+ };
14
+
15
+ export const GitOwnerSelect: FC<GitOwnerSelectProps> = ({
16
+ disabled,
17
+ owners = [],
18
+ selectedOwner,
19
+ onSelectedOwnerChange,
20
+ sx,
21
+ }) => (
22
+ <Select
23
+ color="grey"
24
+ constrainContentWidth
25
+ disabled={disabled}
26
+ flexContent
27
+ onValueChange={(value) => {
28
+ const [provider, id] = parseGitOwnerKey(value);
29
+ const selectedOwner = owners.find(
30
+ (owner) => owner.provider === provider && owner.id === id,
31
+ );
32
+ if (selectedOwner) {
33
+ onSelectedOwnerChange?.(selectedOwner);
34
+ }
35
+ }}
36
+ placeholder="Owner"
37
+ renderStartIcon={() => <GitOwnerIcon owner={selectedOwner} />}
38
+ size="large"
39
+ sx={sx}
40
+ value={selectedOwner ? formatGitOwnerKey(selectedOwner) : undefined}
41
+ >
42
+ {owners.map((owner) => (
43
+ <SelectItem
44
+ key={formatGitOwnerKey(owner)}
45
+ renderStartIcon={() => <GitOwnerIcon owner={owner} />}
46
+ size="large"
47
+ value={formatGitOwnerKey(owner)}
48
+ >
49
+ {owner.name}
50
+ </SelectItem>
51
+ ))}
52
+ </Select>
53
+ );
54
+
55
+ type GitOwnerIconProps = { owner: GitOwner | undefined };
56
+
57
+ const GitOwnerIcon: FC<GitOwnerIconProps> = ({ owner }) => {
58
+ const { Icon } = gitProviderToConfig[owner?.provider ?? "gitHub"];
59
+ return <Icon color={tokens.color.greyLight11} />;
60
+ };
61
+
62
+ function formatGitOwnerKey(owner: GitOwner): string {
63
+ return `${owner.provider}@${owner.id}`;
64
+ }
65
+
66
+ function parseGitOwnerKey(key: string): string[] {
67
+ return key.split("@");
68
+ }
69
+
70
+ // TODO(DT-1928): export the `SX` type from `@prismicio/editor-ui`.
71
+ type SX = ComponentProps<typeof Select>["sx"];
@@ -0,0 +1,40 @@
1
+ import { BitbucketIcon } from "@src/icons/BitbucketIcon";
2
+ import { GitHubIcon } from "@src/icons/GitHubIcon";
3
+ import { GitLabIcon } from "@src/icons/GitLabIcon";
4
+ import { managerClient } from "@src/managerClient";
5
+
6
+ export type GitProvider = keyof typeof gitProviderToConfig;
7
+
8
+ const GITHUB_APP_SLUG = "prismic-io";
9
+
10
+ export const gitProviderToConfig = {
11
+ gitHub: {
12
+ connect: async () => {
13
+ const state = await managerClient.git.createGitHubAuthState();
14
+ const url = new URL(
15
+ `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new`,
16
+ );
17
+ url.searchParams.set("state", state.key);
18
+ window.open(url, "git-hub-app-installation");
19
+ },
20
+ Icon: GitHubIcon,
21
+ name: "GitHub",
22
+ supported: true,
23
+ },
24
+ bitbucket: {
25
+ connect: () => {
26
+ throw new Error("Not implemented.");
27
+ },
28
+ Icon: BitbucketIcon,
29
+ name: "Bitbucket",
30
+ supported: false,
31
+ },
32
+ gitLab: {
33
+ connect: () => {
34
+ throw new Error("Not implemented.");
35
+ },
36
+ Icon: GitLabIcon,
37
+ name: "GitLab",
38
+ supported: false,
39
+ },
40
+ };
@@ -0,0 +1,63 @@
1
+ import { keys } from "@prismicio/editor-support/Object";
2
+ import { Button, ButtonGroup, Text, tokens } from "@prismicio/editor-ui";
3
+ import { type ComponentProps, type FC, useState } from "react";
4
+ import { toast } from "react-toastify";
5
+
6
+ import {
7
+ type GitProvider,
8
+ gitProviderToConfig,
9
+ } from "@src/features/settings/git/GitProvider";
10
+
11
+ export const GitProviderConnectButtons: FC = () => (
12
+ <ButtonGroup>
13
+ {keys(gitProviderToConfig).map((provider) => (
14
+ <GitProviderConnectButton
15
+ key={provider}
16
+ provider={provider}
17
+ sx={{ flexBasis: 0, flexGrow: 1 }}
18
+ />
19
+ ))}
20
+ </ButtonGroup>
21
+ );
22
+
23
+ type GitProviderConnectButtonProps = { provider: GitProvider; sx?: SX };
24
+
25
+ const GitProviderConnectButton: FC<GitProviderConnectButtonProps> = ({
26
+ provider,
27
+ sx,
28
+ }) => {
29
+ const { connect, Icon, name, supported } = gitProviderToConfig[provider];
30
+ const [loading, setLoading] = useState(false);
31
+ return (
32
+ <Button
33
+ color="grey"
34
+ disabled={!supported}
35
+ loading={loading}
36
+ onClick={() => {
37
+ void (async () => {
38
+ setLoading(true);
39
+ try {
40
+ await connect();
41
+ } catch (error) {
42
+ const message = `Could not connect to ${name}`;
43
+ console.error(message, error);
44
+ toast.error(message);
45
+ setLoading(false);
46
+ }
47
+ })();
48
+ }}
49
+ renderStartIcon={() => <Icon color={tokens.color.greyLight11} />}
50
+ sx={sx}
51
+ >
52
+ {name}
53
+ {supported ? undefined : (
54
+ <Text color="inherit" variant="small">
55
+ {" (soon)"}
56
+ </Text>
57
+ )}
58
+ </Button>
59
+ );
60
+ };
61
+
62
+ // TODO(DT-1928): export the `SX` type from `@prismicio/editor-ui`.
63
+ type SX = ComponentProps<typeof Button>["sx"];
@@ -0,0 +1,76 @@
1
+ import { Button, Skeleton, Text } from "@prismicio/editor-ui";
2
+ import type { GitRepo, GitRepoSpecifier } from "@slicemachine/manager";
3
+ import type { FC } from "react";
4
+
5
+ import { FieldSetList, FieldSetListItem } from "@src/components/FieldSet";
6
+ import { RelativeTime } from "@src/components/RelativeTime";
7
+ import { GitRepositoryConnectDialog } from "@src/features/settings/git/GitRepositoryConnectDialog";
8
+ import { GitRepositoryDisconnectDialog } from "@src/features/settings/git/GitRepositoryDisconnectDialog";
9
+ import { useLinkedGitRepos } from "@src/features/settings/git/useLinkedGitRepos";
10
+
11
+ type GitRepositoriesListProps =
12
+ | { mode: "link"; repos: GitRepo[] }
13
+ | { mode: "unlink"; repos: GitRepoSpecifier[] };
14
+
15
+ export const GitRepositoriesList: FC<GitRepositoriesListProps> = ({
16
+ mode,
17
+ repos,
18
+ }) => {
19
+ const { linkRepo, unlinkRepo } = useLinkedGitRepos();
20
+ return (
21
+ <FieldSetList>
22
+ {mode === "link"
23
+ ? repos.map((repo) => (
24
+ <FieldSetListItem
25
+ action={
26
+ <GitRepositoryConnectDialog
27
+ linkRepo={linkRepo}
28
+ repo={repo}
29
+ trigger={<Button color="grey">Connect</Button>}
30
+ />
31
+ }
32
+ key={`${repo.provider}@${repo.id}`}
33
+ >
34
+ {repo.name}
35
+ <Text color="grey11">
36
+ {" • "}
37
+ <RelativeTime date={repo.pushedAt} />
38
+ </Text>
39
+ </FieldSetListItem>
40
+ ))
41
+ : repos.map((repo) => (
42
+ <FieldSetListItem
43
+ action={
44
+ <GitRepositoryDisconnectDialog
45
+ repo={repo}
46
+ trigger={<Button color="grey">Disconnect</Button>}
47
+ unlinkRepo={unlinkRepo}
48
+ />
49
+ }
50
+ key={`${repo.provider}@${repo.owner}/${repo.name}`}
51
+ >
52
+ {repo.name}
53
+ </FieldSetListItem>
54
+ ))}
55
+ </FieldSetList>
56
+ );
57
+ };
58
+
59
+ export const GitRepositoriesListSkeleton: FC = () => (
60
+ <FieldSetList>
61
+ {[...Array(4).keys()].map((index) => (
62
+ <FieldSetListItem
63
+ action={<Skeleton height={32} width={67.59} />}
64
+ key={index}
65
+ >
66
+ <Skeleton
67
+ height={24}
68
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
69
+ // @ts-ignore TODO(DT-1918): add `verticalAlign: "middle"` to the `sx` prop.
70
+ sx={{ verticalAlign: "middle" }}
71
+ width={129.92}
72
+ />
73
+ </FieldSetListItem>
74
+ ))}
75
+ </FieldSetList>
76
+ );