Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Feat]: Improve Profile Dropdown, and Workspaces Page using "myorg" endpoint#1787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
29 commits
Select commitHold shift + click to select a range
035fc26
redesign profile dropdown
iamfaranJun 16, 2025
a42f83c
testing workspaces endpoint
iamfaranJun 16, 2025
8b40672
fix profile dropdown
iamfaranJun 16, 2025
98695fb
setup redux, sagas for the new myorg endpoint
iamfaranJun 16, 2025
35b7c68
fix profile dropdown create workspace issue
iamfaranJun 16, 2025
a5d372a
test
iamfaranJun 17, 2025
1b63471
fix params
iamfaranJun 17, 2025
049d372
make currentOrg selector
iamfaranJun 17, 2025
a902532
add page size param
iamfaranJun 17, 2025
7709d58
replace orgs data with myorg for dropdown
iamfaranJun 17, 2025
e202fcb
remove dispatch from the profile dropdown
iamfaranJun 17, 2025
818fdf8
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into profileDr…
iamfaranJun 18, 2025
f851353
fetch 10 workspaces initially
iamfaranJun 18, 2025
9ea107b
add pagination and filtering for the dropdown
iamfaranJun 18, 2025
28a2101
refactor profile dropdown
iamfaranJun 18, 2025
0d59610
fix debouncing
iamfaranJun 18, 2025
c7edbd1
fix loading when search
iamfaranJun 18, 2025
238698d
fix shrinking issues when page 1 to page 2
iamfaranJun 18, 2025
0611910
add antD loader in UI
iamfaranJun 18, 2025
ffa7757
fix delete sync
iamfaranJun 19, 2025
96acd6a
fix sync after edit
iamfaranJun 19, 2025
db11f0e
add pagination/filtering for the Workspaces page
iamfaranJun 19, 2025
2f291f7
add selector for the current org
iamfaranJun 19, 2025
b3abc61
add active indicator in the workspaces page
iamfaranJun 19, 2025
23fcbf9
add the ability to switch workspaces from workspaces page
iamfaranJun 19, 2025
cd92f7c
disable row click on switch
iamfaranJun 19, 2025
73a64b4
fix switch org button
iamfaranJun 19, 2025
763bd98
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into feat/myor…
iamfaranJun 19, 2025
24e04c0
Merge branch 'dev' into feat/myorg-endpoint
raheeliftikhar5Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletionclient/packages/lowcoder/src/api/userApi.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import Api from "api/api";
import { AxiosPromise } from "axios";
import { OrgAndRole } from "constants/orgConstants";
import {Org,OrgAndRole } from "constants/orgConstants";
import { BaseUserInfo, CurrentUser } from "constants/userConstants";
import { MarkUserStatusPayload, UpdateUserPayload } from "redux/reduxActions/userActions";
import { ApiResponse, GenericApiResponse } from "./apiResponses";
Expand DownExpand Up@@ -60,10 +60,23 @@ export interface FetchApiKeysResponse extends ApiResponse {

export type GetCurrentUserResponse = GenericApiResponse<CurrentUser>;

export interface GetMyOrgsResponse extends ApiResponse {
data: {
data: Array<{
orgId: string;
orgName: string;
}>;
pageNum: number;
pageSize: number;
total: number;
};
}

class UserApi extends Api {
static thirdPartyLoginURL = "/auth/tp/login";
static thirdPartyBindURL = "/auth/tp/bind";
static usersURL = "/users";
static myOrgsURL = "/users/myorg";
static sendVerifyCodeURL = "/auth/otp/send";
static logoutURL = "/auth/logout";
static userURL = "/users/me";
Expand DownExpand Up@@ -127,6 +140,19 @@ class UserApi extends Api {
static getCurrentUser(): AxiosPromise<GetCurrentUserResponse> {
return Api.get(UserApi.currentUserURL);
}
static getMyOrgs(
pageNum: number = 1,
pageSize: number = 20,
orgName?: string
): AxiosPromise<GetMyOrgsResponse> {
const params = new URLSearchParams({
pageNum: pageNum.toString(),
pageSize: pageSize.toString(),
...(orgName && { orgName })
});

return Api.get(`${UserApi.myOrgsURL}?${params}`);
}

static getRawCurrentUser(): AxiosPromise<GetCurrentUserResponse> {
return Api.get(UserApi.rawCurrentUserURL);
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,12 @@ export const ReduxActionTypes = {
FETCH_API_KEYS_SUCCESS: "FETCH_API_KEYS_SUCCESS",
MOVE_TO_FOLDER2_SUCCESS: "MOVE_TO_FOLDER2_SUCCESS",

/* workspace RELATED */
FETCH_WORKSPACES_INIT: "FETCH_WORKSPACES_INIT",
FETCH_WORKSPACES_SUCCESS: "FETCH_WORKSPACES_SUCCESS",



/* plugin RELATED */
FETCH_DATA_SOURCE_TYPES: "FETCH_DATA_SOURCE_TYPES",
FETCH_DATA_SOURCE_TYPES_SUCCESS: "FETCH_DATA_SOURCE_TYPES_SUCCESS",
Expand Down
1 change: 1 addition & 0 deletionsclient/packages/lowcoder/src/i18n/locales/en.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3639,6 +3639,7 @@ export const en = {
"profile": {
"orgSettings": "Workspace Settings",
"switchOrg": "Switch Workspace",
"switchWorkspace": "Switch",
"joinedOrg": "My Workspaces",
"createOrg": "Create Workspace",
"logout": "Log Out",
Expand Down
288 changes: 288 additions & 0 deletionsclient/packages/lowcoder/src/pages/common/WorkspaceSection.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { Input, Pagination, Spin } from 'antd';
import { User } from 'constants/userConstants';
import { switchOrg, createOrgAction } from 'redux/reduxActions/orgActions';
import { selectSystemConfig } from 'redux/selectors/configSelectors';
import { showSwitchOrg } from '@lowcoder-ee/pages/common/customerService';
import { useWorkspaceManager } from 'util/useWorkspaceManager';
import { trans } from 'i18n';
import {
AddIcon,
CheckoutIcon,
SearchIcon,
} from 'lowcoder-design';
import { ORGANIZATION_SETTING } from 'constants/routesURL';
import history from 'util/history';
import { Org } from 'constants/orgConstants';

// Styled Components
const WorkspaceSection = styled.div`
padding: 8px 0;
`;

const SectionHeader = styled.div`
padding: 8px 16px;
font-size: 12px;
font-weight: 500;
color: #8b8fa3;
text-transform: uppercase;
letter-spacing: 0.5px;
`;

const SearchContainer = styled.div`
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
`;

const StyledSearchInput = styled(Input)`
.ant-input {
border: 1px solid #e1e3eb;
border-radius: 6px;
font-size: 13px;

&:focus {
border-color: #4965f2;
box-shadow: 0 0 0 2px rgba(73, 101, 242, 0.1);
}
}
`;

const WorkspaceList = styled.div`
max-height: 200px;
overflow-y: auto;

&::-webkit-scrollbar {
width: 4px;
}

&::-webkit-scrollbar-track {
background: #f1f1f1;
}

&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 2px;
}

&::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
`;

const WorkspaceItem = styled.div<{ isActive?: boolean }>`
display: flex;
align-items: center;
padding: 10px 16px;
cursor: pointer;
transition: background-color 0.2s;
background-color: ${props => props.isActive ? '#f0f5ff' : 'transparent'};

&:hover {
background-color: ${props => props.isActive ? '#f0f5ff' : '#f8f9fa'};
}
`;

const WorkspaceName = styled.div`
flex: 1;
font-size: 13px;
color: #222222;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;

const ActiveIcon = styled(CheckoutIcon)`
width: 16px;
height: 16px;
color: #4965f2;
margin-left: 8px;
`;

const CreateWorkspaceItem = styled.div`
display: flex;
align-items: center;
padding: 12px 16px;
cursor: pointer;
transition: background-color 0.2s;
font-size: 13px;
color: #4965f2;
font-weight: 500;

&:hover {
background-color: #f0f5ff;
color: #3651d4;
}

svg {
width: 16px;
height: 16px;
margin-right: 10px;
color: #4965f2;
}

&:hover svg {
color: #3651d4;
}
`;

const EmptyState = styled.div`
padding: 20px 16px;
text-align: center;
color: #8b8fa3;
font-size: 13px;
`;

const PaginationContainer = styled.div`
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;

.ant-pagination {
margin: 0;

.ant-pagination-item {
min-width: 24px;
height: 24px;
line-height: 22px;
font-size: 12px;
margin-right: 4px;
}

.ant-pagination-prev,
.ant-pagination-next {
min-width: 24px;
height: 24px;
line-height: 22px;
margin-right: 4px;
}

.ant-pagination-item-link {
font-size: 11px;
}
}
`;

const LoadingContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 24px 16px;
`;

// Component Props
interface WorkspaceSectionProps {
user: User;
isDropdownOpen: boolean;
onClose: () => void;
}

// Main Component
export default function WorkspaceSectionComponent({
user,
isDropdownOpen,
onClose
}: WorkspaceSectionProps) {
const dispatch = useDispatch();
const sysConfig = useSelector(selectSystemConfig);

// Use our custom hook
const {
searchTerm,
currentPage,
totalCount,
isLoading,
displayWorkspaces,
handleSearchChange,
handlePageChange,
pageSize,
} = useWorkspaceManager({});

// Early returns for better performance
if (!showSwitchOrg(user, sysConfig)) return null;

// Event handlers
const handleOrgSwitch = (orgId: string) => {
if (user.currentOrgId !== orgId) {
dispatch(switchOrg(orgId));
}
onClose();
};

const handleCreateOrg = () => {
dispatch(createOrgAction(user.orgs));
history.push(ORGANIZATION_SETTING);
onClose();
};

return (
<WorkspaceSection>
<SectionHeader>{trans("profile.switchOrg")}</SectionHeader>

{/* Search Input - Only show if more than 3 workspaces */}
<SearchContainer>
<StyledSearchInput
placeholder="Search workspaces..."
value={searchTerm}
onChange={(e) => handleSearchChange(e.target.value)}
prefix={<SearchIcon style={{ color: "#8b8fa3" }} />}
size="small"
/>
</SearchContainer>

{/* Workspace List */}
<WorkspaceList>
{isLoading ? (
<LoadingContainer>
<Spin size="small" />
</LoadingContainer>
) : displayWorkspaces.length > 0 ? (
displayWorkspaces.map((org: Org) => (
<WorkspaceItem
key={org.id}
isActive={user.currentOrgId === org.id}
onClick={() => handleOrgSwitch(org.id)}
>
<WorkspaceName title={org.name}>{org.name}</WorkspaceName>
{user.currentOrgId === org.id && <ActiveIcon />}
</WorkspaceItem>
))
) : (
<EmptyState>
{searchTerm.trim()
? "No workspaces found"
: "No workspaces available"
}
</EmptyState>
)}
</WorkspaceList>

{/* Pagination - Only show when needed */}
{totalCount > pageSize && !isLoading && (
<PaginationContainer>
<Pagination
current={currentPage}
total={totalCount}
pageSize={pageSize}
size="small"
showSizeChanger={false}
showQuickJumper={false}
showTotal={(total, range) =>
`${range[0]}-${range[1]} of ${total}`
}
onChange={handlePageChange}
simple={totalCount > 100} // Simple mode for large datasets
/>
</PaginationContainer>
)}

{/* Create Workspace Button */}
<CreateWorkspaceItem onClick={handleCreateOrg}>
<AddIcon />
{trans("profile.createOrg")}
</CreateWorkspaceItem>
</WorkspaceSection>
);
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp