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.
- package/.depcheckrc +3 -0
- package/components/ChangesEmptyState/OfflinePage.tsx +1 -1
- package/components/ChangesItems/ChangesItems.tsx +6 -2
- package/components/CustomTypeTable/changesPage.tsx +12 -7
- package/components/DeleteSliceModal/index.tsx +3 -0
- package/components/DeleteVariationModal/index.tsx +1 -0
- package/components/Forms/CreateCustomTypeModal/CreateCustomTypeModal.tsx +4 -0
- package/components/Forms/CreateSliceModal/CreateSliceModal.tsx +3 -1
- package/components/Forms/RenameCustomTypeModal/RenameCustomTypeModal.tsx +6 -1
- package/components/Forms/RenameSliceModal/RenameSliceModal.tsx +3 -0
- package/components/Forms/RenameVariationModal/RenameVariationModal.tsx +0 -1
- package/components/LoginModal/index.tsx +40 -2
- package/components/ModalFormCard/index.tsx +0 -1
- package/components/Navigation/ChangesItem.tsx +117 -0
- package/components/Navigation/Environment.tsx +51 -4
- package/components/Navigation/index.css.ts +1 -2
- package/components/Navigation/index.test.tsx +5 -85
- package/components/Navigation/index.tsx +32 -20
- package/components/ReviewModal/ReviewForm.tsx +0 -1
- package/lib/builders/SliceBuilder/index.tsx +3 -3
- package/lib/builders/common/EditModal/Form.jsx +0 -1
- package/lib/builders/common/Zone/Card/components/NewField.tsx +0 -1
- package/lib/models/common/ModelStatus/index.ts +25 -0
- package/out/404.html +1 -1
- package/out/_next/static/chunks/18539003-5f78714a35fde4a1.js +97 -0
- package/out/_next/static/chunks/25-3aa4d4b24167d340.js +1 -0
- package/out/_next/static/chunks/318-19dbcae10cdb6408.js +1 -0
- package/out/_next/static/chunks/727-de03055e40815a3a.js +1 -0
- package/out/_next/static/chunks/777-8c5507e3b119b5dc.js +8 -0
- package/out/_next/static/chunks/799-0af1215eb2b3c17e.js +1 -0
- package/out/_next/static/chunks/904-40686b0426716111.js +1 -0
- package/out/_next/static/chunks/pages/_app-bca191df4ac7078e.js +434 -0
- package/out/_next/static/chunks/pages/{changelog-afd18276fe018b24.js → changelog-cb1df3a70b20f1b8.js} +1 -1
- package/out/_next/static/chunks/pages/changes-ccfd715c2bdc4f83.js +1 -0
- package/out/_next/static/chunks/pages/{labs-ee206005948f8e87.js → labs-2adfb5127f9c4081.js} +1 -1
- package/out/_next/static/chunks/pages/settings-94bcd7422e8b237d.js +1 -0
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-52c3e1f9d14160b8.js → simulator-aaf65fbec8f5839c.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-90bb2ea76d2abf42.js +1 -0
- package/out/_next/static/chunks/pages/slices-e0ce615c38e8338d.js +1 -0
- package/out/_next/static/css/39e089aad1742a3a.css +1 -0
- package/out/_next/static/css/{37153180c3b0dd66.css → 838fd8bb5a7c32b0.css} +1 -1
- package/out/_next/static/css/ca32882a5d2d8d88.css +1 -0
- package/out/_next/static/eNujlD43QIdmra7ggcNtK/_buildManifest.js +1 -0
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/settings.html +1 -0
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +10 -8
- package/pages/_app.tsx +34 -32
- package/pages/changes.tsx +27 -41
- package/pages/settings.tsx +1 -0
- package/postcss.config.js +10 -0
- package/src/components/FieldSet/FieldSet.stories.tsx +20 -11
- package/src/components/RelativeTime/RelativeTime.stories.tsx +25 -0
- package/src/components/RelativeTime/RelativeTime.tsx +40 -0
- package/src/components/RelativeTime/index.ts +1 -0
- package/src/components/SideNav/SideNav.css.ts +1 -1
- package/src/components/SideNav/SideNav.tsx +9 -5
- package/src/components/SideNav/SideNavEnvironmentSelector.tsx +31 -11
- package/src/domain/__tests__/customType.test.ts +26 -19
- package/src/domain/customType.ts +22 -5
- package/src/features/autoSave/AutoSaveStatusIndicator.tsx +4 -4
- package/src/features/customTypes/customTypesBuilder/CustomTypeProvider.tsx +11 -10
- package/src/features/customTypes/customTypesBuilder/CustomTypesBuilderPage.tsx +3 -3
- package/src/features/environments/actions/setEnvironment.ts +4 -4
- package/src/features/settings/SettingsPage.tsx +47 -0
- package/src/features/settings/git/ConnectGitRepository.tsx +126 -0
- package/src/features/settings/git/ConnectGitRepositoryBlankSlate.tsx +33 -0
- package/src/features/settings/git/GitOwnerSelect.tsx +71 -0
- package/src/features/settings/git/GitProvider.ts +40 -0
- package/src/features/settings/git/GitProviderConnectButtons.tsx +63 -0
- package/src/features/settings/git/GitRepositoriesList.tsx +76 -0
- package/src/features/settings/git/GitRepositoriesSearch.tsx +69 -0
- package/src/features/settings/git/GitRepositoryConnectDialog.tsx +85 -0
- package/src/features/settings/git/GitRepositoryDisconnectDialog.tsx +60 -0
- package/src/features/settings/git/useGitIntegrationExperiment.ts +8 -0
- package/src/features/settings/git/useGitOwners.ts +12 -0
- package/src/features/settings/git/useGitRepos.ts +24 -0
- package/src/features/settings/git/useLinkedGitRepos.ts +41 -0
- package/src/features/settings/git/useWriteAPIToken.ts +23 -0
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx +0 -1
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +0 -1
- package/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx +0 -1
- package/src/features/slices/sliceBuilder/SliceBuilderProvider.tsx +12 -10
- package/src/features/sync/AutoSyncProvider.tsx +252 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.module.css +17 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.module.css.d.ts +5 -0
- package/src/features/sync/components/AutoSyncStatusIndicator.tsx +58 -0
- package/src/features/sync/fetchUnSyncChanges.ts +40 -0
- package/src/features/sync/getUnSyncChanges.ts +144 -0
- package/src/features/sync/useUnSyncChanges.ts +32 -0
- package/src/hooks/useActionQueue.tsx +177 -0
- package/src/hooks/useAuthStatus.ts +12 -0
- package/src/hooks/useUser.ts +14 -0
- package/src/icons/SettingsIcon.tsx +19 -0
- package/src/icons/SyncFailed.tsx +30 -0
- package/src/icons/Synced.tsx +19 -0
- package/src/icons/Syncing.tsx +23 -0
- package/test/__testutils__/index.tsx +8 -5
- package/test/src/modules/__fixtures__/serverState.ts +1 -2
- package/components/Navigation/ChangesListItem.tsx +0 -74
- package/components/Navigation/ChangesRightElement.tsx +0 -33
- package/out/_next/static/chunks/18539003-5cc559566b738915.js +0 -97
- package/out/_next/static/chunks/25-01f422cea8066349.js +0 -1
- package/out/_next/static/chunks/318-387e3f23d717c53d.js +0 -1
- package/out/_next/static/chunks/376-7e784d6069e01654.js +0 -8
- package/out/_next/static/chunks/727-358d7fae68a880b3.js +0 -1
- package/out/_next/static/chunks/799-d95a93e965557aeb.js +0 -1
- package/out/_next/static/chunks/904-1ff7519adece2b55.js +0 -1
- package/out/_next/static/chunks/pages/_app-e31adcf75e9176d3.js +0 -432
- package/out/_next/static/chunks/pages/changes-92c84f6cac0285fd.js +0 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-608d206d10a719e3.js +0 -1
- package/out/_next/static/chunks/pages/slices-b0b58192a87df822.js +0 -1
- package/out/_next/static/css/200244e3e0463c39.css +0 -1
- package/out/_next/static/vxOhh_oqUIQ5PTSKUKgMR/_buildManifest.js +0 -1
- package/src/features/autoSave/useAutoSave.tsx +0 -177
- package/src/features/legacyState/actions/getLegacySliceMachineState.ts +0 -53
- package/src/features/legacyState/useLegacySliceMachineState.ts +0 -7
- package/src/hooks/useModelStatus.ts +0 -78
- package/src/hooks/useUnSyncChanges.ts +0 -88
- package/test/components/LoginModal.test.tsx +0 -67
- package/test/src/hooks/useModelStatus.test.ts +0 -115
- /package/out/_next/static/{vxOhh_oqUIQ5PTSKUKgMR → eNujlD43QIdmra7ggcNtK}/_ssgManifest.js +0 -0
- /package/src/features/{changes → sync}/actions/pushChanges.ts +0 -0
- /package/src/features/{changes → sync}/actions/trackPushChangesSuccess.ts +0 -0
|
@@ -173,10 +173,14 @@ export const UpdateInfo: FC<UpdateInfoProps> = ({
|
|
|
173
173
|
Some updates of Slice Machine are available.
|
|
174
174
|
</p>
|
|
175
175
|
|
|
176
|
-
{
|
|
177
|
-
component
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
{
|
|
177
|
+
// TODO(DT-1942): This should be a Button with a link component for
|
|
178
|
+
// accessibility
|
|
179
|
+
createElement(
|
|
180
|
+
component,
|
|
181
|
+
{ ...{ className: styles.updateInfoLink, onClick }, href },
|
|
182
|
+
"Learn more",
|
|
183
|
+
)
|
|
184
|
+
}
|
|
181
185
|
</div>
|
|
182
186
|
);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Icon,
|
|
9
9
|
IconButton,
|
|
10
10
|
InvisibleButton,
|
|
11
|
+
ProgressCircle,
|
|
11
12
|
Text,
|
|
12
13
|
} from "@prismicio/editor-ui";
|
|
13
14
|
import { Environment } from "@slicemachine/manager/client";
|
|
@@ -21,22 +22,26 @@ import * as styles from "./SideNavEnvironmentSelector.css";
|
|
|
21
22
|
import { LoginIcon } from "@src/icons/LoginIcon";
|
|
22
23
|
|
|
23
24
|
type SideNavEnvironmentSelectorProps = {
|
|
24
|
-
variant?: "default" | "offline" | "unauthorized" | "unauthenticated";
|
|
25
|
-
environments?: Environment[];
|
|
26
25
|
activeEnvironment?: Environment;
|
|
27
|
-
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
environments?: Environment[];
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
variant?: "default" | "offline" | "unauthorized" | "unauthenticated";
|
|
28
30
|
onLogInClick?: () => void;
|
|
31
|
+
onSelect?: (environment: Environment) => void | Promise<void>;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
export const SideNavEnvironmentSelector: FC<SideNavEnvironmentSelectorProps> = (
|
|
32
35
|
props,
|
|
33
36
|
) => {
|
|
34
37
|
const {
|
|
35
|
-
variant = "default",
|
|
36
|
-
environments = [],
|
|
37
38
|
activeEnvironment,
|
|
38
|
-
|
|
39
|
+
disabled = false,
|
|
40
|
+
environments = [],
|
|
41
|
+
loading = false,
|
|
42
|
+
variant = "default",
|
|
39
43
|
onLogInClick,
|
|
44
|
+
onSelect,
|
|
40
45
|
} = props;
|
|
41
46
|
|
|
42
47
|
const isProductionEnvironmentActive = activeEnvironment?.kind === "prod";
|
|
@@ -60,7 +65,7 @@ export const SideNavEnvironmentSelector: FC<SideNavEnvironmentSelectorProps> = (
|
|
|
60
65
|
overflow="hidden"
|
|
61
66
|
alignItems="flex-start"
|
|
62
67
|
>
|
|
63
|
-
{variant === "default"
|
|
68
|
+
{variant === "default" ? (
|
|
64
69
|
<Text component="span" variant="small" color="grey11">
|
|
65
70
|
Environment
|
|
66
71
|
</Text>
|
|
@@ -70,6 +75,12 @@ export const SideNavEnvironmentSelector: FC<SideNavEnvironmentSelectorProps> = (
|
|
|
70
75
|
<InvisibleButton buttonText="Login required" onClick={onLogInClick} />
|
|
71
76
|
) : undefined}
|
|
72
77
|
|
|
78
|
+
{variant === "offline" ? (
|
|
79
|
+
<Text component="span" variant="bold">
|
|
80
|
+
Offline
|
|
81
|
+
</Text>
|
|
82
|
+
) : undefined}
|
|
83
|
+
|
|
73
84
|
{variant === "default" ? (
|
|
74
85
|
<Text
|
|
75
86
|
component="span"
|
|
@@ -93,8 +104,10 @@ export const SideNavEnvironmentSelector: FC<SideNavEnvironmentSelectorProps> = (
|
|
|
93
104
|
|
|
94
105
|
{environments.length > 1 ? (
|
|
95
106
|
<EnvironmentDropdownMenu
|
|
96
|
-
environments={environments}
|
|
97
107
|
activeEnvironment={activeEnvironment}
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
environments={environments}
|
|
110
|
+
loading={loading}
|
|
98
111
|
onSelect={onSelect}
|
|
99
112
|
/>
|
|
100
113
|
) : undefined}
|
|
@@ -107,11 +120,14 @@ type EnvironmentDropdownMenuProps = Pick<
|
|
|
107
120
|
SideNavEnvironmentSelectorProps,
|
|
108
121
|
"activeEnvironment" | "onSelect"
|
|
109
122
|
> & {
|
|
123
|
+
disabled: boolean;
|
|
110
124
|
environments: Environment[];
|
|
125
|
+
loading: boolean;
|
|
111
126
|
};
|
|
112
127
|
|
|
113
128
|
const EnvironmentDropdownMenu: FC<EnvironmentDropdownMenuProps> = (props) => {
|
|
114
|
-
const { environments,
|
|
129
|
+
const { activeEnvironment, disabled, environments, loading, onSelect } =
|
|
130
|
+
props;
|
|
115
131
|
|
|
116
132
|
const nonPersonalEnvironments = environments.filter(
|
|
117
133
|
(environment) => environment.kind !== "dev",
|
|
@@ -122,8 +138,12 @@ const EnvironmentDropdownMenu: FC<EnvironmentDropdownMenuProps> = (props) => {
|
|
|
122
138
|
|
|
123
139
|
return (
|
|
124
140
|
<DropdownMenu modal>
|
|
125
|
-
<DropdownMenuTrigger disabled={
|
|
126
|
-
<IconButton
|
|
141
|
+
<DropdownMenuTrigger disabled={disabled}>
|
|
142
|
+
<IconButton
|
|
143
|
+
icon={loading ? <ProgressCircle /> : "unfoldMore"}
|
|
144
|
+
hiddenLabel="Select environment"
|
|
145
|
+
disabled={disabled}
|
|
146
|
+
/>
|
|
127
147
|
</DropdownMenuTrigger>
|
|
128
148
|
<DropdownMenuContent align="end" minWidth={256}>
|
|
129
149
|
{personalEnvironment ? (
|
|
@@ -393,32 +393,39 @@ describe("CustomTypeModel test suite", () => {
|
|
|
393
393
|
|
|
394
394
|
it("addField should return the given custom type with the field added to the section", () => {
|
|
395
395
|
expect(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
config: {
|
|
402
|
-
label: "NewField",
|
|
403
|
-
},
|
|
404
|
-
type: "UID",
|
|
405
|
-
},
|
|
406
|
-
}),
|
|
407
|
-
).toEqual({
|
|
408
|
-
...mockCustomType,
|
|
409
|
-
json: {
|
|
410
|
-
...mockCustomType.json,
|
|
411
|
-
mainSection: {
|
|
412
|
-
...mainSection,
|
|
396
|
+
JSON.stringify(
|
|
397
|
+
CustomTypeModel.addField({
|
|
398
|
+
customType: mockCustomType,
|
|
399
|
+
sectionId: "mainSection",
|
|
400
|
+
newFieldId: "newField",
|
|
413
401
|
newField: {
|
|
414
402
|
config: {
|
|
415
403
|
label: "NewField",
|
|
416
404
|
},
|
|
417
405
|
type: "UID",
|
|
418
406
|
},
|
|
407
|
+
}),
|
|
408
|
+
),
|
|
409
|
+
).toEqual(
|
|
410
|
+
JSON.stringify({
|
|
411
|
+
...mockCustomType,
|
|
412
|
+
json: {
|
|
413
|
+
...mockCustomType.json,
|
|
414
|
+
mainSection: {
|
|
415
|
+
uid: mainSection.uid,
|
|
416
|
+
booleanField: mainSection.booleanField,
|
|
417
|
+
groupField: mainSection.groupField,
|
|
418
|
+
newField: {
|
|
419
|
+
config: {
|
|
420
|
+
label: "NewField",
|
|
421
|
+
},
|
|
422
|
+
type: "UID",
|
|
423
|
+
},
|
|
424
|
+
slices: mainSection.slices,
|
|
425
|
+
},
|
|
419
426
|
},
|
|
420
|
-
},
|
|
421
|
-
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
422
429
|
});
|
|
423
430
|
|
|
424
431
|
it("deleteField should return the given custom type without the field", () => {
|
package/src/domain/customType.ts
CHANGED
|
@@ -370,15 +370,32 @@ export function renameSection(
|
|
|
370
370
|
|
|
371
371
|
export function addField(args: AddFieldArgs): CustomType {
|
|
372
372
|
const { customType, sectionId, newField, newFieldId } = args;
|
|
373
|
+
const sectionJson = customType.json[sectionId];
|
|
374
|
+
const maybeSliceZoneEntry = getSectionSliceZoneEntry(customType, sectionId);
|
|
375
|
+
|
|
376
|
+
// Separate the fields into slices and non-slices
|
|
377
|
+
const sectionFields = Object.fromEntries(
|
|
378
|
+
Object.entries(sectionJson).filter(
|
|
379
|
+
([_, value]) => value.type !== SlicesFieldType,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const updatedSection = {
|
|
384
|
+
...sectionFields,
|
|
385
|
+
// Add the new field to the section
|
|
386
|
+
[newFieldId]: newField,
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Merge the SlicesFieldType field back into the section, if it exists
|
|
390
|
+
if (maybeSliceZoneEntry !== undefined) {
|
|
391
|
+
const [sliceZoneKey, sliceZoneField] = maybeSliceZoneEntry;
|
|
392
|
+
updatedSection[sliceZoneKey] = sliceZoneField;
|
|
393
|
+
}
|
|
373
394
|
|
|
374
395
|
const newCustomType = updateSection({
|
|
375
396
|
customType,
|
|
376
397
|
sectionId,
|
|
377
|
-
updatedSection
|
|
378
|
-
...customType.json[sectionId],
|
|
379
|
-
// Add the new field to the section
|
|
380
|
-
[newFieldId]: newField,
|
|
381
|
-
},
|
|
398
|
+
updatedSection,
|
|
382
399
|
});
|
|
383
400
|
|
|
384
401
|
return newCustomType;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
2
|
import { Box, Icon, ProgressCircle, Text } from "@prismicio/editor-ui";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { ActionQueueStatus } from "@src/hooks/useActionQueue";
|
|
5
5
|
|
|
6
6
|
type AutoSaveStatusIndicatorProps = {
|
|
7
|
-
status:
|
|
7
|
+
status: ActionQueueStatus;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export const AutoSaveStatusIndicator: FC<AutoSaveStatusIndicatorProps> = (
|
|
@@ -14,7 +14,7 @@ export const AutoSaveStatusIndicator: FC<AutoSaveStatusIndicatorProps> = (
|
|
|
14
14
|
let autoSaveStatusInfo;
|
|
15
15
|
|
|
16
16
|
switch (status) {
|
|
17
|
-
case "
|
|
17
|
+
case "pending":
|
|
18
18
|
autoSaveStatusInfo = {
|
|
19
19
|
icon: <ProgressCircle color="grey11" />,
|
|
20
20
|
text: "Saving...",
|
|
@@ -26,7 +26,7 @@ export const AutoSaveStatusIndicator: FC<AutoSaveStatusIndicatorProps> = (
|
|
|
26
26
|
text: "Failed to save",
|
|
27
27
|
};
|
|
28
28
|
break;
|
|
29
|
-
case "
|
|
29
|
+
case "done":
|
|
30
30
|
autoSaveStatusInfo = {
|
|
31
31
|
icon: <Icon name="check" color="green11" size="medium" />,
|
|
32
32
|
text: "Auto-saved",
|
|
@@ -10,18 +10,16 @@ import { CustomType } from "@prismicio/types-internal/lib/customtypes";
|
|
|
10
10
|
import { useStableCallback } from "@prismicio/editor-support/React";
|
|
11
11
|
|
|
12
12
|
import useSliceMachineActions from "@src/modules/useSliceMachineActions";
|
|
13
|
-
import {
|
|
14
|
-
AutoSaveStatus,
|
|
15
|
-
useAutoSave,
|
|
16
|
-
} from "@src/features/autoSave/useAutoSave";
|
|
13
|
+
import { useAutoSync } from "@src/features/sync/AutoSyncProvider";
|
|
17
14
|
import { getFormat } from "@src/domain/customType";
|
|
18
15
|
import { updateCustomType } from "@src/apiClient";
|
|
16
|
+
import { ActionQueueStatus, useActionQueue } from "@src/hooks/useActionQueue";
|
|
19
17
|
|
|
20
18
|
import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
|
|
21
19
|
|
|
22
20
|
type CustomTypeContext = {
|
|
23
21
|
customType: CustomType;
|
|
24
|
-
|
|
22
|
+
actionQueueStatus: ActionQueueStatus;
|
|
25
23
|
setCustomType: (customType: CustomType) => void;
|
|
26
24
|
};
|
|
27
25
|
|
|
@@ -40,16 +38,17 @@ export function CustomTypeProvider(props: CustomTypeProviderProps) {
|
|
|
40
38
|
const [customType, setCustomTypeState] = useState(initialCustomType);
|
|
41
39
|
const format = getFormat(customType);
|
|
42
40
|
const customTypeMessages = CUSTOM_TYPES_MESSAGES[format];
|
|
43
|
-
const {
|
|
41
|
+
const { actionQueueStatus, setNextAction } = useActionQueue({
|
|
44
42
|
errorMessage: customTypeMessages.autoSaveFailed,
|
|
45
43
|
});
|
|
46
44
|
const { saveCustomTypeSuccess } = useSliceMachineActions();
|
|
47
45
|
const stableSaveCustomTypeSuccess = useStableCallback(saveCustomTypeSuccess);
|
|
46
|
+
const { syncChanges } = useAutoSync();
|
|
48
47
|
|
|
49
48
|
const setCustomType = useCallback(
|
|
50
49
|
(customType: CustomType) => {
|
|
51
50
|
setCustomTypeState(customType);
|
|
52
|
-
|
|
51
|
+
setNextAction(async () => {
|
|
53
52
|
const { errors } = await updateCustomType(customType);
|
|
54
53
|
|
|
55
54
|
if (errors.length > 0) {
|
|
@@ -58,18 +57,20 @@ export function CustomTypeProvider(props: CustomTypeProviderProps) {
|
|
|
58
57
|
|
|
59
58
|
// Update available custom types store with new custom type
|
|
60
59
|
stableSaveCustomTypeSuccess(customType);
|
|
60
|
+
|
|
61
|
+
syncChanges();
|
|
61
62
|
});
|
|
62
63
|
},
|
|
63
|
-
[
|
|
64
|
+
[setNextAction, stableSaveCustomTypeSuccess, syncChanges],
|
|
64
65
|
);
|
|
65
66
|
|
|
66
67
|
const contextValue: CustomTypeContext = useMemo(
|
|
67
68
|
() => ({
|
|
68
|
-
|
|
69
|
+
actionQueueStatus,
|
|
69
70
|
customType,
|
|
70
71
|
setCustomType,
|
|
71
72
|
}),
|
|
72
|
-
[
|
|
73
|
+
[actionQueueStatus, customType, setCustomType],
|
|
73
74
|
);
|
|
74
75
|
|
|
75
76
|
return (
|
|
@@ -41,7 +41,7 @@ export const CustomTypesBuilderPage: FC = () => {
|
|
|
41
41
|
useEffect(() => {
|
|
42
42
|
// This is a fallback in case the user try to directly access a custom type
|
|
43
43
|
// that don't exist
|
|
44
|
-
// TODO(DT-1801):
|
|
44
|
+
// TODO(DT-1801): implement a 404 page.
|
|
45
45
|
if (!selectedCustomType || !hasLocal(selectedCustomType)) {
|
|
46
46
|
void router.replace("/");
|
|
47
47
|
}
|
|
@@ -78,7 +78,7 @@ const CustomTypesBuilderPageWithProvider: React.FC<
|
|
|
78
78
|
<CustomTypeProvider
|
|
79
79
|
initialCustomType={CustomTypes.fromSM(customTypeFromStore)}
|
|
80
80
|
>
|
|
81
|
-
{({
|
|
81
|
+
{({ actionQueueStatus, customType, setCustomType }) => {
|
|
82
82
|
const format = getFormat(customType);
|
|
83
83
|
const config = CUSTOM_TYPES_CONFIG[customTypeFromStore.format];
|
|
84
84
|
const messages = CUSTOM_TYPES_MESSAGES[customTypeFromStore.format];
|
|
@@ -92,7 +92,7 @@ const CustomTypesBuilderPageWithProvider: React.FC<
|
|
|
92
92
|
page={customType.label ?? customType.id}
|
|
93
93
|
/>
|
|
94
94
|
<AppLayoutActions>
|
|
95
|
-
<AutoSaveStatusIndicator status={
|
|
95
|
+
<AutoSaveStatusIndicator status={actionQueueStatus} />
|
|
96
96
|
{customType.format === "page" ? (
|
|
97
97
|
<PageSnippetDialog model={customType} />
|
|
98
98
|
) : undefined}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { revalidateData } from "@prismicio/editor-support/Suspense";
|
|
2
|
-
import { Environment } from "@slicemachine/manager/client";
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { Environment } from "@slicemachine/manager/client";
|
|
5
4
|
import { managerClient } from "@src/managerClient";
|
|
5
|
+
import { getState } from "@src/apiClient";
|
|
6
6
|
|
|
7
7
|
import { getActiveEnvironment } from "./getActiveEnvironment";
|
|
8
8
|
|
|
@@ -13,6 +13,6 @@ export async function setEnvironment(
|
|
|
13
13
|
environment: environment.domain,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
revalidateData(getActiveEnvironment, []);
|
|
17
|
-
revalidateData(
|
|
16
|
+
void revalidateData(getActiveEnvironment, []);
|
|
17
|
+
void revalidateData(getState, []);
|
|
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"];
|