Skip to content

Commit 9a8517f

Browse files
authored
Pre release fixes (#6821)
* Drop changeset skill * Drop the last-row border on in-card assignable lists Pagination already draws the footer separator; the extra line was the last table row stacking a second border on top of it. * Restore dashed empty placeholders on product availability * Keep Home tab apps mounted so switching Pulse and Onboarding does not reboot them * Open invite staff in place instead of navigating to the staff list * Keep Home iframes mounted when /home redirects to the leftmost tab Sidebar Home always goes to /home; returning Redirect alone unmounted keep-alive panels and rebooted Pulse and Onboarding.
1 parent bd32cb2 commit 9a8517f

31 files changed

Lines changed: 940 additions & 431 deletions

File tree

.changeset/home-tab-keep-alive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"saleor-dashboard": patch
3+
---
4+
5+
Switching Home tabs (Pulse, Onboarding, Widgets) no longer reloads the embedded app. The last tab you opened stays ready in the background.

.claude/skills/saleor-dashboard-changesets/SKILL.md

Lines changed: 0 additions & 22 deletions
This file was deleted.

.claude/skills/saleor-dashboard-entity-detail/SKILL.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -266,17 +266,17 @@ Path: `src/components/AssignableListTable/`. Canonical references: collection pr
266266
</AssignableListCard>
267267
```
268268

269-
| Piece | Contract (owned by the primitive) |
270-
| --------------- | ---------------------------------------------------------------------------------------------------------------------------- |
271-
| Card header | `DetailSettingsCard` tinted band; title `size={5}` bold; `headerEnd` coerced to `small`; with-action right inset `spacing-4` |
272-
| Search band | Y `spacing-3`, X `spacing-6`, bottom border |
273-
| Table heading | 40px; `Text size={2}`; selected count + bulk action **in the heading**, not the card header |
274-
| Drag column | 40px, grip centered, first-cell padding 0 |
275-
| Checkbox column | 20px control + leading inset (`card` = 6, `nested` = 4) |
276-
| Body rows | `compact` (text) or `media` (50px thumbnails); cell padding `spacing-2` |
277-
| Row delete | Hover or delete-control `:focus-within` (not the row — checkbox focus would pin the icon); always visible on `hover: none` |
278-
| Pagination | Top border; Y `spacing-2`; small select + chevrons; `inset` aligns to first content column |
279-
| Empty | `padding={4}` + `Placeholder` |
269+
| Piece | Contract (owned by the primitive) |
270+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
271+
| Card header | `DetailSettingsCard` tinted band; title `size={5}` bold; `headerEnd` coerced to `small`; with-action right inset `spacing-4` |
272+
| Search band | Y `spacing-3`, X `spacing-6`, bottom border |
273+
| Table heading | 40px; `Text size={2}`; selected count + bulk action **in the heading**, not the card header |
274+
| Drag column | 40px, grip centered, first-cell padding 0 |
275+
| Checkbox column | 20px control + leading inset (`card` = 6, `nested` = 4) |
276+
| Body rows | `compact` (text) or `media` (50px thumbnails); cell padding `spacing-2` |
277+
| Row delete | Hover or delete-control `:focus-within` (not the row — checkbox focus would pin the icon); always visible on `hover: none` |
278+
| Pagination | Top border (the only separator above the footer); last table row has no bottom border; Y `spacing-2`; small select + chevrons; `inset` aligns to first content column |
279+
| Empty | `padding={4}` + `Placeholder` |
280280

281281
Tokens live in `assignableListTableLayout.ts`. MUI tables that still need dnd-kit (`SortableTable`) use `tableStyles.assignableTable` so heading height and density match GridTable.
282282

.claude/skills/saleor-dashboard-styles/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ Flush lists inside `DetailSettingsCard` (products in a collection, attribute val
354354
| Heading row | 40px, `Text size={2}` |
355355
| Rows | `density="compact"` (text) or `"media"` (50px thumbnails) |
356356
| Hover | Row delete opacity 0 → 1 on hover or delete-control focus (not row `:focus-within`) |
357-
| Pagination | Component draws the top border. `inset="card" \| "nested" \| "drag"` |
357+
| Pagination | Component draws the top border. Last table row has no bottom border. `inset="card" \| "nested" \| "drag"` |
358358

359359
Layout numbers: `src/components/AssignableListTable/assignableListTableLayout.ts`. Full composition and anti-patterns: [`saleor-dashboard-entity-detail`](./saleor-dashboard-entity-detail/SKILL.md) → In-card assignable lists.
360360

src/collections/components/CollectionProducts/ProductsTable.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@
3030
.headerRow + tr > td {
3131
border-top-width: 0 !important;
3232
}
33+
34+
/* Pagination (or the card edge) owns the separator — last row must not add a second line. */
35+
.table tr:last-child > td {
36+
border-bottom-width: 0 !important;
37+
}

src/components/AssignableListTable/AssignableListTable.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
border-top-width: 0 !important;
4646
}
4747

48+
/* Pagination (or the card edge) owns the separator — last row must not add a second line. */
49+
.table tr:last-child > td {
50+
border-bottom-width: 0 !important;
51+
}
52+
4853
.truncateCell {
4954
overflow: hidden;
5055
max-width: 0;

src/components/ChannelAvailability/ChannelAvailabilityCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { iconSize, iconStrokeWidth } from "@dashboard/components/icons";
2+
import { Placeholder } from "@dashboard/components/Placeholder";
23
import RequirePermissions from "@dashboard/components/RequirePermissions";
34
import { Skeleton } from "@dashboard/components/Skeleton/Skeleton";
45
import { type PermissionEnum } from "@dashboard/graphql";
@@ -185,9 +186,9 @@ export function ChannelAvailabilityCard<T extends ChannelAvailabilitySummary>({
185186

186187
{filteredChannels.length === 0 ? (
187188
<Box className={styles.body}>
188-
<Text size={2} color="default2">
189+
<Placeholder>
189190
{intl.formatMessage(channelAvailabilityMessages.noChannelsMatchSearch)}
190-
</Text>
191+
</Placeholder>
191192
</Box>
192193
) : (
193194
<>

src/components/NavigatorSearch/useActionTriggers.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { orderListUrl } from "@dashboard/orders/urls";
1212
import { productListUrl } from "@dashboard/products/urls";
1313
import { productTypeAddUrl, productTypeListUrl } from "@dashboard/productTypes/urls";
1414
import { shippingZoneAddUrl, shippingZonesListUrl } from "@dashboard/shipping/urls";
15+
import { useStaffInviteDialog } from "@dashboard/staff/components/StaffInviteProvider/StaffInviteProvider";
1516
import { staffListUrl } from "@dashboard/staff/urls";
1617
import { warehouseAddUrl, warehouseListUrl } from "@dashboard/warehouses/urls";
1718
import { Box, Text } from "@saleor/macaw-ui-next";
@@ -40,6 +41,26 @@ const ActionLinkItem = ({ href, children }: { href: string; children: React.Reac
4041
);
4142
};
4243

44+
const ActionButtonItem = ({ children }: { children: React.ReactNode }) => {
45+
return (
46+
<Box className="command-menu-item" cursor="pointer" role="option" tabIndex={-1}>
47+
<Box
48+
className="command-menu-item-content"
49+
display="flex"
50+
alignItems="center"
51+
color="default1"
52+
gap={2}
53+
paddingX={6}
54+
paddingY={1.5}
55+
>
56+
<Text size={2} fontWeight="medium" color="default1">
57+
{children}
58+
</Text>
59+
</Box>
60+
</Box>
61+
);
62+
};
63+
4364
interface TriggerDescriptor {
4465
section: MessageDescriptor;
4566
name: MessageDescriptor;
@@ -115,6 +136,27 @@ const allMessages = defineMessages({
115136
},
116137
});
117138

139+
const InviteStaffNavigatorItem = ({
140+
onClick,
141+
}: {
142+
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
143+
}) => {
144+
const { openInvite } = useStaffInviteDialog();
145+
146+
return (
147+
<Box
148+
onClick={event => {
149+
onClick?.(event as React.MouseEvent<HTMLAnchorElement>);
150+
openInvite();
151+
}}
152+
>
153+
<ActionButtonItem>
154+
<FormattedMessage {...allMessages.inviteUser} />
155+
</ActionButtonItem>
156+
</Box>
157+
);
158+
};
159+
118160
const allActions: TriggerDescriptor[] = [
119161
{
120162
section: {
@@ -532,13 +574,7 @@ const allActions: TriggerDescriptor[] = [
532574
defaultMessage: "Configuration",
533575
},
534576
name: allMessages.inviteUser,
535-
Component: ({ onClick }) => (
536-
<Box onClick={onClick}>
537-
<ActionLinkItem href={staffListUrl({ action: "add" })}>
538-
<FormattedMessage {...allMessages.inviteUser} />
539-
</ActionLinkItem>
540-
</Box>
541-
),
577+
Component: InviteStaffNavigatorItem,
542578
},
543579
{
544580
section: allMessages.discountsSection,

src/extensions/components/AppWidgets/AppWidgetExtensionItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export const AppWidgetExtensionItem = ({
103103
extensionId={extension.id}
104104
extensionUrl={extensionUrl}
105105
params={params}
106+
refetch={extension.refetch}
106107
/>
107108
) : (
108109
<AppFrame

src/extensions/components/IframePost/IframePost.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import {
55
WIDGET_DEFAULT_HEIGHT,
66
} from "@dashboard/extensions/hooks/useWidgetIframeAutoHeight";
77
import { type AppDetailsUrlMountQueryParams } from "@dashboard/extensions/urls";
8+
import { usePostToExtension } from "@dashboard/extensions/views/ViewManifestExtension/components/AppFrame/usePostToExtension";
9+
import { useTokenRefresh } from "@dashboard/extensions/views/ViewManifestExtension/components/AppFrame/useTokenRefresh";
10+
import { useUpdateAppToken } from "@dashboard/extensions/views/ViewManifestExtension/components/AppFrame/useUpdateAppToken";
811
import { useNodeRef } from "@dashboard/hooks/useNodeRef";
912
import { Box, Skeleton } from "@saleor/macaw-ui-next";
10-
import { type CSSProperties, useEffect, useRef } from "react";
13+
import { type CSSProperties, useEffect, useRef, useState } from "react";
1114

1215
const hiddenStyle: CSSProperties = { visibility: "hidden" };
1316

@@ -25,6 +28,11 @@ interface IframePostProps {
2528
*/
2629
autoHeight?: boolean;
2730
loaderType?: "skeleton" | "throbber";
31+
/**
32+
* Refetches the extension list so a long-lived iframe can receive a fresh JWT
33+
* via App Bridge `tokenRefresh` instead of being remounted.
34+
*/
35+
refetch?: () => void;
2836
}
2937

3038
/**
@@ -40,37 +48,50 @@ export const IframePost = ({
4048
height = WIDGET_DEFAULT_HEIGHT,
4149
autoHeight = false,
4250
loaderType = "skeleton",
51+
refetch,
4352
}: IframePostProps) => {
4453
const formRef = useRef<HTMLFormElement | null>(null);
4554
const loadingRef = useRef<HTMLDivElement | null>(null);
4655
const { ref: iframeRef, node: iframeEl, setRef: setIframeRef } = useNodeRef<HTMLIFrameElement>();
4756
const appOrigin = new URL(extensionUrl).origin;
57+
const [iframeReady, setIframeReady] = useState(false);
58+
const postToExtension = usePostToExtension(iframeEl, appOrigin);
4859

4960
useWidgetIframeAutoHeight(iframeEl, autoHeight, { appOrigin });
61+
useTokenRefresh(accessToken, refetch);
62+
useUpdateAppToken({
63+
enabled: iframeReady,
64+
appToken: accessToken,
65+
postToExtension,
66+
});
5067

51-
useEffect(() => {
52-
if (formRef.current) {
53-
formRef.current.submit();
54-
}
68+
useEffect(
69+
function submitPostFormAndRevealIframe() {
70+
if (formRef.current) {
71+
formRef.current.submit();
72+
}
5573

56-
const iframe = iframeRef.current;
57-
const loading = loadingRef.current;
74+
const iframe = iframeRef.current;
75+
const loading = loadingRef.current;
5876

59-
if (!iframe || !loading) {
60-
return;
61-
}
77+
if (!iframe || !loading) {
78+
return;
79+
}
6280

63-
const onload = () => {
64-
loading.style.display = "none";
65-
iframe.style.visibility = "visible";
66-
};
81+
const onload = () => {
82+
loading.style.display = "none";
83+
iframe.style.visibility = "visible";
84+
setIframeReady(true);
85+
};
6786

68-
iframe.addEventListener("load", onload);
87+
iframe.addEventListener("load", onload);
6988

70-
return () => {
71-
iframe.removeEventListener("load", onload);
72-
};
73-
}, [iframeRef]);
89+
return () => {
90+
iframe.removeEventListener("load", onload);
91+
};
92+
},
93+
[iframeRef],
94+
);
7495

7596
const boxHeight = autoHeight ? undefined : height;
7697
const loaderHeight = autoHeight ? WIDGET_DEFAULT_HEIGHT : height;

0 commit comments

Comments
 (0)