I'll create a plan for you to review and approve.
Here are the important artifacts that you might want to review before I start generating the code. You can review, edit and save (ctrl+s) them.
// Props types (data passed to components)
export interface User {
id: string;
name: string;
username: string;
avatar: string;
status: 'Online' | 'Offline';
joinDate: Date;
lastActive: Date;
role: 'Admin' | 'Editor' | 'Viewer';
selected: boolean;
}
export interface CurrentUser {
id: string;
name: string;
role: string;
avatar: string;
}
export interface SettingsPageProps {
currentUser: CurrentUser;
teamMembers: User[];
totalTeamMembers: number;
currentPage: number;
totalPages: number;
resultsPerPage: number;
totalResults: number;
activeTab: string;
homeNotificationCount: number;
usersNotificationCount: number;
}
// No API endpoints required for this static UI component
// Props for the root SettingsPage component
export interface SettingsPageProps {
currentUser: CurrentUser;
teamMembers: User[];
totalTeamMembers: number;
currentPage: number;
totalPages: number;
resultsPerPage: number;
totalResults: number;
activeTab: string;
homeNotificationCount: number;
usersNotificationCount: number;
}
// Theme configuration for the settings page
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#4f46e5', // Indigo primary color from sidebar
light: '#6366f1',
dark: '#3730a3',
contrastText: '#ffffff'
},
secondary: {
main: '#a5b4fc', // Light indigo for icons
light: '#c7d2fe',
dark: '#8b5cf6',
contrastText: '#1e293b'
},
success: {
main: '#22c55e', // Green for online status
light: '#4ade80',
dark: '#16a34a',
contrastText: '#ffffff'
},
text: {
primary: '#1e293b', // Dark slate for main text
secondary: '#475569' // Medium slate for secondary text
},
background: {
default: '#ffffff',
paper: '#f8fafc' // Light gray for table headers
},
grey: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a'
},
divider: '#e2e8f0'
},
typography: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
h1: {
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px'
},
h2: {
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px'
},
body1: {
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px'
},
body2: {
fontSize: '14px',
fontWeight: 400,
lineHeight: '22.4px'
},
button: {
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none'
},
caption: {
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px'
}
},
shape: {
borderRadius: 8
}
});
// User role types
export enum UserRole {
ADMIN = 'Admin',
EDITOR = 'Editor',
VIEWER = 'Viewer'
}
// User status types
export enum UserStatus {
ONLINE = 'Online',
OFFLINE = 'Offline'
}
// Navigation menu items
export enum NavigationItem {
HOME = 'Home',
TASKS = 'Tasks',
USERS = 'Users',
APIS = 'APIs',
SUBSCRIPTION = 'Subscription',
SETTINGS = 'Settings',
HELP_SUPPORT = 'Help & Support'
}
// Settings tabs
export enum SettingsTab {
GENERAL = 'General',
DETAILS = 'Details',
ACCOUNT = 'Account'
}
Now I'll generate the code files for the settings page with team management functionality.
<svg viewBox="0 0 109 32" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><filter id="filter0_d_243_24834" x="-8" y="-4" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" xmlns="http://www.w3.org/2000/svg">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.309804 0 0 0 0 0.27451 0 0 0 0 0.898039 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_243_24834"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_243_24834" result="shape"/>
</filter><clipPath id="clip0_243_24834" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="32" height="32" rx="12" fill="white"/>
</clipPath><filter id="filter1_d_243_24834" x="0.6934000000000005" y="2.75" width="30.6202" height="34.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" xmlns="http://www.w3.org/2000/svg">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.388235 0 0 0 0 0.4 0 0 0 0 0.945098 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_243_24834"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_243_24834" result="shape"/>
</filter><linearGradient id="paint0_linear_243_24834" x1="16" y1="7" x2="16" y2="25" gradientUnits="userSpaceOnUse" xmlns="http://www.w3.org/2000/svg">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#4F46E5" stop-opacity="0"/>
</linearGradient></defs><g id="Logo" xmlns="http://www.w3.org/2000/svg">
<g id="Logomark" filter="url(#filter0_d_243_24834)">
<g clip-path="url(#clip0_243_24834)">
<rect x="0" y="0" width="32" height="32" rx="12" fill="white"/>
<g id="Vector" filter="url(#filter1_d_243_24834)">
<path d="M16.5168,25c-1.8166,0 -3.4035,-0.4331 -4.7606,-1.2993c-1.3364,-0.8873 -2.2551,-2.081 -2.7562,-3.581l3.0694,-1.4894c0.4384,0.9718 1.0439,1.7324 1.8165,2.2817c0.7934,0.5493 1.6704,0.8239 2.6309,0.8239c0.7517,0 1.3467,-0.169 1.7852,-0.507c0.4385,-0.3381 0.6577,-0.7817 0.6577,-1.331c0,-0.338 -0.0939,-0.6127 -0.2818,-0.824c-0.1671,-0.2324 -0.4072,-0.4225 -0.7204,-0.5704c-0.2923,-0.169 -0.616,-0.3063 -0.9709,-0.412l-2.7875,-0.7922c-1.4407,-0.4225 -2.5369,-1.0669 -3.2886,-1.9331c-0.7308,-0.8662 -1.0962,-1.8908 -1.0962,-3.0739c0,-1.0564 0.261,-1.9754 0.783,-2.7571c0.5429,-0.8028 1.2841,-1.426 2.2237,-1.8697c0.9605,-0.4437 2.0567,-0.6655 3.2886,-0.6655c1.6078,0 3.0276,0.3908 4.2595,1.1725c1.2319,0.7817 2.1089,1.8803 2.6309,3.2958l-3.132,1.4894c-0.2923,-0.7816 -0.783,-1.4049 -1.472,-1.8697c-0.6891,-0.4648 -1.4616,-0.6972 -2.3177,-0.6972c-0.689,0 -1.2319,0.1585 -1.6286,0.4754c-0.3968,0.3169 -0.5951,0.7289 -0.5951,1.2359c0,0.3169 0.0835,0.5916 0.2505,0.824c0.1671,0.2324 0.3968,0.4225 0.6891,0.5704c0.3132,0.1479 0.6681,0.2852 1.0649,0.412l2.7248,0.8239c1.3989,0.4225 2.4743,1.0563 3.2259,1.9014c0.7726,0.8451 1.1589,1.8803 1.1589,3.1056c0,1.0352 -0.2715,1.9543 -0.8143,2.7571c-0.5429,0.7817 -1.2946,1.3943 -2.2551,1.838c-0.9604,0.4437 -2.088,0.6655 -3.3825,0.6655z" fill="#4F46E5" shape-rendering="crispEdges"/>
<path d="M16.1094,6.875c1.6289,0 3.0732,0.3967 4.3271,1.1924c1.2556,0.7968 2.1496,1.9174 2.6807,3.3574l0.04,0.1074l-0.1035,0.0489l-3.1318,1.4892l-0.1231,0.0586l-0.0478,-0.1269c-0.2833,-0.7575 -0.7575,-1.3604 -1.4248,-1.8106c-0.6678,-0.4504 -1.4161,-0.6758 -2.2481,-0.6758c-0.6706,0.0001 -1.183,0.1545 -1.5508,0.4483c-0.3669,0.2932 -0.5478,0.6698 -0.5478,1.1386c0.0001,0.2935 0.0775,0.5423 0.2275,0.751c0.1537,0.2137 0.3668,0.3912 0.6436,0.5313h-0.001c0.3068,0.1447 0.6563,0.2792 1.0479,0.4043h-0.001l2.7236,0.8232c1.4142,0.4271 2.5114,1.0713 3.2822,1.9375l0.1446,0.165c0.6986,0.8384 1.0468,1.8488 1.0469,3.0245c0,1.0583 -0.2782,2.0022 -0.836,2.8271l-0.001,0.001c-0.5569,0.8019 -1.3264,1.4289 -2.3047,1.8808c-0.9801,0.4528 -2.1268,0.6768 -3.4355,0.6768c-1.8372,0 -3.4484,-0.4388 -4.8281,-1.3193l-0.002,-0.001c-1.3589,-0.9025 -2.2946,-2.1189 -2.8047,-3.6455l-0.0351,-0.1035l0.0986,-0.0489l3.0693,-1.4892l0.1163,-0.0557l0.0527,0.1172c0.4303,0.9536 1.0217,1.6957 1.7734,2.2305l0.294,0.1875c0.6934,0.4092 1.4477,0.6142 2.2656,0.6142c0.7338,0 1.299,-0.1654 1.709,-0.4814c0.4081,-0.3146 0.6094,-0.7223 0.6094,-1.2324c-0.0001,-0.3135 -0.0874,-0.5563 -0.251,-0.7403l-0.0078,-0.0097l0.1015,-0.0733l-0.1015,0.0723c-0.1523,-0.2117 -0.374,-0.3896 -0.6719,-0.5303l-0.0098,-0.0039l0.0625,-0.1084l-0.0625,0.1074c-0.2826,-0.1633 -0.5965,-0.2974 -0.9424,-0.4004l-2.7871,-0.791l-0.0009,-0.001c-1.4566,-0.4271 -2.5762,-1.0817 -3.3477,-1.9707l-0.001,-0.0009c-0.7507,-0.8898 -1.1259,-1.9441 -1.1259,-3.1553c0,-1.0777 0.2664,-2.0215 0.8037,-2.8262l0.0009,-0.001c0.5563,-0.8224 1.3154,-1.4596 2.2735,-1.9121l0.001,-0.0009c0.9793,-0.4524 2.0938,-0.6768 3.3408,-0.6768z" stroke="url(#paint0_linear_243_24834)" stroke-width="0.25" shape-rendering="crispEdges"/>
</g>
</g>
<rect x="0.25" y="0.25" width="31.5" height="31.5" rx="11.75" stroke="#E2E8F0" stroke-width="0.5"/>
</g>
<g id="Vector_2">
<path d="M45.6431,24.5c-1.3708,0 -2.5664,-0.3095 -3.5869,-0.9285c-1.0053,-0.6337 -1.6907,-1.4811 -2.0562,-2.5422l2.5131,-1.1496c0.3199,0.6485 0.754,1.1569 1.3023,1.5254c0.5483,0.3684 1.1575,0.5526 1.8277,0.5526c0.4874,0 0.8606,-0.0958 1.1195,-0.2873c0.2589,-0.1916 0.3884,-0.4569 0.3884,-0.7959c0,-0.1768 -0.0457,-0.3242 -0.1371,-0.4421c-0.0914,-0.1327 -0.2285,-0.2506 -0.4112,-0.3537c-0.1828,-0.1032 -0.4113,-0.1916 -0.6854,-0.2653l-2.1248,-0.5748c-1.0205,-0.28 -1.8048,-0.7295 -2.3532,-1.3485c-0.5483,-0.6337 -0.8224,-1.378 -0.8224,-2.2328c0,-0.7516 0.198,-1.4074 0.594,-1.9674c0.396,-0.5601 0.9519,-0.9948 1.6678,-1.3043c0.7158,-0.3243 1.5383,-0.4864 2.4674,-0.4864c1.2185,0 2.2847,0.28 3.1985,0.8401c0.9291,0.5453 1.584,1.319 1.9648,2.3212l-2.536,1.1495c-0.1827,-0.5011 -0.5178,-0.899 -1.0052,-1.1937c-0.4722,-0.3095 -1.0129,-0.4643 -1.6221,-0.4643c-0.4417,0 -0.792,0.0884 -1.0509,0.2653c-0.2437,0.1768 -0.3656,0.42 -0.3656,0.7295c0,0.1621 0.0457,0.3095 0.1371,0.4421c0.0914,0.1327 0.2361,0.2506 0.4341,0.3538c0.2132,0.1031 0.4721,0.1989 0.7768,0.2873l1.9876,0.5748c1.0357,0.2948 1.8277,0.7443 2.376,1.3485c0.5484,0.5895 0.8225,1.319 0.8225,2.1886c0,0.7516 -0.2056,1.4074 -0.6168,1.9675c-0.396,0.56 -0.952,1.0021 -1.6678,1.3264c-0.7159,0.3095 -1.5612,0.4642 -2.536,0.4642z" fill="white"/>
<path d="M56.0074,24.3674c-1.5231,0 -2.6426,-0.3169 -3.3584,-0.9506c-0.7007,-0.6485 -1.051,-1.658 -1.051,-3.0286v-12.8882h3.427v12.645c0,0.899 0.5026,1.3485 1.5079,1.3485h0.0914v2.8739z" fill="white"/>
<path d="M63.792,24.5c-1.2337,0 -2.3608,-0.2726 -3.3813,-0.8179c-1.0052,-0.5453 -1.8125,-1.2896 -2.4217,-2.2328c-0.594,-0.958 -0.891,-2.0412 -0.891,-3.2497c0,-1.2232 0.297,-2.3064 0.891,-3.2497c0.6092,-0.9432 1.4165,-1.6874 2.4217,-2.2327c1.0205,-0.5453 2.1476,-0.818 3.3813,-0.818c1.2337,0 2.3532,0.2727 3.3585,0.818c1.0052,0.5453 1.8048,1.2895 2.3988,2.2327c0.6093,0.9433 0.9139,2.0265 0.9139,3.2497c0,1.2085 -0.3046,2.2917 -0.9139,3.2497c-0.594,0.9432 -1.3936,1.6875 -2.3988,2.2328c-1.0053,0.5453 -2.1248,0.8179 -3.3585,0.8179zM63.792,21.5156c0.6245,0 1.1652,-0.14 1.6221,-0.42c0.4722,-0.28 0.8377,-0.6706 1.0967,-1.1717c0.2741,-0.5011 0.4112,-1.0758 0.4112,-1.7243c0,-0.6485 -0.1371,-1.2159 -0.4112,-1.7022c-0.259,-0.5011 -0.6245,-0.8916 -1.0967,-1.1717c-0.4569,-0.2947 -0.9976,-0.4421 -1.6221,-0.4421c-0.6245,0 -1.1728,0.1474 -1.6449,0.4421c-0.4722,0.2801 -0.8454,0.6706 -1.1195,1.1717c-0.2589,0.4863 -0.3884,1.0537 -0.3884,1.7022c0,0.6485 0.1295,1.2232 0.3884,1.7243c0.2741,0.5011 0.6473,0.8917 1.1195,1.1717c0.4721,0.28 1.0204,0.42 1.6449,0.42z" fill="white"/>
<path d="M77.3538,24.3674c-1.5079,0 -2.6807,-0.3906 -3.5184,-1.1717c-0.8225,-0.7958 -1.2337,-1.9012 -1.2337,-3.316v-4.8413h-2.1019v-2.8739h0.1142c0.6397,0 1.1271,-0.1547 1.4622,-0.4642c0.3503,-0.3095 0.5255,-0.7738 0.5255,-1.3928v-0.8842h3.427v2.7412h2.9243v2.8739h-2.9243v4.6202c0,0.4127 0.0761,0.759 0.2284,1.0391c0.1523,0.2652 0.3884,0.4642 0.7083,0.5968c0.3198,0.1327 0.7158,0.199 1.188,0.199c0.1066,0 0.2285,-0.0074 0.3655,-0.0221c0.1371,-0.0147 0.2818,-0.0295 0.4341,-0.0442v2.8075c-0.2284,0.0295 -0.4874,0.059 -0.7768,0.0884c-0.2893,0.0295 -0.5635,0.0443 -0.8224,0.0443z" fill="white"/>
<path d="M80.0473,24.2347v-16.7347h3.427v4.6645h2.4907c1.142,0 2.124,0.1916 2.947,0.5748c0.822,0.3832 1.454,0.9284 1.896,1.6359c0.442,0.7074 0.663,1.5474 0.663,2.5201v7.3394h-3.427v-7.1625c0,-0.619 -0.191,-1.1127 -0.572,-1.4812c-0.38,-0.3684 -0.883,-0.5526 -1.507,-0.5526h-2.4907v9.1963z" fill="white"/>
<path d="M98.271,24.5c-1.143,0 -2.14,-0.2137 -2.993,-0.6411c-0.853,-0.4274 -1.523,-1.0243 -2.011,-1.7906c-0.472,-0.7664 -0.708,-1.658 -0.708,-2.6749v-7.2289h3.427v7.1625c0,0.4422 0.091,0.8254 0.274,1.1496c0.198,0.3242 0.472,0.5821 0.822,0.7737c0.351,0.1769 0.747,0.2653 1.189,0.2653c0.456,0 0.852,-0.0884 1.188,-0.2653c0.35,-0.1916 0.616,-0.4495 0.799,-0.7737c0.198,-0.3242 0.297,-0.7074 0.297,-1.1496v-7.1625h3.427v7.2289c0,1.0169 -0.244,1.9085 -0.731,2.6749c-0.472,0.7663 -1.135,1.3632 -1.988,1.7906c-0.853,0.4274 -1.85,0.6411 -2.992,0.6411z" fill="white"/>
<path d="M105.573,24.2347v-12.0702h3.427v12.0702zM105.573,11.0813v-3.316h3.427v3.316z" fill="white"/>
</g>
</g></svg>
<svg viewBox="0 0 16.8947 16.8955" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_3" d="M16.6165,15.2901l-3.7102,-3.712c1.1125,-1.449 1.6318,-3.268 1.4528,-5.086c-0.1791,-1.819 -1.0431,-3.501 -2.4169,-4.706c-1.3738,-1.205 -3.1545,-1.842 -4.9808,-1.782c-1.8263,0.06 -3.5615,0.812 -4.8535,2.104c-1.2921,1.292 -2.0443,3.027 -2.1041,4.853c-0.0598,1.827 0.5774,3.607 1.7822,4.981c1.2048,1.374 2.8871,2.238 4.7056,2.417c1.8185,0.179 3.637,-0.34 5.0866,-1.453l3.7133,3.714c0.0872,0.088 0.1907,0.157 0.3047,0.204c0.1139,0.047 0.236,0.071 0.3594,0.071c0.1233,0 0.2454,-0.024 0.3593,-0.071c0.114,-0.047 0.2175,-0.116 0.3047,-0.204c0.0872,-0.087 0.1564,-0.19 0.2036,-0.304c0.0472,-0.114 0.0715,-0.236 0.0715,-0.36c0,-0.123 -0.0243,-0.245 -0.0715,-0.359c-0.0472,-0.114 -0.1164,-0.218 -0.2036,-0.305zM1.8907,7.2031c0,-1.051 0.3116,-2.078 0.8953,-2.951c0.5838,-0.874 1.4135,-1.555 2.3842,-1.957c0.9707,-0.402 2.0389,-0.507 3.0694,-0.302c1.0306,0.205 1.9772,0.711 2.7201,1.454c0.743,0.743 1.249,1.689 1.4539,2.72c0.205,1.03 0.0998,2.098 -0.3023,3.069c-0.4021,0.971 -1.083,1.801 -1.9566,2.384c-0.8737,0.584 -1.9008,0.896 -2.9515,0.896c-1.4085,-0.002 -2.7589,-0.562 -3.7549,-1.558c-0.996,-0.996 -1.5561,-2.346 -1.5576,-3.755z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 18.75 19.5005" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_4" d="M18.2006,8.049l-7.5,-7.5c-0.3516,-0.352 -0.8284,-0.549 -1.3256,-0.549c-0.4972,0 -0.974,0.197 -1.3256,0.549l-7.5,7.5c-0.1748,0.174 -0.3134,0.38 -0.4077,0.608c-0.0943,0.228 -0.1425,0.472 -0.1417,0.719v9c0,0.298 0.1185,0.584 0.3295,0.795c0.211,0.211 0.4971,0.33 0.7955,0.33h16.5c0.2984,0 0.5845,-0.119 0.7955,-0.33c0.211,-0.211 0.3295,-0.497 0.3295,-0.795v-9c0.0008,-0.247 -0.0474,-0.491 -0.1417,-0.719c-0.0943,-0.228 -0.2328,-0.434 -0.4077,-0.608zM16.5,17.251h-14.25v-7.721l7.125,-7.125l7.125,7.125z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 18.75 15" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_5" d="M4.5,1.5c0,-0.298 0.1185,-0.585 0.3295,-0.795c0.211,-0.211 0.4971,-0.33 0.7955,-0.33h12c0.2984,0 0.5845,0.119 0.7955,0.33c0.211,0.21 0.3295,0.497 0.3295,0.795c0,0.298 -0.1185,0.585 -0.3295,0.795c-0.211,0.211 -0.4971,0.33 -0.7955,0.33h-12c-0.2984,0 -0.5845,-0.119 -0.7955,-0.33c-0.211,-0.21 -0.3295,-0.497 -0.3295,-0.795zM17.625,6.375h-12c-0.2984,0 -0.5845,0.119 -0.7955,0.33c-0.211,0.21 -0.3295,0.497 -0.3295,0.795c0,0.298 0.1185,0.585 0.3295,0.795c0.211,0.211 0.4971,0.33 0.7955,0.33h12c0.2984,0 0.5845,-0.119 0.7955,-0.33c0.211,-0.21 0.3295,-0.497 0.3295,-0.795c0,-0.298 -0.1185,-0.585 -0.3295,-0.795c-0.211,-0.211 -0.4971,-0.33 -0.7955,-0.33zM17.625,12.375h-12c-0.2984,0 -0.5845,0.119 -0.7955,0.33c-0.211,0.21 -0.3295,0.497 -0.3295,0.795c0,0.298 0.1185,0.585 0.3295,0.795c0.211,0.211 0.4971,0.33 0.7955,0.33h12c0.2984,0 0.5845,-0.119 0.7955,-0.33c0.211,-0.21 0.3295,-0.497 0.3295,-0.795c0,-0.298 -0.1185,-0.585 -0.3295,-0.795c-0.211,-0.211 -0.4971,-0.33 -0.7955,-0.33zM1.5,6c-0.2967,0 -0.5867,0.088 -0.8334,0.253c-0.2466,0.165 -0.4389,0.399 -0.5524,0.673c-0.1136,0.274 -0.1433,0.576 -0.0854,0.867c0.0579,0.291 0.2008,0.558 0.4105,0.768c0.2098,0.209 0.4771,0.352 0.7681,0.41c0.2909,0.058 0.5925,0.028 0.8666,-0.085c0.2741,-0.114 0.5084,-0.306 0.6732,-0.553c0.1648,-0.246 0.2528,-0.536 0.2528,-0.833c0,-0.398 -0.158,-0.779 -0.4393,-1.061c-0.2813,-0.281 -0.6629,-0.439 -1.0607,-0.439zM1.5,0c-0.2967,0 -0.5867,0.088 -0.8334,0.253c-0.2466,0.165 -0.4389,0.399 -0.5524,0.673c-0.1136,0.274 -0.1433,0.576 -0.0854,0.867c0.0579,0.291 0.2008,0.558 0.4105,0.768c0.2098,0.209 0.4771,0.352 0.7681,0.41c0.2909,0.058 0.5925,0.028 0.8666,-0.085c0.2741,-0.114 0.5084,-0.306 0.6732,-0.553c0.1648,-0.246 0.2528,-0.536 0.2528,-0.833c0,-0.398 -0.158,-0.779 -0.4393,-1.061c-0.2813,-0.281 -0.6629,-0.439 -1.0607,-0.439zM1.5,12c-0.2967,0 -0.5867,0.088 -0.8334,0.253c-0.2466,0.165 -0.4389,0.399 -0.5524,0.673c-0.1136,0.274 -0.1433,0.576 -0.0854,0.867c0.0579,0.291 0.2008,0.558 0.4105,0.768c0.2098,0.209 0.4771,0.352 0.7681,0.41c0.2909,0.058 0.5925,0.028 0.8666,-0.085c0.2741,-0.114 0.5084,-0.306 0.6732,-0.553c0.1648,-0.246 0.2528,-0.536 0.2528,-0.833c0,-0.398 -0.158,-0.779 -0.4393,-1.061c-0.2813,-0.281 -0.6629,-0.439 -1.0607,-0.439z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 23.8099 15.3841" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_6" d="M11.6415,10.5929c0.9422,-0.792 1.6181,-1.855 1.9359,-3.044c0.3178,-1.189 0.2622,-2.447 -0.1593,-3.603c-0.4214,-1.157 -1.1884,-2.156 -2.1968,-2.862c-1.0085,-0.705 -2.2096,-1.084 -3.4404,-1.084c-1.2309,0 -2.432,0.379 -3.4405,1.084c-1.0084,0.706 -1.7753,1.705 -2.1968,2.862c-0.4215,1.156 -0.4771,2.414 -0.1593,3.603c0.3178,1.189 0.9937,2.252 1.9359,3.044c-1.4713,0.665 -2.7464,1.699 -3.7021,3c-0.1766,0.241 -0.2503,0.541 -0.2051,0.836c0.0453,0.295 0.2059,0.56 0.4465,0.737c0.2405,0.176 0.5414,0.25 0.8363,0.205c0.2949,-0.046 0.5598,-0.206 0.7363,-0.447c0.6616,-0.903 1.527,-1.638 2.5258,-2.145c0.9988,-0.506 2.103,-0.77 3.223,-0.77c1.1199,0 2.2241,0.264 3.2229,0.77c0.9988,0.507 1.8642,1.242 2.5258,2.145c0.1765,0.241 0.4415,0.402 0.7365,0.447c0.295,0.045 0.596,-0.028 0.8366,-0.205c0.2407,-0.176 0.4014,-0.441 0.4468,-0.736c0.0453,-0.295 -0.0284,-0.596 -0.2049,-0.837c-0.9564,-1.301 -2.2317,-2.334 -3.7031,-3zM4.0309,6.0049c0,-0.742 0.2199,-1.467 0.632,-2.083c0.412,-0.617 0.9977,-1.098 1.6829,-1.382c0.6852,-0.283 1.4392,-0.358 2.1667,-0.213c0.7274,0.145 1.3956,0.502 1.92,1.026c0.5245,0.525 0.8816,1.193 1.0263,1.92c0.1447,0.728 0.0704,1.482 -0.2134,2.167c-0.2838,0.685 -0.7645,1.271 -1.3811,1.683c-0.6167,0.412 -1.3418,0.632 -2.0834,0.632c-0.9946,0 -1.9484,-0.395 -2.6517,-1.098c-0.7032,-0.704 -1.0983,-1.657 -1.0983,-2.652zM23.3527,15.1619c-0.1191,0.087 -0.2542,0.15 -0.3978,0.185c-0.1435,0.036 -0.2926,0.042 -0.4386,0.02c-0.1461,-0.022 -0.2864,-0.073 -0.4128,-0.15c-0.1264,-0.077 -0.2364,-0.177 -0.3239,-0.296c-0.6633,-0.902 -1.5289,-1.636 -2.5274,-2.142c-0.9984,-0.507 -2.1018,-0.772 -3.2213,-0.774c-0.2984,0 -0.5846,-0.119 -0.7955,-0.33c-0.211,-0.211 -0.3295,-0.497 -0.3295,-0.795c0,-0.298 0.1185,-0.585 0.3295,-0.796c0.2109,-0.211 0.4971,-0.329 0.7955,-0.329c0.533,-0.001 1.0596,-0.116 1.5449,-0.336c0.4852,-0.221 0.9179,-0.542 1.2691,-0.943c0.3512,-0.401 0.6129,-0.872 0.7677,-1.382c0.1548,-0.51 0.199,-1.047 0.1298,-1.576c-0.0692,-0.528 -0.2502,-1.036 -0.5311,-1.489c-0.2808,-0.453 -0.655,-0.841 -1.0976,-1.138c-0.4426,-0.297 -0.9434,-0.496 -1.4691,-0.584c-0.5257,-0.089 -1.0641,-0.064 -1.5794,0.073c-0.1439,0.041 -0.2947,0.054 -0.4435,0.036c-0.1488,-0.017 -0.2926,-0.064 -0.423,-0.138c-0.1304,-0.074 -0.2447,-0.173 -0.3363,-0.291c-0.0916,-0.119 -0.1586,-0.254 -0.1971,-0.399c-0.0385,-0.145 -0.0476,-0.296 -0.0269,-0.444c0.0207,-0.149 0.0708,-0.292 0.1474,-0.42c0.0766,-0.129 0.1782,-0.241 0.2987,-0.33c0.1205,-0.089 0.2576,-0.153 0.4032,-0.188c1.3177,-0.35 2.7149,-0.24 3.9617,0.311c1.2468,0.551 2.2689,1.51 2.8979,2.719c0.6291,1.21 0.8277,2.597 0.5631,3.934c-0.2647,1.337 -0.9767,2.545 -2.0189,3.423c1.4713,0.665 2.7464,1.699 3.7022,3c0.1752,0.24 0.2483,0.54 0.2031,0.834c-0.0451,0.294 -0.2048,0.558 -0.4441,0.735z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 20.25 21.7498" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_7" d="M19.275,4.747l-8.25,-4.516c-0.2757,-0.151 -0.5853,-0.231 -0.9,-0.231c-0.3147,0 -0.6243,0.08 -0.9,0.231l-8.25,4.516c-0.2952,0.162 -0.5415,0.4 -0.713,0.689c-0.1716,0.29 -0.2621,0.62 -0.262,0.957v8.964c-0.0001,0.336 0.0904,0.667 0.262,0.956c0.1715,0.29 0.4178,0.528 0.713,0.689l8.25,4.516c0.2756,0.152 0.5853,0.232 0.9,0.232c0.3147,0 0.6244,-0.08 0.9,-0.232l8.25,-4.516c0.2952,-0.161 0.5415,-0.399 0.713,-0.689c0.1716,-0.289 0.2621,-0.62 0.262,-0.956v-8.964c0.0001,-0.337 -0.0904,-0.667 -0.262,-0.957c-0.1715,-0.289 -0.4178,-0.527 -0.713,-0.689zM10.125,2.303l6.75,3.697l-6.75,3.693l-6.75,-3.693zM2.25,7.949l6.75,3.694v7.187l-6.75,-3.695zM11.25,18.83v-7.187l6.75,-3.694v7.186z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 21.75 15.75" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_8" d="M19.875,0h-18c-0.4973,0 -0.9742,0.198 -1.3258,0.549c-0.3517,0.352 -0.5492,0.829 -0.5492,1.326v12c0,0.497 0.1975,0.974 0.5492,1.326c0.3516,0.351 0.8285,0.549 1.3258,0.549h18c0.4973,0 0.9742,-0.198 1.3258,-0.549c0.3517,-0.352 0.5492,-0.829 0.5492,-1.326v-12c0,-0.497 -0.1975,-0.974 -0.5492,-1.326c-0.3516,-0.351 -0.8285,-0.549 -1.3258,-0.549zM19.5,2.25v1.875h-17.25v-1.875zM2.25,13.5v-7.125h17.25v7.125zM18.375,11.25c0,0.298 -0.1185,0.585 -0.3295,0.795c-0.211,0.211 -0.4971,0.33 -0.7955,0.33h-3c-0.2984,0 -0.5845,-0.119 -0.7955,-0.33c-0.211,-0.21 -0.3295,-0.497 -0.3295,-0.795c0,-0.298 0.1185,-0.585 0.3295,-0.795c0.211,-0.211 0.4971,-0.33 0.7955,-0.33h3c0.2984,0 0.5845,0.119 0.7955,0.33c0.211,0.21 0.3295,0.497 0.3295,0.795zM12,11.25c0,0.298 -0.1185,0.585 -0.3295,0.795c-0.211,0.211 -0.4971,0.33 -0.7955,0.33h-1.125c-0.2984,0 -0.5845,-0.119 -0.7955,-0.33c-0.211,-0.21 -0.3295,-0.497 -0.3295,-0.795c0,-0.298 0.1185,-0.585 0.3295,-0.795c0.211,-0.211 0.4971,-0.33 0.7955,-0.33h1.125c0.2984,0 0.5845,0.119 0.7955,0.33c0.211,0.21 0.3295,0.497 0.3295,0.795z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 21.746 20.25" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_9" d="M10.8721,5.25c-0.9642,0 -1.9067,0.286 -2.7084,0.822c-0.8017,0.535 -1.4265,1.297 -1.7955,2.187c-0.369,0.891 -0.4655,1.871 -0.2774,2.817c0.1881,0.946 0.6524,1.814 1.3342,2.496c0.6817,0.682 1.5504,1.146 2.496,1.334c0.9457,0.188 1.9259,0.092 2.8167,-0.277c0.8908,-0.369 1.6521,-0.994 2.1878,-1.796c0.5357,-0.801 0.8216,-1.744 0.8216,-2.708c-0.0015,-1.292 -0.5156,-2.532 -1.4295,-3.446c-0.9139,-0.913 -2.153,-1.428 -3.4455,-1.429zM10.8721,12.75c-0.5192,0 -1.0267,-0.154 -1.4584,-0.442c-0.4316,-0.289 -0.7681,-0.699 -0.9668,-1.178c-0.1987,-0.48 -0.2506,-1.008 -0.1494,-1.517c0.1013,-0.509 0.3513,-0.977 0.7185,-1.344c0.3671,-0.367 0.8348,-0.617 1.344,-0.719c0.5092,-0.101 1.037,-0.049 1.5167,0.15c0.4796,0.198 0.8896,0.535 1.178,0.967c0.2885,0.431 0.4424,0.939 0.4424,1.458c0,0.696 -0.2766,1.364 -0.7688,1.856c-0.4923,0.492 -1.16,0.769 -1.8562,0.769zM21.5465,8.103c-0.0317,-0.159 -0.0972,-0.308 -0.192,-0.439c-0.0948,-0.131 -0.2167,-0.24 -0.3574,-0.32l-2.6053,-1.486l-0.0103,-2.935c-0.0006,-0.163 -0.0365,-0.324 -0.1053,-0.471c-0.0688,-0.148 -0.1688,-0.279 -0.2932,-0.384c-1.0499,-0.888 -2.2587,-1.569 -3.5625,-2.007c-0.1487,-0.05 -0.3062,-0.069 -0.4625,-0.054c-0.1563,0.014 -0.3079,0.061 -0.445,0.137l-2.6409,1.475l-2.6409,-1.476c-0.1373,-0.076 -0.2891,-0.124 -0.4458,-0.138c-0.1566,-0.015 -0.3146,0.004 -0.4636,0.054c-1.3041,0.44 -2.513,1.123 -3.5625,2.013c-0.1239,0.105 -0.2236,0.236 -0.2923,0.383c-0.0686,0.147 -0.1045,0.308 -0.1052,0.47l-0.0131,2.938l-2.6016,1.482c-0.1407,0.08 -0.2625,0.189 -0.3572,0.321c-0.0946,0.131 -0.1599,0.281 -0.1912,0.44c-0.2649,1.334 -0.2649,2.707 0,4.041c0.0316,0.159 0.0969,0.308 0.1916,0.439c0.0946,0.131 0.2163,0.24 0.3568,0.32l2.6081,1.486l0.0103,2.935c0.0006,0.163 0.0365,0.324 0.1053,0.471c0.0688,0.148 0.1688,0.279 0.2932,0.384c1.0499,0.888 2.2587,1.569 3.5625,2.007c0.1487,0.05 0.3063,0.069 0.4626,0.054c0.1562,-0.014 0.3078,-0.061 0.4449,-0.137l2.6381,-1.475l2.6381,1.476c0.1373,0.076 0.2891,0.124 0.4458,0.138c0.1566,0.015 0.3146,-0.004 0.4636,-0.054c1.3041,-0.44 2.513,-1.123 3.5625,-2.013c0.1239,-0.105 0.2236,-0.236 0.2923,-0.383c0.0686,-0.147 0.1045,-0.308 0.1052,-0.47l0.0131,-2.938l2.6072,-1.482c0.1407,-0.08 0.2626,-0.189 0.3572,-0.321c0.0947,-0.131 0.1599,-0.281 0.1913,-0.44c0.2642,-1.334 0.2636,-2.707 -0.0019,-4.041zM19.4249,11.213l-2.52,1.433c-0.179,0.101 -0.3266,0.25 -0.4265,0.43c-0.0507,0.094 -0.1041,0.181 -0.1594,0.27c-0.1111,0.178 -0.1705,0.382 -0.1716,0.592l-0.0131,2.843c-0.6065,0.457 -1.2707,0.831 -1.9753,1.113l-2.5453,-1.424c-0.1679,-0.094 -0.357,-0.143 -0.5494,-0.143h-0.0272c-0.1069,0 -0.2156,0 -0.3225,0c-0.201,-0.005 -0.3997,0.044 -0.5756,0.141l-2.5481,1.421c-0.7058,-0.28 -1.3716,-0.653 -1.98,-1.108l-0.0104,-2.835c-0.0008,-0.209 -0.0602,-0.415 -0.1715,-0.592c-0.0544,-0.088 -0.1088,-0.179 -0.1594,-0.27c-0.1,-0.179 -0.2472,-0.327 -0.4256,-0.428l-2.5219,-1.441c-0.098,-0.723 -0.098,-1.456 0,-2.178l2.52,-1.433c0.1787,-0.101 0.3263,-0.25 0.4266,-0.429c0.0506,-0.094 0.104,-0.182 0.1593,-0.271c0.1111,-0.178 0.1706,-0.383 0.1716,-0.592l0.0103,-2.843c0.6067,-0.455 1.2709,-0.828 1.9753,-1.109l2.5453,1.424c0.1759,0.098 0.3751,0.148 0.5766,0.142c0.1069,0 0.2156,0 0.3225,0c0.201,0.005 0.3997,-0.044 0.5756,-0.141l2.5482,-1.424c0.7057,0.28 1.3715,0.653 1.98,1.108l0.0103,2.835c0.0008,0.209 0.0603,0.415 0.1715,0.592c0.0544,0.088 0.1088,0.179 0.1594,0.27c0.1,0.179 0.2473,0.327 0.4256,0.428l2.5219,1.437c0.0993,0.724 0.1002,1.458 0.0028,2.182z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 19.5 19.5" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_10" d="M9.75,0c-2.585,0.003 -5.0634,1.031 -6.8913,2.859c-1.8279,1.828 -2.856,4.306 -2.8587,6.891v7.875c0,0.497 0.1975,0.974 0.5492,1.326c0.3516,0.351 0.8285,0.549 1.3258,0.549h7.875c2.5859,0 5.0658,-1.027 6.8943,-2.856c1.8285,-1.828 2.8557,-4.308 2.8557,-6.894c0,-2.586 -1.0272,-5.066 -2.8557,-6.894c-1.8285,-1.829 -4.3084,-2.856 -6.8943,-2.856zM9.75,17.25h-7.5v-7.5c0,-1.483 0.4399,-2.933 1.264,-4.167c0.8241,-1.233 1.9954,-2.194 3.3659,-2.762c1.3704,-0.568 2.8784,-0.716 4.3333,-0.427c1.4548,0.289 2.7912,1.004 3.8401,2.053c1.0489,1.049 1.7632,2.385 2.0526,3.84c0.2894,1.455 0.1409,2.963 -0.4268,4.333c-0.5677,1.371 -1.529,2.542 -2.7623,3.366c-1.2334,0.824 -2.6834,1.264 -4.1668,1.264zM9,10.125c0,0.297 -0.088,0.587 -0.2528,0.833c-0.1648,0.247 -0.3991,0.439 -0.6732,0.553c-0.2741,0.113 -0.5757,0.143 -0.8666,0.085c-0.291,-0.058 -0.5583,-0.201 -0.7681,-0.41c-0.2097,-0.21 -0.3526,-0.477 -0.4105,-0.768c-0.0579,-0.291 -0.0282,-0.593 0.0854,-0.867c0.1135,-0.274 0.3058,-0.508 0.5524,-0.673c0.2467,-0.165 0.5367,-0.253 0.8334,-0.253c0.3978,0 0.7794,0.158 1.0607,0.439c0.2813,0.282 0.4393,0.663 0.4393,1.061zM13.5,10.125c0,0.297 -0.088,0.587 -0.2528,0.833c-0.1648,0.247 -0.3991,0.439 -0.6732,0.553c-0.2741,0.113 -0.5757,0.143 -0.8666,0.085c-0.291,-0.058 -0.5583,-0.201 -0.7681,-0.41c-0.2097,-0.21 -0.3526,-0.477 -0.4105,-0.768c-0.0579,-0.291 -0.0282,-0.593 0.0854,-0.867c0.1135,-0.274 0.3058,-0.508 0.5524,-0.673c0.2467,-0.165 0.5367,-0.253 0.8334,-0.253c0.3978,0 0.7794,0.158 1.0607,0.439c0.2813,0.282 0.4393,0.663 0.4393,1.061z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 248 40" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="Frame_16" xmlns="http://www.w3.org/2000/svg">
<g id="Frame_17">
<g id="Icon Container">
<rect x="0" y="0" width="40" height="40" rx="20" fill="currentColor"/>
<g id="Warning">
<path id="Vector_11" d="M28.7704,24.539l-6.8321,-11.865c-0.1979,-0.338 -0.4809,-0.619 -0.821,-0.814c-0.34,-0.195 -0.7252,-0.298 -1.1172,-0.298c-0.3921,0 -0.7773,0.103 -1.1173,0.298c-0.3401,0.195 -0.6231,0.476 -0.821,0.814l-6.8321,11.865c-0.192,0.329 -0.2932,0.702 -0.2932,1.083c0,0.38 0.1012,0.754 0.2932,1.082c0.1957,0.34 0.4784,0.622 0.819,0.817c0.3406,0.195 0.7269,0.296 1.1193,0.292h13.6641c0.3921,0.003 0.7781,-0.098 1.1184,-0.292c0.3403,-0.195 0.6227,-0.477 0.8183,-0.817c0.1923,-0.328 0.2937,-0.702 0.294,-1.082c0.0003,-0.38 -0.1007,-0.754 -0.2924,-1.083zM27.1454,25.766c-0.0318,0.054 -0.0776,0.099 -0.1326,0.129c-0.055,0.03 -0.1172,0.045 -0.1799,0.043h-13.6649c-0.0627,0.002 -0.1249,-0.013 -0.1799,-0.043c-0.055,-0.03 -0.1008,-0.075 -0.1326,-0.129c-0.0269,-0.044 -0.0412,-0.094 -0.0412,-0.146c0,-0.051 0.0143,-0.102 0.0412,-0.146l6.8321,-11.864c0.0338,-0.052 0.0801,-0.095 0.1347,-0.125c0.0545,-0.03 0.1157,-0.045 0.1778,-0.045c0.062,0 0.1232,0.015 0.1777,0.045c0.0546,0.03 0.1009,0.073 0.1348,0.125l6.8312,11.864c0.0272,0.044 0.0417,0.094 0.042,0.146c0.0003,0.051 -0.0137,0.102 -0.0404,0.146zM19.0626,20.625v-2.5c0,-0.248 0.0987,-0.487 0.2745,-0.663c0.1759,-0.175 0.4143,-0.274 0.663,-0.274c0.2486,0 0.487,0.099 0.6629,0.274c0.1758,0.176 0.2746,0.415 0.2746,0.663v2.5c0,0.249 -0.0988,0.487 -0.2746,0.663c-0.1759,0.176 -0.4143,0.275 -0.6629,0.275c-0.2487,0 -0.4871,-0.099 -0.663,-0.275c-0.1758,-0.176 -0.2745,-0.414 -0.2745,-0.663zM21.2501,23.75c0,0.247 -0.0734,0.489 -0.2107,0.695c-0.1374,0.205 -0.3326,0.365 -0.561,0.46c-0.2284,0.095 -0.4797,0.119 -0.7222,0.071c-0.2425,-0.048 -0.4652,-0.167 -0.64,-0.342c-0.1748,-0.175 -0.2939,-0.397 -0.3421,-0.64c-0.0483,-0.242 -0.0235,-0.494 0.0711,-0.722c0.0946,-0.229 0.2548,-0.424 0.4604,-0.561c0.2055,-0.137 0.4472,-0.211 0.6945,-0.211c0.3315,0 0.6494,0.132 0.8838,0.366c0.2345,0.235 0.3662,0.553 0.3662,0.884z" fill="white"/>
</g>
</g>
</g>
<g id="X">
<path id="Vector_12" d="M244.288,14.961c0.176,0.176 0.275,0.415 0.275,0.664c0,0.249 -0.099,0.488 -0.275,0.664c-0.176,0.176 -0.415,0.275 -0.664,0.275c-0.249,0 -0.488,-0.099 -0.664,-0.275l-4.96,-4.962l-4.962,4.961c-0.176,0.176 -0.415,0.275 -0.664,0.275c-0.249,0 -0.488,-0.099 -0.664,-0.275c-0.176,-0.177 -0.275,-0.415 -0.275,-0.664c0,-0.25 0.099,-0.488 0.275,-0.665l4.962,-4.96l-4.96,-4.961c-0.176,-0.177 -0.275,-0.415 -0.275,-0.664c0,-0.25 0.099,-0.488 0.275,-0.665c0.176,-0.176 0.415,-0.275 0.664,-0.275c0.249,0 0.488,0.099 0.664,0.275l4.96,4.962l4.962,-4.962c0.176,-0.176 0.415,-0.275 0.664,-0.275c0.249,0 0.488,0.099 0.664,0.275c0.176,0.176 0.275,0.415 0.275,0.664c0,0.249 -0.099,0.488 -0.275,0.664l-4.962,4.962z" fill="white" fill-opacity="0.8"/>
</g>
</g></svg>
<svg viewBox="0 0 18.7514 18.75" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_13" d="M1.125,0.045h6c0.286,0 0.561,0.12 0.763,0.32c0.202,0.2 0.315,0.47 0.315,0.76c0,0.29 -0.113,0.56 -0.315,0.76c-0.202,0.2 -0.477,0.32 -0.763,0.32h-4.922v14.34h4.922c0.286,0 0.561,0.12 0.763,0.32c0.202,0.2 0.315,0.47 0.315,0.76c0,0.29 -0.113,0.56 -0.315,0.76c-0.202,0.2 -0.477,0.32 -0.763,0.32h-6c-0.286,0 -0.561,-0.12 -0.763,-0.32c-0.202,-0.2 -0.315,-0.47 -0.315,-0.76v-16.5c0,-0.29 0.113,-0.56 0.315,-0.76c0.202,-0.2 0.477,-0.32 0.763,-0.32zM13.874,4.545c0.286,0 0.561,0.11 0.764,0.32l3.75,3.75c0.1,0.1 0.18,0.22 0.234,0.35c0.054,0.13 0.082,0.27 0.082,0.41c0,0.14 -0.028,0.28 -0.082,0.41c-0.041,0.1 -0.096,0.19 -0.164,0.28l-0.071,0.07l-3.75,3.75c-0.203,0.21 -0.478,0.32 -0.764,0.32c-0.286,0 -0.561,-0.11 -0.764,-0.32c-0.202,-0.2 -0.316,-0.47 -0.316,-0.76c0,-0.29 0.114,-0.56 0.316,-0.76l1.83,-1.83l0.081,-0.08h-7.895c-0.286,0 -0.561,-0.12 -0.763,-0.32c-0.202,-0.2 -0.315,-0.47 -0.315,-0.76c0,-0.29 0.113,-0.56 0.315,-0.76c0.202,-0.2 0.477,-0.32 0.763,-0.32h7.895l-0.081,-0.08l-1.829,-1.83c-0.202,-0.2 -0.316,-0.47 -0.316,-0.76c0,-0.29 0.114,-0.56 0.316,-0.76c0.203,-0.21 0.478,-0.32 0.764,-0.32z" fill="currentColor" stroke="currentColor" stroke-width="0.09375" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 17.25 20.2509" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_15" d="M1.875,7.5479h1.87c0.29,0 0.57,0.1132 0.77,0.3154c0.2,0.2022 0.31,0.4768 0.31,0.7627c0,0.2859 -0.11,0.5605 -0.31,0.7627c-0.2,0.2021 -0.48,0.3154 -0.77,0.3154h-1.54v8.3438h12.84v-8.3438h-1.54c-0.29,0 -0.57,-0.1133 -0.77,-0.3154c-0.2,-0.2022 -0.31,-0.4768 -0.31,-0.7627c0,-0.2859 0.11,-0.5605 0.31,-0.7627c0.2,-0.2022 0.48,-0.3154 0.77,-0.3154h1.87c0.48,0 0.95,0.1923 1.29,0.5351c0.35,0.3428 0.54,0.8081 0.54,1.293v9c0,0.4848 -0.19,0.9501 -0.54,1.2929c-0.34,0.3429 -0.81,0.5352 -1.29,0.5352h-13.5c-0.48,0 -0.95,-0.1923 -1.29,-0.5352c-0.35,-0.3428 -0.54,-0.8081 -0.54,-1.2929v-9c0,-0.4849 0.19,-0.9502 0.54,-1.293c0.34,-0.3428 0.81,-0.5351 1.29,-0.5351zM8.625,0.0469c0.14,0 0.28,0.0277 0.41,0.082c0.1,0.0409 0.19,0.0965 0.28,0.1641l0.07,0.0713l3.75,3.75c0.1,0.1002 0.18,0.2195 0.24,0.3505c0.05,0.131 0.08,0.2714 0.08,0.4131c0,0.1418 -0.03,0.2822 -0.08,0.4131c-0.06,0.1311 -0.14,0.2503 -0.24,0.3506c-0.1,0.1003 -0.21,0.1801 -0.35,0.2344c-0.13,0.0542 -0.27,0.082 -0.41,0.082c-0.14,0 -0.28,-0.0278 -0.41,-0.082c-0.13,-0.0543 -0.25,-0.1342 -0.35,-0.2344l-1.83,-1.8301l-0.08,-0.0801v7.8946c0,0.2859 -0.12,0.5605 -0.32,0.7627c-0.2,0.2021 -0.47,0.3154 -0.76,0.3154c-0.29,0 -0.56,-0.1133 -0.76,-0.3154c-0.2,-0.2022 -0.32,-0.4768 -0.32,-0.7627v-7.8946l-0.08,0.0801l-1.83,1.8272l-0.02,0.0273v0.001c-0.2,0.1856 -0.47,0.291 -0.74,0.291c-0.29,0 -0.56,-0.1139 -0.76,-0.3164c-0.21,-0.2025 -0.32,-0.4773 -0.32,-0.7637c0,-0.1417 0.03,-0.2821 0.08,-0.4131c0.06,-0.131 0.14,-0.2503 0.24,-0.3505l3.75,-3.75c0.1,-0.1006 0.22,-0.181 0.35,-0.2354c0.13,-0.0543 0.27,-0.082 0.41,-0.082z" fill="currentColor" stroke="currentColor" stroke-width="0.09375" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 19.3816 14.375" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_16" d="M15.0441,9.0245c0.18,0.176 0.28,0.415 0.28,0.664c0,0.249 -0.1,0.488 -0.28,0.664c-0.18,0.176 -0.41,0.275 -0.66,0.275c-0.25,0 -0.49,-0.099 -0.67,-0.275l-0.9,-0.899v3.984c0,0.249 -0.09,0.487 -0.27,0.663c-0.18,0.176 -0.41,0.275 -0.66,0.275c-0.25,0 -0.49,-0.099 -0.67,-0.275c-0.17,-0.176 -0.27,-0.414 -0.27,-0.663v-3.984l-0.9,0.9c-0.18,0.176 -0.41,0.275 -0.66,0.275c-0.25,0 -0.49,-0.099 -0.67,-0.275c-0.17,-0.176 -0.27,-0.415 -0.27,-0.664c0,-0.25 0.1,-0.488 0.27,-0.664l2.5,-2.501c0.09,-0.087 0.19,-0.156 0.31,-0.204c0.11,-0.047 0.23,-0.071 0.36,-0.071c0.12,0 0.24,0.024 0.36,0.071c0.11,0.048 0.21,0.117 0.3,0.204zM12.1941,-0.0005c-1.3,0.001 -2.57,0.353 -3.68,1.018c-1.12,0.665 -2.03,1.619 -2.65,2.761c-0.72,-0.075 -1.44,-0.003 -2.13,0.211c-0.69,0.214 -1.33,0.567 -1.87,1.035c-0.55,0.469 -1,1.045 -1.32,1.692c-0.32,0.647 -0.5,1.352 -0.54,2.072c-0.03,0.72 0.08,1.441 0.33,2.117c0.25,0.677 0.63,1.295 1.13,1.818c0.5,0.522 1.09,0.938 1.76,1.222c0.66,0.284 1.37,0.43 2.09,0.43h2.19c0.25,0 0.49,-0.099 0.67,-0.275c0.17,-0.176 0.27,-0.414 0.27,-0.663c0,-0.249 -0.1,-0.487 -0.27,-0.663c-0.18,-0.176 -0.42,-0.275 -0.67,-0.275h-2.19c-0.89,-0.005 -1.75,-0.36 -2.39,-0.989c-0.64,-0.629 -1,-1.482 -1.02,-2.377c-0.02,-0.896 0.31,-1.763 0.92,-2.418c0.62,-0.654 1.46,-1.044 2.35,-1.087c-0.11,0.512 -0.17,1.035 -0.17,1.559c0,0.249 0.1,0.487 0.28,0.663c0.17,0.176 0.41,0.275 0.66,0.275c0.25,0 0.49,-0.099 0.66,-0.275c0.18,-0.176 0.28,-0.414 0.28,-0.663c0,-0.769 0.17,-1.528 0.49,-2.225c0.32,-0.698 0.79,-1.317 1.38,-1.815c0.58,-0.499 1.27,-0.864 2.01,-1.071c0.74,-0.207 1.51,-0.252 2.27,-0.13c0.76,0.122 1.48,0.407 2.12,0.835c0.64,0.429 1.18,0.991 1.58,1.647c0.4,0.657 0.65,1.392 0.74,2.155c0.09,0.764 0.01,1.537 -0.23,2.267c-0.04,0.117 -0.06,0.241 -0.05,0.363c0.01,0.123 0.04,0.243 0.1,0.353c0.06,0.11 0.13,0.208 0.23,0.288c0.09,0.081 0.2,0.142 0.31,0.18c0.12,0.039 0.24,0.054 0.37,0.045c0.12,-0.009 0.24,-0.043 0.35,-0.098c0.11,-0.056 0.21,-0.132 0.29,-0.226c0.08,-0.093 0.14,-0.201 0.18,-0.318c0.35,-1.08 0.45,-2.228 0.27,-3.352c-0.17,-1.123 -0.61,-2.188 -1.28,-3.109c-0.66,-0.921 -1.54,-1.671 -2.55,-2.188c-1.01,-0.518 -2.13,-0.788 -3.27,-0.789z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 15.625 15.625" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_17" d="M15.6225,7.8125c0,0.249 -0.1,0.487 -0.27,0.663c-0.18,0.176 -0.42,0.275 -0.66,0.275h-5.94v5.937c0,0.249 -0.1,0.487 -0.28,0.663c-0.17,0.176 -0.41,0.274 -0.66,0.274c-0.25,0 -0.49,-0.098 -0.66,-0.274c-0.18,-0.176 -0.28,-0.414 -0.28,-0.663v-5.937h-5.94c-0.24,0 -0.48,-0.099 -0.66,-0.275c-0.17,-0.176 -0.27,-0.414 -0.27,-0.663c0,-0.249 0.1,-0.487 0.27,-0.663c0.18,-0.176 0.42,-0.275 0.66,-0.275h5.94v-5.937c0,-0.249 0.1,-0.487 0.28,-0.663c0.17,-0.176 0.41,-0.274 0.66,-0.274c0.25,0 0.49,0.098 0.66,0.274c0.18,0.176 0.28,0.414 0.28,0.663v5.937h5.94c0.24,0 0.48,0.099 0.66,0.275c0.17,0.176 0.27,0.414 0.27,0.663z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 8.751 16.251" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_19" d="M0.0485,4.615c-0.048,-0.115 -0.06,-0.24 -0.036,-0.362c0.024,-0.121 0.083,-0.232 0.171,-0.32l3.75,-3.75c0.058,-0.058 0.127,-0.104 0.203,-0.135c0.076,-0.032 0.157,-0.048 0.239,-0.048c0.082,0 0.163,0.016 0.239,0.048c0.076,0.031 0.145,0.077 0.203,0.135l3.75,3.75c0.088,0.088 0.147,0.199 0.171,0.32c0.025,0.122 0.012,0.247 -0.035,0.362c-0.047,0.114 -0.128,0.212 -0.231,0.28c-0.102,0.069 -0.223,0.106 -0.347,0.105h-7.5c-0.124,0 -0.244,-0.036 -0.347,-0.105c-0.103,-0.069 -0.183,-0.166 -0.23,-0.28zM8.1255,11.25h-7.5c-0.124,0 -0.245,0.037 -0.347,0.106c-0.103,0.068 -0.184,0.166 -0.231,0.28c-0.047,0.115 -0.06,0.24 -0.035,0.362c0.024,0.121 0.083,0.232 0.171,0.32l3.75,3.75c0.058,0.058 0.127,0.104 0.203,0.135c0.076,0.032 0.157,0.048 0.239,0.048c0.082,0 0.163,-0.016 0.239,-0.048c0.076,-0.031 0.145,-0.077 0.203,-0.135l3.75,-3.75c0.088,-0.088 0.147,-0.199 0.171,-0.32c0.025,-0.122 0.012,-0.247 -0.035,-0.362c-0.047,-0.114 -0.128,-0.212 -0.231,-0.28c-0.102,-0.069 -0.223,-0.106 -0.347,-0.106z" fill="currentColor" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 10.625 8" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_20" d="M1,4.375l2.625,2.625l6,-6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 88.5 721" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="7ebde95a" x="0" y="0"><g id="Desktop" data-node-id="243:24834"><g id="Frame_22" data-node-id="243:24836"><g id="Frame_29" data-node-id="243:24852"><g id="Frame_30" data-node-id="243:24853"><g clip-path="url(#clip3_243_24834)"><g id="Frame_36" data-node-id="243:24863"><g id="Frame_102" data-node-id="243:24924"><g id="Table Content Cell_51" data-node-id="243:24926">
<mask id="path-300-inside-62_243_24834" fill="white">
<path d="M0.5,0.5h88v72h-88z"/>
</mask>
<path d="M0.5,0.5h88v72h-88z" fill="white"/>
<path d="M88.5,72.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-300-inside-62_243_24834)"/>
<g id="Button Icon_3" data-node-id="I243:24926;5633:54396">
<g id="Monotone add_3" data-node-id="I243:24926;5633:54396;5590:22158">
<path id="Vector_30" data-node-id="I243:24926;5633:54396;5590:22158;5606:17908" d="M36.25,29.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,43.672h11.34v-12.469h-11.34zM40.75,25.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_52" data-node-id="243:24927">
<mask id="path-304-inside-63_243_24834" fill="white">
<path d="M0.5,72.5h88v72h-88z"/>
</mask>
<path d="M0.5,72.5h88v72h-88z" fill="white"/>
<path d="M88.5,144.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-304-inside-63_243_24834)"/>
<g id="Button Icon_4" data-node-id="I243:24927;5633:54396">
<g id="Monotone add_4" data-node-id="I243:24927;5633:54396;5590:22158">
<path id="Vector_31" data-node-id="I243:24927;5633:54396;5590:22158;5606:17908" d="M36.25,101.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,115.672h11.34v-12.469h-11.34zM40.75,97.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_53" data-node-id="243:24928">
<mask id="path-308-inside-64_243_24834" fill="white">
<path d="M0.5,144.5h88v72h-88z"/>
</mask>
<path d="M0.5,144.5h88v72h-88z" fill="white"/>
<path d="M88.5,216.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-308-inside-64_243_24834)"/>
<g id="Button Icon_5" data-node-id="I243:24928;5633:54396">
<g id="Monotone add_5" data-node-id="I243:24928;5633:54396;5590:22158">
<path id="Vector_32" data-node-id="I243:24928;5633:54396;5590:22158;5606:17908" d="M36.25,173.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,187.672h11.34v-12.469h-11.34zM40.75,169.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_54" data-node-id="243:24929">
<mask id="path-312-inside-65_243_24834" fill="white">
<path d="M0.5,216.5h88v72h-88z"/>
</mask>
<path d="M0.5,216.5h88v72h-88z" fill="white"/>
<path d="M88.5,288.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-312-inside-65_243_24834)"/>
<g id="Button Icon_6" data-node-id="I243:24929;5633:54396">
<g id="Monotone add_6" data-node-id="I243:24929;5633:54396;5590:22158">
<path id="Vector_33" data-node-id="I243:24929;5633:54396;5590:22158;5606:17908" d="M36.25,245.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,259.672h11.34v-12.469h-11.34zM40.75,241.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_55" data-node-id="243:24930">
<mask id="path-316-inside-66_243_24834" fill="white">
<path d="M0.5,288.5h88v72h-88z"/>
</mask>
<path d="M0.5,288.5h88v72h-88z" fill="white"/>
<path d="M88.5,360.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-316-inside-66_243_24834)"/>
<g id="Button Icon_7" data-node-id="I243:24930;5633:54396">
<g id="Monotone add_7" data-node-id="I243:24930;5633:54396;5590:22158">
<path id="Vector_34" data-node-id="I243:24930;5633:54396;5590:22158;5606:17908" d="M36.25,317.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,331.672h11.34v-12.469h-11.34zM40.75,313.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_56" data-node-id="243:24931">
<mask id="path-320-inside-67_243_24834" fill="white">
<path d="M0.5,360.5h88v72h-88z"/>
</mask>
<path d="M0.5,360.5h88v72h-88z" fill="white"/>
<path d="M88.5,432.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-320-inside-67_243_24834)"/>
<g id="Button Icon_8" data-node-id="I243:24931;5633:54396">
<g id="Monotone add_8" data-node-id="I243:24931;5633:54396;5590:22158">
<path id="Vector_35" data-node-id="I243:24931;5633:54396;5590:22158;5606:17908" d="M36.25,389.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,403.672h11.34v-12.469h-11.34zM40.75,385.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_57" data-node-id="243:24932">
<mask id="path-324-inside-68_243_24834" fill="white">
<path d="M0.5,432.5h88v72h-88z"/>
</mask>
<path d="M0.5,432.5h88v72h-88z" fill="white"/>
<path d="M88.5,504.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-324-inside-68_243_24834)"/>
<g id="Button Icon_9" data-node-id="I243:24932;5633:54396">
<g id="Monotone add_9" data-node-id="I243:24932;5633:54396;5590:22158">
<path id="Vector_36" data-node-id="I243:24932;5633:54396;5590:22158;5606:17908" d="M36.25,461.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,475.672h11.34v-12.469h-11.34zM40.75,457.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_58" data-node-id="243:24933">
<mask id="path-328-inside-69_243_24834" fill="white">
<path d="M0.5,504.5h88v72h-88z"/>
</mask>
<path d="M0.5,504.5h88v72h-88z" fill="white"/>
<path d="M88.5,576.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-328-inside-69_243_24834)"/>
<g id="Button Icon_10" data-node-id="I243:24933;5633:54396">
<g id="Monotone add_10" data-node-id="I243:24933;5633:54396;5590:22158">
<path id="Vector_37" data-node-id="I243:24933;5633:54396;5590:22158;5606:17908" d="M36.25,533.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,547.672h11.34v-12.469h-11.34zM40.75,529.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_59" data-node-id="243:24934">
<mask id="path-332-inside-70_243_24834" fill="white">
<path d="M0.5,576.5h88v72h-88z"/>
</mask>
<path d="M0.5,576.5h88v72h-88z" fill="white"/>
<path d="M88.5,648.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-332-inside-70_243_24834)"/>
<g id="Button Icon_11" data-node-id="I243:24934;5633:54396">
<g id="Monotone add_11" data-node-id="I243:24934;5633:54396;5590:22158">
<path id="Vector_38" data-node-id="I243:24934;5633:54396;5590:22158;5606:17908" d="M36.25,605.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.485 -0.19,0.95 -0.54,1.293c-0.34,0.343 -0.81,0.535 -1.29,0.535h-12c-0.48,0 -0.95,-0.192 -1.29,-0.535c-0.35,-0.343 -0.54,-0.808 -0.54,-1.293v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,619.672h11.34v-12.469h-11.34zM40.75,601.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g><g id="Table Content Cell_60" data-node-id="243:24935">
<mask id="path-336-inside-71_243_24834" fill="white">
<path d="M0.5,648.5h88v72h-88z"/>
</mask>
<path d="M0.5,648.5h88v72h-88z" fill="white"/>
<path d="M88.5,720.5v-1h-88v1v1h88z" fill="#E2E8F0" mask="url(#path-336-inside-71_243_24834)"/>
<g id="Button Icon_12" data-node-id="I243:24935;5633:54396">
<g id="Monotone add_12" data-node-id="I243:24935;5633:54396;5590:22158">
<path id="Vector_39" data-node-id="I243:24935;5633:54396;5590:22158;5606:17908" d="M36.25,677.047h16.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-0.42v12.797c0,0.48 -0.19,0.95 -0.54,1.29c-0.34,0.35 -0.81,0.54 -1.29,0.54h-12c-0.48,0 -0.95,-0.19 -1.29,-0.54c-0.35,-0.34 -0.54,-0.81 -0.54,-1.29v-12.797h-0.42c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315zM38.83,691.67h11.34v-12.467h-11.34zM40.75,673.297h7.5c0.29,0 0.56,0.113 0.76,0.315c0.2,0.202 0.32,0.477 0.32,0.763c0,0.286 -0.12,0.561 -0.32,0.763c-0.2,0.202 -0.47,0.315 -0.76,0.315h-7.5c-0.29,0 -0.56,-0.113 -0.76,-0.315c-0.2,-0.202 -0.32,-0.477 -0.32,-0.763c0,-0.286 0.12,-0.561 0.32,-0.763c0.2,-0.202 0.47,-0.315 0.76,-0.315z" fill="#475569" stroke="#475569" stroke-width="0.09375"/>
</g>
</g>
</g></g></g></g></g></g></g></g><defs><clipPath id="clip3_243_24834">
<rect x="-975.5" y="-133.5" width="1064" height="926" rx="24" fill="white"/>
</clipPath><mask id="path-300-inside-62_243_24834" fill="white">
<path d="M1320 310H1408V382H1320V310Z"/>
</mask><mask id="path-304-inside-63_243_24834" fill="white">
<path d="M1320 382H1408V454H1320V382Z"/>
</mask><mask id="path-308-inside-64_243_24834" fill="white">
<path d="M1320 454H1408V526H1320V454Z"/>
</mask><mask id="path-312-inside-65_243_24834" fill="white">
<path d="M1320 526H1408V598H1320V526Z"/>
</mask><mask id="path-316-inside-66_243_24834" fill="white">
<path d="M1320 598H1408V670H1320V598Z"/>
</mask><mask id="path-320-inside-67_243_24834" fill="white">
<path d="M1320 670H1408V742H1320V670Z"/>
</mask><mask id="path-324-inside-68_243_24834" fill="white">
<path d="M1320 742H1408V814H1320V742Z"/>
</mask><mask id="path-328-inside-69_243_24834" fill="white">
<path d="M1320 814H1408V886H1320V814Z"/>
</mask><mask id="path-332-inside-70_243_24834" fill="white">
<path d="M1320 886H1408V958H1320V886Z"/>
</mask><mask id="path-336-inside-71_243_24834" fill="white">
<path d="M1320 958H1408V1030H1320V958Z"/>
</mask></defs></svg>
<svg viewBox="0 0 8.1289 14.3783" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_40" d="M7.1896,0.0375c0.239,0 0.468,0.1 0.637,0.27c0.168,0.16 0.263,0.39 0.263,0.63c0,0.24 -0.095,0.47 -0.263,0.64l-5.585,5.58l-0.028,0.03l0.028,0.03l5.585,5.59c0.168,0.16 0.263,0.39 0.263,0.63c0,0.24 -0.095,0.47 -0.263,0.64c-0.169,0.17 -0.398,0.26 -0.637,0.26c-0.239,0 -0.467,-0.09 -0.636,-0.26l-6.25,-6.25c-0.084,-0.09 -0.151,-0.19 -0.196,-0.29c-0.045,-0.11 -0.068,-0.23 -0.068,-0.35c0,-0.12 0.023,-0.23 0.068,-0.34c0.045,-0.11 0.112,-0.21 0.196,-0.29l6.25,-6.25c0.169,-0.17 0.397,-0.27 0.636,-0.27z" fill="currentColor" stroke="currentColor" stroke-width="0.078125" xmlns="http://www.w3.org/2000/svg"/></svg>
<svg viewBox="0 0 8.1297 14.3783" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="Vector_41" d="M0.9399,0.0414c0.119,0 0.236,0.02 0.345,0.07c0.109,0.04 0.209,0.11 0.292,0.19l6.25,6.25c0.084,0.08 0.15,0.18 0.196,0.29c0.045,0.11 0.068,0.23 0.068,0.35c0,0.12 -0.024,0.23 -0.069,0.34c-0.046,0.11 -0.113,0.21 -0.197,0.29l-6.25,6.25c-0.168,0.17 -0.397,0.27 -0.635,0.27c-0.239,0 -0.468,-0.1 -0.637,-0.27c-0.169,-0.16 -0.264,-0.39 -0.264,-0.63c0,-0.24 0.095,-0.47 0.264,-0.64l5.587,-5.58l0.027,-0.03l-0.027,-0.03l-5.585,-5.59c-0.084,-0.08 -0.15,-0.18 -0.196,-0.29c-0.045,-0.11 -0.068,-0.22 -0.068,-0.34c0,-0.12 0.023,-0.24 0.068,-0.35c0.046,-0.11 0.112,-0.21 0.196,-0.29c0.083,-0.08 0.183,-0.15 0.292,-0.19c0.109,-0.05 0.226,-0.07 0.343,-0.07z" fill="currentColor" stroke="currentColor" stroke-width="0.078125" xmlns="http://www.w3.org/2000/svg"/></svg>
// User role types
export enum UserRole {
ADMIN = 'Admin',
EDITOR = 'Editor',
VIEWER = 'Viewer'
}
// User status types
export enum UserStatus {
ONLINE = 'Online',
OFFLINE = 'Offline'
}
// Navigation menu items
export enum NavigationItem {
HOME = 'Home',
TASKS = 'Tasks',
USERS = 'Users',
APIS = 'APIs',
SUBSCRIPTION = 'Subscription',
SETTINGS = 'Settings',
HELP_SUPPORT = 'Help & Support'
}
// Settings tabs
export enum SettingsTab {
GENERAL = 'General',
DETAILS = 'Details',
ACCOUNT = 'Account'
}
import { UserStatus, UserRole } from '../types/enums';
// Date formatting functions
export const formatJoinDate = (date: Date): string => {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
export const formatLastActive = (date: Date): string => {
return date.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric'
});
};
// Status formatting
export const formatUserStatus = (status: UserStatus): string => {
return status;
};
// Role formatting
export const formatUserRole = (role: UserRole): string => {
return role;
};
// Props types (data passed to components)
export interface User {
id: string;
name: string;
username: string;
avatar: string;
status: 'Online' | 'Offline';
joinDate: Date;
lastActive: Date;
role: 'Admin' | 'Editor' | 'Viewer';
selected: boolean;
}
export interface CurrentUser {
id: string;
name: string;
role: string;
avatar: string;
}
export interface SettingsPageProps {
currentUser: CurrentUser;
teamMembers: User[];
totalTeamMembers: number;
currentPage: number;
totalPages: number;
resultsPerPage: number;
totalResults: number;
activeTab: string;
homeNotificationCount: number;
usersNotificationCount: number;
}
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#4f46e5', // Indigo primary color from sidebar
light: '#6366f1',
dark: '#3730a3',
contrastText: '#ffffff'
},
secondary: {
main: '#a5b4fc', // Light indigo for icons
light: '#c7d2fe',
dark: '#8b5cf6',
contrastText: '#1e293b'
},
success: {
main: '#22c55e', // Green for online status
light: '#4ade80',
dark: '#16a34a',
contrastText: '#ffffff'
},
text: {
primary: '#1e293b', // Dark slate for main text
secondary: '#475569' // Medium slate for secondary text
},
background: {
default: '#ffffff',
paper: '#f8fafc' // Light gray for table headers
},
grey: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a'
},
divider: '#e2e8f0'
},
typography: {
fontFamily: "'Plus Jakarta Sans', sans-serif",
h1: {
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px'
},
h2: {
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px'
},
body1: {
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px'
},
body2: {
fontSize: '14px',
fontWeight: 400,
lineHeight: '22.4px'
},
button: {
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none'
},
caption: {
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px'
}
},
shape: {
borderRadius: 8
}
});
export default theme;
// Mock data for the settings page
// Data passed as props to the root component
export const mockRootProps = {
currentUser: {
id: 'user-1',
name: 'Azunyan U. Wu',
role: 'Basic Member',
avatar: '/avatars/azunyan-wu.png'
},
teamMembers: [
{
id: 'user-1',
name: 'Alice Smith',
username: '@alicesmith',
avatar: '/avatars/alice-smith.png',
status: 'Online' as const,
joinDate: new Date('2025-01-12'),
lastActive: new Date('2026-06-25'),
role: 'Admin' as const,
selected: false
},
{
id: 'user-2',
name: 'Bob Johnson',
username: '@bobjohnson',
avatar: '/avatars/bob-johnson.png',
status: 'Online' as const,
joinDate: new Date('2025-01-11'),
lastActive: new Date('2026-06-25'),
role: 'Editor' as const,
selected: true
},
// ... existing code ...
],
totalTeamMembers: 118,
currentPage: 1,
totalPages: 99,
resultsPerPage: 100,
totalResults: 1000,
activeTab: 'Details',
homeNotificationCount: 10,
usersNotificationCount: 2
};
import React from 'react';
import { Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
interface StatusChipProps {
status: 'Online' | 'Offline';
}
const StatusText = styled(Typography)<{ status: 'Online' | 'Offline' }>(({ theme, status }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: status === 'Online' ? theme.palette.success.main : theme.palette.text.secondary
}));
const StatusChip: React.FC<StatusChipProps> = ({ status }) => {
return (
<StatusText status={status} variant="caption">
{status}
</StatusText>
);
};
export default StatusChip;
import React from 'react';
import { Stack, Typography, Badge } from '@mui/material';
import { styled } from '@mui/material/styles';
interface NavigationItemProps {
icon: React.ReactNode;
label: string;
notificationCount?: number;
active?: boolean;
}
const NavigationContainer = styled(Stack)<{ active?: boolean }>(({ theme, active }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 16px',
borderRadius: active ? '9999px' : '0',
backgroundColor: active ? theme.palette.primary.main : 'transparent',
cursor: 'pointer',
'&:hover': {
backgroundColor: active ? theme.palette.primary.main : 'rgba(255, 255, 255, 0.1)'
}
}));
const LabelContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '8px'
});
const NavigationLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 700,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NotificationBadge = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.primary.main,
backgroundColor: theme.palette.primary.contrastText,
borderRadius: '9999px',
padding: '2px 8px',
minWidth: '20px',
textAlign: 'center'
}));
const NavigationItem: React.FC<NavigationItemProps> = ({
icon,
label,
notificationCount,
active = false
}) => {
return (
<NavigationContainer active={active}>
<LabelContainer>
{icon}
<NavigationLabel>{label}</NavigationLabel>
</LabelContainer>
{notificationCount && (
<NotificationBadge>{notificationCount}</NotificationBadge>
)}
</NavigationContainer>
);
};
export default NavigationItem;
import React from 'react';
import { Stack, Typography, Button } from '@mui/material';
import { styled } from '@mui/material/styles';
import WarningIcon from './Icons/WarningIcon.svg';
const PromoContainer = styled(Stack)(({ theme }) => ({
backgroundColor: '#6366f1',
borderRadius: '24px',
padding: '16px',
gap: '16px'
}));
const PromoText = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
lineHeight: '22.4px',
color: '#e0e7ff'
}));
const ButtonContainer = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const DismissButton = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: '#c7d2fe',
cursor: 'pointer'
}));
const GoProButton = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.primary.contrastText,
cursor: 'pointer'
}));
const PromoCard: React.FC = () => {
return (
<PromoContainer>
<WarningIcon width={248} height={40} />
<PromoText>
Enjoy unlimited access to our app with only a small price monthly.
</PromoText>
<ButtonContainer>
<DismissButton>Dismiss</DismissButton>
<GoProButton>Go Pro</GoProButton>
</ButtonContainer>
</PromoContainer>
);
};
export default PromoCard;
import React from 'react';
import { Stack, Typography, Avatar } from '@mui/material';
import { styled } from '@mui/material/styles';
import MoreIcon from './Icons/MoreIcon.svg';
import { CurrentUser } from '../types';
interface UserProfileProps {
user: CurrentUser;
}
const ProfileContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
borderTop: '1px solid #6366f1',
gap: '16px'
}));
const UserInfo = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserDetails = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 700,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const UserRole = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: '#c7d2fe'
}));
const MoreButton = styled('div')(({ theme }) => ({
border: '0.09375px solid #ffffff',
borderRadius: '50%',
padding: '4px',
cursor: 'pointer'
}));
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<ProfileContainer>
<UserInfo>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserDetails>
<UserName>{user.name}</UserName>
<UserRole>{user.role}</UserRole>
</UserDetails>
</UserInfo>
<MoreButton>
<MoreIcon width={19} height={19} color="#ffffff" />
</MoreButton>
</ProfileContainer>
);
};
export default UserProfile;
import React from 'react';
import { Stack, Typography, TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import SearchIcon from './Icons/SearchIcon.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const SearchContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '8px'
});
const SearchLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchContainer>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchContainer>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SearchIcon from './Icons/SearchIcon.svg';
const StyledTextField = styled(TextField)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '123px',
border: '1px solid #cbd5e1',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.text.secondary,
'& fieldset': {
border: 'none'
}
},
'& .MuiInputBase-input': {
padding: '12px 16px'
}
}));
const SearchInput: React.FC = () => {
return (
<StyledTextField
placeholder="Search anything..."
variant="outlined"
fullWidth
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon width={17} height={17} color="#475569" />
</InputAdornment>
)
}}
/>
);
};
export default SearchInput;
import React from 'react';
import { Button } from '@mui/material';
import { styled } from '@mui/material/styles';
interface ActionButtonProps {
children: React.ReactNode;
variant?: 'outlined' | 'contained';
startIcon?: React.ReactNode;
onClick?: () => void;
}
const StyledButton = styled(Button)<{ variant?: 'outlined' | 'contained' }>(({ theme, variant }) => ({
borderRadius: '1234px',
padding: '12px 16px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
gap: '8px',
...(variant === 'outlined' && {
border: '1px solid #cbd5e1',
color: theme.palette.text.secondary,
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}),
...(variant === 'contained' && {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
'&:hover': {
backgroundColor: theme.palette.primary.dark
}
})
}));
const ActionButton: React.FC<ActionButtonProps> = ({
children,
variant = 'outlined',
startIcon,
onClick
}) => {
return (
<StyledButton
variant={variant}
startIcon={startIcon}
onClick={onClick}
>
{children}
</StyledButton>
);
};
export default ActionButton;
import React from 'react';
import { TableRow, TableCell, Checkbox, Avatar, Stack, Typography, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import CheckedIcon from './Icons/CheckedIcon.svg';
import MenuDotsIcon from './Icons/MenuDotsIcon.svg';
import StatusChip from './StatusChip';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const CheckboxContainer = styled('div')({
display: 'flex',
alignItems: 'center'
});
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onSelectionChange(user.id, event.target.checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<CheckboxContainer>
{user.selected ? (
<CheckedIcon width={11} height={8} color="#ffffff" />
) : (
<Checkbox
checked={user.selected}
onChange={handleCheckboxChange}
sx={{
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20
}}
/>
)}
</CheckboxContainer>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
<StyledTableCell align="center">
<IconButton size="small">
<MenuDotsIcon width={88} height={721} color="#475569" />
</IconButton>
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import CheckedIcon from './Icons/CheckedIcon.svg';
import TeamTableRow from './TeamTableRow';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const SelectAllIndicator = styled('div')(({ theme }) => ({
width: '10px',
height: '2px',
border: '2px solid #ffffff',
backgroundColor: 'transparent'
}));
const TeamTable: React.FC<TeamTableProps> = ({ users, onSelectionChange }) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<StyledTableContainer component={Paper}>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell />
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React from 'react';
import { Stack, Typography, Button, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import PreviousIcon from './Icons/PreviousIcon.svg';
import NextIcon from './Icons/NextIcon.svg';
interface PaginationControlsProps {
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const PaginationContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const PaginationButtons = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '4px'
});
const PaginationButton = styled(Button)(({ theme }) => ({
borderRadius: '123px',
padding: '12px 16px',
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary,
textTransform: 'none',
minWidth: 'auto',
gap: '8px',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}));
const PageButton = styled(Button)<{ active?: boolean }>(({ theme, active }) => ({
minWidth: '32px',
height: '32px',
borderRadius: '4px',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: active ? theme.palette.primary.main : theme.palette.text.secondary,
backgroundColor: active ? 'rgba(79, 70, 229, 0.1)' : 'transparent',
'&:hover': {
backgroundColor: active ? 'rgba(79, 70, 229, 0.2)' : 'rgba(0, 0, 0, 0.04)'
}
}));
const ResultsText = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const PaginationControls: React.FC<PaginationControlsProps> = ({
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const startResult = (currentPage - 1) * resultsPerPage + 1;
const endResult = Math.min(currentPage * resultsPerPage, totalResults);
const renderPageNumbers = () => {
const pages = [];
const maxVisiblePages = 7;
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
} else {
// Show first few pages, ellipsis, and last page
for (let i = 1; i <= 5; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
pages.push(
<Typography key="ellipsis" sx={{ px: 1, color: 'text.secondary' }}>
...
</Typography>
);
pages.push(
<PageButton
key={totalPages}
active={totalPages === currentPage}
onClick={() => onPageChange(totalPages)}
>
{totalPages}
</PageButton>
);
}
return pages;
};
return (
<PaginationContainer>
<PaginationButtons>
<PaginationButton
startIcon={<PreviousIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
>
Previous
</PaginationButton>
<Stack direction="row" spacing={0.5} sx={{ mx: 2 }}>
{renderPageNumbers()}
</Stack>
<PaginationButton
endIcon={<NextIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
>
Next
</PaginationButton>
</PaginationButtons>
<ResultsText>
Showing {endResult} of {totalResults.toLocaleString()} results
</ResultsText>
</PaginationContainer>
);
};
export default PaginationControls;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, Badge } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import PaginationControls from './PaginationControls';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StatusIndicator = styled('div')(({ theme }) => ({
width: '15px',
height: '15px',
borderRadius: '123px',
border: '1.5px solid #ffffff',
backgroundColor: theme.palette.success.main
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<StatusIndicator />
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
/>
</TeamSection>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
// Mock data for the settings page
// Data passed as props to the root component
export const mockRootProps = {
currentUser: {
id: 'user-1',
name: 'Azunyan U. Wu',
role: 'Basic Member',
avatar: '/avatars/azunyan-wu.png'
},
teamMembers: [
{
id: 'user-1',
name: 'Alice Smith',
username: '@alicesmith',
avatar: '/avatars/alice-smith.png',
status: 'Online' as const,
joinDate: new Date('2025-01-12'),
lastActive: new Date('2026-06-25'),
role: 'Admin' as const,
selected: false
},
{
id: 'user-2',
name: 'Bob Johnson',
username: '@bobjohnson',
avatar: '/avatars/bob-johnson.png',
status: 'Online' as const,
joinDate: new Date('2025-01-11'),
lastActive: new Date('2026-06-25'),
role: 'Editor' as const,
selected: true
},
{
id: 'user-3',
name: 'Clara Garcia',
username: '@claragarcia',
avatar: '/avatars/clara-garcia.png',
status: 'Offline' as const,
joinDate: new Date('2025-01-11'),
lastActive: new Date('2026-06-25'),
role: 'Viewer' as const,
selected: false
},
{
id: 'user-4',
name: 'David Brown',
username: '@davidbrown',
avatar: '/avatars/david-brown.png',
status: 'Online' as const,
joinDate: new Date('2025-01-10'),
lastActive: new Date('2026-06-25'),
role: 'Viewer' as const,
selected: false
},
{
id: 'user-5',
name: 'Emma Lee',
username: '@emmalee',
avatar: '/avatars/emma-lee.png',
status: 'Online' as const,
joinDate: new Date('2025-01-09'),
lastActive: new Date('2026-06-25'),
role: 'Viewer' as const,
selected: true
},
{
id: 'user-6',
name: 'Frank Wong',
username: '@frankwong',
avatar: '/avatars/frank-wong.png',
status: 'Offline' as const,
joinDate: new Date('2025-01-08'),
lastActive: new Date('2026-06-25'),
role: 'Editor' as const,
selected: true
},
{
id: 'user-7',
name: 'Grace Taylor',
username: '@gracetaylor',
avatar: '/avatars/grace-taylor.png',
status: 'Online' as const,
joinDate: new Date('2025-01-06'),
lastActive: new Date('2026-06-25'),
role: 'Admin' as const,
selected: false
},
{
id: 'user-8',
name: 'Isabella Clark',
username: '@isabellaclark',
avatar: '/avatars/isabella-clark.png',
status: 'Online' as const,
joinDate: new Date('2025-01-05'),
lastActive: new Date('2026-06-25'),
role: 'Admin' as const,
selected: true
},
{
id: 'user-9',
name: 'X_AE_A-22',
username: '@xtheobliterator',
avatar: '/avatars/x-ae-a-22.png',
status: 'Online' as const,
joinDate: new Date('2025-01-03'),
lastActive: new Date('2026-06-25'),
role: 'Admin' as const,
selected: true
},
{
id: 'user-10',
name: 'Azunyan Senpai',
username: '@azunyansenpai',
avatar: '/avatars/azunyan-senpai.png',
status: 'Online' as const,
joinDate: new Date('2025-01-02'),
lastActive: new Date('2026-06-25'),
role: 'Editor' as const,
selected: true
}
],
totalTeamMembers: 118,
currentPage: 1,
totalPages: 99,
resultsPerPage: 100,
totalResults: 1000,
activeTab: 'Details',
homeNotificationCount: 10,
usersNotificationCount: 2
};
import React from 'react';
import { ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import SettingsPage from './src/components/SettingsPage';
import theme from './src/theme/theme';
import { mockRootProps } from './src/data/settingsPageMockData';
const createEmotionCache = () => {
return createCache({
key: "mui",
prepend: true,
});
};
const emotionCache = createEmotionCache();
const App: React.FC = () => {
return (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={theme}>
<CssBaseline />
<SettingsPage {...mockRootProps} />
</ThemeProvider>
</CacheProvider>
);
};
export default App;
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#root {
margin: auto;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Plus Jakarta Sans', sans-serif;
}
import React, { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import Example from './App.settingspage'
import ErrorBoundary from '@kombai/react-error-boundary'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ErrorBoundary>
<Example />
</ErrorBoundary>
</StrictMode>,
)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Settings Page - Team Management</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
I've finished generating all the code files for the settings page with team management functionality. Here's a summary of what was created:
The code follows all MUI v7 guidelines, uses proper TypeScript types, implements state management for interactive features, and matches the visual design from the Figma mockup.
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import CheckedIcon from './Icons/CheckedIcon.svg';
import TeamTableRow from './TeamTableRow';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const SelectAllIndicator = styled('div')(({ theme }) => ({
width: '10px',
height: '2px',
border: '2px solid #ffffff',
backgroundColor: 'transparent'
}));
const TeamTable: React.FC<TeamTableProps> = ({ users, onSelectionChange }) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell />
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
Fixed the TypeScript error by removing the invalid component={Paper}
prop from TableContainer.
I'll update the table to remove empty columns, add a profile avatar with online status in the header, and fix the sidebar search alignment. This involves modifying the TeamTable, TeamTableRow, SettingsPage components and creating a new AvatarWithStatus component.
import React from 'react';
import { Avatar, Badge } from '@mui/material';
import { styled } from '@mui/material/styles';
interface AvatarWithStatusProps {
src: string;
alt: string;
size?: number;
showStatus?: boolean;
}
const StatusBadge = styled(Badge)(({ theme }) => ({
'& .MuiBadge-badge': {
width: '15px',
height: '15px',
borderRadius: '50%',
border: '1.5px solid #ffffff',
backgroundColor: theme.palette.success.main,
'&::after': {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
content: '""'
}
}
}));
const AvatarWithStatus: React.FC<AvatarWithStatusProps> = ({
src,
alt,
size = 40,
showStatus = true
}) => {
const avatar = (
<Avatar
src={src}
alt={alt}
sx={{ width: size, height: size }}
/>
);
if (showStatus) {
return (
<StatusBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
>
{avatar}
</StatusBadge>
);
}
return avatar;
};
export default AvatarWithStatus;
import React from 'react';
import { TableRow, TableCell, Checkbox, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import CheckedIcon from './Icons/CheckedIcon.svg';
import StatusChip from './StatusChip';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const CheckboxContainer = styled('div')({
display: 'flex',
alignItems: 'center'
});
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onSelectionChange(user.id, event.target.checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<CheckboxContainer>
{user.selected ? (
<CheckedIcon width={11} height={8} color="#ffffff" />
) : (
<Checkbox
checked={user.selected}
onChange={handleCheckboxChange}
sx={{
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20
}}
/>
)}
</CheckboxContainer>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
<StyledTableCell align="center">
<IconButton size="small">
<MenuDotsIcon width={88} height={721} color="#475569" />
</IconButton>
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React from 'react';
import { TableRow, TableCell, Checkbox, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import CheckedIcon from './Icons/CheckedIcon.svg';
import StatusChip from './StatusChip';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const CheckboxContainer = styled('div')({
display: 'flex',
alignItems: 'center'
});
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onSelectionChange(user.id, event.target.checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<CheckboxContainer>
{user.selected ? (
<CheckedIcon width={11} height={8} color="#ffffff" />
) : (
<Checkbox
checked={user.selected}
onChange={handleCheckboxChange}
sx={{
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20
}}
/>
)}
</CheckboxContainer>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const SelectAllIndicator = styled('div')(({ theme }) => ({
width: '10px',
height: '2px',
border: '2px solid #ffffff',
backgroundColor: 'transparent'
}));
const TeamTable: React.FC<TeamTableProps> = ({ users, onSelectionChange }) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell />
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const SelectAllIndicator = styled('div')(({ theme }) => ({
width: '10px',
height: '2px',
border: '2px solid #ffffff',
backgroundColor: 'transparent'
}));
const TeamTable: React.FC<TeamTableProps> = ({ users, onSelectionChange }) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import SearchIcon from './Icons/SearchIcon.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const SearchContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '8px'
});
const SearchLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchContainer>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchContainer>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import SearchIcon from './Icons/SearchIcon.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const SearchNavigationItem = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
padding: '12px 16px',
gap: '8px',
cursor: 'pointer',
borderRadius: '0',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}));
const SearchLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchContainer>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchContainer>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import SearchIcon from './Icons/SearchIcon.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const SearchNavigationItem = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
padding: '12px 16px',
gap: '8px',
cursor: 'pointer',
borderRadius: '0',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}));
const SearchLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchNavigationItem>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchNavigationItem>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import PaginationControls from './PaginationControls';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StatusIndicator = styled('div')(({ theme }) => ({
width: '15px',
height: '15px',
borderRadius: '123px',
border: '1.5px solid #ffffff',
backgroundColor: theme.palette.success.main
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<StatusIndicator />
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
/>
</TeamSection>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import PaginationControls from './PaginationControls';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<StatusIndicator />
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
/>
</TeamSection>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import PaginationControls from './PaginationControls';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
/>
</TeamSection>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
I've successfully updated the components to address all three requirements:
Removed empty table column: Eliminated the last empty column from both TeamTable and TeamTableRow components by removing the menu dots column and its header.
Added profile avatar with online status: Created a new AvatarWithStatus component and integrated it into the header, replacing the simple status indicator with the user's avatar showing an online status badge.
Fixed sidebar search styling: Updated the AppSidebar component to style the search section consistently with other navigation items, using proper padding and hover effects to match the navigation menu alignment.
The changes maintain all existing functionality while improving the visual consistency and removing unnecessary UI elements.
I'll create reusable components for the sidebar search input and tri-state checkbox, update the table structure to include pagination as the last row, and modify the styling for the profile section and search inputs to match the requirements.
import React from 'react';
import { TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SearchIcon from './Icons/SearchIcon.svg';
const StyledTextField = styled(TextField)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '123px',
border: '1px solid rgba(255, 255, 255, 0.3)',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText,
'& fieldset': {
border: 'none'
},
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.15)'
},
'&.Mui-focused': {
backgroundColor: 'rgba(255, 255, 255, 0.2)'
}
},
'& .MuiInputBase-input': {
padding: '12px 16px',
'&::placeholder': {
color: 'rgba(255, 255, 255, 0.7)',
opacity: 1
}
}
}));
const SidebarSearchInput: React.FC = () => {
return (
<StyledTextField
placeholder="Search"
variant="outlined"
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon width={17} height={17} color="rgba(255, 255, 255, 0.7)" />
</InputAdornment>
)
}}
/>
);
};
export default SidebarSearchInput;
import React from 'react';
import { Checkbox } from '@mui/material';
import { styled } from '@mui/material/styles';
import CheckedIcon from './Icons/CheckedIcon.svg';
interface TriStateCheckboxProps {
checked: boolean;
indeterminate?: boolean;
onChange: (checked: boolean) => void;
size?: 'small' | 'medium';
}
const StyledCheckbox = styled(Checkbox)(({ theme }) => ({
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20,
'&.Mui-checked': {
backgroundColor: theme.palette.primary.main,
border: `1px solid ${theme.palette.primary.main}`,
color: '#ffffff'
},
'&.MuiCheckbox-indeterminate': {
backgroundColor: theme.palette.primary.main,
border: `1px solid ${theme.palette.primary.main}`,
color: '#ffffff'
}
}));
const TriStateCheckbox: React.FC<TriStateCheckboxProps> = ({
checked,
indeterminate = false,
onChange,
size = 'medium'
}) => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.checked);
};
return (
<StyledCheckbox
checked={checked}
indeterminate={indeterminate}
onChange={handleChange}
size={size}
icon={<div style={{ width: 18, height: 18, border: '1px solid #cbd5e1', borderRadius: '8px' }} />}
checkedIcon={<CheckedIcon width={11} height={8} color="#ffffff" />}
indeterminateIcon={
<div style={{
width: 18,
height: 18,
backgroundColor: '#4f46e5',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{
width: 8,
height: 2,
backgroundColor: '#ffffff',
borderRadius: '1px'
}} />
</div>
}
/>
);
};
export default TriStateCheckbox;
import React from 'react';
import { Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import SidebarSearchInput from './SidebarSearchInput';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const SearchNavigationItem = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
padding: '12px 16px',
gap: '8px',
cursor: 'pointer',
borderRadius: '0',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}));
const SearchLabel = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchNavigationItem>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchNavigationItem>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import SidebarSearchInput from './SidebarSearchInput';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<SearchNavigationItem>
<SearchIcon width={17} height={17} color="#a5b4fc" />
<SearchLabel>Search</SearchLabel>
</SearchNavigationItem>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import SidebarSearchInput from './SidebarSearchInput';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<Stack sx={{ px: 2 }}>
<SidebarSearchInput />
</Stack>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
import React from 'react';
import { Stack, Typography, Avatar } from '@mui/material';
import { styled } from '@mui/material/styles';
import MoreIcon from './Icons/MoreIcon.svg';
import { CurrentUser } from '../types';
interface UserProfileProps {
user: CurrentUser;
}
const ProfileContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
borderTop: '1px solid #6366f1',
gap: '16px'
}));
const UserInfo = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserDetails = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '16px',
fontWeight: 700,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText
}));
const UserRole = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: '#c7d2fe'
}));
const MoreButton = styled('div')(({ theme }) => ({
borderRadius: '50%',
padding: '4px',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}));
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<ProfileContainer>
<UserInfo>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserDetails>
<UserName>{user.name}</UserName>
<UserRole>{user.role}</UserRole>
</UserDetails>
</UserInfo>
<MoreButton>
<MoreIcon width={19} height={19} color="#ffffff" />
</MoreButton>
</ProfileContainer>
);
};
export default UserProfile;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const SelectAllIndicator = styled('div')(({ theme }) => ({
width: '10px',
height: '2px',
border: '2px solid #ffffff',
backgroundColor: 'transparent'
}));
const TeamTable: React.FC<TeamTableProps> = ({ users, onSelectionChange }) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<SelectAllIndicator />
<Typography variant="button">Full Name</Typography>
</NameHeaderContent>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)'
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React from 'react';
import { TableRow, TableCell, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import StatusChip from './StatusChip';
import TriStateCheckbox from './TriStateCheckbox';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const CheckboxContainer = styled('div')({
display: 'flex',
alignItems: 'center'
});
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onSelectionChange(user.id, event.target.checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<CheckboxContainer>
{user.selected ? (
<CheckedIcon width={11} height={8} color="#ffffff" />
) : (
<Checkbox
checked={user.selected}
onChange={handleCheckboxChange}
sx={{
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20
}}
/>
)}
</CheckboxContainer>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React from 'react';
import { TableRow, TableCell, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import StatusChip from './StatusChip';
import TriStateCheckbox from './TriStateCheckbox';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (checked: boolean) => {
onSelectionChange(user.id, checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<CheckboxContainer>
{user.selected ? (
<CheckedIcon width={11} height={8} color="#ffffff" />
) : (
<Checkbox
checked={user.selected}
onChange={handleCheckboxChange}
sx={{
borderRadius: '8px',
border: '1px solid #cbd5e1',
padding: 0,
width: 20,
height: 20
}}
/>
)}
</CheckboxContainer>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React from 'react';
import { TableRow, TableCell, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import StatusChip from './StatusChip';
import TriStateCheckbox from './TriStateCheckbox';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (checked: boolean) => {
onSelectionChange(user.id, checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<TriStateCheckbox
checked={user.selected}
onChange={handleCheckboxChange}
size="small"
/>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
/>
</TeamSection>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
I've successfully implemented all the requested changes:
Added rounded outline to sidebar search: Created a new SidebarSearchInput
component with proper styling that matches the header search with rounded borders and appropriate hover/focus states.
Removed outline from profile icon: Updated the UserProfile
component to remove the border from the more icon button and added a subtle hover effect instead.
Moved pagination to table: Integrated the PaginationControls
as the last row within the table structure by adding it to the TableBody
and spanning all columns.
Added tri-state checkbox functionality: Created a TriStateCheckbox
component that handles checked, unchecked, and indeterminate states, and integrated it into both the table header for "Select All" functionality and individual rows for user selection.
The changes maintain all existing functionality while improving the user interface according to the specifications. The tri-state checkbox in the header will show indeterminate when some (but not all) users are selected, and checked when all users are selected.
I'll make the page mobile responsive by updating existing components to use MUI's responsive breakpoints and ensuring the table is horizontally scrollable on mobile. The mobile version shows a collapsed sidebar with hamburger menu and stacked layout elements.
<svg viewBox="0.0000 0.0000 375.0000 64.0000" x="0" y="0" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><filter id="filter0_d_243_24938" x="8" y="12" width="48" height="48" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" xmlns="http://www.w3.org/2000/svg">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.309804 0 0 0 0 0.27451 0 0 0 0 0.898039 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_243_24938"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_243_24938" result="shape"/>
</filter><clipPath id="clip1_243_24938" xmlns="http://www.w3.org/2000/svg">
<rect x="16" y="16" width="32" height="32" rx="12" fill="white"/>
</clipPath><filter id="filter1_d_243_24938" x="16.6934" y="18.75" width="30.6201" height="34.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB" xmlns="http://www.w3.org/2000/svg">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.388235 0 0 0 0 0.4 0 0 0 0 0.945098 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_243_24938"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_243_24938" result="shape"/>
</filter><linearGradient id="paint0_linear_243_24938" x1="32" y1="23" x2="32" y2="41" gradientUnits="userSpaceOnUse" xmlns="http://www.w3.org/2000/svg">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#4F46E5" stop-opacity="0"/>
</linearGradient></defs><g id="App Sidebar" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H375V64H0V0Z" fill="#4F46E5"/>
<g id="Frame">
<g id="Logo">
<g id="Logomark" filter="url(#filter0_d_243_24938)">
<g clip-path="url(#clip1_243_24938)">
<rect x="16" y="16" width="32" height="32" rx="12" fill="white"/>
<g id="Vector" filter="url(#filter1_d_243_24938)">
<path d="M32.5168 41C30.7002 41 29.1133 40.5669 27.7562 39.7007C26.4198 38.8134 25.5011 37.6197 25 36.1197L28.0694 34.6303C28.5078 35.6021 29.1133 36.3627 29.8859 36.912C30.6793 37.4613 31.5563 37.7359 32.5168 37.7359C33.2685 37.7359 33.8635 37.5669 34.302 37.2289C34.7405 36.8908 34.9597 36.4472 34.9597 35.8979C34.9597 35.5599 34.8658 35.2852 34.6779 35.0739C34.5108 34.8415 34.2707 34.6514 33.9575 34.5035C33.6652 34.3345 33.3415 34.1972 32.9866 34.0915L30.1991 33.2993C28.7584 32.8768 27.6622 32.2324 26.9105 31.3662C26.1797 30.5 25.8143 29.4754 25.8143 28.2923C25.8143 27.2359 26.0753 26.3169 26.5973 25.5352C27.1402 24.7324 27.8814 24.1092 28.821 23.6655C29.7815 23.2218 30.8777 23 32.1096 23C33.7174 23 35.1372 23.3908 36.3691 24.1725C37.601 24.9542 38.478 26.0528 39 27.4683L35.868 28.9577C35.5757 28.1761 35.085 27.5528 34.396 27.088C33.7069 26.6232 32.9344 26.3908 32.0783 26.3908C31.3893 26.3908 30.8464 26.5493 30.4497 26.8662C30.0529 27.1831 29.8546 27.5951 29.8546 28.1021C29.8546 28.419 29.9381 28.6937 30.1051 28.9261C30.2722 29.1585 30.5019 29.3486 30.7942 29.4965C31.1074 29.6444 31.4623 29.7817 31.8591 29.9085L34.5839 30.7324C35.9828 31.1549 37.0582 31.7887 37.8098 32.6338C38.5824 33.4789 38.9687 34.5141 38.9687 35.7394C38.9687 36.7746 38.6972 37.6937 38.1544 38.4965C37.6115 39.2782 36.8598 39.8908 35.8993 40.3345C34.9389 40.7782 33.8113 41 32.5168 41Z" fill="#4F46E5" shape-rendering="crispEdges"/>
<path d="M32.1094 22.875C33.7383 22.875 35.1826 23.2717 36.4365 24.0674C37.6921 24.8642 38.5861 25.9848 39.1172 27.4248L39.1572 27.5322L39.0537 27.5811L35.9219 29.0703L35.7988 29.1289L35.751 29.002C35.4677 28.2445 34.9935 27.6416 34.3262 27.1914C33.6584 26.741 32.9101 26.5156 32.0781 26.5156C31.4075 26.5157 30.8951 26.6701 30.5273 26.9639C30.1604 27.2571 29.9795 27.6337 29.9795 28.1025C29.9796 28.396 30.057 28.6448 30.207 28.8535C30.3607 29.0672 30.5738 29.2447 30.8506 29.3848H30.8496C31.1564 29.5295 31.5059 29.664 31.8975 29.7891H31.8965L34.6201 30.6123C36.0343 31.0394 37.1315 31.6836 37.9023 32.5498L38.0469 32.7148C38.7455 33.5532 39.0937 34.5636 39.0938 35.7393C39.0938 36.7976 38.8156 37.7415 38.2578 38.5664L38.2568 38.5674C37.6999 39.3693 36.9304 39.9963 35.9521 40.4482C34.972 40.901 33.8253 41.125 32.5166 41.125C30.6794 41.125 29.0682 40.6862 27.6885 39.8057L27.6865 39.8047C26.3276 38.9022 25.3919 37.6858 24.8818 36.1592L24.8467 36.0557L24.9453 36.0068L28.0146 34.5176L28.1309 34.4619L28.1836 34.5791C28.6139 35.5327 29.2053 36.2748 29.957 36.8096L30.251 36.9971C30.9444 37.4063 31.6987 37.6113 32.5166 37.6113C33.2504 37.6113 33.8156 37.4459 34.2256 37.1299C34.6337 36.8153 34.835 36.4076 34.835 35.8975C34.8349 35.584 34.7476 35.3412 34.584 35.1572L34.5762 35.1475L34.6777 35.0742L34.5762 35.1465C34.4239 34.9348 34.2022 34.7569 33.9043 34.6162L33.8945 34.6123L33.957 34.5039L33.8945 34.6113C33.6119 34.448 33.298 34.3139 32.9521 34.2109L30.165 33.4199L30.1641 33.4189C28.7075 32.9918 27.5879 32.3372 26.8164 31.4482L26.8154 31.4473C26.0647 30.5575 25.6895 29.5032 25.6895 28.292C25.6895 27.2143 25.9559 26.2705 26.4932 25.4658L26.4941 25.4648C27.0504 24.6424 27.8095 24.0052 28.7676 23.5527L28.7686 23.5518C29.7479 23.0994 30.8624 22.875 32.1094 22.875Z" stroke="url(#paint0_linear_243_24938)" stroke-width="0.25" shape-rendering="crispEdges"/>
</g>
</g>
<rect x="16.25" y="16.25" width="31.5" height="31.5" rx="11.75" stroke="#E2E8F0" stroke-width="0.5"/>
</g>
<g id="Vector_2">
<path d="M61.6431 40.5C60.2723 40.5 59.0767 40.1905 58.0562 39.5715C57.0509 38.9378 56.3655 38.0904 56 37.0293L58.5131 35.8797C58.833 36.5282 59.2671 37.0366 59.8154 37.4051C60.3637 37.7735 60.9729 37.9577 61.6431 37.9577C62.1305 37.9577 62.5037 37.8619 62.7626 37.6704C63.0215 37.4788 63.151 37.2135 63.151 36.8745C63.151 36.6977 63.1053 36.5503 63.0139 36.4324C62.9225 36.2997 62.7854 36.1818 62.6027 36.0787C62.4199 35.9755 62.1914 35.8871 61.9173 35.8134L59.7925 35.2386C58.772 34.9586 57.9877 34.5091 57.4393 33.8901C56.891 33.2564 56.6169 32.5121 56.6169 31.6573C56.6169 30.9057 56.8149 30.2499 57.2109 29.6899C57.6069 29.1298 58.1628 28.6951 58.8787 28.3856C59.5945 28.0613 60.417 27.8992 61.3461 27.8992C62.5646 27.8992 63.6308 28.1792 64.5446 28.7393C65.4737 29.2846 66.1286 30.0583 66.5094 31.0605L63.9734 32.21C63.7907 31.7089 63.4556 31.311 62.9682 31.0163C62.496 30.7068 61.9553 30.552 61.3461 30.552C60.9044 30.552 60.5541 30.6404 60.2952 30.8173C60.0515 30.9941 59.9296 31.2373 59.9296 31.5468C59.9296 31.7089 59.9753 31.8563 60.0667 31.9889C60.1581 32.1216 60.3028 32.2395 60.5008 32.3427C60.714 32.4458 60.9729 32.5416 61.2776 32.63L63.2652 33.2048C64.3009 33.4996 65.0929 33.9491 65.6412 34.5533C66.1896 35.1428 66.4637 35.8723 66.4637 36.7419C66.4637 37.4935 66.2581 38.1493 65.8469 38.7094C65.4509 39.2694 64.8949 39.7115 64.1791 40.0358C63.4632 40.3453 62.6179 40.5 61.6431 40.5Z" fill="white"/>
<path d="M72.0074 40.3674C70.4843 40.3674 69.3648 40.0505 68.649 39.4168C67.9483 38.7683 67.598 37.7588 67.598 36.3882V23.5H71.025V36.145C71.025 37.044 71.5276 37.4935 72.5329 37.4935H72.6243V40.3674H72.0074Z" fill="white"/>
<path d="M79.792 40.5C78.5583 40.5 77.4312 40.2274 76.4107 39.6821C75.4055 39.1368 74.5982 38.3925 73.989 37.4493C73.395 36.4913 73.098 35.4081 73.098 34.1996C73.098 32.9764 73.395 31.8932 73.989 30.9499C74.5982 30.0067 75.4055 29.2625 76.4107 28.7172C77.4312 28.1719 78.5583 27.8992 79.792 27.8992C81.0257 27.8992 82.1452 28.1719 83.1505 28.7172C84.1557 29.2625 84.9553 30.0067 85.5493 30.9499C86.1586 31.8932 86.4632 32.9764 86.4632 34.1996C86.4632 35.4081 86.1586 36.4913 85.5493 37.4493C84.9553 38.3925 84.1557 39.1368 83.1505 39.6821C82.1452 40.2274 81.0257 40.5 79.792 40.5ZM79.792 37.5156C80.4165 37.5156 80.9572 37.3756 81.4141 37.0956C81.8863 36.8156 82.2518 36.425 82.5108 35.9239C82.7849 35.4228 82.922 34.8481 82.922 34.1996C82.922 33.5511 82.7849 32.9837 82.5108 32.4974C82.2518 31.9963 81.8863 31.6058 81.4141 31.3257C80.9572 31.031 80.4165 30.8836 79.792 30.8836C79.1675 30.8836 78.6192 31.031 78.1471 31.3257C77.6749 31.6058 77.3017 31.9963 77.0276 32.4974C76.7687 32.9837 76.6392 33.5511 76.6392 34.1996C76.6392 34.8481 76.7687 35.4228 77.0276 35.9239C77.3017 36.425 77.6749 36.8156 78.1471 37.0956C78.6192 37.3756 79.1675 37.5156 79.792 37.5156Z" fill="white"/>
<path d="M93.3538 40.3674C91.8459 40.3674 90.6731 39.9768 89.8354 39.1957C89.0129 38.3999 88.6017 37.2945 88.6017 35.8797V31.0384H86.4998V28.1645H86.614C87.2537 28.1645 87.7411 28.0098 88.0762 27.7003C88.4265 27.3908 88.6017 26.9265 88.6017 26.3075V25.4233H92.0287V28.1645H94.953V31.0384H92.0287V35.6586C92.0287 36.0713 92.1048 36.4176 92.2571 36.6977C92.4094 36.9629 92.6455 37.1619 92.9654 37.2945C93.2852 37.4272 93.6812 37.4935 94.1534 37.4935C94.26 37.4935 94.3819 37.4861 94.5189 37.4714C94.656 37.4567 94.8007 37.4419 94.953 37.4272V40.2347C94.7246 40.2642 94.4656 40.2937 94.1762 40.3231C93.8869 40.3526 93.6127 40.3674 93.3538 40.3674Z" fill="white"/>
<path d="M96.0473 40.2347V23.5H99.4743V28.1645H101.965C103.107 28.1645 104.089 28.3561 104.912 28.7393C105.734 29.1225 106.366 29.6677 106.808 30.3752C107.25 31.0826 107.471 31.9226 107.471 32.8953V40.2347H104.044V33.0722C104.044 32.4532 103.853 31.9595 103.472 31.591C103.092 31.2226 102.589 31.0384 101.965 31.0384H99.4743V40.2347H96.0473Z" fill="white"/>
<path d="M114.271 40.5C113.128 40.5 112.131 40.2863 111.278 39.8589C110.425 39.4315 109.755 38.8346 109.267 38.0683C108.795 37.3019 108.559 36.4103 108.559 35.3934V28.1645H111.986V35.327C111.986 35.7692 112.077 36.1524 112.26 36.4766C112.458 36.8008 112.732 37.0587 113.082 37.2503C113.433 37.4272 113.829 37.5156 114.271 37.5156C114.727 37.5156 115.123 37.4272 115.459 37.2503C115.809 37.0587 116.075 36.8008 116.258 36.4766C116.456 36.1524 116.555 35.7692 116.555 35.327V28.1645H119.982V35.3934C119.982 36.4103 119.738 37.3019 119.251 38.0683C118.779 38.8346 118.116 39.4315 117.263 39.8589C116.41 40.2863 115.413 40.5 114.271 40.5Z" fill="white"/>
<path d="M121.573 40.2347V28.1645H125V40.2347H121.573ZM121.573 27.0813V23.7653H125V27.0813H121.573Z" fill="white"/>
</g>
</g>
</g>
<g id="Button Icon">
<g id="Monotone add">
<path id="Vector_3" d="M330.75 36.9219H347.25C347.536 36.9219 347.811 37.0351 348.013 37.2373C348.215 37.4395 348.328 37.7141 348.328 38C348.328 38.2859 348.215 38.5605 348.013 38.7627C347.811 38.9649 347.536 39.0781 347.25 39.0781H330.75C330.464 39.0781 330.189 38.9649 329.987 38.7627C329.785 38.5605 329.672 38.2859 329.672 38C329.672 37.7141 329.785 37.4395 329.987 37.2373C330.189 37.0351 330.464 36.9219 330.75 36.9219ZM330.75 30.9219H347.25C347.536 30.9219 347.811 31.0351 348.013 31.2373C348.215 31.4395 348.328 31.7141 348.328 32C348.328 32.2859 348.215 32.5605 348.013 32.7627C347.811 32.9649 347.536 33.0781 347.25 33.0781H330.75C330.464 33.0781 330.189 32.9649 329.987 32.7627C329.785 32.5605 329.672 32.2859 329.672 32C329.672 31.7141 329.785 31.4395 329.987 31.2373C330.189 31.0351 330.464 30.9219 330.75 30.9219ZM330.75 24.9219H347.25C347.536 24.9219 347.811 25.0351 348.013 25.2373C348.215 25.4395 348.328 25.7141 348.328 26C348.328 26.2859 348.215 26.5605 348.013 26.7627C347.811 26.9649 347.536 27.0781 347.25 27.0781H330.75C330.464 27.0781 330.189 26.9649 329.987 26.7627C329.785 26.5605 329.672 26.2859 329.672 26C329.672 25.7141 329.785 25.4395 329.987 25.2373C330.189 25.0351 330.464 24.9219 330.75 24.9219Z" fill="white" stroke="white" stroke-width="0.09375"/>
</g>
</g>
</g></svg>
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, IconButton, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import HamburgerMenuIcon from './Icons/HamburgerMenuIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff'
});
const MainContent = styled(Stack)({
flex: 1,
height: '100vh',
overflow: 'auto'
});
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px'
}));
const HeaderTop = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px'
});
const HeaderActions = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '16px'
});
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, IconButton, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import HamburgerMenuIcon from './Icons/HamburgerMenuIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
color: theme.palette.primary.contrastText
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)({
padding: '32px',
gap: '32px'
});
const TeamSection = styled(Stack)({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden'
});
const TeamHeader = styled(Stack)({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px'
});
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)({
flexDirection: 'row',
gap: '16px'
});
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, IconButton, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import HamburgerMenuIcon from './Icons/HamburgerMenuIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
color: theme.palette.primary.contrastText
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
return (
<PageContainer>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
<MainContent>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, IconButton, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import HamburgerMenuIcon from './Icons/HamburgerMenuIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
color: theme.palette.primary.contrastText
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
)}
<MainContent>
{isMobile && (
<MobileHeader>
<Stack direction="row" alignItems="center" gap={2}>
<IconButton onClick={toggleMobileDrawer} sx={{ color: 'inherit' }}>
<HamburgerMenuIcon width={24} height={24} color="#ffffff" />
</IconButton>
<MobileTitle>Team Settings</MobileTitle>
</Stack>
<Stack direction="row" alignItems="center" gap={1}>
<ShareIcon width={17} height={20} color="#ffffff" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={32}
showStatus={true}
/>
</Stack>
</MobileHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ p: 2 }}>
<MobileSearchContainer>
<SearchInput />
</MobileSearchContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
sx={{ mt: 2 }}
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table sx={{ minWidth: 650 }}>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React from 'react';
import { TableRow, TableCell, Avatar, Stack, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
import StatusChip from './StatusChip';
import TriStateCheckbox from './TriStateCheckbox';
import { User } from '../types';
import { formatJoinDate, formatLastActive } from '../utils/formatters';
interface TeamTableRowProps {
user: User;
onSelectionChange: (userId: string, selected: boolean) => void;
}
const StyledTableRow = styled(TableRow)(({ theme }) => ({
borderBottom: '1px solid #e2e8f0',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.02)'
}
}));
const StyledTableCell = styled(TableCell)(({ theme }) => ({
border: 'none',
padding: '12px',
fontSize: '14px',
fontWeight: 500,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const NameContainer = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const UserInfo = styled(Stack)({
gap: '2px'
});
const UserName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.primary
}));
const Username = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamTableRow: React.FC<TeamTableRowProps> = ({ user, onSelectionChange }) => {
const handleCheckboxChange = (checked: boolean) => {
onSelectionChange(user.id, checked);
};
return (
<StyledTableRow>
<StyledTableCell>
<NameContainer>
<TriStateCheckbox
checked={user.selected}
onChange={handleCheckboxChange}
size="small"
/>
<Avatar
src={user.avatar}
alt={user.name}
sx={{ width: 40, height: 40 }}
/>
<UserInfo>
<UserName>{user.name}</UserName>
<Username>{user.username}</Username>
</UserInfo>
</NameContainer>
</StyledTableCell>
<StyledTableCell>
<StatusChip status={user.status} />
</StyledTableCell>
<StyledTableCell>
{formatJoinDate(user.joinDate)}
</StyledTableCell>
<StyledTableCell>
{formatLastActive(user.lastActive)}
</StyledTableCell>
<StyledTableCell>
{user.role}
</StyledTableCell>
</StyledTableRow>
);
};
export default TeamTableRow;
import React from 'react';
import { Stack, Typography, Button, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import PreviousIcon from './Icons/PreviousIcon.svg';
import NextIcon from './Icons/NextIcon.svg';
interface PaginationControlsProps {
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const PaginationContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '12px'
}
}));
const PaginationButtons = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '4px',
[theme.breakpoints.down('sm')]: {
order: 1
}
}));
const PaginationButton = styled(Button)(({ theme }) => ({
borderRadius: '123px',
padding: '12px 16px',
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary,
textTransform: 'none',
minWidth: 'auto',
gap: '8px',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}));
const PageButton = styled(Button)<{ active?: boolean }>(({ theme, active }) => ({
minWidth: '32px',
height: '32px',
borderRadius: '4px',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: active ? theme.palette.primary.main : theme.palette.text.secondary,
backgroundColor: active ? 'rgba(79, 70, 229, 0.1)' : 'transparent',
'&:hover': {
backgroundColor: active ? 'rgba(79, 70, 229, 0.2)' : 'rgba(0, 0, 0, 0.04)'
}
}));
const ResultsText = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary
}));
const PaginationControls: React.FC<PaginationControlsProps> = ({
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const startResult = (currentPage - 1) * resultsPerPage + 1;
const endResult = Math.min(currentPage * resultsPerPage, totalResults);
const renderPageNumbers = () => {
const pages = [];
const maxVisiblePages = 7;
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
} else {
// Show first few pages, ellipsis, and last page
for (let i = 1; i <= 5; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
pages.push(
<Typography key="ellipsis" sx={{ px: 1, color: 'text.secondary' }}>
...
</Typography>
);
pages.push(
<PageButton
key={totalPages}
active={totalPages === currentPage}
onClick={() => onPageChange(totalPages)}
>
{totalPages}
</PageButton>
);
}
return pages;
};
return (
<PaginationContainer>
<PaginationButtons>
<PaginationButton
startIcon={<PreviousIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
>
Previous
</PaginationButton>
<Stack direction="row" spacing={0.5} sx={{ mx: 2 }}>
{renderPageNumbers()}
</Stack>
<PaginationButton
endIcon={<NextIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
>
Next
</PaginationButton>
</PaginationButtons>
<ResultsText>
Showing {endResult} of {totalResults.toLocaleString()} results
</ResultsText>
</PaginationContainer>
);
};
export default PaginationControls;
import React from 'react';
import { Stack, Typography, Button, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import PreviousIcon from './Icons/PreviousIcon.svg';
import NextIcon from './Icons/NextIcon.svg';
interface PaginationControlsProps {
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
}
const PaginationContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '12px'
}
}));
const PaginationButtons = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '4px',
[theme.breakpoints.down('sm')]: {
order: 1
}
}));
const PaginationButton = styled(Button)(({ theme }) => ({
borderRadius: '123px',
padding: '12px 16px',
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary,
textTransform: 'none',
minWidth: 'auto',
gap: '8px',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}));
const PageButton = styled(Button)<{ active?: boolean }>(({ theme, active }) => ({
minWidth: '32px',
height: '32px',
borderRadius: '4px',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: active ? theme.palette.primary.main : theme.palette.text.secondary,
backgroundColor: active ? 'rgba(79, 70, 229, 0.1)' : 'transparent',
'&:hover': {
backgroundColor: active ? 'rgba(79, 70, 229, 0.2)' : 'rgba(0, 0, 0, 0.04)'
}
}));
const ResultsText = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 600,
letterSpacing: '-0.08px',
color: theme.palette.text.secondary,
[theme.breakpoints.down('sm')]: {
order: 2,
fontSize: '12px'
}
}));
const PaginationControls: React.FC<PaginationControlsProps> = ({
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const startResult = (currentPage - 1) * resultsPerPage + 1;
const endResult = Math.min(currentPage * resultsPerPage, totalResults);
const renderPageNumbers = () => {
const pages = [];
const maxVisiblePages = 7;
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
} else {
// Show first few pages, ellipsis, and last page
for (let i = 1; i <= 5; i++) {
pages.push(
<PageButton
key={i}
active={i === currentPage}
onClick={() => onPageChange(i)}
>
{i}
</PageButton>
);
}
pages.push(
<Typography key="ellipsis" sx={{ px: 1, color: 'text.secondary' }}>
...
</Typography>
);
pages.push(
<PageButton
key={totalPages}
active={totalPages === currentPage}
onClick={() => onPageChange(totalPages)}
>
{totalPages}
</PageButton>
);
}
return pages;
};
return (
<PaginationContainer>
<PaginationButtons>
<PaginationButton
startIcon={<PreviousIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
>
Previous
</PaginationButton>
<Stack direction="row" spacing={0.5} sx={{ mx: 2 }}>
{renderPageNumbers()}
</Stack>
<PaginationButton
endIcon={<NextIcon width={8} height={14} color="#475569" />}
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
>
Next
</PaginationButton>
</PaginationButtons>
<ResultsText>
Showing {endResult} of {totalResults.toLocaleString()} results
</ResultsText>
</PaginationContainer>
);
};
export default PaginationControls;
import React from 'react';
import { TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SearchIcon from './Icons/SearchIcon.svg';
const StyledTextField = styled(TextField)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '123px',
border: '1px solid #cbd5e1',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.text.secondary,
'& fieldset': {
border: 'none'
}
},
'& .MuiInputBase-input': {
padding: '12px 16px'
},
[theme.breakpoints.down('sm')]: {
width: '100%',
'& .MuiInputBase-input': {
padding: '10px 14px'
}
}
}));
const SearchInput: React.FC = () => {
return (
<StyledTextField
placeholder="Search anything..."
variant="outlined"
fullWidth
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon width={17} height={17} color="#475569" />
</InputAdornment>
)
}}
/>
);
};
export default SearchInput;
src/data/settingsPageMockData.ts already has the required edits.
I've successfully made the page mobile responsive by implementing the following changes:
minWidth
and overflowX: auto
to ensure tables scroll horizontally on mobiletheme.breakpoints.down('md')
- For tablet and mobile (< 900px)theme.breakpoints.down('sm')
- For mobile only (< 600px)The implementation follows MUI's responsive design patterns and avoids creating separate mobile components, instead using responsive styling within existing components.
I'll restructure the mobile header to separate the logo and hamburger menu, move "Team Settings" out of the mobile header into the main content area, and keep the HeaderActions in a single row below the title.
import React from 'react';
import { Stack, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import HamburgerMenuIcon from './Icons/HamburgerMenuIcon.svg';
interface MobileTopBarProps {
onMenuClick: () => void;
}
const TopBarContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: theme.palette.primary.main,
minHeight: '64px'
}
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const MobileTopBar: React.FC<MobileTopBarProps> = ({ onMenuClick }) => {
return (
<TopBarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<IconButton onClick={onMenuClick} sx={{ color: '#ffffff' }}>
<HamburgerMenuIcon width={24} height={24} color="#ffffff" />
</IconButton>
</TopBarContainer>
);
};
export default MobileTopBar;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
color: theme.palette.primary.contrastText
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
)}
<MainContent>
{isMobile && (
<MobileHeader>
<Stack direction="row" alignItems="center" gap={2}>
<IconButton onClick={toggleMobileDrawer} sx={{ color: 'inherit' }}>
<HamburgerMenuIcon width={24} height={24} color="#ffffff" />
</IconButton>
<MobileTitle>Team Settings</MobileTitle>
</Stack>
<Stack direction="row" alignItems="center" gap={1}>
<ShareIcon width={17} height={20} color="#ffffff" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={32}
showStatus={true}
/>
</Stack>
</MobileHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ p: 2 }}>
<MobileSearchContainer>
<SearchInput />
</MobileSearchContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
sx={{ mt: 2 }}
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
)}
<MainContent>
{isMobile && (
<MobileHeader>
<Stack direction="row" alignItems="center" gap={2}>
<IconButton onClick={toggleMobileDrawer} sx={{ color: 'inherit' }}>
<HamburgerMenuIcon width={24} height={24} color="#ffffff" />
</IconButton>
<MobileTitle>Team Settings</MobileTitle>
</Stack>
<Stack direction="row" alignItems="center" gap={1}>
<ShareIcon width={17} height={20} color="#ffffff" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={32}
showStatus={true}
/>
</Stack>
</MobileHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ p: 2 }}>
<MobileSearchContainer>
<SearchInput />
</MobileSearchContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
sx={{ mt: 2 }}
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ p: 2 }}>
<MobileSearchContainer>
<SearchInput />
</MobileSearchContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
sx={{ mt: 2 }}
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const MobileSearchContainer = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('sm')]: {
display: 'flex',
width: '100%'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '8px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
I've successfully restructured the mobile header according to the requirements:
Created MobileTopBar Component: A new separate component that only contains the Slothui logo and hamburger menu icon, matching the mobile design pattern.
Removed "Team Settings" from Mobile Header: The title is now moved to the main content area in a MobileContentHeader
component.
HeaderActions in Single Row: The search input, share icon, and avatar are now displayed in a single row below "Team Settings" on mobile.
Separate SVGs: The logo (SlothuiLogo.svg
) and hamburger menu (HamburgerMenuIcon.svg
) are now used as separate components in the mobile top bar.
The mobile layout now properly matches the design with the logo and hamburger menu in the top bar, while the "Team Settings" title and actions are in the content area below.
I'll update the team header layout to use column direction on mobile, fix the hamburger icon to be a proper menu icon, remove padding from sidebar search, and ensure consistent sidebar width across devices.
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z" fill="currentColor"/>
</svg>
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</Stack>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '280px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React from 'react';
import { TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SearchIcon from './Icons/SearchIcon.svg';
const StyledTextField = styled(TextField)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '123px',
border: '1px solid rgba(255, 255, 255, 0.3)',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.primary.contrastText,
'& fieldset': {
border: 'none'
},
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.15)'
},
'&.Mui-focused': {
backgroundColor: 'rgba(255, 255, 255, 0.2)'
}
},
'& .MuiInputBase-input': {
padding: '12px 16px',
'&::placeholder': {
color: 'rgba(255, 255, 255, 0.7)',
opacity: 1
}
}
}));
const SidebarSearchInput: React.FC = () => {
return (
<StyledTextField
placeholder="Search"
variant="outlined"
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon width={17} height={17} color="rgba(255, 255, 255, 0.7)" />
</InputAdornment>
)
}}
sx={{ mx: 0, px: 0 }}
/>
);
};
export default SidebarSearchInput;
import React from 'react';
import { Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import SlothuiLogo from './Icons/SlothuiLogo.svg';
import HomeIcon from './Icons/HomeIcon.svg';
import TasksIcon from './Icons/TasksIcon.svg';
import UsersIcon from './Icons/UsersIcon.svg';
import ApisIcon from './Icons/ApisIcon.svg';
import SubscriptionIcon from './Icons/SubscriptionIcon.svg';
import SettingsIcon from './Icons/SettingsIcon.svg';
import HelpSupportIcon from './Icons/HelpSupportIcon.svg';
import NavigationItem from './NavigationItem';
import PromoCard from './PromoCard';
import UserProfile from './UserProfile';
import SidebarSearchInput from './SidebarSearchInput';
import { CurrentUser } from '../types';
interface AppSidebarProps {
currentUser: CurrentUser;
homeNotificationCount: number;
usersNotificationCount: number;
}
const SidebarContainer = styled(Stack)(({ theme }) => ({
width: '312px',
height: '100vh',
backgroundColor: theme.palette.primary.main,
borderRight: '1px solid #e2e8f0',
padding: '32px 16px 16px 16px',
gap: '24px'
}));
const LogoContainer = styled(Stack)({
alignItems: 'flex-start'
});
const NavigationMenu = styled(Stack)({
gap: '8px'
});
const AppSidebar: React.FC<AppSidebarProps> = ({
currentUser,
homeNotificationCount,
usersNotificationCount
}) => {
return (
<SidebarContainer>
<LogoContainer>
<SlothuiLogo width={109} height={32} />
</LogoContainer>
<Stack sx={{ px: 0, mx: 2 }}>
<SidebarSearchInput />
</Stack>
<NavigationMenu>
<NavigationItem
icon={<HomeIcon width={19} height={20} color="#a5b4fc" />}
label="Home"
notificationCount={homeNotificationCount}
active
/>
<NavigationItem
icon={<TasksIcon width={19} height={15} color="#a5b4fc" />}
label="Tasks"
/>
<NavigationItem
icon={<UsersIcon width={24} height={15} color="#a5b4fc" />}
label="Users"
notificationCount={usersNotificationCount}
/>
<NavigationItem
icon={<ApisIcon width={20} height={22} color="#a5b4fc" />}
label="APIs"
/>
<NavigationItem
icon={<SubscriptionIcon width={22} height={16} color="#a5b4fc" />}
label="Subscription"
/>
<NavigationItem
icon={<SettingsIcon width={22} height={20} color="#a5b4fc" />}
label="Settings"
/>
<NavigationItem
icon={<HelpSupportIcon width={20} height={20} color="#a5b4fc" />}
label="Help & Support"
/>
</NavigationMenu>
<Stack sx={{ flex: 1, justifyContent: 'flex-end', gap: '24px' }}>
<PromoCard />
<UserProfile user={currentUser} />
</Stack>
</SidebarContainer>
);
};
export default AppSidebar;
I've successfully implemented all the requested changes:
Team Header Layout: Created a new TeamHeaderContainer
styled component that uses column direction on mobile while keeping TeamActions
in a single row. The container switches from row to column layout on small screens, ensuring proper stacking.
Hamburger Icon: Completely replaced the complex Slothui-branded hamburger icon with a clean, simple three-line menu icon that fills the entire SVG space (24x24 viewBox) and uses currentColor
for proper theming.
Sidebar Search Padding: Removed padding from the sidebar search by updating both SidebarSearchInput
component (added sx={{ mx: 0, px: 0 }}
) and AppSidebar
component (changed from px: 2
to px: 0, mx: 2
).
Sidebar Width Consistency: Updated the mobile drawer width from 280px
to 312px
to match the desktop sidebar width, ensuring consistent sizing across devices.
The layout now properly adapts to mobile with the team header components stacking vertically while keeping action buttons in a horizontal row, and the sidebar maintains consistent width and styling across all devices.
I'll implement search and sort functionality, create modals for project info and adding users, fix the gray background issue, and add a comprehensive README file. The functionality will be organized into reusable components with proper state management.
# Team Settings Dashboard
A modern, responsive team management interface built with React, TypeScript, and Material-UI v7. This application provides a comprehensive solution for managing team members with features like user selection, sorting, pagination, and responsive design.
## đ Features
### Core Functionality
- **Team Member Management**: View, add, and manage team members
- **Advanced Search**: Real-time search across user names and usernames
- **Multi-column Sorting**: Sort by name, status, join date, last active, and role
- **Bulk Selection**: Select individual users or use select-all functionality with tri-state checkboxes
- **Pagination**: Navigate through large datasets with customizable page sizes
- **Responsive Design**: Fully responsive layout that works on desktop, tablet, and mobile
### User Interface
- **Modern Design**: Clean, professional interface following Material Design principles
- **Dark Sidebar**: Purple-themed sidebar with navigation and user profile
- **Mobile-First**: Collapsible sidebar on mobile with hamburger menu
- **Status Indicators**: Visual status indicators for online/offline users
- **Avatar Support**: User avatars with online status badges
### Technical Features
- **TypeScript**: Full type safety and better developer experience
- **Component Architecture**: Modular, reusable components
- **State Management**: Efficient local state management with React hooks
- **Theme System**: Consistent theming with Material-UI's theme provider
- **Accessibility**: WCAG compliant with proper ARIA labels and keyboard navigation
## đ ī¸ Technology Stack
- **Frontend Framework**: React 18+ with TypeScript
- **UI Library**: Material-UI v7 (MUI)
- **Styling**: Emotion (CSS-in-JS) with Material-UI's styled API
- **Icons**: Custom SVG icons with Material-UI integration
- **State Management**: React hooks (useState, useEffect, custom hooks)
- **Responsive Design**: Material-UI breakpoints and responsive utilities
## đą Responsive Design
The application is fully responsive and adapts to different screen sizes:
- **Desktop (>900px)**: Full sidebar with main content area
- **Tablet (600-900px)**: Collapsible sidebar with drawer
- **Mobile (<600px)**: Hamburger menu with mobile-optimized layout
### Mobile Features
- Collapsible sidebar accessible via hamburger menu
- Horizontally scrollable tables
- Stacked action buttons
- Optimized touch targets
- Simplified navigation
## đ¨ Design System
### Color Palette
- **Primary**: Indigo (#4f46e5) - Used for buttons, active states, and branding
- **Secondary**: Light Indigo (#a5b4fc) - Used for icons and secondary elements
- **Success**: Green (#22c55e) - Used for online status and success states
- **Text**: Slate colors for hierarchy and readability
- **Background**: Light grays for surfaces and containers
### Typography
- **Font Family**: Plus Jakarta Sans
- **Hierarchy**: 8 levels from h1 (30px) to caption (12px)
- **Weights**: 400 (regular), 500 (medium), 600 (semibold), 700 (bold), 800 (extrabold)
## đ§ Component Architecture
### Core Components
- `SettingsPage`: Main container component
- `AppSidebar`: Navigation sidebar with user profile
- `TeamTable`: Data table with sorting and selection
- `PaginationControls`: Pagination with page navigation
- `SearchInput`: Search functionality with debouncing
### Reusable Components
- `ActionButton`: Consistent button styling
- `StatusChip`: User status indicators
- `AvatarWithStatus`: User avatars with status badges
- `TriStateCheckbox`: Advanced checkbox with indeterminate state
- `NavigationItem`: Sidebar navigation items
### Mobile Components
- `MobileTopBar`: Mobile header with logo and menu
- `SidebarSearchInput`: Mobile-optimized search
## đ Data Management
### User Data Structure
```typescript
interface User {
id: string;
name: string;
username: string;
avatar: string;
status: 'Online' | 'Offline';
joinDate: Date;
lastActive: Date;
role: 'Admin' | 'Editor' | 'Viewer';
selected: boolean;
}
```
### Features
- **Search**: Real-time filtering across name and username fields
- **Sorting**: Multi-column sorting with ascending/descending order
- **Selection**: Individual and bulk selection with tri-state checkboxes
- **Pagination**: Configurable page sizes with navigation controls
## đ Getting Started
### Prerequisites
- Node.js 16+
- npm or yarn package manager
### Installation
```bash
# Clone the repository
git clone <repository-url>
# Install dependencies
npm install
# Start development server
npm run dev
```
### Development
```bash
# Run in development mode
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
```
## đ¯ Usage
### Basic Navigation
1. Use the sidebar to navigate between different sections
2. On mobile, tap the hamburger menu to access the sidebar
3. Use the search bar to find specific team members
4. Click column headers to sort the table data
### Team Management
1. **View Members**: Browse the team member list with pagination
2. **Search**: Use the search bar to filter members by name or username
3. **Select Members**: Use checkboxes to select individual members or select all
4. **Add Members**: Click "Add New User" to add new team members
5. **Export Data**: Use the export button to download team data
### Mobile Usage
1. Tap the hamburger menu (â°) to open the sidebar
2. Swipe horizontally to scroll through table columns
3. Use the mobile-optimized search and navigation
4. All features are accessible on mobile devices
## đ Search & Filter
The search functionality supports:
- **Real-time search**: Results update as you type
- **Multiple fields**: Searches across name and username
- **Case-insensitive**: Works regardless of capitalization
- **Debounced input**: Optimized performance with delayed search
## đ Performance
### Optimizations
- **Component memoization**: Prevents unnecessary re-renders
- **Debounced search**: Reduces API calls and improves performance
- **Lazy loading**: Components load only when needed
- **Efficient state updates**: Minimal state changes for better performance
### Best Practices
- Modular component architecture
- TypeScript for type safety
- Consistent naming conventions
- Proper error handling
- Accessibility compliance
## đ¨ Customization
### Theme Customization
The application uses Material-UI's theming system. You can customize:
- Colors and color palette
- Typography scales and fonts
- Component variants and styles
- Breakpoints and spacing
### Component Customization
- All components are built with styled-components
- Easy to override styles and behavior
- Consistent prop interfaces
- Extensible component architecture
## đą Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
- Mobile browsers (iOS Safari, Chrome Mobile)
## đ¤ Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## đ License
This project is licensed under the MIT License - see the LICENSE file for details.
## đ Acknowledgments
- Material-UI team for the excellent component library
- Plus Jakarta Sans font family
- React team for the amazing framework
- TypeScript team for type safety
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Stack,
Chip,
Divider,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
interface ProjectInfoModalProps {
open: boolean;
onClose: () => void;
}
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: '16px',
maxWidth: '600px',
width: '90%'
}
}));
const FeatureChip = styled(Chip)(({ theme }) => ({
margin: '4px',
backgroundColor: theme.palette.primary.light,
color: theme.palette.primary.contrastText,
'& .MuiChip-label': {
fontSize: '12px',
fontWeight: 600
}
}));
const TechChip = styled(Chip)(({ theme }) => ({
margin: '4px',
backgroundColor: theme.palette.grey[100],
color: theme.palette.text.primary,
'& .MuiChip-label': {
fontSize: '12px',
fontWeight: 500
}
}));
const ProjectInfoModal: React.FC<ProjectInfoModalProps> = ({ open, onClose }) => {
const features = [
'Team Management',
'Real-time Search',
'Multi-column Sorting',
'Bulk Selection',
'Responsive Design',
'Mobile-First',
'TypeScript',
'Material-UI v7'
];
const technologies = [
'React 18+',
'TypeScript',
'Material-UI v7',
'Emotion CSS-in-JS',
'React Hooks',
'Responsive Design',
'Custom SVG Icons',
'Modern Architecture'
];
return (
<StyledDialog open={open} onClose={onClose} maxWidth="md">
<DialogTitle>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Typography variant="h2" component="h2">
Team Settings Dashboard
</Typography>
<IconButton onClick={onClose} size="small">
â
</IconButton>
</Stack>
</DialogTitle>
<DialogContent>
<Stack gap={3}>
<Typography variant="body1" color="text.secondary">
A modern, responsive team management interface built with React, TypeScript, and Material-UI v7.
This application provides a comprehensive solution for managing team members with advanced features
and a mobile-first design approach.
</Typography>
<Divider />
<Stack gap={2}>
<Typography variant="h6" fontWeight={700}>
⨠Key Features
</Typography>
<Stack direction="row" flexWrap="wrap" gap={0.5}>
{features.map((feature) => (
<FeatureChip key={feature} label={feature} size="small" />
))}
</Stack>
</Stack>
<Stack gap={2}>
<Typography variant="h6" fontWeight={700}>
đ ī¸ Technology Stack
</Typography>
<Stack direction="row" flexWrap="wrap" gap={0.5}>
{technologies.map((tech) => (
<TechChip key={tech} label={tech} size="small" variant="outlined" />
))}
</Stack>
</Stack>
<Stack gap={2}>
<Typography variant="h6" fontWeight={700}>
đą Responsive Design
</Typography>
<Typography variant="body2" color="text.secondary">
The application adapts seamlessly across devices:
</Typography>
<Stack gap={1} sx={{ pl: 2 }}>
<Typography variant="body2">
<strong>Desktop:</strong> Full sidebar with comprehensive table view
</Typography>
<Typography variant="body2">
<strong>Tablet:</strong> Collapsible sidebar with optimized layout
</Typography>
<Typography variant="body2">
<strong>Mobile:</strong> Hamburger menu with horizontally scrollable tables
</Typography>
</Stack>
</Stack>
<Stack gap={2}>
<Typography variant="h6" fontWeight={700}>
đ¯ Interactive Features
</Typography>
<Stack gap={1} sx={{ pl: 2 }}>
<Typography variant="body2">
âĸ <strong>Search:</strong> Real-time filtering across user data
</Typography>
<Typography variant="body2">
âĸ <strong>Sorting:</strong> Click column headers to sort data
</Typography>
<Typography variant="body2">
âĸ <strong>Selection:</strong> Individual and bulk user selection
</Typography>
<Typography variant="body2">
âĸ <strong>Pagination:</strong> Navigate through large datasets
</Typography>
<Typography variant="body2">
âĸ <strong>Add Users:</strong> Create new team members
</Typography>
</Stack>
</Stack>
</Stack>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 1 }}>
<Button onClick={onClose} variant="contained" fullWidth>
Got it!
</Button>
</DialogActions>
</StyledDialog>
);
};
export default ProjectInfoModal;
import React, { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
Typography,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { User } from '../types';
interface AddUserModalProps {
open: boolean;
onClose: () => void;
onAddUser: (user: Omit<User, 'id' | 'selected'>) => void;
}
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: '16px',
maxWidth: '500px',
width: '90%'
}
}));
const AddUserModal: React.FC<AddUserModalProps> = ({ open, onClose, onAddUser }) => {
const [formData, setFormData] = useState({
name: '',
username: '',
role: 'Viewer' as 'Admin' | 'Editor' | 'Viewer',
status: 'Online' as 'Online' | 'Offline'
});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: '' }));
}
};
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.username.trim()) {
newErrors.username = 'Username is required';
} else if (!formData.username.startsWith('@')) {
newErrors.username = 'Username must start with @';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = () => {
if (!validateForm()) return;
const newUser: Omit<User, 'id' | 'selected'> = {
name: formData.name.trim(),
username: formData.username.trim(),
avatar: `https://i.pravatar.cc/150?img=${Math.floor(Math.random() * 70) + 1}`,
status: formData.status,
joinDate: new Date(),
lastActive: new Date(),
role: formData.role
};
onAddUser(newUser);
handleClose();
};
const handleClose = () => {
setFormData({
name: '',
username: '',
role: 'Viewer',
status: 'Online'
});
setErrors({});
onClose();
};
return (
<StyledDialog open={open} onClose={handleClose} maxWidth="sm">
<DialogTitle>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Typography variant="h2" component="h2">
Add New User
</Typography>
<IconButton onClick={handleClose} size="small">
â
</IconButton>
</Stack>
</DialogTitle>
<DialogContent>
<Stack gap={3} sx={{ mt: 1 }}>
<TextField
label="Full Name"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
error={!!errors.name}
helperText={errors.name}
fullWidth
variant="outlined"
/>
<TextField
label="Username"
value={formData.username}
onChange={(e) => handleInputChange('username', e.target.value)}
error={!!errors.username}
helperText={errors.username || 'Must start with @'}
fullWidth
variant="outlined"
placeholder="@username"
/>
<FormControl fullWidth>
<InputLabel>Role</InputLabel>
<Select
value={formData.role}
label="Role"
onChange={(e) => handleInputChange('role', e.target.value)}
>
<MenuItem value="Admin">Admin</MenuItem>
<MenuItem value="Editor">Editor</MenuItem>
<MenuItem value="Viewer">Viewer</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth>
<InputLabel>Status</InputLabel>
<Select
value={formData.status}
label="Status"
onChange={(e) => handleInputChange('status', e.target.value)}
>
<MenuItem value="Online">Online</MenuItem>
<MenuItem value="Offline">Offline</MenuItem>
</Select>
</FormControl>
</Stack>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 1 }}>
<Button onClick={handleClose} variant="outlined" sx={{ mr: 1 }}>
Cancel
</Button>
<Button onClick={handleSubmit} variant="contained">
Add User
</Button>
</DialogActions>
</StyledDialog>
);
};
export default AddUserModal;
import React from 'react';
import { Fab, Tooltip } from '@mui/material';
import { styled } from '@mui/material/styles';
interface FloatingActionButtonProps {
onClick: () => void;
icon: React.ReactNode;
tooltip: string;
}
const StyledFab = styled(Fab)(({ theme }) => ({
position: 'fixed',
bottom: '24px',
right: '24px',
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
boxShadow: '0px 8px 16px rgba(79, 70, 229, 0.3)',
'&:hover': {
backgroundColor: theme.palette.primary.dark,
boxShadow: '0px 12px 20px rgba(79, 70, 229, 0.4)'
},
zIndex: 1000,
[theme.breakpoints.down('sm')]: {
bottom: '16px',
right: '16px'
}
}));
const FloatingActionButton: React.FC<FloatingActionButtonProps> = ({
onClick,
icon,
tooltip
}) => {
return (
<Tooltip title={tooltip} placement="left">
<StyledFab onClick={onClick} size="medium">
{icon}
</StyledFab>
</Tooltip>
);
};
export default FloatingActionButton;
import { useState, useMemo } from 'react';
import { User } from '../types';
export const useSearch = (users: User[]) => {
const [searchTerm, setSearchTerm] = useState('');
const filteredUsers = useMemo(() => {
if (!searchTerm.trim()) return users;
const term = searchTerm.toLowerCase().trim();
return users.filter(user =>
user.name.toLowerCase().includes(term) ||
user.username.toLowerCase().includes(term)
);
}, [users, searchTerm]);
return {
searchTerm,
setSearchTerm,
filteredUsers
};
};
import { useState, useMemo } from 'react';
import { User } from '../types';
type SortField = 'name' | 'status' | 'joinDate' | 'lastActive' | 'role';
type SortDirection = 'asc' | 'desc';
export const useSort = (users: User[]) => {
const [sortField, setSortField] = useState<SortField | null>(null);
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
const sortedUsers = useMemo(() => {
if (!sortField) return users;
return [...users].sort((a, b) => {
let aValue: any = a[sortField];
let bValue: any = b[sortField];
// Handle date sorting
if (sortField === 'joinDate' || sortField === 'lastActive') {
aValue = new Date(aValue).getTime();
bValue = new Date(bValue).getTime();
}
// Handle string sorting
if (typeof aValue === 'string') {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
}
if (aValue < bValue) {
return sortDirection === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortDirection === 'asc' ? 1 : -1;
}
return 0;
});
}, [users, sortField, sortDirection]);
const handleSort = (field: SortField) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
return {
sortField,
sortDirection,
sortedUsers,
handleSort
};
};
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
console.log('Export clicked');
};
const handleAddUser = () => {
console.log('Add user clicked');
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput />
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput />
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={handleAddUser}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={() => setAddUserOpen(true)}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={teamMembers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={() => setAddUserOpen(true)}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={sortedUsers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
/>
</TeamSection>
</ContentArea>
</MainContent>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
</Header>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={() => setAddUserOpen(true)}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={sortedUsers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
/>
</TeamSection>
</ContentArea>
</MainContent>
<FloatingActionButton
onClick={() => setProjectInfoOpen(true)}
icon="âšī¸"
tooltip="About this project"
/>
<ProjectInfoModal
open={projectInfoOpen}
onClose={() => setProjectInfoOpen(false)}
/>
<AddUserModal
open={addUserOpen}
onClose={() => setAddUserOpen(false)}
onAddUser={handleAddUser}
/>
</PageContainer>
);
};
export default SettingsPage;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
sortField: string | null;
sortDirection: 'asc' | 'desc';
onSort: (field: 'name' | 'status' | 'joinDate' | 'lastActive' | 'role') => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange
}) => {
const [sortField, setSortField] = useState<string | null>(null);
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSort = (field: string) => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('asc');
}
};
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
return (
<StyledTableContainer>
<Table sx={{ minWidth: 650 }}>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
sortField: string | null;
sortDirection: 'asc' | 'desc';
onSort: (field: 'name' | 'status' | 'joinDate' | 'lastActive' | 'role') => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange,
sortField,
sortDirection,
onSort
}) => {
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
const getSortIcon = (field: string) => {
if (sortField !== field) return <SortIcon width={9} height={16} color="#475569" />;
return (
<SortIcon
width={9}
height={16}
color="#4f46e5"
style={{
transform: sortDirection === 'desc' ? 'rotate(180deg)' : 'none',
transition: 'transform 0.2s ease'
}}
/>
);
};
return (
<StyledTableContainer>
<Table sx={{ minWidth: 650 }}>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => handleSort('name')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
sortField: string | null;
sortDirection: 'asc' | 'desc';
onSort: (field: 'name' | 'status' | 'joinDate' | 'lastActive' | 'role') => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange,
sortField,
sortDirection,
onSort
}) => {
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
const getSortIcon = (field: string) => {
if (sortField !== field) return <SortIcon width={9} height={16} color="#475569" />;
return (
<SortIcon
width={9}
height={16}
color="#4f46e5"
style={{
transform: sortDirection === 'desc' ? 'rotate(180deg)' : 'none',
transition: 'transform 0.2s ease'
}}
/>
);
};
return (
<StyledTableContainer>
<Table sx={{ minWidth: 650 }}>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => onSort('name')}>
{getSortIcon('name')}
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => handleSort('status')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => handleSort('joinDate')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => handleSort('lastActive')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => handleSort('role')}>
<SortIcon width={9} height={16} color="#475569" />
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Stack,
Typography,
IconButton
} from '@mui/material';
import { styled } from '@mui/material/styles';
import SortIcon from './Icons/SortIcon.svg';
import TeamTableRow from './TeamTableRow';
import TriStateCheckbox from './TriStateCheckbox';
import PaginationControls from './PaginationControls';
import { User } from '../types';
interface TeamTableProps {
users: User[];
onSelectionChange: (userId: string, selected: boolean) => void;
currentPage: number;
totalPages: number;
totalResults: number;
resultsPerPage: number;
onPageChange: (page: number) => void;
sortField: string | null;
sortDirection: 'asc' | 'desc';
onSort: (field: 'name' | 'status' | 'joinDate' | 'lastActive' | 'role') => void;
}
const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
borderRadius: '24px',
border: '1px solid #e2e8f0',
boxShadow: '0px 2px 4px rgba(23, 23, 23, 0.06), 0px 4px 8px rgba(23, 23, 23, 0.10)',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px',
overflowX: 'auto'
}
}));
const StyledTableHead = styled(TableHead)(({ theme }) => ({
backgroundColor: theme.palette.background.paper
}));
const HeaderCell = styled(TableCell)(({ theme }) => ({
borderBottom: '1px solid #cbd5e1',
padding: '12px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
color: theme.palette.text.primary,
whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: {
minWidth: '120px'
}
}));
const HeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '12px'
});
const NameHeaderContent = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%'
});
const NameHeaderLeft = styled(Stack)({
flexDirection: 'row',
alignItems: 'center',
gap: '12px'
});
const PaginationRow = styled(TableRow)(({ theme }) => ({
'& td': {
border: 'none',
padding: '16px 12px'
}
}));
const TeamTable: React.FC<TeamTableProps> = ({
users,
onSelectionChange,
currentPage,
totalPages,
totalResults,
resultsPerPage,
onPageChange,
sortField,
sortDirection,
onSort
}) => {
const selectedUsers = useMemo(() => users.filter(user => user.selected), [users]);
const allSelected = selectedUsers.length === users.length && users.length > 0;
const someSelected = selectedUsers.length > 0 && selectedUsers.length < users.length;
const handleSelectAll = (checked: boolean) => {
users.forEach(user => {
onSelectionChange(user.id, checked);
});
};
const getSortIcon = (field: string) => {
if (sortField !== field) return <SortIcon width={9} height={16} color="#475569" />;
return (
<SortIcon
width={9}
height={16}
color="#4f46e5"
style={{
transform: sortDirection === 'desc' ? 'rotate(180deg)' : 'none',
transition: 'transform 0.2s ease'
}}
/>
);
};
return (
<StyledTableContainer>
<Table sx={{ minWidth: 650 }}>
<StyledTableHead>
<TableRow>
<HeaderCell>
<NameHeaderContent>
<NameHeaderLeft>
<TriStateCheckbox
checked={allSelected}
indeterminate={someSelected}
onChange={handleSelectAll}
size="small"
/>
<Typography variant="button">Full Name</Typography>
</NameHeaderLeft>
<IconButton size="small" onClick={() => onSort('name')}>
{getSortIcon('name')}
</IconButton>
</NameHeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Status</Typography>
<IconButton size="small" onClick={() => onSort('status')}>
{getSortIcon('status')}
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Join Date</Typography>
<IconButton size="small" onClick={() => onSort('joinDate')}>
{getSortIcon('joinDate')}
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Last Active</Typography>
<IconButton size="small" onClick={() => onSort('lastActive')}>
{getSortIcon('lastActive')}
</IconButton>
</HeaderContent>
</HeaderCell>
<HeaderCell>
<HeaderContent>
<Typography variant="button">Role</Typography>
<IconButton size="small" onClick={() => onSort('role')}>
{getSortIcon('role')}
</IconButton>
</HeaderContent>
</HeaderCell>
</TableRow>
</StyledTableHead>
<TableBody>
{users.map((user) => (
<TeamTableRow
key={user.id}
user={user}
onSelectionChange={onSelectionChange}
/>
))}
<PaginationRow>
<TableCell colSpan={5}>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={onPageChange}
/>
</TableCell>
</PaginationRow>
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default TeamTable;
import React from 'react';
import { TextField, InputAdornment } from '@mui/material';
import { styled } from '@mui/material/styles';
import SearchIcon from './Icons/SearchIcon.svg';
interface SearchInputProps {
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
}
const StyledTextField = styled(TextField)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '123px',
border: '1px solid #cbd5e1',
fontSize: '16px',
fontWeight: 500,
letterSpacing: '-0.11px',
color: theme.palette.text.secondary,
'& fieldset': {
border: 'none'
}
},
'& .MuiInputBase-input': {
padding: '12px 16px'
},
[theme.breakpoints.down('sm')]: {
width: '100%',
'& .MuiInputBase-input': {
padding: '10px 14px'
}
}
}));
const SearchInput: React.FC<SearchInputProps> = ({
value,
onChange,
placeholder = "Search anything..."
}) => {
return (
<StyledTextField
placeholder={placeholder}
variant="outlined"
fullWidth
value={value}
onChange={onChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon width={17} height={17} color="#475569" />
</InputAdornment>
)
}}
/>
);
};
export default SearchInput;
I've successfully implemented all the requested features:
ProjectInfoModal
component with comprehensive project detailsuseSearch
custom hook for clean state managementuseSort
custom hook with proper state managementAddUserModal
with form validationuseSearch
and useSort
The application now provides a complete team management experience with all the requested functionality working seamlessly across desktop and mobile devices.
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={() => setAddUserOpen(true)}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={sortedUsers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
/>
</TeamSection>
</ContentArea>
</MainContent>
<FloatingActionButton
onClick={() => setProjectInfoOpen(true)}
icon="âšī¸"
tooltip="About this project"
/>
<ProjectInfoModal
open={projectInfoOpen}
onClose={() => setProjectInfoOpen(false)}
/>
<AddUserModal
open={addUserOpen}
onClose={() => setAddUserOpen(false)}
onAddUser={handleAddUser}
/>
</PageContainer>
);
};
export default SettingsPage;
src/components/SettingsPage.tsx already has the required edits.
import React, { useState } from 'react';
import { Stack, Typography, Tabs, Tab, useMediaQuery, useTheme, Drawer } from '@mui/material';
import { styled } from '@mui/material/styles';
import AppSidebar from './AppSidebar';
import SearchInput from './SearchInput';
import ActionButton from './ActionButton';
import TeamTable from './TeamTable';
import AvatarWithStatus from './AvatarWithStatus';
import MobileTopBar from './MobileTopBar';
import ProjectInfoModal from './ProjectInfoModal';
import AddUserModal from './AddUserModal';
import FloatingActionButton from './FloatingActionButton';
import ShareIcon from './Icons/ShareIcon.svg';
import ExportIcon from './Icons/ExportIcon.svg';
import AddIcon from './Icons/AddIcon.svg';
import { SettingsPageProps, User } from '../types';
import { useSearch } from '../hooks/useSearch';
import { useSort } from '../hooks/useSort';
const PageContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
height: '100vh',
backgroundColor: '#ffffff',
[theme.breakpoints.down('md')]: {
flexDirection: 'column'
}
}));
const MainContent = styled(Stack)(({ theme }) => ({
flex: 1,
height: '100vh',
overflow: 'auto',
[theme.breakpoints.down('md')]: {
height: 'auto',
minHeight: '100vh'
}
}));
const Header = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
padding: '24px 24px 0 24px',
gap: '24px',
[theme.breakpoints.down('sm')]: {
padding: '16px 16px 0 16px',
gap: '16px'
}
}));
const TabsContainer = styled(Stack)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
paddingLeft: '24px',
paddingRight: '24px',
[theme.breakpoints.down('sm')]: {
paddingLeft: '16px',
paddingRight: '16px'
}
}));
const HeaderTop = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const HeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const PageTitle = styled(Typography)(({ theme }) => ({
fontSize: '30px',
fontWeight: 800,
letterSpacing: '-0.39px',
color: theme.palette.text.primary,
[theme.breakpoints.down('sm')]: {
fontSize: '24px'
}
}));
const MobileContentHeader = styled(Stack)(({ theme }) => ({
display: 'none',
[theme.breakpoints.down('md')]: {
display: 'flex',
padding: '16px',
gap: '16px'
}
}));
const MobileTitle = styled(Typography)(({ theme }) => ({
fontSize: '24px',
fontWeight: 800,
letterSpacing: '-0.29px',
color: theme.palette.text.primary
}));
const MobileHeaderActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
[theme.breakpoints.down('sm')]: {
gap: '8px'
}
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.primary.main,
height: '2px'
}
}));
const StyledTab = styled(Tab)(({ theme }) => ({
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
color: theme.palette.text.secondary,
'&.Mui-selected': {
color: theme.palette.text.primary
}
}));
const ContentArea = styled(Stack)(({ theme }) => ({
padding: '32px',
gap: '32px',
[theme.breakpoints.down('sm')]: {
padding: '16px',
gap: '16px'
}
}));
const TeamSection = styled(Stack)(({ theme }) => ({
border: '1px solid #e2e8f0',
borderRadius: '24px',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
borderRadius: '16px'
}
}));
const TeamHeader = styled(Stack)(({ theme }) => ({
padding: '16px',
border: '1px solid #e2e8f0',
gap: '16px',
[theme.breakpoints.down('sm')]: {
padding: '12px',
gap: '12px'
}
}));
const TeamInfo = styled(Stack)({
gap: '4px'
});
const TeamTitle = styled(Typography)(({ theme }) => ({
fontSize: '18px',
fontWeight: 700,
letterSpacing: '-0.14px',
color: theme.palette.text.primary
}));
const TeamCount = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 600,
letterSpacing: '-0.06px',
color: theme.palette.primary.main
}));
const TeamDescription = styled(Typography)(({ theme }) => ({
fontSize: '14px',
fontWeight: 400,
color: theme.palette.text.secondary
}));
const TeamActions = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
gap: '16px'
}));
const TeamHeaderContainer = styled(Stack)(({ theme }) => ({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
gap: '16px'
}
}));
const DesktopSearchContainer = styled(Stack)(({ theme }) => ({
display: 'flex',
[theme.breakpoints.down('sm')]: {
display: 'none'
}
}));
const SettingsPage: React.FC<SettingsPageProps> = ({
currentUser,
teamMembers: initialTeamMembers,
totalTeamMembers,
currentPage: initialCurrentPage,
totalPages,
resultsPerPage,
totalResults,
activeTab: initialActiveTab,
homeNotificationCount,
usersNotificationCount
}) => {
const [teamMembers, setTeamMembers] = useState<User[]>(initialTeamMembers);
const [currentPage, setCurrentPage] = useState(initialCurrentPage);
const [activeTab, setActiveTab] = useState(initialActiveTab);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [projectInfoOpen, setProjectInfoOpen] = useState(false);
const [addUserOpen, setAddUserOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { searchTerm, setSearchTerm, filteredUsers } = useSearch(teamMembers);
const { sortField, sortDirection, sortedUsers, handleSort } = useSort(filteredUsers);
const handleSelectionChange = (userId: string, selected: boolean) => {
setTeamMembers(prev =>
prev.map(user =>
user.id === userId ? { ...user, selected } : user
)
);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
// In a real app, this would trigger data fetching
};
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
setActiveTab(newValue);
};
const handleExport = () => {
const selectedUsers = teamMembers.filter(user => user.selected);
const dataToExport = selectedUsers.length > 0 ? selectedUsers : teamMembers;
const csvContent = [
'Name,Username,Status,Role,Join Date,Last Active',
...dataToExport.map(user =>
`${user.name},${user.username},${user.status},${user.role},${user.joinDate.toLocaleDateString()},${user.lastActive.toLocaleDateString()}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'team-members.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const handleAddUser = (newUserData: Omit<User, 'id' | 'selected'>) => {
const newUser: User = {
...newUserData,
id: `user-${Date.now()}`,
selected: false
};
setTeamMembers(prev => [newUser, ...prev]);
};
const toggleMobileDrawer = () => {
setMobileDrawerOpen(!mobileDrawerOpen);
};
return (
<PageContainer>
{!isMobile && (
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
)}
{isMobile && (
<>
<MobileTopBar onMenuClick={toggleMobileDrawer} />
<Drawer
anchor="left"
open={mobileDrawerOpen}
onClose={toggleMobileDrawer}
sx={{
'& .MuiDrawer-paper': {
width: '312px'
}
}}
>
<AppSidebar
currentUser={currentUser}
homeNotificationCount={homeNotificationCount}
usersNotificationCount={usersNotificationCount}
/>
</Drawer>
</>
)}
<MainContent>
{isMobile && (
<MobileContentHeader>
<Stack gap={2} width="100%">
<MobileTitle>Team Settings</MobileTitle>
<MobileHeaderActions>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</MobileHeaderActions>
</Stack>
</MobileContentHeader>
)}
{!isMobile && (
<>
<Header>
<HeaderTop>
<PageTitle>Settings</PageTitle>
<HeaderActions>
<DesktopSearchContainer>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</DesktopSearchContainer>
<ShareIcon width={17} height={20} color="#475569" />
<AvatarWithStatus
src={currentUser.avatar}
alt={currentUser.name}
size={40}
showStatus={true}
/>
</HeaderActions>
</HeaderTop>
</Header>
<TabsContainer>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="standard"
>
<StyledTab label="General" value="General" />
<StyledTab label="Details" value="Details" />
<StyledTab label="Account" value="Account" />
</StyledTabs>
</TabsContainer>
</>
)}
{isMobile && (
<Stack sx={{ px: 2, pb: 2 }}>
<StyledTabs
value={activeTab}
onChange={handleTabChange}
variant="scrollable"
scrollButtons="auto"
>
<StyledTab label="Details" value="Details" />
<StyledTab label="Personal" value="Personal" />
<StyledTab label="Account" value="Account" />
<StyledTab label="Profile" value="Profile" />
<StyledTab label="Security" value="Security" />
</StyledTabs>
</Stack>
)}
<ContentArea>
<TeamSection>
<TeamHeader>
<TeamHeaderContainer>
<TeamInfo>
<Stack direction="row" alignItems="center" gap="8px">
<TeamTitle>My Teams</TeamTitle>
<TeamCount>{totalTeamMembers} Total</TeamCount>
</Stack>
<TeamDescription>
You can manage your team here seamlessly.
</TeamDescription>
</TeamInfo>
<TeamActions>
<ActionButton
variant="outlined"
startIcon={<ExportIcon width={19} height={14} color="#475569" />}
onClick={handleExport}
>
Export
</ActionButton>
<ActionButton
variant="contained"
startIcon={<AddIcon width={16} height={16} color="#ffffff" />}
onClick={() => setAddUserOpen(true)}
>
Add New User
</ActionButton>
</TeamActions>
</TeamHeaderContainer>
</TeamHeader>
<TeamTable
users={sortedUsers}
onSelectionChange={handleSelectionChange}
currentPage={currentPage}
totalPages={totalPages}
totalResults={totalResults}
resultsPerPage={resultsPerPage}
onPageChange={handlePageChange}
sortField={sortField}
sortDirection={sortDirection}
onSort={handleSort}
/>
</TeamSection>
</ContentArea>
</MainContent>
<FloatingActionButton
onClick={() => setProjectInfoOpen(true)}
icon="âšī¸"
tooltip="About this project"
/>
<ProjectInfoModal
open={projectInfoOpen}
onClose={() => setProjectInfoOpen(false)}
/>
<AddUserModal
open={addUserOpen}
onClose={() => setAddUserOpen(false)}
onAddUser={handleAddUser}
/>
</PageContainer>
);
};
export default SettingsPage;
Fixed the JSX parent element error by wrapping the Header and TabsContainer in a React Fragment.
Open this conversation in your favorite IDE
import React from 'react';
import { Button } from '@mui/material';
import { styled } from '@mui/material/styles';
interface ActionButtonProps {
children: React.ReactNode;
variant?: 'outlined' | 'contained';
startIcon?: React.ReactNode;
onClick?: () => void;
}
const StyledButton = styled(Button)<{ variant?: 'outlined' | 'contained' }>(({ theme, variant }) => ({
borderRadius: '1234px',
padding: '12px 16px',
fontSize: '14px',
fontWeight: 700,
letterSpacing: '-0.08px',
textTransform: 'none',
gap: '8px',
...(variant === 'outlined' && {
border: '1px solid #cbd5e1',
color: theme.palette.text.secondary,
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)'
}
}),
...(variant === 'contained' && {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
'&:hover': {
backgroundColor: theme.palette.primary.dark
}
})
}));
const ActionButton: React.FC<ActionButtonProps> = ({
children,
variant = 'outlined',
startIcon,
onClick
}) => {
return (
<StyledButton
variant={variant}
startIcon={startIcon}
onClick={onClick}
>
{children}
</StyledButton>
);
};
export default ActionButton;
Read-onlyPlease wait while we set everything up