mirror of
https://github.com/umami-software/umami.git
synced 2026-05-30 06:47:25 +00:00
guard the localStorage read and drop the empty span placeholder
Address Greptile review on PR #4262. P2 (line 62): the value read back from localStorage was typed as any and trusted blindly, so a value written by an extension or a manual edit could end up as the chart's displayMode. Read it once, keep only 'table' or 'cards', otherwise fall back to null and let the existing useMobile-driven default decide. Self-correcting on the next click is no longer necessary because the next render is already clean. P2 (line 114): the empty <span /> was a flex placeholder that existed only to make justify-content: space-between push the toggle button to the right when allowSearch was false. Drop the span, drop space-between, and put marginLeft: auto on the inner Row holding the actions and the toggle. The toggle now hugs the right edge whether search is rendered or not, with no extra DOM node. style={{}} is used because the react-zen Row marginLeft prop only accepts spacing tokens, not auto. Verified in Playwright at 1400 viewport: the toggle is now flush with the right edge of the action row, the toggle still flips between table and cards on click, and a deliberately invalid localStorage value ({"malicious":true}) is rejected on reload so the page falls back to the table default.
This commit is contained in:
@@ -57,9 +57,13 @@ export function DataGrid({
|
||||
const [search, setSearch] = useState(queryParams?.search || data?.search || '');
|
||||
const showPager = allowPaging && data && data.count > data.pageSize;
|
||||
const { isMobile } = useMobile();
|
||||
const [userDisplayMode, setUserDisplayMode] = useState<DisplayMode | null>(
|
||||
() => getItem(DISPLAY_MODE_STORAGE_KEY) ?? null,
|
||||
);
|
||||
const [userDisplayMode, setUserDisplayMode] = useState<DisplayMode | null>(() => {
|
||||
// localStorage can hold anything (extensions, manual edits, schema drift),
|
||||
// so accept only the two values we know how to render and otherwise fall
|
||||
// back to the useMobile-driven default.
|
||||
const stored = getItem(DISPLAY_MODE_STORAGE_KEY);
|
||||
return stored === 'table' || stored === 'cards' ? stored : null;
|
||||
});
|
||||
|
||||
// Effective mode: explicit user choice wins, otherwise fall back to the
|
||||
// mobile-driven default (cards on small viewports, table elsewhere).
|
||||
@@ -100,8 +104,8 @@ export function DataGrid({
|
||||
|
||||
return (
|
||||
<Column gap="4" minHeight="300px">
|
||||
<Row alignItems="center" justifyContent="space-between" wrap="wrap" gap>
|
||||
{allowSearch ? (
|
||||
<Row alignItems="center" wrap="wrap" gap>
|
||||
{allowSearch && (
|
||||
<SearchField
|
||||
value={search}
|
||||
onSearch={handleSearch}
|
||||
@@ -109,10 +113,8 @@ export function DataGrid({
|
||||
autoFocus={autoFocus}
|
||||
placeholder={t(labels.search)}
|
||||
/>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<Row alignItems="center" gap>
|
||||
<Row alignItems="center" gap style={{ marginLeft: 'auto' }}>
|
||||
{renderActions?.()}
|
||||
{viewToggleButton}
|
||||
</Row>
|
||||
|
||||
Reference in New Issue
Block a user