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
@@ -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
- CustomTypeModel.addField({
397
- customType: mockCustomType,
398
- sectionId: "mainSection",
399
- newFieldId: "newField",
400
- newField: {
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", () => {
@@ -782,4 +789,183 @@ describe("CustomTypeModel test suite", () => {
782
789
  },
783
790
  });
784
791
  });
792
+
793
+ it("should create a custom type with repeatable true", () => {
794
+ expect(
795
+ CustomTypeModel.create({
796
+ id: "id",
797
+ label: "lama",
798
+ repeatable: true,
799
+ format: "custom",
800
+ }),
801
+ ).toMatchInlineSnapshot(`
802
+ {
803
+ "format": "custom",
804
+ "id": "id",
805
+ "json": {
806
+ "Main": {
807
+ "uid": {
808
+ "config": {
809
+ "label": "UID",
810
+ },
811
+ "type": "UID",
812
+ },
813
+ },
814
+ },
815
+ "label": "lama",
816
+ "repeatable": true,
817
+ "status": true,
818
+ }
819
+ `);
820
+ });
821
+
822
+ it("should create a custom type with repeatable false", () => {
823
+ const result = CustomTypeModel.create({
824
+ id: "id",
825
+ label: "lama",
826
+ repeatable: false,
827
+ format: "custom",
828
+ });
829
+
830
+ expect(result.id).toBe("id");
831
+ expect(result.label).toBe("lama");
832
+ expect(result.format).toBe("custom");
833
+ expect(result.json).toHaveProperty("Main");
834
+ expect(result).toMatchInlineSnapshot(`
835
+ {
836
+ "format": "custom",
837
+ "id": "id",
838
+ "json": {
839
+ "Main": {},
840
+ },
841
+ "label": "lama",
842
+ "repeatable": false,
843
+ "status": true,
844
+ }
845
+ `);
846
+ });
847
+
848
+ it("should create a page type with a slice zone", () => {
849
+ const result = CustomTypeModel.create({
850
+ id: "🥪",
851
+ label: "label",
852
+ repeatable: true,
853
+ format: "page",
854
+ });
855
+
856
+ expect(result.format).toBe("page");
857
+ expect(result.json).toHaveProperty("Main");
858
+ expect(result.json.Main).toHaveProperty("slices");
859
+ expect(result.json).toHaveProperty("SEO & Metadata");
860
+ expect(result).toMatchInlineSnapshot(`
861
+ {
862
+ "format": "page",
863
+ "id": "🥪",
864
+ "json": {
865
+ "Main": {
866
+ "slices": {
867
+ "config": {
868
+ "choices": {},
869
+ },
870
+ "fieldset": "Slice Zone",
871
+ "type": "Slices",
872
+ },
873
+ "uid": {
874
+ "config": {
875
+ "label": "UID",
876
+ },
877
+ "type": "UID",
878
+ },
879
+ },
880
+ "SEO & Metadata": {
881
+ "meta_description": {
882
+ "config": {
883
+ "label": "Meta Description",
884
+ "placeholder": "A brief summary of the page",
885
+ },
886
+ "type": "Text",
887
+ },
888
+ "meta_image": {
889
+ "config": {
890
+ "constraint": {
891
+ "height": 1260,
892
+ "width": 2400,
893
+ },
894
+ "label": "Meta Image",
895
+ "thumbnails": [],
896
+ },
897
+ "type": "Image",
898
+ },
899
+ "meta_title": {
900
+ "config": {
901
+ "label": "Meta Title",
902
+ "placeholder": "A title of the page used for social media and search engines",
903
+ },
904
+ "type": "Text",
905
+ },
906
+ },
907
+ },
908
+ "label": "label",
909
+ "repeatable": true,
910
+ "status": true,
911
+ }
912
+ `);
913
+ });
914
+ });
915
+
916
+ it("when non repeatable page type is should contain Main with a slice-zone, no uid, and a SEO tab", () => {
917
+ const result = CustomTypeModel.create({
918
+ id: "foo",
919
+ label: "bar",
920
+ repeatable: false,
921
+ format: "page",
922
+ });
923
+
924
+ expect(result).toMatchInlineSnapshot(`
925
+ {
926
+ "format": "page",
927
+ "id": "foo",
928
+ "json": {
929
+ "Main": {
930
+ "slices": {
931
+ "config": {
932
+ "choices": {},
933
+ },
934
+ "fieldset": "Slice Zone",
935
+ "type": "Slices",
936
+ },
937
+ },
938
+ "SEO & Metadata": {
939
+ "meta_description": {
940
+ "config": {
941
+ "label": "Meta Description",
942
+ "placeholder": "A brief summary of the page",
943
+ },
944
+ "type": "Text",
945
+ },
946
+ "meta_image": {
947
+ "config": {
948
+ "constraint": {
949
+ "height": 1260,
950
+ "width": 2400,
951
+ },
952
+ "label": "Meta Image",
953
+ "thumbnails": [],
954
+ },
955
+ "type": "Image",
956
+ },
957
+ "meta_title": {
958
+ "config": {
959
+ "label": "Meta Title",
960
+ "placeholder": "A title of the page used for social media and search engines",
961
+ },
962
+ "type": "Text",
963
+ },
964
+ },
965
+ },
966
+ "label": "bar",
967
+ "repeatable": false,
968
+ "status": true,
969
+ }
970
+ `);
785
971
  });
