slice-machine-ui 2.17.3-alpha.dani-git-integration.2 → 2.17.3-alpha.jp-revert-section-naming-experiment.1

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 (82) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/QRJGmIoJFXeQ96FN-TwH5/_buildManifest.js +1 -0
  3. package/out/_next/static/chunks/248-03446cd9e9f13730.js +1 -0
  4. package/out/_next/static/chunks/489-dd74b228384df643.js +1 -0
  5. package/out/_next/static/chunks/{630-29c729ad2a291ef6.js → 630-799c128fd87fa645.js} +1 -1
  6. package/out/_next/static/chunks/903-04bef419234ad926.js +1 -0
  7. package/out/_next/static/chunks/pages/{_app-8c46096695410bd2.js → _app-abfff64c4bacad47.js} +1 -1
  8. package/out/_next/static/chunks/pages/changelog-063c5e11dfc8fd55.js +1 -0
  9. package/out/_next/static/chunks/pages/changes-b4b7d3047cf012a0.js +1 -0
  10. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-041985d94bb9649f.js → [customTypeId]-1958f229bf899036.js} +1 -1
  11. package/out/_next/static/chunks/pages/labs-9630bfb1005be02b.js +1 -0
  12. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-338f685c0723043b.js → [pageTypeId]-1c048ceedced0df1.js} +1 -1
  13. package/out/_next/static/chunks/pages/settings-01f4aeb9112a1f87.js +1 -0
  14. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-0ecd552897e61e29.js +1 -0
  15. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-0a51da2e35d6e62f.js +1 -0
  16. package/out/_next/static/chunks/pages/slices-e057c5c9cb56b1ef.js +1 -0
  17. package/out/_next/static/css/e5f781f20e24a5ea.css +1 -0
  18. package/out/changelog.html +1 -1
  19. package/out/changes.html +1 -1
  20. package/out/custom-types/[customTypeId].html +1 -1
  21. package/out/custom-types.html +1 -1
  22. package/out/index.html +1 -1
  23. package/out/labs.html +1 -1
  24. package/out/page-types/[pageTypeId].html +1 -1
  25. package/out/settings.html +1 -0
  26. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  27. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  28. package/out/slices.html +1 -1
  29. package/package.json +3 -3
  30. package/src/components/FieldSet/FieldSet.module.css +84 -0
  31. package/src/components/FieldSet/FieldSet.module.css.d.ts +16 -0
  32. package/src/components/FieldSet/FieldSet.stories.tsx +244 -0
  33. package/src/components/FieldSet/FieldSet.tsx +67 -0
  34. package/src/components/FieldSet/index.ts +9 -0
  35. package/src/features/changes/PushChangesButton.tsx +67 -4
  36. package/src/features/customTypes/customTypesBuilder/CreateSliceFromImageModal/CreateSliceFromImageModal.tsx +8 -21
  37. package/src/features/customTypes/customTypesBuilder/SliceZoneBlankSlate.tsx +52 -36
  38. package/src/features/navigation/Navigation.tsx +13 -7
  39. package/src/features/settings/SettingsPage.tsx +50 -0
  40. package/src/features/settings/git/ConnectGitRepository.tsx +112 -0
  41. package/src/features/settings/git/ConnectGitRepositoryBlankSlate.tsx +33 -0
  42. package/src/features/settings/git/GitOwnerSelect.tsx +71 -0
  43. package/src/features/settings/git/GitProvider.ts +40 -0
  44. package/src/features/settings/git/GitProviderConnectButtons.tsx +63 -0
  45. package/src/features/settings/git/GitRepositoriesList.tsx +76 -0
  46. package/src/features/settings/git/GitRepositoriesSearch.tsx +69 -0
  47. package/src/features/settings/git/GitRepositoryConnectDialog.tsx +97 -0
  48. package/src/features/settings/git/GitRepositoryDisconnectDialog.tsx +62 -0
  49. package/src/features/settings/git/useGitIntegrationExperiment.ts +8 -0
  50. package/src/features/settings/git/useGitOwners.ts +12 -0
  51. package/src/features/settings/git/useGitRepos.ts +24 -0
  52. package/src/features/settings/git/useLinkedGitRepos.ts +41 -0
  53. package/src/features/settings/git/useWriteAPIToken.ts +23 -0
  54. package/src/icons/BitbucketIcon.tsx +19 -0
  55. package/src/icons/GitHubIcon.tsx +17 -0
  56. package/src/icons/GitLabIcon.tsx +19 -0
  57. package/src/icons/SettingsIcon.tsx +19 -0
  58. package/src/legacy/components/ChangesItems/ChangesItems.tsx +1 -8
  59. package/src/legacy/components/Forms/CreateSliceModal/CreateSliceModal.tsx +3 -8
  60. package/src/legacy/components/Simulator/components/FailedConnect/index.tsx +51 -56
  61. package/src/legacy/components/ToasterContainer/index.tsx +3 -14
  62. package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/SlicesTemplatesModal.tsx +1 -4
  63. package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/UpdateSliceZoneModal.tsx +1 -5
  64. package/src/legacy/lib/builders/CustomTypeBuilder/SliceZone/index.tsx +53 -40
  65. package/src/legacy/lib/builders/SliceBuilder/index.tsx +1 -6
  66. package/src/legacy/lib/builders/common/Zone/components/ZoneEmptyState/ZoneEmptyState.tsx +5 -10
  67. package/src/pages/settings.tsx +1 -0
  68. package/src/pages/slices.tsx +36 -57
  69. package/out/_next/static/OERdRe23c5buVNy_8FtIU/_buildManifest.js +0 -1
  70. package/out/_next/static/chunks/248-6f20227ad4764216.js +0 -1
  71. package/out/_next/static/chunks/268-6a9214b97195af9c.js +0 -1
  72. package/out/_next/static/chunks/489-eaa4848c00e8986b.js +0 -1
  73. package/out/_next/static/chunks/pages/changelog-98836c22c6a40c5d.js +0 -1
  74. package/out/_next/static/chunks/pages/changes-db800bf4a08faa31.js +0 -1
  75. package/out/_next/static/chunks/pages/labs-ad7f36c6f544c1a8.js +0 -1
  76. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-5008e29008aa04f4.js +0 -1
  77. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-0bc862dd7bd99611.js +0 -1
  78. package/out/_next/static/chunks/pages/slices-6dbc3df2c4ef058a.js +0 -1
  79. package/src/features/builder/useSectionsNamingExperiment.ts +0 -15
  80. package/src/features/customTypes/customTypesBuilder/sliceCreationOptions.tsx +0 -74
  81. package/src/utils/textConversion.ts +0 -11
  82. /package/out/_next/static/{OERdRe23c5buVNy_8FtIU → QRJGmIoJFXeQ96FN-TwH5}/_ssgManifest.js +0 -0
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ActionList,
3
3
  ActionListItem,
