mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 11:17:07 +00:00
fix: harden stack label permissions (#1036)
* fix: harden stack label permissions * fix: avoid test db init from debug logging
This commit is contained in:
@@ -2,7 +2,6 @@ import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { toast } from '@/components/ui/toast-store';
|
||||
import { useNodes } from '@/context/NodeContext';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { useImageUpdates } from '@/hooks/useImageUpdates';
|
||||
import { usePinnedStacks } from '@/hooks/usePinnedStacks';
|
||||
import { useSidebarGroupCollapse } from '@/hooks/useSidebarGroupCollapse';
|
||||
@@ -32,7 +31,6 @@ export interface RemoteResult {
|
||||
|
||||
export function useStackListState() {
|
||||
const { nodes, activeNode } = useNodes();
|
||||
const { isPaid } = useLicense();
|
||||
|
||||
const [files, setFiles] = useState<string[]>([]);
|
||||
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
||||
@@ -84,7 +82,6 @@ export function useStackListState() {
|
||||
};
|
||||
|
||||
const refreshLabels = useCallback(async () => {
|
||||
if (!isPaid) return;
|
||||
try {
|
||||
const [labelsRes, assignmentsRes] = await Promise.all([
|
||||
apiFetch('/labels'),
|
||||
@@ -95,7 +92,7 @@ export function useStackListState() {
|
||||
} catch {
|
||||
// Labels are non-critical; fail silently
|
||||
}
|
||||
}, [isPaid]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => refreshLabels();
|
||||
|
||||
@@ -44,13 +44,13 @@ const MAX_VISIBLE_LABELS = 3;
|
||||
|
||||
export function StackRow(props: StackRowProps) {
|
||||
const {
|
||||
file, displayName, status, isBusy, isActive, isPaid, labels,
|
||||
file, displayName, status, isBusy, isActive, labels,
|
||||
hasUpdate, hasGitPending, onSelect, kebabSlot,
|
||||
bulkMode = false, isSelected = false, onToggleSelect,
|
||||
} = props;
|
||||
|
||||
const visibleLabels = isPaid ? labels.slice(0, MAX_VISIBLE_LABELS) : [];
|
||||
const overflowCount = isPaid ? Math.max(0, labels.length - MAX_VISIBLE_LABELS) : 0;
|
||||
const visibleLabels = labels.slice(0, MAX_VISIBLE_LABELS);
|
||||
const overflowCount = Math.max(0, labels.length - MAX_VISIBLE_LABELS);
|
||||
|
||||
const handleClick = () => {
|
||||
if (bulkMode) onToggleSelect?.(file);
|
||||
|
||||
@@ -73,4 +73,13 @@ describe('StackRow', () => {
|
||||
expect(container.querySelector('.animate-spin')).not.toBeNull();
|
||||
expect(screen.queryByText('UP')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders label indicators on community tier', () => {
|
||||
const labels: Label[] = [
|
||||
{ id: 1, node_id: 0, name: 'prod', color: 'teal' },
|
||||
{ id: 2, node_id: 0, name: 'media', color: 'blue' },
|
||||
];
|
||||
const { container } = render(<StackRow {...base({ isPaid: false, labels })} />);
|
||||
expect(container.querySelectorAll('[style*="--label-"]')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,6 +95,17 @@ describe('useStackMenuItems', () => {
|
||||
expect(inspect.items.find(i => i.id === 'auto-update')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps label assignment available when !isPaid', () => {
|
||||
const { result } = renderHook(() => useStackMenuItems('web.yml', makeCtx({
|
||||
isPaid: false,
|
||||
labels: [{ id: 1, node_id: 0, name: 'prod', color: 'teal' }],
|
||||
})));
|
||||
const organize = result.current.find(g => g.id === 'organize')!;
|
||||
const labelsItem = organize.items.find(i => i.id === 'labels');
|
||||
expect(labelsItem).toBeDefined();
|
||||
expect(labelsItem?.subItems?.[0]).toMatchObject({ id: 'label:1', label: 'prod' });
|
||||
});
|
||||
|
||||
it('auto-update toggle calls setAutoUpdateEnabled with toggled value', () => {
|
||||
const setAutoUpdateEnabled = vi.fn();
|
||||
const { result } = renderHook(() =>
|
||||
|
||||
@@ -48,21 +48,19 @@ export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[]
|
||||
groups.push({ id: 'inspect', items: inspect });
|
||||
|
||||
const organize: MenuItem[] = [];
|
||||
if (isPaid) {
|
||||
organize.push({
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
organize.push({
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
icon: Tag,
|
||||
shortcut: 'L ›',
|
||||
onSelect: () => {},
|
||||
subItems: labels.map(l => ({
|
||||
id: `label:${l.id}`,
|
||||
label: l.name,
|
||||
icon: Tag,
|
||||
shortcut: 'L ›',
|
||||
onSelect: () => {},
|
||||
subItems: labels.map(l => ({
|
||||
id: `label:${l.id}`,
|
||||
label: l.name,
|
||||
icon: Tag,
|
||||
onSelect: () => toggleLabel(l.id),
|
||||
})),
|
||||
});
|
||||
}
|
||||
onSelect: () => toggleLabel(l.id),
|
||||
})),
|
||||
});
|
||||
organize.push(
|
||||
isPinned
|
||||
? { id: 'pin', label: 'Unpin', icon: PinOff, shortcut: 'P', onSelect: unpin }
|
||||
|
||||
Reference in New Issue
Block a user