@@ -104,8 +104,15 @@ type GetGroupFieldArgs = {
104
104
  groupFieldId: string;
105
105
  };
106
106
 
107
- export function getFormat(custom: CustomType): CustomTypeFormat {
108
- return custom.format ?? "custom";
107
+ type CreateArgs = {
108
+ format: CustomTypeFormat;
109
+ id: string;
110
+ label: string;
111
+ repeatable: boolean;
112
+ };
113
+
114
+ export function getFormat(customType: CustomType): CustomTypeFormat {
115
+ return customType.format ?? "custom";
109
116
  }
110
117
 
111
118
  export function getSectionEntries(
@@ -363,15 +370,32 @@ export function renameSection(
363
370
 
364
371
  export function addField(args: AddFieldArgs): CustomType {
365
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
+ }
366
394
 
367
395
  const newCustomType = updateSection({
368
396
  customType,
369
397
  sectionId,
370
- updatedSection: {
371
- ...customType.json[sectionId],
372
- // Add the new field to the section
373
- [newFieldId]: newField,
374
- },
398
+ updatedSection,
375
399
  });
376
400
 
377
401
  return newCustomType;
@@ -633,3 +657,104 @@ export function updateGroupFields(
633
657
  },
634
658
  };
635
659
  }
660
+
661
+ export function create(args: CreateArgs) {
662
+ const { id, label, repeatable, format } = args;
663
+ const mainTab = makeMainTab(repeatable, format);
664
+
665
+ return {
666
+ format,
667
+ id,
668
+ json: mainTab,
669
+ label,
670
+ repeatable,
671
+ status: true,
672
+ };
673
+ }
674
+
675
+ function makeMainTab(
676
+ repeatable: boolean,
677
+ format: CustomTypeFormat,
678
+ ): CustomType["json"] {
679
+ if (repeatable === false && format === "page") {
680
+ return { ...DEFAULT_MAIN_WITH_SLICE_ZONE, ...DEFAULT_SEO_TAB };
681
+ }
682
+
683
+ if (repeatable === false) {
684
+ return DEFAULT_MAIN;
685
+ }
686
+
687
+ if (format === "page") {
688
+ return {
689
+ ...DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE,
690
+ ...DEFAULT_SEO_TAB,
691
+ };
692
+ }
693
+
694
+ return DEFAULT_MAIN_WITH_UID;
695
+ }
696
+
697
+ const DEFAULT_MAIN: CustomType["json"] = {
698
+ Main: {},
699
+ };
700
+
701
+ const DEFAULT_MAIN_WITH_SLICE_ZONE: CustomType["json"] = {
702
+ Main: {
703
+ slices: {
704
+ config: {
705
+ choices: {},
706
+ },
707
+ fieldset: "Slice Zone",
708
+ type: "Slices",
709
+ },
710
+ },
711
+ };
712
+
713
+ const DEFAULT_MAIN_WITH_UID: CustomType["json"] = {
714
+ Main: {
715
+ uid: {
716
+ config: {
717
+ label: "UID",
718
+ },
719
+ type: "UID",
720
+ },
721
+ },
722
+ };
723
+
724
+ const DEFAULT_MAIN_WITH_UID_AND_SLICE_ZONE: CustomType["json"] = {
725
+ Main: {
726
+ ...DEFAULT_MAIN_WITH_UID.Main,
727
+ ...DEFAULT_MAIN_WITH_SLICE_ZONE.Main,
728
+ },
729
+ };
730
+
731
+ const DEFAULT_SEO_TAB: CustomType["json"] = {
732
+ "SEO & Metadata": {
733
+ meta_description: {
734
+ config: {
735
+ label: "Meta Description",
736
+ placeholder: "A brief summary of the page",
737
+ },
738
+ type: "Text",
739
+ },
740
+ meta_image: {
741
+ config: {
742
+ constraint: {
743
+ height: 1260,
744
+ width: 2400,
745
+ },
746
+ label: "Meta Image",
747
+ thumbnails: [],
748
+ },
749
+ type: "Image",
750
+ },
751
+ meta_title: {
752
+ config: {
753
+ label: "Meta Title",
754
+ placeholder:
755
+ "A title of the page used for social media and search engines",
756
+ },
757
+ type: "Text",
758
+ },
759
+ },
760
+ };
@@ -1,11 +1,13 @@
1
1
  import type {
2
2
  CompositeSlice,
3
3
  LegacySlice,
4
+ SharedSlice,
4
5
  } from "@prismicio/types-internal/lib/customtypes/widgets/slices";
5
6
  import { NestableWidget } from "@prismicio/types-internal/lib/customtypes";
6
7
 
7
8
  import type { ComponentUI } from "@lib/models/common/ComponentUI";
8
9
  import type { VariationSM, WidgetsArea } from "@lib/models/common/Slice";
10
+ import { pascalize, snakelize } from "@lib/utils/str";
9
11
 
10
12
  type CopySliceVariationArgs = {
11
13
  copiedVariation: VariationSM;
@@ -46,6 +48,8 @@ type ReorderFieldArgs = {
46
48
  destinationIndex: number;
47
49
  };
48
50
 
51
+ const DEFAULT_VARIATION_ID = "default";
52
+
49
53
  export function countMissingScreenshots(slice: ComponentUI): number {
50
54
  return slice.model.variations.length - Object.keys(slice.screenshots).length;
51
55
  }
@@ -243,3 +247,37 @@ export function reorderField(args: ReorderFieldArgs): ComponentUI {
243
247
  },
244
248
  };
245
249
  }
