Skip to content

Commit 1cb7ffd

Browse files
CiaranMnCopilot
andauthored
H-6625: Improve loading, org invites, action bell routing (#8917)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent b8971f3 commit 1cb7ffd

15 files changed

Lines changed: 269 additions & 91 deletions

File tree

apps/hash-api/src/graphql/resolvers/index.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ export const resolvers: Omit<Resolvers, "Query" | "Mutation"> & {
143143
/** Logged in users (who may not have completed signup) */
144144
me: loggedInMiddleware(meResolver),
145145
getWaitlistPosition: loggedInMiddleware(getWaitlistPositionResolver),
146+
getMyPendingInvitations: loggedInMiddleware(
147+
getMyPendingInvitationsResolver,
148+
),
146149

147150
/** Logged in and signed up users */
148151
getBlockProtocolBlocks: loggedInAndSignedUpMiddleware(
@@ -166,10 +169,6 @@ export const resolvers: Omit<Resolvers, "Query" | "Mutation"> & {
166169
queryEntitySubgraph: loggedInAndSignedUpMiddleware(
167170
queryEntitySubgraphResolver,
168171
),
169-
getMyPendingInvitations: loggedInAndSignedUpMiddleware(
170-
getMyPendingInvitationsResolver,
171-
),
172-
173172
getLinearOrganization: loggedInAndSignedUpMiddleware(
174173
getLinearOrganizationResolver,
175174
),

apps/hash-frontend/src/pages/_app.page.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,7 @@ const featureFlagHiddenPathnames: Record<FeatureFlag, string[]> = {
333333
notes: ["/notes"],
334334
workers: ["/goals", "/flows", "/workers", "/agents"],
335335
ai: ["/goals"],
336-
supplyChain: [
337-
"/supply-chain",
338-
"/supply-chain/product/[product-id]",
339-
"/supply-chain/site/[site-id]",
340-
"/supply-chain/site/[site-id]/opportunity/[opportunity-type]/[product-id]/[step-id]",
341-
],
336+
supplyChain: [],
342337
};
343338

344339
AppWithTypeSystemContextProvider.getInitialProps = async (appContext) => {

apps/hash-frontend/src/pages/shared/entities-visualizer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ export const EntitiesVisualizer: FunctionComponent<{
649649
currentlyDisplayedColumnsRef={currentlyDisplayedColumnsRef}
650650
currentlyDisplayedRowsRef={currentlyDisplayedRowsRef}
651651
handleEntityClick={handleEntityClick}
652+
hasMoreRowsAvailable={nextCursor != null}
652653
loading={dataLoading}
653654
isViewingOnlyPages={isViewingOnlyPages}
654655
maxHeight={`calc(${tableHeight} - ${toolbarHeight}px)`}

apps/hash-frontend/src/pages/shared/entities-visualizer/entities-table.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export const EntitiesTable: FunctionComponent<
106106
loading: boolean;
107107
isViewingOnlyPages: boolean;
108108
maxHeight: string | number;
109+
hasMoreRowsAvailable: boolean;
109110
loadMoreRows?: () => void;
110111
selectedRows: EntitiesTableRow[];
111112
setActiveConversions: Dispatch<
@@ -136,6 +137,7 @@ export const EntitiesTable: FunctionComponent<
136137
loading: entityDataLoading,
137138
isViewingOnlyPages,
138139
maxHeight,
140+
hasMoreRowsAvailable,
139141
loadMoreRows,
140142
selectedRows,
141143
setActiveConversions,
@@ -810,8 +812,6 @@ export const EntitiesTable: FunctionComponent<
810812
});
811813
}, [rows.length]);
812814

813-
const hasMoreRowsAvailable =
814-
!!totalResultCount && totalResultCount > rows.length;
815815
const loadMoreRowHeight = 60;
816816

817817
return (
@@ -906,7 +906,9 @@ export const EntitiesTable: FunctionComponent<
906906
component="span"
907907
sx={{ color: ({ palette }) => palette.gray[50], ml: 0.5 }}
908908
>
909-
- {formatNumber(totalResultCount - rows.length)} remaining
909+
{totalResultCount != null
910+
? `- ${formatNumber(totalResultCount - rows.length)} remaining`
911+
: ""}
910912
</Box>
911913
<ArrowDownRegularIcon
912914
sx={{

apps/hash-frontend/src/pages/signup.page.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ import { AlertModal, ArrowUpRightRegularIcon } from "@hashintel/design-system";
88
import { useUpdateAuthenticatedUser } from "../components/hooks/use-update-authenticated-user";
99
import {
1010
acceptOrgInvitationMutation,
11+
getMyPendingInvitationsQuery,
1112
getPendingInvitationByEntityIdQuery,
1213
} from "../graphql/queries/knowledge/org.queries";
1314
import { hasAccessToHashQuery } from "../graphql/queries/user.queries";
15+
import { useInvites } from "../shared/invites-context";
1416
import { getPlainLayout } from "../shared/layout";
1517
import { Button } from "../shared/ui";
1618
import { useAuthInfo } from "./shared/auth-info-context";
1719
import { AuthLayout } from "./shared/auth-layout";
1820
import { parseGraphQLError } from "./shared/auth-utils";
1921
import { VerifyEmailStep } from "./shared/verify-email-step";
22+
import { useActiveWorkspace } from "./shared/workspace-context";
2023
import { AcceptOrgInvitation } from "./signup.page/accept-org-invitation";
2124
import { AccountSetupForm } from "./signup.page/account-setup-form";
2225
import { SignupRegistrationForm } from "./signup.page/signup-registration-form";
@@ -26,6 +29,8 @@ import { SignupSteps } from "./signup.page/signup-steps";
2629
import type {
2730
AcceptOrgInvitationMutation,
2831
AcceptOrgInvitationMutationVariables,
32+
GetMyPendingInvitationsQuery,
33+
GetMyPendingInvitationsQueryVariables,
2934
GetPendingInvitationByEntityIdQuery,
3035
GetPendingInvitationByEntityIdQueryVariables,
3136
HasAccessToHashQuery,
@@ -95,6 +100,8 @@ const SignupPage: NextPageWithLayout = () => {
95100

96101
const { authenticatedUser, refetch: refetchAuthenticatedUser } =
97102
useAuthInfo();
103+
const { refetch: refetchInvites } = useInvites();
104+
const { updateActiveWorkspaceWebId } = useActiveWorkspace();
98105

99106
const userHasVerifiedEmail =
100107
authenticatedUser?.emails.find(({ verified }) => verified) !== undefined;
@@ -139,12 +146,24 @@ const SignupPage: NextPageWithLayout = () => {
139146
skip: !invitationId,
140147
});
141148

149+
const { data: pendingInvitationsData, loading: pendingInvitationsLoading } =
150+
useQuery<
151+
GetMyPendingInvitationsQuery,
152+
GetMyPendingInvitationsQueryVariables
153+
>(getMyPendingInvitationsQuery, {
154+
skip: !!invitationId || !authenticatedUser || !userHasVerifiedEmail,
155+
});
156+
142157
const [acceptInvitation] = useMutation<
143158
AcceptOrgInvitationMutation,
144159
AcceptOrgInvitationMutationVariables
145160
>(acceptOrgInvitationMutation);
146161

147-
const invitation = invitationData?.getPendingInvitationByEntityId;
162+
const invitation =
163+
invitationData?.getPendingInvitationByEntityId ??
164+
pendingInvitationsData?.getMyPendingInvitations[0];
165+
166+
const loadingInvitation = invitationLoading || pendingInvitationsLoading;
148167

149168
const [acceptingInvitation, setAcceptingInvitation] = useState(false);
150169
const acceptingInvitationEntityIdRef = useRef<EntityId | undefined>(
@@ -220,7 +239,9 @@ const SignupPage: NextPageWithLayout = () => {
220239
})
221240
.then(async (result) => {
222241
if (result?.accepted || result?.alreadyAMember) {
242+
refetchInvites();
223243
await refetchAuthenticatedUser();
244+
updateActiveWorkspaceWebId(invitation.org.webId);
224245
void router.replace("/");
225246
return;
226247
}
@@ -245,15 +266,17 @@ const SignupPage: NextPageWithLayout = () => {
245266
clearInvitationAcceptanceReservation,
246267
invitation,
247268
refetchAuthenticatedUser,
269+
refetchInvites,
248270
router,
271+
updateActiveWorkspaceWebId,
249272
]);
250273

251274
useEffect(() => {
252275
if (
253276
router.isReady &&
254277
authenticatedUser?.accountSignupComplete &&
255278
invitationId &&
256-
!invitationLoading &&
279+
!loadingInvitation &&
257280
!invitation &&
258281
!acceptingInvitation
259282
) {
@@ -264,7 +287,7 @@ const SignupPage: NextPageWithLayout = () => {
264287
authenticatedUser?.accountSignupComplete,
265288
invitation,
266289
invitationId,
267-
invitationLoading,
290+
loadingInvitation,
268291
router,
269292
]);
270293

@@ -340,15 +363,21 @@ const SignupPage: NextPageWithLayout = () => {
340363
}
341364

342365
await refetchAuthenticatedUser();
366+
refetchInvites();
367+
if (invitation) {
368+
updateActiveWorkspaceWebId(invitation.org.webId);
369+
}
343370

344371
void router.push("/");
345372
},
346373
[
347374
acceptInvitationOnce,
348375
clearInvitationAcceptanceReservation,
349376
invitation,
377+
refetchInvites,
350378
refetchAuthenticatedUser,
351379
updateAuthenticatedUser,
380+
updateActiveWorkspaceWebId,
352381
router,
353382
],
354383
);
@@ -395,7 +424,9 @@ const SignupPage: NextPageWithLayout = () => {
395424
>
396425
<Grid container columnSpacing={16}>
397426
<Grid item xs={12} md={7}>
398-
{invitationLoading ? null : invitation && showInvitationStep ? (
427+
{loadingInvitation ? null : invitation &&
428+
showInvitationStep &&
429+
!authenticatedUser ? (
399430
<AcceptOrgInvitation
400431
invitation={invitation}
401432
onAccept={() => setShowInvitationStep(false)}

apps/hash-frontend/src/pages/supply-chain/product/[product-id].page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from "react";
44
import { css } from "@hashintel/ds-helpers/css";
55

66
import { fetchGraph } from "../shared/data";
7-
import { ErrorState, LoadingState } from "../shared/load-state";
7+
import { ErrorState, SupplyChainAppSkeleton } from "../shared/load-state";
88
import { getSupplyChainLayout } from "../shared/supply-chain-layout";
99
import {
1010
trackSupplyChainError,
@@ -16,7 +16,6 @@ import { Overview } from "../supply-chain-data-shell/product";
1616
import type { NextPageWithLayout } from "../../../shared/layout";
1717
import type { GraphData } from "../shared/types";
1818

19-
const loadingH = css({ h: "64" });
2019
const errorPad = css({ px: "6", py: "4" });
2120

2221
const ProductPage: NextPageWithLayout = () => {
@@ -79,9 +78,7 @@ const ProductPage: NextPageWithLayout = () => {
7978
}, [productId]);
8079

8180
if (loading) {
82-
return (
83-
<LoadingState message="Loading product data..." className={loadingH} />
84-
);
81+
return <SupplyChainAppSkeleton />;
8582
}
8683
if (error) {
8784
return <ErrorState message={error} className={errorPad} />;

apps/hash-frontend/src/pages/supply-chain/shared/load-state.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,43 @@ const loadingRow = css({
1414
});
1515
const loadingText = css({ textStyle: "sm", color: "fg.subtle" });
1616
const errorText = css({ textStyle: "sm", color: "status.error.fg.body" });
17+
const appSkeletonRoot = css({
18+
display: "flex",
19+
flexDirection: "column",
20+
flex: "1",
21+
minH: "0",
22+
h: "full",
23+
w: "full",
24+
bg: "bgSolid.min",
25+
});
26+
const appSkeletonTopBar = css({
27+
display: "flex",
28+
alignItems: "center",
29+
px: "6",
30+
py: "3",
31+
borderBottomWidth: "1px",
32+
borderColor: "bd.subtle",
33+
flexShrink: 0,
34+
});
35+
const appSkeletonSitePicker = css({
36+
h: "10",
37+
w: "64",
38+
maxW: "[min(260px,45vw)]",
39+
rounded: "md",
40+
bg: "bg.subtle",
41+
});
42+
const appSkeletonContent = css({
43+
flex: "1",
44+
minH: "0",
45+
p: "6",
46+
display: "flex",
47+
});
48+
const appSkeletonContentBlock = css({
49+
flex: "1",
50+
minH: "[280px]",
51+
rounded: "lg",
52+
bg: "bg.subtle",
53+
});
1754

1855
/** Centered, muted "loading…" message with a ds spinner. Size the area via `className` (e.g. `h-32`). */
1956
export const LoadingState = ({
@@ -33,6 +70,26 @@ export const LoadingState = ({
3370
);
3471
};
3572

73+
/** Supply-chain route loading skeleton matching the page chrome. */
74+
export const SupplyChainAppSkeleton = ({
75+
className,
76+
}: {
77+
className?: string;
78+
}) => (
79+
<div
80+
aria-label="Loading"
81+
className={cx(appSkeletonRoot, className)}
82+
role="status"
83+
>
84+
<div className={appSkeletonTopBar}>
85+
<div className={appSkeletonSitePicker} />
86+
</div>
87+
<div className={appSkeletonContent}>
88+
<div className={appSkeletonContentBlock} />
89+
</div>
90+
</div>
91+
);
92+
3693
/** Inline error message. Pad/position via `className` (e.g. `px-6 py-4`). */
3794
export const ErrorState = ({
3895
message,

apps/hash-frontend/src/pages/supply-chain/shared/supply-chain-layout.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { useRef } from "react";
22

33
import { PortalContainerContext } from "@hashintel/ds-components";
4-
import { css } from "@hashintel/ds-helpers/css";
54

65
import { getLayoutWithSidebar } from "../../../shared/layout";
76
import { HEADER_HEIGHT } from "../../../shared/layout/layout-with-header/page-header";
87
import { useActiveWorkspace } from "../../shared/workspace-context";
98
import { SupplyChainDataShell } from "../supply-chain-data-shell";
10-
import { LoadingState } from "./load-state";
9+
import { SupplyChainAppSkeleton } from "./load-state";
1110

1211
import type { ReactElement, ReactNode } from "react";
1312

14-
const emptyState = css({
15-
h: "full",
16-
display: "flex",
17-
alignItems: "center",
18-
justifyContent: "center",
19-
color: "fg.subtle",
20-
textStyle: "sm",
21-
});
22-
2313
/**
2414
* Layout shared by every `/supply-chain/*` page.
2515
*
@@ -46,7 +36,7 @@ const SupplyChainShell = ({ children }: { children: ReactNode }) => {
4636
}}
4737
>
4838
{activeWorkspaceWebId === undefined ? (
49-
<LoadingState className={emptyState} message="Loading workspace..." />
39+
<SupplyChainAppSkeleton />
5040
) : (
5141
<SupplyChainDataShell
5242
key={activeWorkspaceWebId}

apps/hash-frontend/src/pages/supply-chain/site/[site-id]/opportunity/[opportunity-type]/[product-id]/[step-id].page.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { useRouter } from "next/router";
22
import { useEffect } from "react";
33

4-
import { LoadingState, ErrorState } from "../../../../../shared/load-state";
4+
import {
5+
ErrorState,
6+
SupplyChainAppSkeleton,
7+
} from "../../../../../shared/load-state";
58
import { useRegistry } from "../../../../../shared/registry-context";
69
import { getSupplyChainLayout } from "../../../../../shared/supply-chain-layout";
710
import {
@@ -55,7 +58,7 @@ const OpportunityPage: NextPageWithLayout = () => {
5558
}, [opportunityType, productId, siteId, stepId]);
5659

5760
if (!router.isReady) {
58-
return <LoadingState message="Loading opportunity brief..." />;
61+
return <SupplyChainAppSkeleton />;
5962
}
6063

6164
if (!siteId || !productId || !stepId) {

0 commit comments

Comments
 (0)