mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
fix UI bugs
This commit is contained in:
parent
577cac68b4
commit
699a507d9d
11 changed files with 164 additions and 56 deletions
|
|
@ -16,6 +16,7 @@ import { RenameInput } from './RenameInput';
|
|||
import IconSelector from './IconSelector';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
export const PageMenuItem = withRouter(
|
||||
memo(({ darkMode, page, navigate }) => {
|
||||
|
|
@ -27,6 +28,8 @@ export const PageMenuItem = withRouter(
|
|||
const isDisabled = page?.disabled ?? false;
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
|
||||
const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
|
||||
const showEditingPopover = useStore((state) => state.showEditingPopover);
|
||||
const {
|
||||
definition: { styles, properties },
|
||||
|
|
@ -195,8 +198,11 @@ export const PageMenuItem = withRouter(
|
|||
{isHidden && !isDisabled && 'Hidden'}
|
||||
</span>
|
||||
</div>
|
||||
{!shouldFreeze && (
|
||||
<div className={cx('right', { 'handler-menu-open': showEditingPopover })}>
|
||||
<div style={{ marginLeft: '8px', marginRight: 'auto' }}>
|
||||
{licenseValid && page?.restricted && <SolidIcon width="16" name="lock" fill="var(--icon-strong)" />}
|
||||
</div>
|
||||
<div className={cx('right', { 'handler-menu-open': showEditingPopover })}>
|
||||
{!shouldFreeze && (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
|
|
@ -215,8 +221,8 @@ export const PageMenuItem = withRouter(
|
|||
>
|
||||
<SolidIcon width="20" dataCy={`page-menu`} name="morevertical" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export default function PagePermission({ darkMode }) {
|
|||
const showPagePermissionModal = useStore((state) => state.showPagePermissionModal);
|
||||
const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal);
|
||||
const editingPage = useStore((state) => state.editingPage);
|
||||
const setEditingPage = useStore((state) => state.setEditingPage);
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const selectedUserGroups = useStore((state) => state.selectedUserGroups);
|
||||
const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
|
||||
|
|
@ -31,15 +32,14 @@ export default function PagePermission({ darkMode }) {
|
|||
const [showUsersSelect, toggleUsersSelect] = useState(false);
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
console.log({ editingPage, showUserGroupSelect });
|
||||
const [pageToDelete, setPageToDelete] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editingPage?.id && !showPagePermissionModal) return;
|
||||
const fetchPagePermission = () => {
|
||||
appPermissionService.getPagePermission(appId, editingPage?.id).then((data) => {
|
||||
if (data) {
|
||||
if (data[0]) {
|
||||
if (data[0] && data[0]?.type === PERMISSION_TYPES.group) {
|
||||
setPagePermissionType(data[0]?.type?.toLowerCase());
|
||||
setPagePermission(data);
|
||||
toggleUserGroupSelect(true);
|
||||
|
|
@ -48,14 +48,32 @@ export default function PagePermission({ darkMode }) {
|
|||
data[0]?.users?.map((user) => ({
|
||||
label: user?.permissionGroup?.name,
|
||||
value: user?.permissionGroup?.id,
|
||||
count: user?.permissionGroup?.count,
|
||||
}))
|
||||
);
|
||||
} else if (data[0] && data[0]?.type === PERMISSION_TYPES.single) {
|
||||
setPagePermissionType(data[0]?.type?.toLowerCase());
|
||||
setPagePermission(data);
|
||||
toggleUsersSelect(true);
|
||||
data?.length &&
|
||||
setSelectedUsers(
|
||||
data[0]?.users?.map(({ user }) => {
|
||||
const firstName = user.firstName || '';
|
||||
const lastName = user.lastName || '';
|
||||
return {
|
||||
value: user.id,
|
||||
label: `${firstName} ${lastName}`.trim(),
|
||||
email: user.email,
|
||||
initials: `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
fetchPagePermission();
|
||||
}, [appId, editingPage, setPagePermission, setSelectedUserGroups, showPagePermissionModal]);
|
||||
}, [editingPage]);
|
||||
|
||||
const permissionTypeOptions = useMemo(
|
||||
() => [
|
||||
|
|
@ -77,9 +95,7 @@ export default function PagePermission({ darkMode }) {
|
|||
],
|
||||
[]
|
||||
);
|
||||
console.log({ pagePermission });
|
||||
const handlePermissionTypeChange = (value) => {
|
||||
console.log({ value });
|
||||
switch (value) {
|
||||
case 'group': {
|
||||
toggleUserGroupSelect(true);
|
||||
|
|
@ -107,6 +123,9 @@ export default function PagePermission({ darkMode }) {
|
|||
toggleUsersSelect(false);
|
||||
setPagePermissionType('all');
|
||||
setPagePermission(null);
|
||||
setEditingPage(null);
|
||||
setSelectedUsers([]);
|
||||
setSelectedUserGroups([]);
|
||||
};
|
||||
|
||||
const createPagePermission = () => {
|
||||
|
|
@ -121,7 +140,7 @@ export default function PagePermission({ darkMode }) {
|
|||
appPermissionService
|
||||
.createPagePermission(appId, editingPage?.id, body)
|
||||
.then((data) => {
|
||||
console.log({ data });
|
||||
toast.success('Permission successfully created!');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Permission could not be created. Please try again!');
|
||||
|
|
@ -129,7 +148,6 @@ export default function PagePermission({ darkMode }) {
|
|||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
handlePagePermissionModalClose();
|
||||
toast.success('Permission successfully created!');
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -145,7 +163,7 @@ export default function PagePermission({ darkMode }) {
|
|||
appPermissionService
|
||||
.updatePagePermission(appId, editingPage?.id, body)
|
||||
.then((data) => {
|
||||
console.log({ data });
|
||||
toast.success('Permission successfully updated!');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Permission could not be updated. Please try again!');
|
||||
|
|
@ -153,16 +171,15 @@ export default function PagePermission({ darkMode }) {
|
|||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
handlePagePermissionModalClose();
|
||||
toast.success('Permission successfully updated!');
|
||||
});
|
||||
};
|
||||
|
||||
const deletePagePermission = () => {
|
||||
setIsLoading(true);
|
||||
appPermissionService
|
||||
.deletePagePermission(appId, editingPage?.id)
|
||||
.deletePagePermission(appId, pageToDelete)
|
||||
.then((data) => {
|
||||
console.log({ data });
|
||||
toast.success('Permission successfully deleted!');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Permission could not be deleted. Please try again!');
|
||||
|
|
@ -170,14 +187,14 @@ export default function PagePermission({ darkMode }) {
|
|||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
setShowConfirmDelete(false);
|
||||
setPageToDelete(null);
|
||||
handlePagePermissionModalClose();
|
||||
toast.success('Permission successfully deleted!');
|
||||
});
|
||||
};
|
||||
|
||||
const renderPermissionTypeOptions = ({ label, icon }) => {
|
||||
return (
|
||||
<div className="row permission-type-select">
|
||||
<div className="row permission-type-select" style={{ padding: '0px 8px' }}>
|
||||
<div className="col-auto">
|
||||
<SolidIcon width="20" name={icon} />
|
||||
</div>
|
||||
|
|
@ -211,6 +228,7 @@ export default function PagePermission({ darkMode }) {
|
|||
pagePermission && (
|
||||
<span
|
||||
onClick={(e) => {
|
||||
setPageToDelete(editingPage?.id);
|
||||
togglePagePermissionModal(false);
|
||||
setShowConfirmDelete(true);
|
||||
}}
|
||||
|
|
@ -268,20 +286,17 @@ export default function PagePermission({ darkMode }) {
|
|||
}
|
||||
|
||||
const UserGroupSelect = () => {
|
||||
console.log('rendering');
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const editingPage = useStore((state) => state.editingPage);
|
||||
const selectedUserGroups = useStore((state) => state.selectedUserGroups);
|
||||
const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
|
||||
const [userGroups, setUserGroups] = useState([]);
|
||||
useEffect(() => {
|
||||
const fetchUserGroups = () => {
|
||||
appPermissionService.getUsers(appId, 'user-groups').then((data) => {
|
||||
console.log({ data });
|
||||
if (data?.length) {
|
||||
const groups = [];
|
||||
data.map((group) => {
|
||||
groups.push({ value: group.id, label: group.name });
|
||||
groups.push({ value: group.id, label: group.name, count: group.count });
|
||||
});
|
||||
setUserGroups(groups);
|
||||
}
|
||||
|
|
@ -290,7 +305,26 @@ const UserGroupSelect = () => {
|
|||
fetchUserGroups();
|
||||
}, []);
|
||||
|
||||
console.log({ selectedUserGroups, userGroups });
|
||||
const CustomOption = (props) => {
|
||||
const { data, isFocused, isSelected } = props;
|
||||
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className={`user-select-option ${isFocused ? 'focused' : ''}`}>
|
||||
<input
|
||||
style={{ width: '1.2rem', height: '1.2rem', borderRadius: '6px !important' }}
|
||||
type={'checkbox'}
|
||||
className="form-check-input"
|
||||
checked={isSelected}
|
||||
/>
|
||||
<div className="group-info">
|
||||
<div className="name">{data.label}</div>
|
||||
<div className="count">{data.count} users</div>
|
||||
</div>
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -300,10 +334,12 @@ const UserGroupSelect = () => {
|
|||
options={userGroups}
|
||||
value={selectedUserGroups}
|
||||
width={'100%'}
|
||||
// customOption={renderPermissionTypeOptions}
|
||||
closeMenuOnSelect={false}
|
||||
components={{ Option: CustomOption, MenuList: CustomMenuList }}
|
||||
useMenuPortal={false}
|
||||
// menuIsOpen={true}
|
||||
hideSelectedOptions={false}
|
||||
onChange={(groups) => setSelectedUserGroups(groups)}
|
||||
info="Only user groups with access to this application can be selected"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -318,7 +354,6 @@ const UserSelect = () => {
|
|||
useEffect(() => {
|
||||
const fetchUsers = () => {
|
||||
appPermissionService.getUsers(appId, 'users').then((data) => {
|
||||
console.log({ data });
|
||||
if (data?.length) {
|
||||
const users = [];
|
||||
data.map((user) => {
|
||||
|
|
@ -343,6 +378,12 @@ const UserSelect = () => {
|
|||
return (
|
||||
<components.Option {...props}>
|
||||
<div className={`user-select-option ${isFocused ? 'focused' : ''}`}>
|
||||
<input
|
||||
style={{ width: '1.2rem', height: '1.2rem', borderRadius: '6px !important' }}
|
||||
type={'checkbox'}
|
||||
className="form-check-input"
|
||||
checked={isSelected}
|
||||
/>
|
||||
<div className="avatar">{data.initials}</div>
|
||||
<div className="user-info">
|
||||
<div className="name">{data.label}</div>
|
||||
|
|
@ -353,8 +394,12 @@ const UserSelect = () => {
|
|||
);
|
||||
};
|
||||
|
||||
console.log({ users });
|
||||
|
||||
const selectStyles = {
|
||||
option: (base) => ({
|
||||
...base,
|
||||
padding: '8px 0px',
|
||||
}),
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<label className="form-label mt-3">Users</label>
|
||||
|
|
@ -363,15 +408,35 @@ const UserSelect = () => {
|
|||
options={users}
|
||||
value={selectedUsers}
|
||||
width={'100%'}
|
||||
// customOption={renderUserSelectOptions}
|
||||
useMenuPortal={false}
|
||||
components={{ Option: CustomOption }}
|
||||
// menuIsOpen={true}
|
||||
closeMenuOnSelect={false}
|
||||
components={{ Option: CustomOption, MenuList: CustomMenuList }}
|
||||
styles={selectStyles}
|
||||
hideSelectedOptions={false}
|
||||
info="Only user with access to this application can be selected"
|
||||
onChange={(users) => {
|
||||
console.log({ userstemp: users });
|
||||
setSelectedUsers(users);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomMenuList = (props) => {
|
||||
const { info } = props.selectProps;
|
||||
return (
|
||||
<components.MenuList {...props}>
|
||||
<div className="info-container" style={{ marginLeft: '12px', marginRight: '12px', marginTop: '8px' }}>
|
||||
<div className="col-md-1 info-btn">
|
||||
<SolidIcon name="informationcircle" fill="#3E63DD" />
|
||||
</div>
|
||||
<div className="col-md-11">
|
||||
<div className="message">
|
||||
<p style={{ lineHeight: '18px' }}>{info}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
</components.MenuList>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -297,6 +297,14 @@
|
|||
gap: 12px;
|
||||
}
|
||||
|
||||
.react-select__option {
|
||||
padding: 8px 0px;
|
||||
|
||||
input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -327,15 +335,32 @@
|
|||
flex-direction: column;
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--slate12);
|
||||
}
|
||||
|
||||
.email {
|
||||
font-size: 12px;
|
||||
color: var(--slate10); // gray-500
|
||||
color: var(--slate10);
|
||||
}
|
||||
}
|
||||
|
||||
.group-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
|
||||
.name {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: var(--slate12);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 12px;
|
||||
color: var(--slate9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -142,8 +142,7 @@ const RenderPageGroup = ({
|
|||
export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => {
|
||||
// Don't render empty folders if displaying only icons
|
||||
const tree = buildTree(pages, !!labelStyle?.label?.hidden);
|
||||
const filteredPages = tree.filter((page) => !page?.isPageGroup || page.children?.length > 0);
|
||||
|
||||
const filteredPages = tree.filter((page) => (!page?.isPageGroup || page.children?.length > 0) && !page?.restricted);
|
||||
const currentPageId = useStore((state) => state.currentPageId);
|
||||
const currentPage = pages.find((page) => page.id === currentPageId);
|
||||
const homePageId = useStore((state) => state.app.homePageId);
|
||||
|
|
|
|||
|
|
@ -329,25 +329,23 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
|
|||
|
||||
if (initialLoadRef.current) {
|
||||
// if initial load, check if the path has a page handle and set that as the starting page
|
||||
const initialLoadPath = location.pathname.split('/')[3];
|
||||
const initialLoadPath = location.pathname.split('/').pop();
|
||||
|
||||
const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
|
||||
if (page) {
|
||||
// if page is disabled, and not editing redirect to home page
|
||||
if (mode !== 'edit' && page?.disabled) {
|
||||
const currentUrl = window.location.href;
|
||||
const replacedUrl = currentUrl.replace(initialLoadPath, startingPage.handle);
|
||||
window.history.replaceState(null, null, replacedUrl);
|
||||
const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
|
||||
|
||||
if (shouldRedirect) {
|
||||
const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
|
||||
window.history.replaceState(null, null, newUrl);
|
||||
|
||||
if (page?.restricted) {
|
||||
toast.error('Access to this page is restricted. Contact admin to know more.');
|
||||
}
|
||||
} else {
|
||||
startingPage = page;
|
||||
}
|
||||
} else {
|
||||
if (mode !== 'edit' && initialLoadPath) {
|
||||
const currentUrl = window.location.href;
|
||||
const replacedUrl = currentUrl.replace(initialLoadPath, startingPage.handle);
|
||||
window.history.replaceState(null, null, replacedUrl);
|
||||
toast.error('Access to this page is restricted. Contact admin to know more.');
|
||||
}
|
||||
}
|
||||
|
||||
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
|
||||
|
|
|
|||
|
|
@ -860,7 +860,9 @@ export const createEventsSlice = (set, get) => ({
|
|||
const { switchPage } = get();
|
||||
const page = get().modules.canvas.pages.find((page) => page.id === event.pageId);
|
||||
const queryParams = event.queryParams || [];
|
||||
if (!page.disabled) {
|
||||
if (page.restricted && mode !== 'edit') {
|
||||
toast.error('Access to this page is restricted. Contact admin to know more.');
|
||||
} else if (!page.disabled) {
|
||||
const resolvedQueryParams = [];
|
||||
queryParams.forEach((param) => {
|
||||
resolvedQueryParams.push([
|
||||
|
|
@ -1118,10 +1120,6 @@ export const createEventsSlice = (set, get) => ({
|
|||
toast('Valid page handle is required', {
|
||||
icon: '⚠️',
|
||||
});
|
||||
mode === 'view' &&
|
||||
toast.error('Access to this page is restricted. Contact admin to know more.', {
|
||||
icon: '⚠️',
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -444,5 +444,9 @@ export const createPageMenuSlice = (set, get) => {
|
|||
set((state) => {
|
||||
state.selectedUsers = users;
|
||||
}),
|
||||
setEditingPage: (page) =>
|
||||
set((state) => {
|
||||
state.editingPage = page;
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit d14468b6954cc33616b02802c49db2deac6be105
|
||||
Subproject commit 7d46f023cea42533c4a8ff387dcc1149553c4671
|
||||
|
|
@ -20,6 +20,7 @@ import { DataSourcesModule } from '@modules/data-sources/module';
|
|||
import { AppsSubscriber } from './subscribers/apps.subscriber';
|
||||
import { AiModule } from '@modules/ai/module';
|
||||
import { AppPermissionsModule } from '@modules/app-permissions/module';
|
||||
import { RolesRepository } from '@modules/roles/repository';
|
||||
@Module({})
|
||||
export class AppsModule {
|
||||
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
|
||||
|
|
@ -37,7 +38,15 @@ export class AppsModule {
|
|||
return {
|
||||
module: AppsModule,
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([App, Page, EventHandler, Organization, Component, VersionRepository]),
|
||||
TypeOrmModule.forFeature([
|
||||
App,
|
||||
Page,
|
||||
EventHandler,
|
||||
Organization,
|
||||
Component,
|
||||
VersionRepository,
|
||||
RolesRepository,
|
||||
]),
|
||||
await FolderAppsModule.register(configs),
|
||||
await ThemesModule.register(configs),
|
||||
await FoldersModule.register(configs),
|
||||
|
|
@ -63,6 +72,7 @@ export class AppsModule {
|
|||
AppsSubscriber,
|
||||
DataSourcesRepository,
|
||||
AppImportExportService,
|
||||
RolesRepository,
|
||||
],
|
||||
exports: [AppsUtilService],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
VersionReleaseDto,
|
||||
} from './dto';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { APP_TYPES, FEATURE_KEY } from './constants';
|
||||
import { FEATURE_KEY } from './constants';
|
||||
import { camelizeKeys, decamelizeKeys } from 'humps';
|
||||
import { App } from '@entities/app.entity';
|
||||
import { AppsUtilService } from './util.service';
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { App } from '@entities/app.entity';
|
|||
import { AiModule } from '@modules/ai/module';
|
||||
import { DataSourcesRepository } from '@modules/data-sources/repository';
|
||||
import { AppPermissionsModule } from '@modules/app-permissions/module';
|
||||
import { RolesRepository } from '@modules/roles/repository';
|
||||
export class WorkflowsModule {
|
||||
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
|
||||
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
|
||||
|
|
@ -70,6 +71,7 @@ export class WorkflowsModule {
|
|||
WorkflowExecutionNode,
|
||||
WorkflowExecutionNode,
|
||||
WorkflowExecutionEdge,
|
||||
RolesRepository,
|
||||
]),
|
||||
ThrottlerModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
|
|
@ -115,6 +117,7 @@ export class WorkflowsModule {
|
|||
WorkflowSchedulesService,
|
||||
TemporalService,
|
||||
FeatureAbilityFactory,
|
||||
RolesRepository,
|
||||
],
|
||||
controllers: [
|
||||
WorkflowsController,
|
||||
|
|
|
|||
Loading…
Reference in a new issue