fix UI bugs

This commit is contained in:
Vijaykant Yadav 2025-04-22 19:41:36 +05:30
parent 577cac68b4
commit 699a507d9d
11 changed files with 164 additions and 56 deletions

View file

@ -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>

View file

@ -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>
);
};

View file

@ -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);
}
}
}
}

View file

@ -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);

View file

@ -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}`);

View file

@ -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();
}

View file

@ -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

View file

@ -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],
};

View file

@ -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';

View file

@ -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,