slice-machine-ui 2.18.1-beta.6 → 2.18.1-beta.8

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 (78) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/U9WVTg1pSTJVt94SbRE5Y/_buildManifest.js +1 -0
  3. package/out/_next/static/chunks/{248-43faecb386a16518.js → 248-a9c33f7fbe2da2f1.js} +1 -1
  4. package/out/_next/static/chunks/256-07f768a2b19b0a0e.js +4 -0
  5. package/out/_next/static/chunks/34-50c64778da33cff6.js +1 -0
  6. package/out/_next/static/chunks/344-fdb3008f4bb3b0c1.js +1 -0
  7. package/out/_next/static/chunks/429-aab52070cad2884b.js +3 -0
  8. package/out/_next/static/chunks/484-3e011e79c41f0342.js +1 -0
  9. package/out/_next/static/chunks/{489-234ed5471aa694b3.js → 489-a046ae0fa4f74435.js} +1 -1
  10. package/out/_next/static/chunks/500-d3989390f5e8da53.js +1 -0
  11. package/out/_next/static/chunks/52d4c156-89c6ec6efca0a0bb.js +1 -0
  12. package/out/_next/static/chunks/{630-bb6e3db525588f16.js → 630-eae75e90b30f201a.js} +1 -1
  13. package/out/_next/static/chunks/pages/{_app-174a369b49b73427.js → _app-1b4541325b4a29ab.js} +105 -105
  14. package/out/_next/static/chunks/pages/{changelog-ba55ed247c20dc21.js → changelog-3901f2fc937d9648.js} +1 -1
  15. package/out/_next/static/chunks/pages/changes-c58a60af39167147.js +1 -0
  16. package/out/_next/static/chunks/pages/custom-types/{[customTypeId]-816acb31b652239b.js → [customTypeId]-af9376721beb489e.js} +1 -1
  17. package/out/_next/static/chunks/pages/{labs-78ac01d97ab02cd7.js → labs-dd04033db23a58f3.js} +1 -1
  18. package/out/_next/static/chunks/pages/page-types/{[pageTypeId]-669d5479e81b638b.js → [pageTypeId]-a24665e91b882169.js} +1 -1
  19. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-b127d948a17968d3.js → simulator-f237aaa755dc9a99.js} +1 -1
  20. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-c8c3e7ffb898261c.js +1 -0
  21. package/out/_next/static/chunks/pages/slices-0aaae79dcc7b8c55.js +1 -0
  22. package/out/_next/static/css/{303dad78be1eb854.css → bc89f2cd4e4781f5.css} +1 -1
  23. package/out/_next/static/css/e8e03c0d4003d1eb.css +1 -0
  24. package/out/changelog.html +1 -1
  25. package/out/changes.html +1 -1
  26. package/out/custom-types/[customTypeId].html +1 -1
  27. package/out/custom-types.html +1 -1
  28. package/out/index.html +1 -1
  29. package/out/labs.html +1 -1
  30. package/out/page-types/[pageTypeId].html +1 -1
  31. package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
  32. package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
  33. package/out/slices.html +1 -1
  34. package/package.json +3 -3
  35. package/src/features/auth/LogoutButton.tsx +42 -36
  36. package/src/features/builder/fields/contentRelationship/ContentRelationshipFieldPicker.tsx +3 -3
  37. package/src/features/changes/StatusBadge.tsx +1 -9
  38. package/src/features/customTypes/customTypesBuilder/PageSnippetDialog/PageSnippetDialog.tsx +3 -3
  39. package/src/features/customTypes/customTypesTable/CustomTypesTablePage.tsx +3 -3
  40. package/src/features/environments/actions/useSetEnvironment.ts +22 -0
  41. package/src/features/environments/useActiveEnvironment.ts +17 -9
  42. package/src/features/environments/useEnvironments.ts +11 -8
  43. package/src/features/errorBoundaries/AppStateErrorBoundary.tsx +108 -0
  44. package/src/{ErrorBoundary.tsx → features/errorBoundaries/DefaultErrorBoundary.tsx} +2 -2
  45. package/src/features/errorBoundaries/index.ts +2 -0
  46. package/src/features/labs/labsList/LabsPage.tsx +3 -3
  47. package/src/features/navigation/Navigation.tsx +7 -8
  48. package/src/features/slices/sliceBuilder/FloatingBackButton.tsx +3 -3
  49. package/src/features/sync/AutoSyncProvider.tsx +2 -2
  50. package/src/features/sync/getUnSyncChanges.ts +1 -3
  51. package/src/legacy/components/AppLayout/index.tsx +10 -85
  52. package/src/legacy/components/ChangesEmptyState/UnauthenticatedView.tsx +31 -0
  53. package/src/legacy/components/ChangesEmptyState/index.ts +1 -1
  54. package/src/legacy/components/ChangesItems/ChangesItems.tsx +3 -3
  55. package/src/legacy/components/LoginModal/index.tsx +14 -6
  56. package/src/legacy/components/Navigation/ChangesItem.tsx +44 -100
  57. package/src/legacy/components/Navigation/Environment.tsx +2 -7
  58. package/src/legacy/components/Navigation/SideNavEnvironmentSelector/SideNavEnvironmentSelector.tsx +3 -8
  59. package/src/legacy/components/Simulator/index.tsx +3 -3
  60. package/src/legacy/lib/builders/CustomTypeBuilder/TabZone/index.tsx +3 -3
  61. package/src/modules/userContext/index.ts +5 -5
  62. package/src/modules/userContext/types.ts +2 -3
  63. package/src/pages/_app.tsx +93 -95
  64. package/src/pages/changes.tsx +4 -5
  65. package/src/queryClient.tsx +24 -0
  66. package/test/__testutils__/index.tsx +13 -10
  67. package/out/_next/static/AMILL72qfLjZDeK2IRMSB/_buildManifest.js +0 -1
  68. package/out/_next/static/chunks/157-54b8336d20b41933.js +0 -3
  69. package/out/_next/static/chunks/34-8d9d9b2944824750.js +0 -1
  70. package/out/_next/static/chunks/349-7118116b93278f73.js +0 -4
  71. package/out/_next/static/chunks/50-586dc7ab5e584e76.js +0 -1
  72. package/out/_next/static/chunks/pages/changes-4c23263cdc8e59c6.js +0 -1
  73. package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]-11802744e89707f7.js +0 -1
  74. package/out/_next/static/chunks/pages/slices-f6552e2fbc491c4d.js +0 -1
  75. package/out/_next/static/css/cdee5b195444f185.css +0 -1
  76. package/src/features/environments/actions/setEnvironment.ts +0 -18
  77. package/src/legacy/components/ChangesEmptyState/AuthErrorPage.tsx +0 -44
  78. /package/out/_next/static/{AMILL72qfLjZDeK2IRMSB → U9WVTg1pSTJVt94SbRE5Y}/_ssgManifest.js +0 -0
