diff --git a/app/components/@settings/core/ControlPanel.tsx b/app/components/@settings/core/ControlPanel.tsx
index 5043e862..e359c058 100644
--- a/app/components/@settings/core/ControlPanel.tsx
+++ b/app/components/@settings/core/ControlPanel.tsx
@@ -5,13 +5,14 @@ import * as RadixDialog from '@radix-ui/react-dialog';
import { classNames } from '~/utils/classNames';
import {
closeSettingsPanel,
+ resetControlPanelHeader,
resetTabConfiguration,
settingsPanelStore,
tabConfigurationStore,
} from '~/lib/stores/settings';
import type { TabType, TabVisibilityConfig } from './types';
import { DEFAULT_TAB_CONFIG, TAB_ICONS, TAB_LABELS } from './constants';
-import { CloseCircle } from 'iconsax-reactjs';
+import { ControlPanelHeader } from './ControlPanelHeader';
import { Settings } from 'lucide-react';
// Import all tab components
@@ -22,6 +23,7 @@ import { useUserStore } from '~/lib/stores/user';
import { DeprecatedRole } from '@prisma/client';
import OrganizationTab from '~/components/@settings/tabs/organization/OrganizationTab';
import MembersTab from '~/components/@settings/tabs/members/MembersTab';
+import RolesTab from '~/components/@settings/tabs/roles/RolesTab';
const LAST_ACCESSED_TAB_KEY = 'control-panel-last-tab';
@@ -148,8 +150,15 @@ export const ControlPanel = () => {
};
const handleTabClick = (tabId: TabType) => {
+ if (activeTab === tabId) {
+ return;
+ }
+
setActiveTab(tabId);
+ // Reset the header when switching tabs
+ resetControlPanelHeader();
+
// Store the selected tab
localStorage.setItem(LAST_ACCESSED_TAB_KEY, tabId);
};
@@ -166,6 +175,8 @@ export const ControlPanel = () => {
return ;
case 'members':
return ;
+ case 'roles':
+ return ;
default:
return null;
}
@@ -221,21 +232,16 @@ export const ControlPanel = () => {
{/* Main Content */}
+
+
-
-
-
{activeTab ? (
getTabComponent(activeTab)
diff --git a/app/components/@settings/core/ControlPanelHeader.tsx b/app/components/@settings/core/ControlPanelHeader.tsx
new file mode 100644
index 00000000..d0f3f012
--- /dev/null
+++ b/app/components/@settings/core/ControlPanelHeader.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { useStore } from '@nanostores/react';
+import { classNames } from '~/utils/classNames';
+import { ArrowLeft } from 'lucide-react';
+import { CloseCircle } from 'iconsax-reactjs';
+import { closeSettingsPanel, settingsPanelStore } from '~/lib/stores/settings';
+
+export const ControlPanelHeader = () => {
+ const { header } = useStore(settingsPanelStore);
+
+ return (
+
+
+
+ {header.onBack && (
+
+ )}
+ {header.title && (
+
{header.title}
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/app/components/@settings/core/constants.ts b/app/components/@settings/core/constants.ts
index 850fa2c4..8cdb7ce3 100644
--- a/app/components/@settings/core/constants.ts
+++ b/app/components/@settings/core/constants.ts
@@ -1,5 +1,5 @@
import type { TabType, TabVisibilityConfig } from './types';
-import { Building, Database, GitBranch, type LucideIcon, Rocket, Users } from 'lucide-react';
+import { Building, Database, GitBranch, type LucideIcon, Rocket, Users, ShieldUser } from 'lucide-react';
export const TAB_ICONS: Record = {
data: Database,
@@ -7,6 +7,7 @@ export const TAB_ICONS: Record = {
'deployed-apps': Rocket,
organization: Building,
members: Users,
+ roles: ShieldUser,
};
export const TAB_LABELS: Record = {
@@ -15,6 +16,7 @@ export const TAB_LABELS: Record = {
'deployed-apps': 'Deployed Apps',
organization: 'Organization',
members: 'Members',
+ roles: 'Roles',
};
export const TAB_DESCRIPTIONS: Record = {
@@ -23,6 +25,7 @@ export const TAB_DESCRIPTIONS: Record = {
'deployed-apps': 'View and manage your deployed applications',
organization: 'Manage your organization',
members: 'Manage your organization members',
+ roles: 'Manage roles and permissions for organization members',
};
export const DEFAULT_TAB_CONFIG: TabVisibilityConfig[] = [
@@ -32,4 +35,5 @@ export const DEFAULT_TAB_CONFIG: TabVisibilityConfig[] = [
{ id: 'organization', visible: true, window: 'admin', order: 0 },
{ id: 'members', visible: true, window: 'admin', order: 1 },
+ { id: 'roles', visible: true, window: 'admin', order: 2 },
];
diff --git a/app/components/@settings/core/types.ts b/app/components/@settings/core/types.ts
index 1faf5b5c..43729c0a 100644
--- a/app/components/@settings/core/types.ts
+++ b/app/components/@settings/core/types.ts
@@ -1,4 +1,4 @@
-export type TabType = 'data' | 'github' | 'deployed-apps' | 'organization' | 'members';
+export type TabType = 'data' | 'github' | 'deployed-apps' | 'organization' | 'members' | 'roles';
export type WindowType = 'user' | 'admin';
@@ -30,4 +30,5 @@ export const TAB_LABELS: Record = {
'deployed-apps': 'Deployed Apps',
organization: 'Organization',
members: 'Members',
+ roles: 'Roles',
};
diff --git a/app/components/@settings/tabs/roles/AssignRoleMembers.tsx b/app/components/@settings/tabs/roles/AssignRoleMembers.tsx
new file mode 100644
index 00000000..bf9b829e
--- /dev/null
+++ b/app/components/@settings/tabs/roles/AssignRoleMembers.tsx
@@ -0,0 +1,181 @@
+import { useState, useEffect, useMemo } from 'react';
+import { setControlPanelHeader } from '~/lib/stores/settings';
+import { Circle, CircleCheck, Search } from 'lucide-react';
+import { toast } from 'sonner';
+import type { Role, User } from './types';
+
+type AssignRoleMembersProps = {
+ role: Role;
+ onRoleUpdate: (updatedRole: Role) => void;
+ closeAssignMembers: () => void;
+};
+
+export default function AssignRoleMembers({ role, onRoleUpdate, closeAssignMembers }: AssignRoleMembersProps) {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [membersNotInRole, setMembersNotInRole] = useState([]);
+ const [selectedUsers, setSelectedUsers] = useState