slice-machine-ui 1.23.2-beta.8 → 1.24.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 (132) hide show
  1. package/.depcheckrc +3 -0
  2. package/components/ChangesEmptyState/OfflinePage.tsx +1 -1
  3. package/components/ChangesItems/ChangesItems.tsx +6 -2
  4. package/components/CustomTypeTable/changesPage.tsx +12 -7
  5. package/components/DeleteSliceModal/index.tsx +3 -0
  6. package/components/DeleteVariationModal/index.tsx +1 -0
  7. package/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +4 -0
  8. package/components/Forms/CreateSliceModal/CreateSliceModal.tsx +3 -1
  9. package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +6 -1
  10. package/components/Forms/RenameSliceModal/RenameSliceModal.tsx +3 -0
  11. package/components/Forms/RenameVariationModal/RenameVariationModal.tsx +0 -1
  12. package/components/LoginModal/index.tsx +40 -2
  13. package/components/ModalFormCard/index.tsx +0 -1
  14. package/components/Navigation/ChangesItem.tsx +117 -0
  15. package/components/Navigation/Environment.tsx +51 -4
  16. package/components/Navigation/index.css.ts +1 -2
  17. package/components/Navigation/index.test.tsx +5 -85
  18. package/components/Navigation/index.tsx +32 -20
  19. package/components/ReviewModal/ReviewForm.tsx +0 -1
  20. package/lib/builders/SliceBuilder/index.tsx +3 -3
  21. package/lib/builders/common/EditModal/Form.jsx +0 -1
  22. package/lib/builders/common/Zone/Card/components/NewField.tsx +0 -1
  23. package/lib/models/common/ModelStatus/index.ts +25 -0
  24. package/out/404.html +1 -1
  25. package/out/_next/static/chunks/18539003-5f78714a35fde4a1.js +97 -0
  26. package/out/_next/static/chunks/25-3aa4d4b24167d340.js +1 -0
  27. package/out/_next/static/chunks/318-19dbcae10cdb6408.js +1 -0
  28. package/out/_next/static/chunks/727-de03055e40815a3a.js +1 -0
  29. package/out/_next/static/chunks/777-8c5507e3b119b5dc.js +8 -0
  30. package/out/_next/static/chunks/799-0af1215eb2b3c17e.js +1 -0
  31. package/out/_next/static/chunks/904-40686b0426716111.js +1 -0
  32. package/out/_next/static/chunks/pages/_app-bca191df4ac7078e.js +434 -0
  33. package/out/_next/static/chunks/pages/{changelog-afd18276fe018b24.js → changelog-cb1df3a70b20f1b8.js} +1 -1
  34. package/out/_next/static/chunks/pages/changes-ccfd715c2bdc4f83.js +1 -0
  35. package/out/_next/static/chunks/pages/{labs-ee206005948f8e87.js → labs-2adfb5127f9c4081.js} +1 -1
  36. package/out/_next/static/chunks/pages/settings-94bcd7422e8b237d.js +1 -0
  37. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-52c3e1f9d14160b8.js → simulator-aaf65fbec8f5839c.js} +1 -1
  38. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-90bb2ea76d2abf42.js +1 -0
  39. package/out/_next/static/chunks/pages/slices-e0ce615c38e8338d.js +1 -0
  40. package/out/_next/static/css/39e089aad1742a3a.css +1 -0
  41. package/out/_next/static/css/{37153180c3b0dd66.css → 838fd8bb5a7c32b0.css} +1 -1
  42. package/out/_next/static/css/ca32882a5d2d8d88.css +1 -0
  43. package/out/_next/static/eNujlD43QIdmra7ggcNtK/_buildManifest.js +1 -0
  44. package/out/changelog.html +1 -1
  45. package/out/changes.html +1 -1
  46. package/out/custom-types/[customTypeId].html +1 -1
  47. package/out/custom-types.html +1 -1
  48. package/out/index.html +1 -1
  49. package/out/labs.html +1 -1
  50. package/out/page-types/[pageTypeId].html +1 -1
  51. package/out/settings.html +1 -0
  52. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  53. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  54. package/out/slices.html +1 -1
  55. package/package.json +10 -8
  56. package/pages/_app.tsx +34 -32
  57. package/pages/changes.tsx +27 -41
  58. package/pages/settings.tsx +1 -0
  59. package/postcss.config.js +10 -0
  60. package/src/components/FieldSet/FieldSet.stories.tsx +20 -11
  61. package/src/components/RelativeTime/RelativeTime.stories.tsx +25 -0
  62. package/src/components/RelativeTime/RelativeTime.tsx +40 -0
  63. package/src/components/RelativeTime/index.ts +1 -0
  64. package/src/components/SideNav/SideNav.css.ts +1 -1
  65. package/src/components/SideNav/SideNav.tsx +9 -5
  66. package/src/components/SideNav/SideNavEnvironmentSelector.tsx +31 -11
  67. package/src/domain/__tests__/customType.test.ts +26 -19
  68. package/src/domain/customType.ts +22 -5
  69. package/src/features/autoSave/AutoSaveStatusIndicator.tsx +4 -4
  70. package/src/features/customTypes/customTypesBuilder/CustomTypeProvider.tsx +11 -10
  71. package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +3 -3
  72. package/src/features/environments/actions/setEnvironment.ts +4 -4
  73. package/src/features/settings/SettingsPage.tsx +47 -0
  74. package/src/features/settings/git/ConnectGitRepository.tsx +126 -0
  75. package/src/features/settings/git/ConnectGitRepositoryBlankSlate.tsx +33 -0
  76. package/src/features/settings/git/GitOwnerSelect.tsx +71 -0
  77. package/src/features/settings/git/GitProvider.ts +40 -0
  78. package/src/features/settings/git/GitProviderConnectButtons.tsx +63 -0
  79. package/src/features/settings/git/GitRepositoriesList.tsx +76 -0
  80. package/src/features/settings/git/GitRepositoriesSearch.tsx +69 -0
  81. package/src/features/settings/git/GitRepositoryConnectDialog.tsx +85 -0
  82. package/src/features/settings/git/GitRepositoryDisconnectDialog.tsx +60 -0
  83. package/src/features/settings/git/useGitIntegrationExperiment.ts +8 -0
  84. package/src/features/settings/git/useGitOwners.ts +12 -0
  85. package/src/features/settings/git/useGitRepos.ts +24 -0
  86. package/src/features/settings/git/useLinkedGitRepos.ts +41 -0
  87. package/src/features/settings/git/useWriteAPIToken.ts +23 -0
  88. package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx +0 -1
  89. package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +0 -1
  90. package/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx +0 -1
  91. package/src/features/slices/sliceBuilder/SliceBuilderProvider.tsx +12 -10
  92. package/src/features/sync/AutoSyncProvider.tsx +252 -0
  93. package/src/features/sync/components/AutoSyncStatusIndicator.module.css +17 -0
  94. package/src/features/sync/components/AutoSyncStatusIndicator.module.css.d.ts +5 -0
  95. package/src/features/sync/components/AutoSyncStatusIndicator.tsx +58 -0
  96. package/src/features/sync/fetchUnSyncChanges.ts +40 -0
  97. package/src/features/sync/getUnSyncChanges.ts +144 -0
  98. package/src/features/sync/useUnSyncChanges.ts +32 -0
  99. package/src/hooks/useActionQueue.tsx +177 -0
  100. package/src/hooks/useAuthStatus.ts +12 -0
  101. package/src/hooks/useUser.ts +14 -0
  102. package/src/icons/SettingsIcon.tsx +19 -0
  103. package/src/icons/SyncFailed.tsx +30 -0
  104. package/src/icons/Synced.tsx +19 -0
  105. package/src/icons/Syncing.tsx +23 -0
  106. package/test/__testutils__/index.tsx +8 -5
  107. package/test/src/modules/__fixtures__/serverState.ts +1 -2
  108. package/components/Navigation/ChangesListItem.tsx +0 -74
  109. package/components/Navigation/ChangesRightElement.tsx +0 -33
  110. package/out/_next/static/chunks/18539003-5cc559566b738915.js +0 -97
  111. package/out/_next/static/chunks/25-01f422cea8066349.js +0 -1
  112. package/out/_next/static/chunks/318-387e3f23d717c53d.js +0 -1
  113. package/out/_next/static/chunks/376-7e784d6069e01654.js +0 -8
  114. package/out/_next/static/chunks/727-358d7fae68a880b3.js +0 -1
  115. package/out/_next/static/chunks/799-d95a93e965557aeb.js +0 -1
  116. package/out/_next/static/chunks/904-1ff7519adece2b55.js +0 -1
  117. package/out/_next/static/chunks/pages/_app-e31adcf75e9176d3.js +0 -432
  118. package/out/_next/static/chunks/pages/changes-92c84f6cac0285fd.js +0 -1
  119. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-608d206d10a719e3.js +0 -1
  120. package/out/_next/static/chunks/pages/slices-b0b58192a87df822.js +0 -1
  121. package/out/_next/static/css/200244e3e0463c39.css +0 -1
  122. package/out/_next/static/vxOhh_oqUIQ5PTSKUKgMR/_buildManifest.js +0 -1
  123. package/src/features/autoSave/useAutoSave.tsx +0 -177
  124. package/src/features/legacyState/actions/getLegacySliceMachineState.ts +0 -53
  125. package/src/features/legacyState/useLegacySliceMachineState.ts +0 -7
  126. package/src/hooks/useModelStatus.ts +0 -78
  127. package/src/hooks/useUnSyncChanges.ts +0 -88
  128. package/test/components/LoginModal.test.tsx +0 -67
  129. package/test/src/hooks/useModelStatus.test.ts +0 -115
  130. /package/out/_next/static/{vxOhh_oqUIQ5PTSKUKgMR → eNujlD43QIdmra7ggcNtK}/_ssgManifest.js +0 -0
  131. /package/src/features/{changes → sync}/actions/pushChanges.ts +0 -0
  132. /package/src/features/{changes → sync}/actions/trackPushChangesSuccess.ts +0 -0