@@ -9,13 +9,6 @@ import {
9
9
  import { useRouter } from "next/router";
10
10
  import { type FC } from "react";
11
11
 
12
- import {
13
- HoverCard,
14
- HoverCardCloseButton,
15
- HoverCardDescription,
16
- HoverCardMedia,
17
- HoverCardTitle,
18
- } from "@/components/HoverCard";
19
12
  import { useAutoSync } from "@/features/sync/AutoSyncProvider";
20
13
  import { AutoSyncStatusIndicator } from "@/features/sync/components/AutoSyncStatusIndicator";
21
14
  import { useUnSyncChanges } from "@/features/sync/useUnSyncChanges";
@@ -23,87 +16,59 @@ import { useAuthStatus } from "@/hooks/useAuthStatus";
23
16
  import { useNetwork } from "@/hooks/useNetwork";
24
17
  import { ChangesIcon } from "@/icons/ChangesIcon";
25
18
  import { AuthStatus } from "@/modules/userContext/types";
26
- import useSliceMachineActions from "@/modules/useSliceMachineActions";
27
19
 
28
20
  export const ChangesItem: FC = () => {
29
- const { setSeenChangesToolTip } = useSliceMachineActions();
30
- const open = useOpenChangesHoverCard();
31
21
  const router = useRouter();
32
22
  const { autoSyncStatus } = useAutoSync();
33
23
  const collapsed = useMediaQuery({ max: "medium" });
34
24
 
35
- const onClose = () => {
36
- setSeenChangesToolTip();
37
- };
38
-
39
25
  return (
40
- <HoverCard
41
- align="start"
42
- open={open}
43
- openDelay={3000}
44
- onClose={onClose}
45
- side="right"
46
- sideOffset={24}
47
- trigger={
48
- <Box
49
- alignItems={collapsed ? "center" : undefined}
50
- flexDirection="column"
51
- >
52
- {autoSyncStatus === "failed" ||
53
- autoSyncStatus === "synced" ||
54
- autoSyncStatus === "syncing" ? (
55
- <AutoSyncStatusIndicator autoSyncStatus={autoSyncStatus} />
56
- ) : collapsed ? (
57
- <Tooltip content="Review changes" side="right">
58
- <Box position="relative">
59
- {/*
60
- * TODO(DT-1942): This should be an IconButton with a link
61
- * component for accessibility
62
- */}
63
- <IconButton
64
- icon={<ChangesIcon />}
65
- onClick={() => {
66
- void router.push("/changes");
67
- }}
68
- variant="solid"
69
- />
70
- <Box
71
- left="100%"
72
- position="absolute"
73
- top={0}
74
- transform="translate(-50%, -50%)"
75
- >
76
- <ChangesCount color="purple" />
77
- </Box>
78
- </Box>
79
- </Tooltip>
80
- ) : (
81
- // TODO(DT-1942): This should be a Button with a link component for
82
- // accessibility
83
- <Button
84
- color="grey"
26
+ <Box alignItems={collapsed ? "center" : undefined} flexDirection="column">
27
+ {autoSyncStatus === "failed" ||
28
+ autoSyncStatus === "synced" ||
29
+ autoSyncStatus === "syncing" ? (
30
+ <AutoSyncStatusIndicator autoSyncStatus={autoSyncStatus} />
31
+ ) : collapsed ? (
32
+ <Tooltip content="Review changes" side="right">
33
+ <Box position="relative">
34
+ {/*
35
+ * TODO(DT-1942): This should be an IconButton with a link
36
+ * component for accessibility
37
+ */}
38
+ <IconButton
39
+ icon={<ChangesIcon />}
85
40
  onClick={() => {
86
41
  void router.push("/changes");
87
42
  }}
88
- sx={{ width: "100%" }}
43
+ variant="solid"
44
+ />
45
+ <Box
46
+ left="100%"
47
+ position="absolute"
48
+ top={0}
49
+ transform="translate(-50%, -50%)"
89
50
  >
90
- <Box alignItems="center" gap={4}>
91
- <Text variant="bold">Review changes</Text>{" "}
92
- <ChangesCount color="grey" />
93
- </Box>
94
- </Button>
95
- )}
96
- </Box>
97
- }
98
- >
99
- <HoverCardTitle>Push your changes</HoverCardTitle>
100
- <HoverCardMedia component="image" src="/push.png" />
101
- <HoverCardDescription>
102
- When you click Save, your changes are saved locally. Then, you can push
103
- your models to Prismic from the Changes page.
104
- </HoverCardDescription>
105
- <HoverCardCloseButton>Got it</HoverCardCloseButton>
106
- </HoverCard>
51
+ <ChangesCount color="purple" />
52
+ </Box>
53
+ </Box>
54
+ </Tooltip>
55
+ ) : (
56
+ // TODO(DT-1942): This should be a Button with a link component for
57
+ // accessibility
58
+ <Button
59
+ color="grey"
60
+ onClick={() => {
61
+ void router.push("/changes");
62
+ }}
63
+ sx={{ width: "100%" }}
64
+ >
65
+ <Box alignItems="center" gap={4}>
66
+ <Text variant="bold">Review changes</Text>{" "}
67
+ <ChangesCount color="grey" />
68
+ </Box>
69
+ </Button>
70
+ )}
71
+ </Box>
107
72
  );
108
73
  };
109
74
 
@@ -117,16 +82,12 @@ export const ChangesCount: FC<ChangesCountProps> = ({ color }) => {
117
82
 
118
83
  if (
119
84
  !isOnline ||
120
- authStatus === AuthStatus.UNAUTHORIZED ||
121
- authStatus === AuthStatus.FORBIDDEN
85
+ authStatus !== AuthStatus.AUTHENTICATED ||
86
+ numberOfChanges === 0
122
87
  ) {
123
88
  return null;
124
89
  }
125
90
 
126
- if (numberOfChanges === 0) {
127
- return null;
128
- }
129
-
130
91
  const formattedNumberOfChanges = numberOfChanges > 9 ? "+9" : numberOfChanges;
131
92
 
132
93
  return (
@@ -141,20 +102,3 @@ export const ChangesCount: FC<ChangesCountProps> = ({ color }) => {
141
102
  </Box>
142
103
  );
143
104
  };
144
-
145
- // TODO(DT-1925): Reactivate this feature
146
- const useOpenChangesHoverCard = () => {
147
- // const { hasSeenChangesToolTip, hasSeenSimulatorToolTip } = useSelector(
148
- // (store: SliceMachineStoreType) => ({
149
- // hasSeenChangesToolTip: userHasSeenChangesToolTip(store),
150
- // hasSeenSimulatorToolTip: userHasSeenSimulatorToolTip(store),
151
- // }),
152
- // );
153
-
154
- // return (
155
- // !hasSeenChangesToolTip &&
156
- // hasSeenSimulatorToolTip
157
- // );
158
-
159
- return false;
160
- };
@@ -5,7 +5,7 @@ import {
5
5
  import { useState } from "react";
6
6
 
7
7
  import { getState, telemetry } from "@/apiClient";
8
- import { setEnvironment } from "@/features/environments/actions/setEnvironment";
8
+ import { useSetEnvironment } from "@/features/environments/actions/useSetEnvironment";
9
9
  import { useActiveEnvironment } from "@/features/environments/useActiveEnvironment";
10
10
  import { useEnvironments } from "@/features/environments/useEnvironments";
11
11
  import { useAutoSync } from "@/features/sync/AutoSyncProvider";
@@ -28,6 +28,7 @@ export function Environment() {
28
28
  const authStatus = useAuthStatus();
29
29
  const [isSwitchingEnv, setIsSwitchingEnv] = useState(false);
30
30
  const { autoSyncStatus } = useAutoSync();
31
+ const setEnvironment = useSetEnvironment();
31
32
 
32
33
  async function onSelect(environment: EnvironmentType) {
33
34
  if (activeEnvironment?.name === environment.name) {
@@ -105,10 +106,4 @@ export function Environment() {
105
106
  />
106
107
  );
107
108
  }
108
-
109
- if (useEnvironmentsError !== undefined) {
110
- throw useEnvironmentsError;
111
- }
112
-
113
- throw activeEnvironmentError;
114
109
  }
@@ -17,9 +17,8 @@ import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
17
17
  import type { Environment } from "@slicemachine/manager/client";
18
18
  import { clsx } from "clsx";
19
19
  import type { FC, ReactNode } from "react";
20
- import { toast } from "react-toastify";
21
20
 
22
- import { LogoutButton } from "@/features/auth/LogoutButton";
21
+ import { EnvironmentLogoutButton } from "@/features/auth/LogoutButton";
23
22
  import { LoginIcon } from "@/icons/LoginIcon";
24
23
  import { LogoIcon } from "@/icons/LogoIcon";
25
24
 
@@ -30,7 +29,7 @@ type SideNavEnvironmentSelectorProps = {
30
29
  disabled?: boolean;
31
30
  environments?: Environment[];
32
31
  loading?: boolean;
33
- variant?: "default" | "offline" | "unauthorized" | "unauthenticated";
32
+ variant?: "default" | "offline" | "unauthenticated";
34
33
  onLogInClick?: () => void;
35
34
  onSelect?: (environment: Environment) => void | Promise<void>;
36
35
  };
@@ -141,11 +140,7 @@ export const SideNavEnvironmentSelector: FC<SideNavEnvironmentSelectorProps> = (
141
140
  />
142
141
  ) : undefined}
143
142
 
144
- {variant === "default" && (
145
- <LogoutButton
146
- onLogoutSuccess={() => toast.success("Logged out")}
147
- />
148
- )}
143
+ {variant === "default" && <EnvironmentLogoutButton />}
149
144
  </Box>
150
145
  </>
151
146
  )}
@@ -17,7 +17,7 @@ import { toast } from "react-toastify";
17
17
  import { BaseStyles, Box, Flex, Spinner } from "theme-ui";
18
18
 
19
19
  import { saveSliceMock, telemetry } from "@/apiClient";
20
- import { ErrorBoundary } from "@/ErrorBoundary";
20
+ import { DefaultErrorBoundary } from "@/features/errorBoundaries";
21
21
  import useThrottle from "@/hooks/useThrottle";
22
22
  import ScreenshotPreviewModal from "@/legacy/components/ScreenshotPreviewModal";
23
23
  import { ComponentUI } from "@/legacy/lib/models/common/ComponentUI";
@@ -287,7 +287,7 @@ const Simulator: FC<SimulatorProps> = ({ slice, variation }) => {
287
287
  overflowY: "auto",
288
288
  }}