250
+
251
+ export function buildEmptySliceModel(sliceName: string): SharedSlice {
252
+ return {
253
+ id: snakelize(sliceName),
254
+ type: "SharedSlice",
255
+ name: sliceName,
256
+ description: sliceName,
257
+ variations: [
258
+ {
259
+ id: DEFAULT_VARIATION_ID,
260
+ name: pascalize(DEFAULT_VARIATION_ID),
261
+ // Property not used yet. Fallback to "...".
262
+ docURL: "...",
263
+ // "initial" is fine here as default value.
264
+ version: "initial",
265
+ description: pascalize(DEFAULT_VARIATION_ID),
266
+ // Empty string is fine, we don't want to save imageUrl.
267
+ // We don't want to compare local and remote image with imageUrl.
268
+ // It will be striped anyway when doing the comparison.
269
+ imageUrl: "",
270
+ },
271
+ ],
272
+ };
273
+ }
274
+
275
+ export function rename(slice: ComponentUI, newSliceName: string): ComponentUI {
276
+ return {
277
+ ...slice,
278
+ model: {
279
+ ...slice.model,
280
+ name: newSliceName,
281
+ },
282
+ };
283
+ }
@@ -1,35 +1,15 @@
1
- import { useEffect, useState } from "react";
2
- import { useSelector } from "react-redux";
1
+ import { useState } from "react";
3
2
  import { Box, Button, Text } from "@prismicio/editor-ui";
