frontend cleanup
This commit is contained in:
+91
-83
@@ -29,6 +29,7 @@ import TagsPanel from './tags/TagsPanel';
|
|||||||
import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
|
import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
|
||||||
import TagManager from './tag_manager';
|
import TagManager from './tag_manager';
|
||||||
import Sidebar from './sidebar/Sidebar';
|
import Sidebar from './sidebar/Sidebar';
|
||||||
|
import SettingsModal from './settings/SettingsModal';
|
||||||
import { ChevronsRightIcon } from './ui/icons';
|
import { ChevronsRightIcon } from './ui/icons';
|
||||||
import { createDocumentsSurface } from './documents/DocumentsTable';
|
import { createDocumentsSurface } from './documents/DocumentsTable';
|
||||||
import { createPreviewSurface } from './preview/PreviewWorkspace';
|
import { createPreviewSurface } from './preview/PreviewWorkspace';
|
||||||
@@ -338,12 +339,12 @@ const LoginView = ({
|
|||||||
<div className="login-card__tenant-list">
|
<div className="login-card__tenant-list">
|
||||||
{tenantSelection.tenants.map((tenant) => (
|
{tenantSelection.tenants.map((tenant) => (
|
||||||
<button
|
<button
|
||||||
key={tenant.tenant_id}
|
key={tenant.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelectTenant?.(tenant)}
|
onClick={() => onSelectTenant?.(tenant)}
|
||||||
disabled={Boolean(selectingTenantId)}
|
disabled={Boolean(selectingTenantId)}
|
||||||
className={
|
className={
|
||||||
selectingTenantId === tenant.tenant_id
|
selectingTenantId === tenant.id
|
||||||
? 'login-card__tenant-button is-loading'
|
? 'login-card__tenant-button is-loading'
|
||||||
: 'login-card__tenant-button'
|
: 'login-card__tenant-button'
|
||||||
}
|
}
|
||||||
@@ -440,7 +441,7 @@ const AppLayout = () => {
|
|||||||
const previewDocumentId = routeDocumentId;
|
const previewDocumentId = routeDocumentId;
|
||||||
const { status: appStatus, token, tenant, tenants: tenantOptions = [] } = appState;
|
const { status: appStatus, token, tenant, tenants: tenantOptions = [] } = appState;
|
||||||
const tenantSlug = tenant?.slug || null;
|
const tenantSlug = tenant?.slug || null;
|
||||||
const currentTenantId = tenant?.tenant_id || null;
|
const currentTenantId = tenant?.id || null;
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState(null);
|
||||||
const setStatusMessage = useCallback((message, variant = 'info') => {
|
const setStatusMessage = useCallback((message, variant = 'info') => {
|
||||||
setStatus(message ? { message, variant } : null);
|
setStatus(message ? { message, variant } : null);
|
||||||
@@ -464,6 +465,7 @@ const AppLayout = () => {
|
|||||||
const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false);
|
const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false);
|
||||||
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
|
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
|
||||||
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
|
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
|
||||||
|
const [isSettingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
const [newFolderName, setNewFolderName] = useState('');
|
const [newFolderName, setNewFolderName] = useState('');
|
||||||
const [createFolderError, setCreateFolderError] = useState('');
|
const [createFolderError, setCreateFolderError] = useState('');
|
||||||
const [creatingFolder, setCreatingFolder] = useState(false);
|
const [creatingFolder, setCreatingFolder] = useState(false);
|
||||||
@@ -2004,38 +2006,6 @@ const AppLayout = () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadFolder = useCallback(
|
|
||||||
async (folderId, { showLoading = true, preserveSearch = false } = {}) => {
|
|
||||||
const targetId = folderId || 'root';
|
|
||||||
setSelectedFolder(targetId);
|
|
||||||
if (showLoading) setLoading(true);
|
|
||||||
try {
|
|
||||||
const contents = await ensureFolderData(targetId, { force: true, prefetchDepth: 1 });
|
|
||||||
if (targetId !== 'root') {
|
|
||||||
try {
|
|
||||||
await ensureFolderData('root', {
|
|
||||||
force: false,
|
|
||||||
includeDocuments: false,
|
|
||||||
prefetchDepth: 1,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to refresh root folder tree', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
applySelectedFolder(targetId, contents);
|
|
||||||
if (!preserveSearch) {
|
|
||||||
setSearchResults(null);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
notifyApiError(error, 'Failed to load folder contents.');
|
|
||||||
} finally {
|
|
||||||
if (showLoading) setLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[ensureFolderData, applySelectedFolder, notifyApiError],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const expandFolderAncestors = useCallback(
|
const expandFolderAncestors = useCallback(
|
||||||
(targetId) => {
|
(targetId) => {
|
||||||
if (!targetId || targetId === 'root') {
|
if (!targetId || targetId === 'root') {
|
||||||
@@ -2097,51 +2067,73 @@ const AppLayout = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visited = new Set();
|
const fetchAncestor = async (folderId, guard = 0) => {
|
||||||
const stack = [];
|
if (!folderId || folderId === 'root' || guard > 32) {
|
||||||
|
return;
|
||||||
let currentId = targetId;
|
|
||||||
let guard = 0;
|
|
||||||
while (currentId && currentId !== 'root' && guard < 32) {
|
|
||||||
guard += 1;
|
|
||||||
if (visited.has(currentId)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
visited.add(currentId);
|
|
||||||
|
|
||||||
const node = folderNodes.get(currentId);
|
|
||||||
if (!node) {
|
|
||||||
stack.push(currentId);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentId = node.parentId ?? 'root';
|
const existing = folderNodes.get(folderId);
|
||||||
if (!parentId || parentId === 'root') {
|
if (existing?.loaded) {
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
stack.push(parentId);
|
|
||||||
currentId = parentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (stack.length) {
|
try {
|
||||||
const ancestorId = stack.pop();
|
const contents = await ensureFolderData(folderId, {
|
||||||
if (!ancestorId || ancestorId === 'root') {
|
includeDocuments: false,
|
||||||
continue;
|
force: false,
|
||||||
|
prefetchDepth: 0,
|
||||||
|
});
|
||||||
|
const parentId = contents?.folder?.parent_id ?? 'root';
|
||||||
|
if (parentId && parentId !== 'root') {
|
||||||
|
await fetchAncestor(parentId, guard + 1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to ensure ancestor folder for navigation', folderId, error);
|
||||||
}
|
}
|
||||||
if (!folderNodes.has(ancestorId)) {
|
};
|
||||||
|
|
||||||
|
await fetchAncestor(targetId, 0);
|
||||||
|
},
|
||||||
|
[folderNodes, ensureFolderData],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadFolder = useCallback(
|
||||||
|
async (folderId, { showLoading = true, preserveSearch = false } = {}) => {
|
||||||
|
const targetId = folderId || 'root';
|
||||||
|
setSelectedFolder(targetId);
|
||||||
|
await ensureFolderAncestorsLoaded(targetId);
|
||||||
|
expandFolderAncestors(targetId);
|
||||||
|
if (showLoading) setLoading(true);
|
||||||
|
try {
|
||||||
|
const contents = await ensureFolderData(targetId, { force: true, prefetchDepth: 1 });
|
||||||
|
if (targetId !== 'root') {
|
||||||
try {
|
try {
|
||||||
await ensureFolderData(ancestorId, {
|
await ensureFolderData('root', {
|
||||||
includeDocuments: false,
|
|
||||||
force: false,
|
force: false,
|
||||||
|
includeDocuments: false,
|
||||||
prefetchDepth: 1,
|
prefetchDepth: 1,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to ensure ancestor folder for navigation', ancestorId, error);
|
console.warn('Failed to refresh root folder tree', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applySelectedFolder(targetId, contents);
|
||||||
|
if (!preserveSearch) {
|
||||||
|
setSearchResults(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notifyApiError(error, 'Failed to load folder contents.');
|
||||||
|
} finally {
|
||||||
|
if (showLoading) setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[folderNodes, ensureFolderData],
|
[
|
||||||
|
ensureFolderData,
|
||||||
|
applySelectedFolder,
|
||||||
|
notifyApiError,
|
||||||
|
ensureFolderAncestorsLoaded,
|
||||||
|
expandFolderAncestors,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectFolder = useCallback(
|
const selectFolder = useCallback(
|
||||||
@@ -2200,10 +2192,7 @@ const AppLayout = () => {
|
|||||||
|
|
||||||
const hasData = folderContents.has(targetParam);
|
const hasData = folderContents.has(targetParam);
|
||||||
if (targetParam !== selectedFolder || !hasData) {
|
if (targetParam !== selectedFolder || !hasData) {
|
||||||
loadFolder(targetParam, {
|
selectFolder(targetParam, { immediate: true });
|
||||||
showLoading: !routeDocumentId,
|
|
||||||
preserveSearch: isFilterActive,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
token,
|
token,
|
||||||
@@ -2211,9 +2200,9 @@ const AppLayout = () => {
|
|||||||
routeFolderId,
|
routeFolderId,
|
||||||
routeDocumentId,
|
routeDocumentId,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
loadFolder,
|
|
||||||
folderContents,
|
folderContents,
|
||||||
isFilterActive,
|
isFilterActive,
|
||||||
|
selectFolder,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const refreshCurrentFolder = useCallback(async () => {
|
const refreshCurrentFolder = useCallback(async () => {
|
||||||
@@ -4048,6 +4037,14 @@ const AppLayout = () => {
|
|||||||
setCorrespondentsModalOpen(false);
|
setCorrespondentsModalOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const openSettingsModal = useCallback(() => {
|
||||||
|
setSettingsModalOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeSettingsModal = useCallback(() => {
|
||||||
|
setSettingsModalOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleCreateFolderSubmit = useCallback(
|
const handleCreateFolderSubmit = useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -4111,10 +4108,11 @@ const AppLayout = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTagsModalOpen(false);
|
setTagsModalOpen(false);
|
||||||
setCorrespondentsModalOpen(false);
|
setCorrespondentsModalOpen(false);
|
||||||
|
setSettingsModalOpen(false);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isTagsModalOpen && !isCorrespondentsModalOpen) {
|
if (!isTagsModalOpen && !isCorrespondentsModalOpen && !isSettingsModalOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
@@ -4124,6 +4122,8 @@ const AppLayout = () => {
|
|||||||
closeTagsModal();
|
closeTagsModal();
|
||||||
} else if (isCorrespondentsModalOpen) {
|
} else if (isCorrespondentsModalOpen) {
|
||||||
closeCorrespondentsModal();
|
closeCorrespondentsModal();
|
||||||
|
} else if (isSettingsModalOpen) {
|
||||||
|
closeSettingsModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4131,7 +4131,14 @@ const AppLayout = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [isTagsModalOpen, isCorrespondentsModalOpen, closeTagsModal, closeCorrespondentsModal]);
|
}, [
|
||||||
|
isTagsModalOpen,
|
||||||
|
isCorrespondentsModalOpen,
|
||||||
|
isSettingsModalOpen,
|
||||||
|
closeTagsModal,
|
||||||
|
closeCorrespondentsModal,
|
||||||
|
closeSettingsModal,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) return undefined;
|
if (!token) return undefined;
|
||||||
@@ -4943,8 +4950,7 @@ const AppLayout = () => {
|
|||||||
|
|
||||||
const handleTenantSelect = useCallback(
|
const handleTenantSelect = useCallback(
|
||||||
async (tenantOption, { refreshOnly = false } = {}) => {
|
async (tenantOption, { refreshOnly = false } = {}) => {
|
||||||
const requestedTenantId =
|
const requestedTenantId = tenantOption?.id ?? null;
|
||||||
tenantOption?.tenant_id || tenantOption?.tenantId || tenantOption?.id || null;
|
|
||||||
if (!refreshOnly && (!requestedTenantId || requestedTenantId === currentTenantId)) {
|
if (!refreshOnly && (!requestedTenantId || requestedTenantId === currentTenantId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4980,7 +4986,7 @@ const AppLayout = () => {
|
|||||||
|
|
||||||
api.defaults.headers.common.Authorization = `Bearer ${data.access_token}`;
|
api.defaults.headers.common.Authorization = `Bearer ${data.access_token}`;
|
||||||
tokenRef.current = data.access_token;
|
tokenRef.current = data.access_token;
|
||||||
tenantIdRef.current = data?.tenant?.tenant_id || null;
|
tenantIdRef.current = data?.tenant?.id ?? null;
|
||||||
|
|
||||||
if (Array.isArray(data?.tenants)) {
|
if (Array.isArray(data?.tenants)) {
|
||||||
appDispatch({ type: 'SET_TENANTS', tenants: data.tenants });
|
appDispatch({ type: 'SET_TENANTS', tenants: data.tenants });
|
||||||
@@ -4992,10 +4998,8 @@ const AppLayout = () => {
|
|||||||
await Promise.all([refreshTags(), refreshCorrespondents()]);
|
await Promise.all([refreshTags(), refreshCorrespondents()]);
|
||||||
await loadFolder('root', { showLoading: false, preserveSearch: false });
|
await loadFolder('root', { showLoading: false, preserveSearch: false });
|
||||||
|
|
||||||
setStatusMessage(
|
const tenantLabel = data?.tenant?.slug || data?.tenant?.id || 'tenant';
|
||||||
`Switched to ${data?.tenant?.slug || data?.tenant?.tenant_id || 'tenant'}.`,
|
setStatusMessage(`Switched to ${tenantLabel}.`, 'info');
|
||||||
'info',
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifyApiError(error, 'Failed to switch tenant.');
|
notifyApiError(error, 'Failed to switch tenant.');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -5102,6 +5106,7 @@ const AppLayout = () => {
|
|||||||
tenants: tenantOptions,
|
tenants: tenantOptions,
|
||||||
activeTenantId: currentTenantId,
|
activeTenantId: currentTenantId,
|
||||||
onSelectTenant: handleTenantSelect,
|
onSelectTenant: handleTenantSelect,
|
||||||
|
onOpenSettings: openSettingsModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarPropsWithActions = useMemo(
|
const sidebarPropsWithActions = useMemo(
|
||||||
@@ -5215,6 +5220,7 @@ const AppLayout = () => {
|
|||||||
resolveApiPath,
|
resolveApiPath,
|
||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
|
openSettingsModal,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
token,
|
token,
|
||||||
@@ -5255,6 +5261,7 @@ const AppLayout = () => {
|
|||||||
resolveApiPath,
|
resolveApiPath,
|
||||||
openTagsModal,
|
openTagsModal,
|
||||||
openCorrespondentsModal,
|
openCorrespondentsModal,
|
||||||
|
openSettingsModal,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5397,6 +5404,7 @@ const AppLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<SettingsModal open={isSettingsModalOpen} onClose={closeSettingsModal} />
|
||||||
</div>
|
</div>
|
||||||
</AppShellContext.Provider>
|
</AppShellContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -5661,15 +5669,15 @@ const LoginRoute = () => {
|
|||||||
|
|
||||||
const handleTenantSelect = useCallback(
|
const handleTenantSelect = useCallback(
|
||||||
async (tenant) => {
|
async (tenant) => {
|
||||||
if (!tenantSelection?.selectionToken || !tenant?.tenant_id) {
|
if (!tenantSelection?.selectionToken || !tenant?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSelectingTenantId(tenant.tenant_id);
|
setSelectingTenantId(tenant.id);
|
||||||
const { data } = await api.post(
|
const { data } = await api.post(
|
||||||
'/auth/select-tenant',
|
'/auth/select-tenant',
|
||||||
{ tenant_id: tenant.tenant_id },
|
{ tenant_id: tenant.id },
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${tenantSelection.selectionToken}`,
|
Authorization: `Bearer ${tenantSelection.selectionToken}`,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
ChevronsLeftIcon,
|
ChevronsLeftIcon,
|
||||||
LogoutIcon,
|
LogoutIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
CheckIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
|
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
@@ -159,6 +161,7 @@ const Sidebar = ({
|
|||||||
tenants = [],
|
tenants = [],
|
||||||
activeTenantId = null,
|
activeTenantId = null,
|
||||||
onSelectTenant,
|
onSelectTenant,
|
||||||
|
onOpenSettings,
|
||||||
}) => {
|
}) => {
|
||||||
const sortedCorrespondents = useMemo(
|
const sortedCorrespondents = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -232,7 +235,7 @@ const Sidebar = ({
|
|||||||
|
|
||||||
const handleTenantSelect = useCallback(
|
const handleTenantSelect = useCallback(
|
||||||
(tenant) => {
|
(tenant) => {
|
||||||
const targetId = tenant?.tenant_id || tenant?.tenantId || tenant?.id || null;
|
const targetId = tenant?.id || null;
|
||||||
if (!targetId) {
|
if (!targetId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -247,6 +250,11 @@ const Sidebar = ({
|
|||||||
onLogout?.();
|
onLogout?.();
|
||||||
}, [onLogout]);
|
}, [onLogout]);
|
||||||
|
|
||||||
|
const handleSettingsFromMenu = useCallback(() => {
|
||||||
|
setTenantMenuOpen(false);
|
||||||
|
onOpenSettings?.();
|
||||||
|
}, [onOpenSettings]);
|
||||||
|
|
||||||
const renderNodes = useCallback(
|
const renderNodes = useCallback(
|
||||||
(ids, depth) =>
|
(ids, depth) =>
|
||||||
ids.map((id) => {
|
ids.map((id) => {
|
||||||
@@ -317,7 +325,7 @@ const Sidebar = ({
|
|||||||
<span className="menu__empty">No tenants available.</span>
|
<span className="menu__empty">No tenants available.</span>
|
||||||
) : (
|
) : (
|
||||||
tenants.map((tenant) => {
|
tenants.map((tenant) => {
|
||||||
const tenantId = tenant?.tenant_id || tenant?.tenantId || tenant?.id || null;
|
const tenantId = tenant?.id || null;
|
||||||
const isActive = tenantId === activeTenantId;
|
const isActive = tenantId === activeTenantId;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -327,16 +335,26 @@ const Sidebar = ({
|
|||||||
onClick={() => handleTenantSelect(tenant)}
|
onClick={() => handleTenantSelect(tenant)}
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
|
<span className="menu__check-slot">
|
||||||
|
{isActive ? <CheckIcon size={16} /> : null}
|
||||||
|
</span>
|
||||||
<span className="menu__label">
|
<span className="menu__label">
|
||||||
{tenant?.slug || tenant?.name || tenantId || 'Tenant'}
|
{tenant?.slug || tenant?.name || tenantId || 'Tenant'}
|
||||||
</span>
|
</span>
|
||||||
{isActive ? <span className="menu__active-indicator">Active</span> : null}
|
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="menu__footer">
|
<div className="menu__footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="menu__settings"
|
||||||
|
onClick={handleSettingsFromMenu}
|
||||||
|
>
|
||||||
|
<SettingsIcon size={16} />
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="menu__logout"
|
className="menu__logout"
|
||||||
|
|||||||
+31
-1
@@ -1191,7 +1191,7 @@ button.danger:hover:not([disabled]) {
|
|||||||
.menu .menu__item {
|
.menu .menu__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: flex-start;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.55rem 1rem;
|
padding: 0.55rem 1rem;
|
||||||
@@ -1230,6 +1230,14 @@ button.danger:hover:not([disabled]) {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu__check-slot {
|
||||||
|
width: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--accent, #2563eb);
|
||||||
|
}
|
||||||
|
|
||||||
.menu__active-indicator {
|
.menu__active-indicator {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -1245,6 +1253,28 @@ button.danger:hover:not([disabled]) {
|
|||||||
.menu__footer {
|
.menu__footer {
|
||||||
border-top: 1px solid var(--border-muted, var(--border));
|
border-top: 1px solid var(--border-muted, var(--border));
|
||||||
padding: 0.35rem 0.5rem;
|
padding: 0.35rem 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__settings {
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu__settings:hover,
|
||||||
|
.menu__settings:focus-visible {
|
||||||
|
background: var(--sidebar-hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu__logout {
|
.menu__logout {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
IconLogout,
|
IconLogout,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconX,
|
IconX,
|
||||||
|
IconSettings,
|
||||||
|
IconCheck,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import FolderSvg from '../assets/folder.svg';
|
import FolderSvg from '../assets/folder.svg';
|
||||||
|
|
||||||
@@ -195,6 +197,24 @@ export const CloseIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) =>
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SettingsIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconSettings
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CheckIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconCheck
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconAnalyze
|
<IconAnalyze
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user