289
289
  >
290
- <ErrorBoundary
290
+ <DefaultErrorBoundary
291
291
  renderError={() => (
292
292
  <DefaultErrorMessage
293
293
  title="Editor error"
@@ -315,7 +315,7 @@ const Simulator: FC<SimulatorProps> = ({ slice, variation }) => {
315
315
  />
316
316
  </QueryClientProvider>
317
317
  </Suspense>
318
- </ErrorBoundary>
318
+ </DefaultErrorBoundary>
319
319
  </Flex>
320
320
  ) : null}
321
321
  </Flex>
@@ -20,8 +20,8 @@ import {
20
20
  reorderField,
21
21
  updateField,
22
22
  } from "@/domain/customType";
23
- import { ErrorBoundary } from "@/ErrorBoundary";
24
23
  import { useCustomTypeState } from "@/features/customTypes/customTypesBuilder/CustomTypeProvider";
24
+ import { DefaultErrorBoundary } from "@/features/errorBoundaries";
25
25
  import {
26
26
  CustomTypes,
27
27
  type TabField,
@@ -246,7 +246,7 @@ const TabZone: FC<TabZoneProps> = ({ tabId }) => {
246
246
  };
247
247
 
248
248
  return (
249
- <ErrorBoundary>
249
+ <DefaultErrorBoundary>
250
250
  <Suspense
251
251
  fallback={
252
252
  <Box padding={32}>
@@ -297,7 +297,7 @@ const TabZone: FC<TabZoneProps> = ({ tabId }) => {
297
297
  />
298
298
  </List>
299
299
  </Suspense>
300
- </ErrorBoundary>
300
+ </DefaultErrorBoundary>
301
301
  );
302
302
  };
303
303
 
@@ -1,3 +1,4 @@
1
+ import { UnauthenticatedError } from "@slicemachine/manager/client";
1
2
  import { Reducer } from "redux";
2
3
  import { ActionType, createAction, getType } from "typesafe-actions";
3
4
 
@@ -90,13 +91,12 @@ const getAuthStatus = (
90
91
  ): AuthStatus => {
91
92
  switch (clientError?.status) {
92
93
  case undefined: {
93
- return AuthStatus.AUTHORIZED;
94
- }
95
- case 403: {
96
- return AuthStatus.UNAUTHORIZED;
94
+ return AuthStatus.AUTHENTICATED;
97
95
  }
98
96
  case 401: {
99
- return AuthStatus.FORBIDDEN;
97
+ if (clientError.name === new UnauthenticatedError().name) {
98
+ return AuthStatus.UNAUTHENTICATED;
99
+ }
100
100
  }
101
101
  default: {
102
102
  return AuthStatus.UNKNOWN;
@@ -1,7 +1,6 @@
1
1
  export enum AuthStatus {
2
- AUTHORIZED = "authorized",
3
- UNAUTHORIZED = "unauthorized",
4
- FORBIDDEN = "forbidden",
2
+ AUTHENTICATED = "authenticated",
3
+ UNAUTHENTICATED = "unauthenticated",
5
4
  UNKNOWN = "unknown",
6
5
  }
7
6
 
@@ -13,34 +13,33 @@ import "@/styles/starry-night.css";
13
13
  import "@/styles/tabs.css";
14
14
  import "@/styles/toaster.css";
15
15
 
16
+ import { ThemeProvider, TooltipProvider } from "@prismicio/editor-ui";
16
17
  import {
17
- Box,
18
- DefaultErrorMessage,
19
- ThemeProvider,
20
- TooltipProvider,
21
- } from "@prismicio/editor-ui";
22
- import { ConnectedRouter } from "connected-next-router";
18
+ isInvalidActiveEnvironmentError,
19
+ isUnauthorizedError,
20
+ } from "@slicemachine/manager/client";
21
+ import { useSuspenseQuery } from "@tanstack/react-query";
22
+ import { ConnectedRouter as StoreConnectedRouter } from "connected-next-router";
23
23
  import type { NextPage } from "next";
24
24
  import type { AppContext, AppInitialProps } from "next/app";
25
25
  import dynamic from "next/dynamic";
26
26
  import Head from "next/head";
27
27
  import Router from "next/router";
28
- import { type FC, type ReactNode, Suspense, useEffect, useState } from "react";
28
+ import { type FC, type ReactNode, Suspense, useEffect } from "react";
29
29
  import { Provider } from "react-redux";
30
- import type { Store } from "redux";
31
- import type { Persistor } from "redux-persist/es/types";
32
30
  import { PersistGate } from "redux-persist/integration/react";
33
31
  import { ThemeProvider as ThemeUIThemeProvider, useThemeUI } from "theme-ui";
34
32
 
35
33
  import { getState } from "@/apiClient";
36
- import { ErrorBoundary } from "@/ErrorBoundary";
34
+ import { useActiveEnvironment } from "@/features/environments/useActiveEnvironment";
35
+ import { AppStateErrorBoundary } from "@/features/errorBoundaries";
37
36
  import { AutoSyncProvider } from "@/features/sync/AutoSyncProvider";
38
37
  import { RouteChangeProvider } from "@/hooks/useRouteChange";
39
38
  import SliceMachineApp from "@/legacy/components/App";
40
39
  import LoadingPage from "@/legacy/components/LoadingPage";
41
40
  import ToastContainer from "@/legacy/components/ToasterContainer";
42
41
  import { normalizeFrontendCustomTypes } from "@/legacy/lib/models/common/normalizers/customType";
43
- import type ServerState from "@/legacy/lib/models/server/ServerState";
42
+ import { QueryClientProvider } from "@/queryClient";
44
43
  import configureStore from "@/redux/store";
45
44
  import theme from "@/theme";
46
45
 
@@ -68,50 +67,10 @@ const RemoveDarkMode: FC<RemoveDarkModeProps> = ({ children }) => {
68
67
  return <>{children}</>;
69
68
  };
70
69
 
71
- function App({
72
- Component,
73
- pageProps,
74
- }: AppContextWithComponentLayout & AppInitialProps) {
75
- const [serverState, setServerState] = useState<ServerState | null>(null);
76
- const [smStore, setSMStore] = useState<{
77
- store: Store;
78
- persistor: Persistor;
79
- } | null>(null);
70
+ function App(props: AppContextWithComponentLayout & AppInitialProps) {
71
+ const { Component } = props;
80
72
 
81
- useEffect(() => {
82
- async function getInitialState() {
83
- const serverState = await getState();
84
- setServerState(serverState);
85
- }
86
- void getInitialState();
87
- }, []);
88
-
89
- useEffect(() => {
90
- if (!serverState || smStore) {
91
- return;
92
- }
93
-
94
- const normalizedCustomTypes = normalizeFrontendCustomTypes(
95
- serverState.customTypes,
96
- serverState.remoteCustomTypes,
97
- );
98
-
99
- const { store, persistor } = configureStore({
100
- environment: serverState.env,
101
- availableCustomTypes: {
102
- ...normalizedCustomTypes,
103
- },
104
- slices: {
105
- libraries: serverState.libraries,
106
- remoteSlices: serverState.remoteSlices,
107
- },
108
- });
109
-
110
- setSMStore({ store, persistor });
111
- }, [serverState, smStore]);
112
-
113
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
114
- const ComponentLayout = Component.CustomLayout || SliceMachineApp;
73
+ const ComponentLayout = Component.CustomLayout ?? SliceMachineApp;
115
74
 
116
75
  return (
117
76
  <>
@@ -119,50 +78,89 @@ function App({
119
78
  <title>Slice Machine</title>
120
79
  </Head>
121
80
  <ThemeUIThemeProvider theme={theme}>
122
- <RemoveDarkMode>
123
- <ThemeProvider mode="light">
124
- <TooltipProvider>
125
- {!smStore || !serverState ? (
126
- <LoadingPage />
127
- ) : (
128
- <Provider store={smStore.store}>
129
- <ConnectedRouter Router={Router}>
130
- <PersistGate loading={null} persistor={smStore.persistor}>
131
- <ErrorBoundary
132
- renderError={() => (
133
- <Box
134
- justifyContent="center"
135
- width="100%"
136
- padding={80}
137
- >
138
- <DefaultErrorMessage
139
- title="Error"
140
- description="An error occurred while rendering the app."
141
- />
142
- </Box>
143
- )}
144
- >
145
- <Suspense fallback={<LoadingPage />}>
146
- <AutoSyncProvider>
147
- <RouteChangeProvider>
148
- <ComponentLayout>
149
- <Component {...pageProps} />
150
- </ComponentLayout>
151
- </RouteChangeProvider>
152
- </AutoSyncProvider>
153
- </Suspense>
154
- </ErrorBoundary>
155
- </PersistGate>
156
- </ConnectedRouter>
157
- <ToastContainer />
158
- </Provider>
159
- )}
160
- </TooltipProvider>
161
- </ThemeProvider>
162
- </RemoveDarkMode>
81
+ <QueryClientProvider>
82
+ <RemoveDarkMode>
83
+ <ThemeProvider mode="light">
84
+ <TooltipProvider>
85
+ <AppStateErrorBoundary>
86
+ <Suspense fallback={<LoadingPage />}>
87
+ <AppStateValidator>
88
+ <AppStateWrapper>
89
+ <AutoSyncProvider>
90
+ <ComponentLayout>
91
+ <Component {...props.pageProps} />
92
+ </ComponentLayout>
93
+ </AutoSyncProvider>
94
+ </AppStateWrapper>
95
+ </AppStateValidator>
96
+ </Suspense>
97
+ </AppStateErrorBoundary>
98
+ <ToastContainer />
99
+ </TooltipProvider>
100
+ </ThemeProvider>
101
+ </RemoveDarkMode>
102
+ </QueryClientProvider>
163
103
  </ThemeUIThemeProvider>
164
104
  </>
165
105
  );
166
106
  }
167
107
 
108
+ /** This is where we should check for unwanted states that should prevent the
109
+ * user from using the app, and trigger the {@link AppStateErrorBoundary} to
110
+ * display something explaining why. */
111
+ function AppStateValidator(props: { children: ReactNode }) {
112
+ const activeEnvironment = useActiveEnvironment({ suspense: true });
113
+
114
+ if (
115
+ // We're using the fetchEnvironments request to check this because it can
116
+ // return an SMUnauthorizedError or SMInvalidActiveEnvironmentError
117
+ // according to the API response, so we handle both cases with just one
118
+ // request. We also perform the request in other parts of the app, so it can
119
+ // reuse its cache.
120
+ isUnauthorizedError(activeEnvironment.error) ||
121
+ isInvalidActiveEnvironmentError(activeEnvironment.error)
122
+ ) {
123
+ throw activeEnvironment.error;
124
+ }
125
+
126
+ return <>{props.children}</>;
127
+ }
128
+
129
+ function AppStateWrapper({ children }: { children: ReactNode }) {
130
+ const { data: state } = useSuspenseQuery({
131
+ queryKey: ["getInitialState"],
132
+ queryFn: async () => {
133
+ const serverState = await getState();
134
+ const { store, persistor } = configureStore({
135
+ environment: serverState.env,
136
+ availableCustomTypes: {
137
+ ...normalizeFrontendCustomTypes(
138
+ serverState.customTypes,
139
+ serverState.remoteCustomTypes,
140
+ ),
141
+ },
142
+ slices: {
143
+ libraries: serverState.libraries,
144
+ remoteSlices: serverState.remoteSlices,
145
+ },
146
+ });
147
+
148
+ return { serverState, store, persistor };
149
+ },
150
+ // avoid refetching
151
+ staleTime: Infinity,
152
+ gcTime: Infinity,
153
+ });
154
+
155
+ return (
156
+ <Provider store={state.store}>
157
+ <StoreConnectedRouter Router={Router}>
158
+ <PersistGate loading={null} persistor={state.persistor}>
159
+ <RouteChangeProvider>{children}</RouteChangeProvider>
160
+ </PersistGate>
161
+ </StoreConnectedRouter>
162
+ </Provider>
163
+ );
164
+ }
165
+
168
166
  export default dynamic(() => Promise.resolve(App), { ssr: false });
@@ -25,8 +25,8 @@ import {
25
25
  AppLayoutHeader,
26
26
  } from "@/legacy/components/AppLayout";
27
27
  import {
28
- AuthErrorPage,
29
28
  OfflinePage,
29
+ UnauthenticatedView,
30
30
  } from "@/legacy/components/ChangesEmptyState";
31
31
  import { ChangesItems } from "@/legacy/components/ChangesItems";
32
32
  import {
@@ -114,8 +114,8 @@ const Changes: React.FunctionComponent = () => {
114
114
  if (!isOnline) {
115
115
  return <OfflinePage />;
116
116
  }
117
- if (authStatus === AuthStatus.FORBIDDEN) {
118
- return <AuthErrorPage authStatus={authStatus} />;
117
+ if (authStatus === AuthStatus.UNAUTHENTICATED) {
118
+ return <UnauthenticatedView />;
119
119
  }
120
120
  if (numberOfChanges === 0) {
121
121
  return (
@@ -160,8 +160,7 @@ const Changes: React.FunctionComponent = () => {
160
160
  disabled={
161
161
  numberOfChanges === 0 ||
162
162
  !isOnline ||
163
- authStatus === AuthStatus.UNAUTHORIZED ||
164
- authStatus === AuthStatus.FORBIDDEN ||
163
+ authStatus !== AuthStatus.AUTHENTICATED ||
165
164
  isSyncing
166
165
  }
167
166
  loading={isSyncing}
@@ -0,0 +1,24 @@
1
+ import {
2
+ QueryClient,
3
+ QueryClientProvider as RCQueryClientProvider,
4
+ } from "@tanstack/react-query";
5
+ import { ReactNode } from "react";
6
+
7
+ export const queryClient = new QueryClient({
8
+ defaultOptions: {
9
+ queries: {
10
+ staleTime: 5 * 60 * 1000,
11
+ gcTime: 10 * 60 * 1000,
12
+ retry: false,
13
+ refetchOnWindowFocus: "always",
14
+ },
15
+ },
16
+ });
17
+
18
+ export function QueryClientProvider(props: { children: ReactNode }) {
19
+ return (
20
+ <RCQueryClientProvider client={queryClient}>
21
+ {props.children}
22
+ </RCQueryClientProvider>
23
+ );
24
+ }
@@ -5,7 +5,8 @@ import { Provider } from "react-redux";
5
5
  import { AnyAction, Store } from "redux";
6
6
  import { BaseStyles, ThemeProvider as ThemeUIThemeProvider } from "theme-ui";
7
7
 
8
- import { ErrorBoundary } from "@/ErrorBoundary";
8
+ import { DefaultErrorBoundary } from "@/features/errorBoundaries";
9
+ import { QueryClientProvider } from "@/queryClient";
9
10
 
10
11
  import configureStore from "../../src/redux/store";
11
12
  import type { SliceMachineStoreType } from "../../src/redux/type";
@@ -44,15 +45,17 @@ function render(
44
45
  children: any;
45
46
  }) {
46
47
  return (
47
- <ThemeUIThemeProvider theme={theme}>
48
- <TooltipProvider>
49
- <BaseStyles>
50
- <Provider store={store}>
51
- <ErrorBoundary>{children}</ErrorBoundary>
52
- </Provider>
53
- </BaseStyles>
54
- </TooltipProvider>
55
- </ThemeUIThemeProvider>
48
+ <QueryClientProvider>
49
+ <ThemeUIThemeProvider theme={theme}>
50
+ <TooltipProvider>
51
+ <BaseStyles>
52
+ <Provider store={store}>
53
+ <DefaultErrorBoundary>{children}</DefaultErrorBoundary>
54
+ </Provider>
55
+ </BaseStyles>
56
+ </TooltipProvider>
57
+ </ThemeUIThemeProvider>
58
+ </QueryClientProvider>
56
59
  );
57
60
  }
58
61
  return {
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST=function(s,c,a,e,t,i,d,b,n,u,h,l,f,k,j,p,g,o,r,m){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,e,p,g,"static/chunks/pages/index-0d8cb369de720a35.js"],"/_error":["static/chunks/pages/_error-fedd2c6ebd3d27b9.js"],"/changelog":[c,t,"static/chunks/870-a72b74312773efea.js","static/chunks/pages/changelog-ba55ed247c20dc21.js"],"/changes":[c,i,"static/chunks/8eec4907-b712959d9f984b68.js","static/chunks/918-fa4f2563cb5fd014.js",a,b,"static/css/d98ebc475f8423a3.css","static/chunks/pages/changes-4c23263cdc8e59c6.js"],"/custom-types":[s,e,p,g,"static/chunks/pages/custom-types-5acd56959b60346f.js"],"/custom-types/[customTypeId]":[c,n,u,h,l,s,t,d,f,o,a,e,k,j,r,m,"static/chunks/pages/custom-types/[customTypeId]-816acb31b652239b.js"],"/labs":["static/chunks/pages/labs-78ac01d97ab02cd7.js"],"/page-types/[pageTypeId]":[c,n,u,h,l,s,t,d,f,o,a,e,k,j,r,m,"static/chunks/pages/page-types/[pageTypeId]-669d5479e81b638b.js"],"/slices":[c,i,s,d,a,b,j,"static/css/efa5152b7c0f35c0.css","static/chunks/pages/slices-f6552e2fbc491c4d.js"],"/slices/[lib]/[sliceName]/[variation]":[c,i,n,u,h,l,s,t,d,f,a,k,b,"static/css/cdee5b195444f185.css","static/chunks/pages/slices/[lib]/[sliceName]/[variation]-11802744e89707f7.js"],"/slices/[lib]/[sliceName]/[variation]/simulator":[i,"static/chunks/72585f70-28b4d7d5384b3703.js","static/chunks/157-54b8336d20b41933.js","static/chunks/pages/slices/[lib]/[sliceName]/[variation]/simulator-b127d948a17968d3.js"],sortedPages:["/","/_app","/_error","/changelog","/changes","/custom-types","/custom-types/[customTypeId]","/labs","/page-types/[pageTypeId]","/slices","/slices/[lib]/[sliceName]/[variation]","/slices/[lib]/[sliceName]/[variation]/simulator"]}}("static/chunks/397-e6c340070a3bcb41.js","static/chunks/59b4e022-ef680789f7cc9b11.js","static/chunks/34-8d9d9b2944824750.js","static/chunks/647-4379a741c8d85d9e.js","static/chunks/183-4ea255b867ff171b.js","static/chunks/c8eae200-966ce352f7b5d2b9.js","static/chunks/50-586dc7ab5e584e76.js","static/chunks/658-8231c0b729e0124a.js","static/chunks/f36c6662-1f3a854183168b10.js","static/chunks/4c744e84-480e426e4b1cfef3.js","static/chunks/065a3ddb-9a38ca0d60f0bf2f.js","static/chunks/1cc2734a-09fb3b997ad1eb70.js","static/chunks/349-7118116b93278f73.js","static/chunks/630-bb6e3db525588f16.js","static/chunks/907-445266211c48b1a5.js","static/chunks/248-43faecb386a16518.js","static/css/4e475d945cf8a890.css","static/chunks/422-c9192a1dbdd2ae0e.js","static/chunks/489-234ed5471aa694b3.js","static/css/56f2a6684a524374.css"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();