4
3
  import { cache } from "@prismicio/editor-support/Suspense";
5
4
 
6
5
  import { CreateCustomTypeModal } from "@components/Forms/CreateCustomTypeModal";
7
- import { SliceMachineStoreType } from "@src/redux/type";
8
- import useSliceMachineActions from "@src/modules/useSliceMachineActions";
9
- import { LoadingKeysEnum } from "@src/modules/loading/types";
10
- import { isLoading } from "@src/modules/loading";
11
6
  import { BlankPageIcon } from "@src/icons/BlankPageIcon";
12
7
  import { getIsEmptyProject } from "@src/hooks/useIsEmptyProject";
13
8
 
14
9
  export function PageTypeOnboarding() {
15
- const { openCreateCustomTypeModal } = useSliceMachineActions();
16
- const { isCreatingCustomType } = useSelector(
17
- (store: SliceMachineStoreType) => ({
18
- isCreatingCustomType: isLoading(
19
- store,
20
- LoadingKeysEnum.CREATE_CUSTOM_TYPE,
21
- ),
22
- }),
23
- );
24
- // State to ensure button loader is still visible while redirecting
25
- const [isButtonLoading, setIsButtonLoading] = useState(false);
26
-
27
- useEffect(() => {
28
- if (isCreatingCustomType) {
29
- setIsButtonLoading(true);
30
- cache.clear(getIsEmptyProject, []);
31
- }
32
- }, [isCreatingCustomType]);
10
+ const [isCreatingCustomType, setIsCreatingCustomType] = useState(false);
11
+ const [isCreateCustomTypeModalOpen, setIsCreateCustomTypeModalOpen] =
12
+ useState(false);
33
13
 
34
14
  return (
35
15
  <Box
@@ -53,10 +33,25 @@ export function PageTypeOnboarding() {
53
33
  Welcome to Slice Machine. Your journey starts with your first page
54
34
  type — the model that you will use to create pages for your website.
55
35
  </Text>
56
- <Button onClick={openCreateCustomTypeModal} loading={isButtonLoading}>
36
+ <Button
37
+ loading={isCreatingCustomType}
38
+ onClick={() => {
39
+ setIsCreateCustomTypeModalOpen(true);
40
+ }}
41
+ >
57
42
  Create a page type
58
43
  </Button>
59
- <CreateCustomTypeModal format="page" origin="onboarding" />
44
+ <CreateCustomTypeModal
45
+ format="page"
46
+ isCreating={isCreatingCustomType}
47
+ isOpen={isCreateCustomTypeModalOpen}
48
+ origin="onboarding"
49
+ onCreateChange={(isCreating) => {
50
+ setIsCreatingCustomType(isCreating);
51
+ cache.clear(getIsEmptyProject, []);
52
+ }}
53
+ onOpenChange={setIsCreateCustomTypeModalOpen}
54
+ />
60
55
  </Box>
61
56
  </Box>
62
57
  );
@@ -9,17 +9,20 @@ import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
9
9
 
10
10
  export async function convertCustomToPageType(
11
11
  customType: CustomType,
12
- saveCustomType: (customType: CustomType) => void,
12
+ onSuccess: (newCustomType: CustomType) => void,
13
13
  ) {
14
14
  const customTypesMessages =
15
15
  CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat];
16
16
 
17
17
  try {
18
18
  const newCustomType = convertToPageType(customType);
19
- await updateCustomType(newCustomType);
19
+ const { errors } = await updateCustomType(newCustomType);
20
20
 
21
- // Update the custom type in the redux store
22
- saveCustomType(newCustomType);
21
+ if (errors.length > 0) {
22
+ throw errors;
23
+ }
24
+
25
+ onSuccess(newCustomType);
23
26
 
24
27
  toast.success(
25
28
  `${customTypesMessages.name({
@@ -28,11 +31,12 @@ export async function convertCustomToPageType(
28
31
  })} converted to page type`,
29
32
  );
30
33
  } catch (e) {
31
- toast.error(
32
- `Internal Error: ${customTypesMessages.name({
33
- start: true,
34
- plural: false,
35
- })} not converted to page type`,
36
- );
34
+ const errorMessage = `Internal Error: ${customTypesMessages.name({
35
+ start: true,
36
+ plural: false,
37
+ })} not converted to page type`;
38
+
39
+ console.error(errorMessage, e);
40
+ toast.error(errorMessage);
37
41
  }
38
42
  }
@@ -0,0 +1,69 @@
1
+ import { toast } from "react-toastify";
2
+ import { CustomType } from "@prismicio/types-internal/lib/customtypes";
3
+
4
+ import { ToastMessageWithPath } from "@components/ToasterContainer";
5
+ import { CustomTypeFormat } from "@slicemachine/manager";
6
+ import { telemetry, updateCustomType } from "@src/apiClient";
7
+ import { create } from "@src/domain/customType";
8
+
9
+ import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
10
+
11
+ type DeleteCustomTypeArgs = {
12
+ format: CustomTypeFormat;
13
+ id: string;
14
+ label: string;
15
+ origin: CustomTypeOrigin;
16
+ repeatable: boolean;
17
+ onSuccess: (newCustomType: CustomType) => Promise<void>;
18
+ };
19
+
20
+ export type CustomTypeOrigin = "onboarding" | "table";
21
+
22
+ export async function createCustomType(args: DeleteCustomTypeArgs) {
23
+ const { id, label, repeatable, format, onSuccess } = args;
24
+ const customTypesMessages = CUSTOM_TYPES_MESSAGES[format];
25
+
26
+ const newCustomType: CustomType = create({
27
+ id,
28
+ label,
29
+ repeatable,
30
+ format,
31
+ });
32
+
33
+ try {
34
+ const { errors } = await updateCustomType(newCustomType);
35
+
36
+ if (errors.length > 0) {
37
+ throw errors;
38
+ }
39
+
40
+ void telemetry.track({
41
+ event: "custom-type:created",
42
+ id: newCustomType.id,
43
+ name: label,
44
+ format: format,
45
+ type: repeatable ? "repeatable" : "single",
46
+ origin: "table",
47
+ });
48
+
49
+ await onSuccess(newCustomType);
50
+
51
+ toast.success(
52
+ ToastMessageWithPath({
53
+ message: `${customTypesMessages.name({
54
+ start: true,
55
+ plural: false,
56
+ })} saved successfully at `,
57
+ path: `./customtypes/${newCustomType.id}/index.json`,
58
+ }),
59
+ );
60
+ } catch (e) {
61
+ const errorMessage = `Internal Error: ${customTypesMessages.name({
62
+ start: true,
63
+ plural: false,
64
+ })} not saved`;
65
+
66
+ console.error(errorMessage, e);
67
+ toast.error(errorMessage);
68
+ }
69
+ }
@@ -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";
5
+ import { CustomTypeFormat } from "@slicemachine/manager";
6
+
3
7
  import { CUSTOM_TYPES_MESSAGES } from "../customTypesMessages";
4
- import { CustomTypeFormat } from "@slicemachine/manager/*";
5
- import { toast } from "react-toastify";
6
8
 
7
9
  type DeleteCustomTypeArgs = {
8
10
  customType: CustomType;
@@ -17,7 +19,13 @@ export async function deleteCustomType({
17
19
  CUSTOM_TYPES_MESSAGES[customType.format as CustomTypeFormat];
18
20
 
19
21
  try {
20
- await managerClient.customTypes.deleteCustomType({ id: customType.id });
22
+ const { errors } = await managerClient.customTypes.deleteCustomType({
23
+ id: customType.id,
24
+ });
25
+
26
+ if (errors.length > 0) {
27
+ throw errors;
28
+ }
21
29
 
22
30
  onSuccess();
23
31
 
@@ -32,6 +40,8 @@ export async function deleteCustomType({
32
40
  start: true,
33
41
  plural: false,
34
42
  })} could not be deleted`;
43
+
35
44
  console.error(errorMessage, e);
45
+ toast.error(errorMessage);
36
46
  }
37
47
  }