@@ -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
+ );
@@ -0,0 +1,69 @@
1
+ import type { GitOwner } from "@slicemachine/manager";
2
+ import { type FC, Suspense, useState } from "react";
3
+
4
+ import { FieldSetContent, FieldSetHeader } from "@src/components/FieldSet";
5
+ import { ConnectGitRepositoryBlankSlate } from "@src/features/settings/git/ConnectGitRepositoryBlankSlate";
6
+ import { GitOwnerSelect } from "@src/features/settings/git/GitOwnerSelect";
7
+ import {
8
+ GitRepositoriesList,
9
+ GitRepositoriesListSkeleton,
10
+ } from "@src/features/settings/git/GitRepositoriesList";
11
+ import { useGitRepos } from "@src/features/settings/git/useGitRepos";
12
+
13
+ type GitRepositoriesSearchProps = { owners: GitOwner[] };
14
+
15
+ export const GitRepositoriesSearch: FC<GitRepositoriesSearchProps> = ({
16
+ owners,
17
+ }) => {
18
+ const [selectedOwner, setSelectedOwner] = useState(owners[0]);
19
+ return (
20
+ <>
21
+ <FieldSetHeader>
22
+ <GitOwnerSelect
23
+ owners={owners}
24
+ selectedOwner={selectedOwner}
25
+ onSelectedOwnerChange={setSelectedOwner}
26
+ sx={{ width: "calc(50% - 8px)" }}
27
+ />
28
+ </FieldSetHeader>
29
+ <Suspense
30
+ fallback={<GitRepositoriesSearchSkeleton gitOwnerSelect={false} />}
31
+ >
32
+ <GitRepositoriesSearchResults owner={selectedOwner} />
33
+ </Suspense>
34
+ </>
35
+ );
36
+ };
37
+
38
+ type GitRepositoriesSearchResultsProps = { owner: GitOwner };
39
+
40
+ const GitRepositoriesSearchResults: FC<GitRepositoriesSearchResultsProps> = ({
41
+ owner,
42
+ }) => {
43
+ const repos = useGitRepos({ provider: owner.provider, owner: owner.name });
44
+ return repos.length > 0 ? (
45
+ <GitRepositoriesList mode="link" repos={repos} />
46
+ ) : (
47
+ <FieldSetContent>
48
+ <ConnectGitRepositoryBlankSlate
49
+ title="No Results Found"
50
+ description="Try selecting a different Git account or organization on the top left."
51
+ />
52
+ </FieldSetContent>
53
+ );
54
+ };
55
+
56
+ type GitRepositoriesSearchSkeletonProps = { gitOwnerSelect: boolean };
57
+
58
+ export const GitRepositoriesSearchSkeleton: FC<
59
+ GitRepositoriesSearchSkeletonProps
60
+ > = ({ gitOwnerSelect }) => (
61
+ <>
62
+ {gitOwnerSelect ? (
63
+ <FieldSetHeader>
64
+ <GitOwnerSelect disabled sx={{ width: "calc(50% - 8px)" }} />
65
+ </FieldSetHeader>
66
+ ) : undefined}
67
+ <GitRepositoriesListSkeleton />
68
+ </>
69
+ );
@@ -0,0 +1,85 @@
1
+ import {
2
+ Box,
3
+ Dialog,
4
+ DialogActions,
5
+ DialogContent,
6
+ DialogHeader,
7
+ FormInput,
8
+ Text,
9
+ } from "@prismicio/editor-ui";
10
+ import type { GitRepo, GitRepoSpecifier } from "@slicemachine/manager";
11
+ import { Formik } from "formik";
12
+ import type { FC, ReactNode } from "react";
13
+ import { toast } from "react-toastify";
14
+
15
+ import { gitProviderToConfig } from "@src/features/settings/git/GitProvider";
16
+ import { useWriteAPIToken } from "@src/features/settings/git/useWriteAPIToken";
17
+
18
+ type GitRepositoryConnectDialogProps = {
19
+ linkRepo: (repo: GitRepoSpecifier) => Promise<void>;
20
+ repo: GitRepo;
21
+ trigger: ReactNode;
22
+ };
23
+
24
+ export const GitRepositoryConnectDialog: FC<
25
+ GitRepositoryConnectDialogProps
26
+ > = ({ linkRepo, repo, trigger }) => {
27
+ const providerConfig = gitProviderToConfig[repo.provider];
28
+ const { updateToken } = useWriteAPIToken({ git: repo });
29
+ return (
30
+ <Dialog size={{ width: 448, height: "auto" }} trigger={trigger}>
31
+ <DialogHeader title="Prismic Write API token required" />
32
+ <DialogContent>
33
+ <Formik
34
+ initialValues={{ writeAPIToken: "" }}
35
+ validate={(values) => {
36
+ if (values.writeAPIToken.length === 0) {
37
+ return { writeAPIToken: "Cannot be empty" };
38
+ }
39
+ }}
40
+ onSubmit={async (values) => {
41
+ try {
42
+ await linkRepo(repo);
43
+ await updateToken(values.writeAPIToken);
44
+ } catch (error) {
45
+ const message = `Could not connect to ${repo.name}`;
46
+ console.error(message, error);
47
+ toast.error(message);
48
+ }
49
+ }}
50
+ >
51
+ {(formikProps) => (
52
+ <form onSubmit={formikProps.handleSubmit}>
53
+ <Box flexDirection="column" gap={4} padding={16}>
54
+ <FormInput
55
+ disabled={formikProps.isSubmitting}
56
+ error={typeof formikProps.errors.writeAPIToken === "string"}
57
+ label="Paste your Write API token"
58
+ onValueChange={(value) => {
59
+ void formikProps.setFieldValue("writeAPIToken", value);
60
+ }}
61
+ placeholder="Write API token"
62
+ size="large"
63
+ value={formikProps.values.writeAPIToken}
64
+ />
65
+ <Text color="grey11">
66
+ Required to sync models from {providerConfig.name} to Prismic.
67
+ </Text>
68
+ </Box>
69
+ <DialogActions
70
+ cancel={{ text: "Cancel" }}
71
+ ok={{
72
+ disabled: !formikProps.isValid,
73
+ loading: formikProps.isSubmitting,
74
+ onClick: () => void formikProps.submitForm(),
75
+ text: "Save Token",
76
+ }}
77
+ size="medium"
78
+ />
79
+ </form>
80
+ )}
81
+ </Formik>
82
+ </DialogContent>
83
+ </Dialog>
84
+ );
85
+ };
@@ -0,0 +1,60 @@
1
+ import {
2
+ Box,
3
+ Dialog,
4
+ DialogActions,
5
+ DialogContent,
6
+ DialogHeader,
7
+ Text,
8
+ } from "@prismicio/editor-ui";
9
+ import type { GitRepoSpecifier } from "@slicemachine/manager";
10
+ import { type FC, type ReactNode, useState } from "react";
11
+ import { toast } from "react-toastify";
12
+
13
+ type GitRepositoryDisconnectDialogProps = {
14
+ repo: GitRepoSpecifier;
15
+ trigger: ReactNode;
16
+ unlinkRepo: (repo: GitRepoSpecifier) => Promise<void>;
17
+ };
18
+
19
+ export const GitRepositoryDisconnectDialog: FC<
20
+ GitRepositoryDisconnectDialogProps
21
+ > = ({ repo, trigger, unlinkRepo }) => {
22
+ const [loading, setLoading] = useState(false);
23
+ return (
24
+ <Dialog size={{ width: 448, height: "auto" }} trigger={trigger}>
25
+ <DialogHeader title="Remove Git Connection" />
26
+ <DialogContent>
27
+ <Box flexDirection="column" padding={16}>
28
+ <Text>
29
+ You will not be able to push your code alongside your models
30
+ anymore. Note that you can reconnect your Git repository at any
31
+ time. Are you sure you want to continue?
32
+ </Text>
33
+ </Box>
34
+ </DialogContent>
35
+ <DialogActions
36
+ asFooter
37
+ cancel={{ text: "Cancel" }}
38
+ ok={{
39
+ color: "tomato",
40
+ loading,
41
+ onClick: () => {
42
+ void (async () => {
43
+ setLoading(true);
44
+ try {
45
+ await unlinkRepo(repo);
46
+ } catch (error) {
47
+ const message = `Could not disconnect from ${repo.name}`;
48
+ console.error(message, error);
49
+ toast.error(message);
50
+ }
51
+ setLoading(false);
52
+ })();
53
+ },
54
+ text: "Disconnect",
55
+ }}
56
+ size="medium"
57
+ />
58
+ </Dialog>
59
+ );
60
+ };
@@ -0,0 +1,8 @@
1
+ import { useExperimentVariant } from "@src/hooks/useExperimentVariant";
2
+
3
+ type UseGitIntegrationExperimentReturnType = { eligible: boolean };
4
+
5
+ export function useGitIntegrationExperiment(): UseGitIntegrationExperimentReturnType {
6
+ const variant = useExperimentVariant("slicemachine-git-integration");
7
+ return { eligible: variant?.value === "on" };
8
+ }
@@ -0,0 +1,12 @@
1
+ import { useRequest } from "@prismicio/editor-support/Suspense";
2
+ import type { GitOwner } from "@slicemachine/manager";
3
+
4
+ import { managerClient } from "@src/managerClient";
5
+
6
+ export function useGitOwners(): GitOwner[] {
7
+ return useRequest(getGitOwners, []);
8
+ }
9
+
10
+ async function getGitOwners(): Promise<GitOwner[]> {
11
+ return await managerClient.git.fetchOwners();
12
+ }
@@ -0,0 +1,24 @@
1
+ import { useRequest } from "@prismicio/editor-support/Suspense";
2
+ import type { GitRepo } from "@slicemachine/manager";
3
+
4
+ import { managerClient } from "@src/managerClient";
5
+
6
+ type UseGitReposArgs = Parameters<typeof managerClient.git.fetchRepos>[0];
7
+
8
+ export function useGitRepos(args: UseGitReposArgs): GitRepo[] {
9
+ return useRequest(getGitRepos, [
10
+ args.provider,
11
+ args.owner,
12
+ args.query,
13
+ args.page,
14
+ ]);
15
+ }
16
+
17
+ async function getGitRepos(
18
+ provider: "gitHub",
19
+ owner: string,
20
+ query?: string,
21
+ page?: number,
22
+ ): Promise<GitRepo[]> {
23
+ return await managerClient.git.fetchRepos({ provider, owner, query, page });
24
+ }
@@ -0,0 +1,41 @@
1
+ import { revalidateData, useRequest } from "@prismicio/editor-support/Suspense";
2
+ import type { GitRepoSpecifier } from "@slicemachine/manager";
3
+
4
+ import { useSliceMachineConfig } from "@src/hooks/useSliceMachineConfig";
5
+ import { managerClient } from "@src/managerClient";
6
+
7
+ type UseLinkedGitReposReturnType = {
8
+ linkedGitRepos: GitRepoSpecifier[];
9
+ linkRepo: (git: GitRepoSpecifier) => Promise<void>;
10
+ unlinkRepo: (git: GitRepoSpecifier) => Promise<void>;
11
+ };
12
+
13
+ export function useLinkedGitRepos(): UseLinkedGitReposReturnType {
14
+ const [config] = useSliceMachineConfig();
15
+ const linkedGitRepos = useRequest(getLinkedGitRepos, [config.repositoryName]);
16
+ return {
17
+ linkedGitRepos,
18
+ linkRepo: async (git) => {
19
+ await managerClient.git.linkRepo({
20
+ prismic: { domain: config.repositoryName },
21
+ git,
22
+ });
23
+ await revalidateData(getLinkedGitRepos, [config.repositoryName]);
24
+ },
25
+ unlinkRepo: async (git) => {
26
+ await managerClient.git.unlinkRepo({
27
+ prismic: { domain: config.repositoryName },
28
+ git,
29
+ });
30
+ await revalidateData(getLinkedGitRepos, [config.repositoryName]);
31
+ },
32
+ };
33
+ }
34
+
35
+ async function getLinkedGitRepos(
36
+ prismicDomain: string,
37
+ ): Promise<GitRepoSpecifier[]> {
38
+ return await managerClient.git.fetchLinkedRepos({
39
+ prismic: { domain: prismicDomain },
40
+ });
41
+ }
@@ -0,0 +1,23 @@
1
+ import type { GitRepoSpecifier } from "@slicemachine/manager";
2
+
3
+ import { useSliceMachineConfig } from "@src/hooks/useSliceMachineConfig";
4
+ import { managerClient } from "@src/managerClient";
5
+
6
+ type UseWriteAPITokenReturnType = {
7
+ updateToken: (token: string) => Promise<void>;
8
+ };
9
+
10
+ export function useWriteAPIToken(args: {
11
+ git: GitRepoSpecifier;
12
+ }): UseWriteAPITokenReturnType {
13
+ const [config] = useSliceMachineConfig();
14
+ return {
15
+ updateToken: async (token) => {
16
+ await managerClient.git.updateWriteAPIToken({
17
+ prismic: { domain: config.repositoryName },
18
+ git: args.git,
19
+ token,
20
+ });
21
+ },
22
+ };
23
+ }
@@ -43,7 +43,6 @@ export const ConvertLegacySliceAsNewSliceDialog: FC<DialogProps> = ({
43
43
  <DialogHeader title="Upgrade slice" />
44
44
  <DialogContent>
45
45
  <Formik
46
- validateOnChange
47
46
  validateOnMount
48
47
  initialValues={{
49
48
  from: libraries[0]?.name,
@@ -47,7 +47,6 @@ export const ConvertLegacySliceAsNewVariationDialog: FC<DialogProps> = ({
47
47
  <DialogHeader title="Convert to slice variation" />
48
48
  <DialogContent>
49
49
  <Formik
50
- validateOnChange
51
50
  initialValues={{
52
51
  libraryID: localSharedSlices[0]?.from,
53
52
  sliceID: localSharedSlices[0]?.model.id,
@@ -31,7 +31,6 @@ export const ConvertLegacySliceMergeWithIdenticalDialog: FC<DialogProps> = ({
31
31
  <DialogHeader title="Merge with an existing slice" />
32
32
  <DialogContent>
33
33
  <Formik
34
- validateOnChange
35
34
  initialValues={{
36
35
  path: identicalSlices[0]?.path ?? "",
37
36
  }}
@@ -9,18 +9,16 @@ import {
9
9
  import { useStableCallback } from "@prismicio/editor-support/React";
10
10
  import { useRouter } from "next/router";
11
11
 
12
- import {
13
- AutoSaveStatus,
14
- useAutoSave,
15
- } from "@src/features/autoSave/useAutoSave";
16
12
  import { ComponentUI } from "@lib/models/common/ComponentUI";
17
13
  import { readSliceMocks, updateSlice } from "@src/apiClient";
18
14
  import useSliceMachineActions from "@src/modules/useSliceMachineActions";
19
15
  import { VariationSM } from "@lib/models/common/Slice";
16
+ import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
17
+ import { ActionQueueStatus, useActionQueue } from "@src/hooks/useActionQueue";
20
18
 
21
19
  type SliceContext = {
22
20
  slice: ComponentUI;
23
- autoSaveStatus: AutoSaveStatus;
21
+ actionQueueStatus: ActionQueueStatus;
24
22
  setSlice: (slice: ComponentUI) => void;
25
23
  variation: VariationSM;
26
24
  };
@@ -37,12 +35,13 @@ export function SliceBuilderProvider(props: SliceBuilderProviderProps) {
37
35
 
38
36
  const router = useRouter();
39
37
  const [slice, setSliceState] = useState(initialSlice);
40
- const { autoSaveStatus, setNextSave } = useAutoSave({
38
+ const { actionQueueStatus, setNextAction } = useActionQueue({
41
39
  errorMessage:
42
40
  "Failed to save slice. Check your browser's console for more information.",
43
41
  });
44
42
  const { saveSliceSuccess } = useSliceMachineActions();
45
43
  const stableSaveSliceSuccess = useStableCallback(saveSliceSuccess);
44
+ const { syncChanges } = useAutoSync();
46
45
 
47
46
  const variation = useMemo(() => {
48
47
  const variationName = router.query.variation;
@@ -60,7 +59,7 @@ export function SliceBuilderProvider(props: SliceBuilderProviderProps) {
60
59
  const setSlice = useCallback(
61
60
  (slice: ComponentUI) => {
62
61
  setSliceState(slice);
63
- setNextSave(async () => {
62
+ setNextAction(async () => {
64
63
  const { errors: updateSliceErrors } = await updateSlice(slice);
65
64
 
66
65
  if (updateSliceErrors.length > 0) {
@@ -76,20 +75,23 @@ export function SliceBuilderProvider(props: SliceBuilderProviderProps) {
76
75
  throw readSliceMockErrors;
77
76
  }
78
77
 
78
+ // Update slices store with new slice
79
79
  stableSaveSliceSuccess({ ...slice, mocks });
80
+
81
+ syncChanges();
80
82
  });
81
83
  },
82
- [setNextSave, stableSaveSliceSuccess],
84
+ [setNextAction, stableSaveSliceSuccess, syncChanges],
83
85
  );
84
86
 
85
87
  const contextValue: SliceContext = useMemo(
86
88
  () => ({
87
- autoSaveStatus,
89
+ actionQueueStatus,
88
90
  slice,
89
91
  setSlice,
90
92
  variation,
91
93
  }),
92
- [autoSaveStatus, slice, setSlice, variation],
94
+ [actionQueueStatus, slice, setSlice, variation],
93
95
  );
94
96
 
95
97
  return (