4
+ BackgroundIcon,
4
5
  BlankSlate,
5
6
  BlankSlateActions,
6
7
  BlankSlateDescription,
@@ -10,10 +11,6 @@ import {
10
11
  import { FC } from "react";
11
12
 
12
13
  import { useAiSliceGenerationExperiment } from "@/features/builder/useAiSliceGenerationExperiment";
13
- import { useSectionsNamingExperiment } from "@/features/builder/useSectionsNamingExperiment";
14
- import { capitalizeFirstLetter, pluralize } from "@/utils/textConversion";
15
-
16
- import { getSliceCreationOptions } from "./sliceCreationOptions";
17
14
 
18
15
  export type SliceZoneBlankSlateProps = {
19
16
  openUpdateSliceZoneModal: () => void;
@@ -33,11 +30,6 @@ export const SliceZoneBlankSlate: FC<SliceZoneBlankSlateProps> = ({
33
30
  isSlicesTemplatesSupported,
34
31
  }) => {
35
32
  const aiSliceGenerationExperiment = useAiSliceGenerationExperiment();
36
- const sectionsNamingExperiment = useSectionsNamingExperiment();
37
- const sliceCreationOptions = getSliceCreationOptions({
38
- menuType: "ActionList",
39
- sectionsNamingExperiment,
40
- });
41
33
 
42
34
  return (
43
35
  <BlankSlate data-testid="slice-zone-blank-slate" sx={{ width: 648 }}>
@@ -47,57 +39,81 @@ export const SliceZoneBlankSlate: FC<SliceZoneBlankSlateProps> = ({
47
39
  name="add"
48
40
  size="large"
49
41
  />
50
- <BlankSlateTitle size="big">
51
- Add {pluralize(sectionsNamingExperiment.value)}
52
- </BlankSlateTitle>
42
+ <BlankSlateTitle size="big">Add slices</BlankSlateTitle>
53
43
  <BlankSlateDescription>
54
- {pluralize(capitalizeFirstLetter(sectionsNamingExperiment.value))} are
55
- website sections that you can reuse on different pages with different
56
- content. Each on different pages with different content. Each{" "}
57
- {sectionsNamingExperiment.value} has its own component in your code.
44
+ Slices are website sections that you can reuse on different pages with
45
+ different content. Each slice has its own component in your code.
58
46
  </BlankSlateDescription>
59
47
  <BlankSlateActions>
60
48
  <ActionList>
61
49
  {aiSliceGenerationExperiment.eligible && (
62
50
  <ActionListItem
63
- renderStartIcon={() =>
64
- sliceCreationOptions.fromImage.BackgroundIcon
65
- }
51
+ renderStartIcon={() => (
52
+ <BackgroundIcon
53
+ name="autoFixHigh"
54
+ size="small"
55
+ iconSize="medium"
56
+ color="purple"
57
+ variant="solid"
58
+ radius={6}
59
+ />
60
+ )}
66
61
  onClick={openCreateSliceFromImageModal}
67
- description={sliceCreationOptions.fromImage.description}
62
+ description="Build a slice based on your design image."
68
63
  >
69
- {sliceCreationOptions.fromImage.title}
64
+ Generate from image
70
65
  </ActionListItem>
71
66
  )}
72
67
  <ActionListItem
73
- renderStartIcon={() =>
74
- sliceCreationOptions.fromScratch.BackgroundIcon
75
- }
68
+ renderStartIcon={() => (
69
+ <BackgroundIcon
70
+ name="add"
71
+ size="small"
72
+ iconSize="medium"
73
+ color="white"
74
+ variant="solid"
75
+ radius={6}
76
+ />
77
+ )}
76
78
  onClick={openCreateSliceModal}
77
- description={sliceCreationOptions.fromScratch.description}
79
+ description="Build a custom slice your way."
78
80
  >
79
- {sliceCreationOptions.fromScratch.title}
81
+ Start from scratch
80
82
  </ActionListItem>
81
83
  {isSlicesTemplatesSupported && (
82
84
  <ActionListItem
83
- renderStartIcon={() =>
84
- sliceCreationOptions.fromTemplate.BackgroundIcon
85
- }
85
+ renderStartIcon={() => (
86
+ <BackgroundIcon
87
+ name="contentCopy"
88
+ size="small"
89
+ iconSize="medium"
90
+ color="white"
91
+ variant="solid"
92
+ radius={6}
93
+ />
94
+ )}
86
95
  onClick={openSlicesTemplatesModal}
87
- description={sliceCreationOptions.fromTemplate.description}
96
+ description="Choose from ready-made examples."
88
97
  >
89
- {sliceCreationOptions.fromTemplate.title}
98
+ Use a template
90
99
  </ActionListItem>
91
100
  )}
92
101
  {projectHasAvailableSlices && (
93
102
  <ActionListItem
94
- renderStartIcon={() =>
95
- sliceCreationOptions.fromExisting.BackgroundIcon
96
- }
103
+ renderStartIcon={() => (
104
+ <BackgroundIcon
105
+ name="folder"
106
+ size="small"
107
+ iconSize="medium"
108
+ color="white"
109
+ variant="solid"
110
+ radius={6}
111
+ />
112
+ )}
97
113
  onClick={openUpdateSliceZoneModal}
98
- description={sliceCreationOptions.fromExisting.description}
114
+ description="Select from your created slices."
99
115
  >
100
- {sliceCreationOptions.fromExisting.title}
116
+ Reuse an existing slice
101
117
  </ActionListItem>
102
118
  )}
103
119
  </ActionList>
@@ -8,16 +8,16 @@ import { CUSTOM_TYPES_CONFIG } from "@/features/customTypes/customTypesConfig";
8
8
  import { CUSTOM_TYPES_MESSAGES } from "@/features/customTypes/customTypesMessages";
9
9
  import { RepositoryInfo } from "@/features/navigation/RepositoryInfo";
10
10
  import { OnboardingGuide } from "@/features/onboarding";
11
+ import { useGitIntegrationExperiment } from "@/features/settings/git/useGitIntegrationExperiment";
11
12
  import { useAdapterName } from "@/hooks/useAdapterName";
12
13
  import { useMarketingContent } from "@/hooks/useMarketingContent";
13
14
  import { FolderIcon } from "@/icons/FolderIcon";
14
15
  import { LightningIcon } from "@/icons/Lightning";
15
16
  import { MenuBookIcon } from "@/icons/MenuBookIcon";
16
- import { capitalizeFirstLetter, pluralize } from "@/utils/textConversion";
17
+ import { SettingsIcon } from "@/icons/SettingsIcon";
17
18
 
18
19
  import { ChangesItem } from "../../legacy/components/Navigation/ChangesItem";
19
20
  import { Environment } from "../../legacy/components/Navigation/Environment";
20
- import { useSectionsNamingExperiment } from "../builder/useSectionsNamingExperiment";
21
21
  import { NavigationItem } from "./NavigationItem";
22
22
  import { SliceMachineVersion } from "./SliceMachineVersion";
23
23
  import { UpdateInfo } from "./UpdateInfo";
@@ -25,8 +25,8 @@ import { UpdateInfo } from "./UpdateInfo";
25
25
  export function Navigation() {
26
26
  const router = useRouter();
27
27
 
28
+ const gitIntegrationExperiment = useGitIntegrationExperiment();
28
29
  const { documentationLink } = useMarketingContent();
29
- const sectionsNamingExperiment = useSectionsNamingExperiment();
30
30
  const adapter = useAdapterName();
31
31
 
32
32
  interface CustomTypeNavigationItemProps {
@@ -75,9 +75,7 @@ export function Navigation() {
75
75
  <CustomTypeNavigationItem type="custom" />
76
76
 
77
77
  <NavigationItem
78
- title={pluralize(
79
- capitalizeFirstLetter(sectionsNamingExperiment.value),
80
- )}
78
+ title="Slices"
81
79
  href="/slices"
82
80
  Icon={FolderIcon}
83
81
  active={router.asPath.startsWith("/slices")}
@@ -98,7 +96,6 @@ export function Navigation() {
98
96
  <OnboardingGuide />
99
97
  </Suspense>
100
98
  </ErrorBoundary>
101
-
102
99
  <NavigationItem
103
100
  title="Documentation"
104
101
  href={documentationLink}
@@ -112,6 +109,15 @@ export function Navigation() {
112
109
  }}
113
110
  />
114
111
 
112
+ {gitIntegrationExperiment.eligible && (
113
+ <NavigationItem
114
+ title="Settings"
115
+ href="/settings"
116
+ Icon={SettingsIcon}
117
+ active={router.asPath.startsWith("/settings")}
118
+ />
119
+ )}
120
+
115
121
  <NavigationItem
116
122
  title="Changelog"
117
123
  href="/changelog"
@@ -0,0 +1,50 @@
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 { BreadcrumbItem } from "@/components/Breadcrumb";
7
+ import { ConnectGitRepository } from "@/features/settings/git/ConnectGitRepository";
8
+ import { useGitIntegrationExperiment } from "@/features/settings/git/useGitIntegrationExperiment";
9
+ import {
10
+ AppLayout,
11
+ AppLayoutBreadcrumb,
12
+ AppLayoutContent,
13
+ AppLayoutHeader,
14
+ } from "@/legacy/components/AppLayout";
15
+
16
+ export const SettingsPage: FC = () => {
17
+ const gitIntegrationExperiment = useGitIntegrationExperiment();
18
+ const router = useRouter();
19
+
20
+ // TODO(DT-1801): implement a 404 page.
21
+ useEffect(() => {
22
+ if (!gitIntegrationExperiment.eligible) {
23
+ void router.replace("/");
24
+ }
25
+ }, [gitIntegrationExperiment.eligible, router]);
26
+
27
+ if (!gitIntegrationExperiment.eligible) {
28
+ return null;
29
+ }
30
+
31
+ return (
32
+ <>
33
+ <Head>
34
+ <title>Settings - Slice Machine</title>
35
+ </Head>
36
+ <AppLayout>
37
+ <AppLayoutHeader>
38
+ <AppLayoutBreadcrumb>
39
+ <BreadcrumbItem>Settings</BreadcrumbItem>
40
+ </AppLayoutBreadcrumb>
41
+ </AppLayoutHeader>
42
+ <AppLayoutContent>
43
+ <Box flexDirection="column" maxWidth={600}>
44
+ <ConnectGitRepository />
45
+ </Box>
46
+ </AppLayoutContent>
47
+ </AppLayout>
48
+ </>
49
+ );
50
+ };
@@ -0,0 +1,112 @@
1
+ import { Button, IconButton, Tooltip } from "@prismicio/editor-ui";
2
+ import {
3
+ isUnauthenticatedError,
4
+ isUnauthorizedError,
5
+ } from "@slicemachine/manager/client";
6
+ import { type FC, type ReactNode, Suspense } from "react";
7
+
8
+ import {
9
+ FieldSet,
10
+ FieldSetContent,
11
+ FieldSetFooter,
12
+ FieldSetLegend,
13
+ } from "@/components/FieldSet";
14
+ import { ErrorBoundary } from "@/ErrorBoundary";
15
+ import { ConnectGitRepositoryBlankSlate } from "@/features/settings/git/ConnectGitRepositoryBlankSlate";
16
+ import { GitProviderConnectButtons } from "@/features/settings/git/GitProviderConnectButtons";
17
+ import { GitRepositoriesList } from "@/features/settings/git/GitRepositoriesList";
18
+ import {
19
+ GitRepositoriesSearch,
20
+ GitRepositoriesSearchSkeleton,
21
+ } from "@/features/settings/git/GitRepositoriesSearch";
22
+ import { useGitOwners } from "@/features/settings/git/useGitOwners";
23
+ import { useLinkedGitRepos } from "@/features/settings/git/useLinkedGitRepos";
24
+ import useSliceMachineActions from "@/modules/useSliceMachineActions";
25
+
26
+ export const ConnectGitRepository: FC = () => (
27
+ <FieldSet>
28
+ <FieldSetLegend>Connected Git Repository</FieldSetLegend>
29
+ <ErrorBoundary renderError={renderError}>
30
+ <Suspense fallback={<GitRepositoriesSearchSkeleton gitOwnerSelect />}>
31
+ <Content />
32
+ </Suspense>
33
+ </ErrorBoundary>
34
+ <FieldSetFooter
35
+ action={
36
+ <Tooltip content="Documentation is coming soon." side="bottom">
37
+ <IconButton disabled icon="openInNew" />
38
+ </Tooltip>
39
+ }
40
+ >
41
+ Learn more about Prismic for Git
42
+ </FieldSetFooter>
43
+ </FieldSet>
44
+ );
45
+
46
+ const Content: FC = () => {
47
+ const { linkedGitRepos } = useLinkedGitRepos();
48
+ return linkedGitRepos.length > 0 ? (
49
+ <GitRepositoriesList mode="unlink" repos={linkedGitRepos} />
50
+ ) : (
51
+ <UnlinkedRepositoryContent />
52
+ );
53
+ };
54
+
55
+ const UnlinkedRepositoryContent: FC = () => {
56
+ const owners = useGitOwners();
57
+ return owners.length > 0 ? (
58
+ <GitRepositoriesSearch owners={owners} />
59
+ ) : (
60
+ <FieldSetContent>
61
+ <GitProviderConnectButtons />
62
+ </FieldSetContent>
63
+ );
64
+ };
65
+
66
+ function renderError(error: unknown, reset: () => void): ReactNode {
67
+ if (isUnauthenticatedError(error)) {
68
+ return <UnauthenticatedErrorContent />;
69
+ } else if (isUnauthorizedError(error)) {
70
+ return <UnauthorizedErrorContent />;
71
+ } else {
72
+ return <UnknownErrorContent reset={reset} />;
73
+ }
74
+ }
75
+
76
+ const UnauthenticatedErrorContent: FC = () => {
77
+ const { openLoginModal } = useSliceMachineActions();
78
+ return (
79
+ <FieldSetContent>
80
+ <ConnectGitRepositoryBlankSlate
81
+ title="It seems like you are logged out"
82
+ description="Log in to connect a Git repository."
83
+ action={<Button onClick={openLoginModal}>Log in to Prismic</Button>}
84
+ />
85
+ </FieldSetContent>
86
+ );
87
+ };
88
+
89
+ const UnauthorizedErrorContent: FC = () => (
90
+ <FieldSetContent>
91
+ <ConnectGitRepositoryBlankSlate
92
+ title="It seems like you do not have permission"
93
+ description="An owner or admin is required to connect a Git repository."
94
+ />
95
+ </FieldSetContent>
96
+ );
97
+
98
+ type UnknownErrorContentProps = { reset: () => void };
99
+
100
+ const UnknownErrorContent: FC<UnknownErrorContentProps> = ({ reset }) => (
101
+ <FieldSetContent>
102
+ <ConnectGitRepositoryBlankSlate
103
+ title="Unable to fetch data"
104
+ description="An error occurred while fetching the list of connected Git repositories."
105
+ action={
106
+ <Button color="grey" onClick={reset}>
107
+ Retry
108
+ </Button>
109
+ }
110
+ />
111
+ </FieldSetContent>
112
+ );
@@ -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, theme } from "@prismicio/editor-ui";
2
+ import type { GitOwner } from "@slicemachine/manager";
3
+ import type { ComponentPropsWithoutRef, FC } from "react";
4
+
5
+ import { gitProviderToConfig } from "@/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={theme.color.grey11} />;
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 = ComponentPropsWithoutRef<typeof Select>["sx"];
@@ -0,0 +1,40 @@
1
+ import { GIT_PROVIDER, GitProvider } from "@slicemachine/manager/client";
2
+
3
+ import { BitbucketIcon } from "@/icons/BitbucketIcon";
4
+ import { GitHubIcon } from "@/icons/GitHubIcon";
5
+ import { GitLabIcon } from "@/icons/GitLabIcon";
6
+ import { managerClient } from "@/managerClient";
7
+
8
+ export const gitProviderToConfig = {
9
+ gitHub: {
10
+ connect: async () => await openInstallationWindow("gitHub"),
11
+ Icon: GitHubIcon,
12
+ name: "GitHub",
13
+ supported: isSupported("gitHub"),
14
+ },
15
+ bitbucket: {
16
+ connect: async () => await openInstallationWindow("bitbucket"),
17
+ Icon: BitbucketIcon,
18
+ name: "Bitbucket",
19
+ supported: isSupported("bitbucket"),
20
+ },
21
+ gitLab: {
22
+ connect: async () => await openInstallationWindow("gitLab"),
23
+ Icon: GitLabIcon,
24
+ name: "GitLab",
25
+ supported: isSupported("gitLab"),
26
+ },
27
+ };
28
+
29
+ async function openInstallationWindow(provider: string) {
30
+ if (!isSupported(provider)) {
31
+ throw new Error("Not implemented.");
32
+ }
33
+
34
+ const url = await managerClient.git.getProviderAppInstallURL({ provider });
35
+ window.open(url, "git-provider-app-installation");
36
+ }
37
+
38
+ function isSupported(provider: string): provider is GitProvider {
39
+ return Boolean(Object.values<string>(GIT_PROVIDER).includes(provider));
40
+ }
@@ -0,0 +1,63 @@
1
+ import { keys } from "@prismicio/editor-support/Object";
2
+ import { Button, ButtonGroup, Text, theme } from "@prismicio/editor-ui";
3
+ import { type ComponentPropsWithoutRef, type FC, useState } from "react";
4
+ import { toast } from "react-toastify";
5
+
6
+ import { gitProviderToConfig } from "@/features/settings/git/GitProvider";
7
+
8
+ export const GitProviderConnectButtons: FC = () => (
9
+ <ButtonGroup>
10
+ {keys(gitProviderToConfig).map((provider) => (
11
+ <GitProviderConnectButton
12
+ key={provider}
13
+ provider={provider}
14
+ sx={{ flexBasis: 0, flexGrow: 1 }}
15
+ />
16
+ ))}
17
+ </ButtonGroup>
18
+ );
19
+
20
+ type GitProviderConnectButtonProps = {
21
+ provider: keyof typeof gitProviderToConfig;
22
+ sx?: SX;
23
+ };
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={theme.color.grey11} />}
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 = ComponentPropsWithoutRef<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 "@/components/FieldSet";
6
+ import { RelativeTime } from "@/components/RelativeTime";
7
+ import { GitRepositoryConnectDialog } from "@/features/settings/git/GitRepositoryConnectDialog";
8
+ import { GitRepositoryDisconnectDialog } from "@/features/settings/git/GitRepositoryDisconnectDialog";
9
+ import { useLinkedGitRepos } from "@/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
+ );