build(ui): enable admin to create teams, add members, create keys for teams

This commit is contained in:
Krrish Dholakia 2024-02-24 22:06:00 -08:00
parent 1151bc268f
commit c33a472611
10 changed files with 347 additions and 93 deletions

View file

@ -226,7 +226,14 @@ class UpdateUserRequest(GenerateRequestBase):
class Member(LiteLLMBase): class Member(LiteLLMBase):
role: Literal["admin", "user"] role: Literal["admin", "user"]
user_id: str user_id: Optional[str] = None
user_email: Optional[str] = None
@root_validator(pre=True)
def check_user_info(cls, values):
if values.get("user_id") is None and values.get("user_email") is None:
raise ValueError("Either user id or user email must be provided")
return values
class NewTeamRequest(LiteLLMBase): class NewTeamRequest(LiteLLMBase):
@ -242,6 +249,11 @@ class NewTeamRequest(LiteLLMBase):
models: list = [] models: list = []
class TeamMemberAddRequest(LiteLLMBase):
team_id: str
member: Optional[Member] = None
class UpdateTeamRequest(LiteLLMBase): class UpdateTeamRequest(LiteLLMBase):
team_id: str # required team_id: str # required
team_alias: Optional[str] = None team_alias: Optional[str] = None
@ -261,6 +273,25 @@ class LiteLLM_TeamTable(NewTeamRequest):
budget_duration: Optional[str] = None budget_duration: Optional[str] = None
budget_reset_at: Optional[datetime] = None budget_reset_at: Optional[datetime] = None
@root_validator(pre=True)
def set_model_info(cls, values):
dict_fields = [
"metadata",
"aliases",
"config",
"permissions",
"model_max_budget",
]
for field in dict_fields:
value = values.get(field)
if value is not None and isinstance(value, str):
try:
values[field] = json.loads(value)
except json.JSONDecodeError:
raise ValueError(f"Field {field} should be a valid dictionary")
return values
class TeamRequest(LiteLLMBase): class TeamRequest(LiteLLMBase):
teams: List[str] teams: List[str]

View file

@ -4063,6 +4063,7 @@ async def user_info(
default=False, default=False,
description="set to true to View all users. When using view_all, don't pass user_id", description="set to true to View all users. When using view_all, don't pass user_id",
), ),
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
): ):
""" """
Use this to get user information. (user row + all user key info) Use this to get user information. (user row + all user key info)
@ -4108,6 +4109,22 @@ async def user_info(
team_id_list=user_info.teams, table_name="team", query_type="find_all" team_id_list=user_info.teams, table_name="team", query_type="find_all"
) )
if teams_2 is not None and isinstance(teams_2, list):
for team in teams_2:
if team.team_id not in team_id_list:
team_list.append(team)
team_id_list.append(team.team_id)
elif user_api_key_dict.user_id is not None:
caller_user_info = await prisma_client.get_data(
user_id=user_api_key_dict.user_id
)
# *NEW* get all teams in user 'teams' field
teams_2 = await prisma_client.get_data(
team_id_list=caller_user_info.teams,
table_name="team",
query_type="find_all",
)
if teams_2 is not None and isinstance(teams_2, list): if teams_2 is not None and isinstance(teams_2, list):
for team in teams_2: for team in teams_2:
if team.team_id not in team_id_list: if team.team_id not in team_id_list:
@ -4137,12 +4154,14 @@ async def user_info(
# if using pydantic v1 # if using pydantic v1
key = key.dict() key = key.dict()
key.pop("token", None) key.pop("token", None)
return {
response_data = {
"user_id": user_id, "user_id": user_id,
"user_info": user_info, "user_info": user_info,
"keys": keys, "keys": keys,
"teams": team_list, "teams": team_list,
} }
return response_data
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
if isinstance(e, HTTPException): if isinstance(e, HTTPException):
@ -4406,6 +4425,17 @@ async def new_team(
}, },
) )
if user_api_key_dict.user_id is not None:
creating_user_in_list = False
for member in data.members_with_roles:
if member.user_id == user_api_key_dict.user_id:
creating_user_in_list = True
if creating_user_in_list == False:
data.members_with_roles.append(
Member(role="admin", user_id=user_api_key_dict.user_id)
)
complete_team_data = LiteLLM_TeamTable( complete_team_data = LiteLLM_TeamTable(
**data.json(), **data.json(),
max_parallel_requests=user_api_key_dict.max_parallel_requests, max_parallel_requests=user_api_key_dict.max_parallel_requests,
@ -4440,6 +4470,9 @@ async def update_team(
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
): ):
""" """
[BETA]
[DEPRECATED] - use the `/team/member_add` and `/team/member_remove` endpoints instead
You can now add / delete users from a team via /team/update You can now add / delete users from a team via /team/update
``` ```
@ -4479,6 +4512,7 @@ async def update_team(
existing_user_id_list = [] existing_user_id_list = []
## Get new users ## Get new users
for user in existing_team_row.members_with_roles: for user in existing_team_row.members_with_roles:
if user["user_id"] is not None:
existing_user_id_list.append(user["user_id"]) existing_user_id_list.append(user["user_id"])
## Update new user rows with team id (info used by /user/info to show all teams, user is a part of) ## Update new user rows with team id (info used by /user/info to show all teams, user is a part of)
@ -4487,26 +4521,32 @@ async def update_team(
if user.user_id not in existing_user_id_list: if user.user_id not in existing_user_id_list:
await prisma_client.update_data( await prisma_client.update_data(
user_id=user.user_id, user_id=user.user_id,
data={"user_id": user.user_id, "teams": [team_row["team_id"]]}, data={
"user_id": user.user_id,
"teams": [team_row["team_id"]],
"models": team_row["data"].models,
},
update_key_values={ update_key_values={
"teams": { "teams": {
"push": [team_row["team_id"]], "push": [team_row["team_id"]],
} }
}, },
table_name="user",
) )
## REMOVE DELETED USERS ## ## REMOVE DELETED USERS ##
### Get list of deleted users (old list - new list) ### Get list of deleted users (old list - new list)
deleted_user_id_list = [] deleted_user_id_list = []
existing_user_id_list = [] new_user_id_list = []
## Get old user list ## Get old user list
for user in existing_team_row.members_with_roles:
existing_user_id_list.append(user["user_id"])
## Get diff
if data.members_with_roles is not None: if data.members_with_roles is not None:
for user in data.members_with_roles: for user in data.members_with_roles:
if user.user_id not in existing_user_id_list: new_user_id_list.append(user.user_id)
deleted_user_id_list.append(user.user_id) ## Get diff
if existing_team_row.members_with_roles is not None:
for user in existing_team_row.members_with_roles:
if user["user_id"] not in new_user_id_list:
deleted_user_id_list.append(user["user_id"])
## SET UPDATED LIST ## SET UPDATED LIST
if len(deleted_user_id_list) > 0: if len(deleted_user_id_list) > 0:
@ -4525,6 +4565,99 @@ async def update_team(
return team_row return team_row
@router.post(
"/team/member_add",
tags=["team management"],
dependencies=[Depends(user_api_key_auth)],
)
async def team_member_add(
data: TeamMemberAddRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
[BETA]
Add new members (either via user_email or user_id) to a team
If user doesn't exist, new user row will also be added to User Table
```
curl -X POST 'http://0.0.0.0:8000/team/update' \
-H 'Authorization: Bearer sk-1234' \
-H 'Content-Type: application/json' \
-D '{
"team_id": "45e3e396-ee08-4a61-a88e-16b3ce7e0849",
"member": {"role": "user", "user_id": "krrish247652@berri.ai"}
}'
```
"""
if prisma_client is None:
raise HTTPException(status_code=500, detail={"error": "No db connected"})
if data.team_id is None:
raise HTTPException(status_code=400, detail={"error": "No team id passed in"})
if data.member is None:
raise HTTPException(status_code=400, detail={"error": "No member passed in"})
existing_team_row = await prisma_client.get_data( # type: ignore
team_id=data.team_id, table_name="team", query_type="find_unique"
)
new_member = data.member
existing_team_row.members_with_roles.append(new_member)
complete_team_data = LiteLLM_TeamTable(
**existing_team_row.model_dump(),
)
team_row = await prisma_client.update_data(
update_key_values=complete_team_data.json(exclude_none=True),
data=complete_team_data.json(exclude_none=True),
table_name="team",
team_id=data.team_id,
)
## ADD USER, IF NEW ##
user_data = { # type: ignore
"teams": [team_row["team_id"]],
"models": team_row["data"].models,
}
if new_member.user_id is not None:
user_data["user_id"] = new_member.user_id # type: ignore
await prisma_client.update_data(
user_id=new_member.user_id,
data=user_data,
update_key_values={
"teams": {
"push": [team_row["team_id"]],
}
},
table_name="user",
)
elif new_member.user_email is not None:
user_data["user_id"] = str(uuid.uuid4())
user_data["user_email"] = new_member.user_email
## user email is not unique acc. to prisma schema -> future improvement
### for now: check if it exists in db, if not - insert it
existing_user_row = await prisma_client.get_data(
key_val={"user_email": new_member.user_email},
table_name="user",
query_type="find_all",
)
if existing_user_row is None or (
isinstance(existing_user_row, list) and len(existing_user_row) == 0
):
await prisma_client.insert_data(data=user_data, table_name="user")
return team_row
@router.post( @router.post(
"/team/delete", tags=["team management"], dependencies=[Depends(user_api_key_auth)] "/team/delete", tags=["team management"], dependencies=[Depends(user_api_key_auth)]
) )

View file

@ -635,11 +635,15 @@ class PrismaClient:
table_name is not None and table_name == "user" table_name is not None and table_name == "user"
): ):
if query_type == "find_unique": if query_type == "find_unique":
if key_val is None:
key_val = {"user_id": user_id}
response = await self.db.litellm_usertable.find_unique( # type: ignore response = await self.db.litellm_usertable.find_unique( # type: ignore
where={ where=key_val # type: ignore
"user_id": user_id, # type: ignore
}
) )
elif query_type == "find_all" and key_val is not None:
response = await self.db.litellm_usertable.find_many(
where=key_val # type: ignore
) # type: ignore
elif query_type == "find_all" and reset_at is not None: elif query_type == "find_all" and reset_at is not None:
response = await self.db.litellm_usertable.find_many( response = await self.db.litellm_usertable.find_many(
where={ # type:ignore where={ # type:ignore
@ -875,6 +879,8 @@ class PrismaClient:
""" """
try: try:
db_data = self.jsonify_object(data=data) db_data = self.jsonify_object(data=data)
if update_key_values is not None:
update_key_values = self.jsonify_object(data=update_key_values)
if token is not None: if token is not None:
print_verbose(f"token: {token}") print_verbose(f"token: {token}")
# check if plain text or hash # check if plain text or hash

View file

@ -14,7 +14,7 @@ import { jwtDecode } from "jwt-decode";
const CreateKeyPage = () => { const CreateKeyPage = () => {
const [userRole, setUserRole] = useState(""); const [userRole, setUserRole] = useState("");
const [userEmail, setUserEmail] = useState<null | string>(null); const [userEmail, setUserEmail] = useState<null | string>(null);
const [teams, setTeams] = useState<null | string[]>(null); const [teams, setTeams] = useState<null | any[]>(null);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const userID = searchParams.get("userID"); const userID = searchParams.get("userID");
@ -113,7 +113,12 @@ const CreateKeyPage = () => {
accessToken={accessToken} accessToken={accessToken}
/> />
) : page == "teams" ? ( ) : page == "teams" ? (
<Teams teams={teams} searchParams={searchParams} /> <Teams
teams={teams}
setTeams={setTeams}
searchParams={searchParams}
accessToken={accessToken}
/>
) : ( ) : (
<Usage <Usage
userID={userID} userID={userID}

View file

@ -18,6 +18,7 @@ const { Option } = Select;
interface CreateKeyProps { interface CreateKeyProps {
userID: string; userID: string;
teamID: string | null;
userRole: string | null; userRole: string | null;
accessToken: string; accessToken: string;
data: any[] | null; data: any[] | null;
@ -27,6 +28,7 @@ interface CreateKeyProps {
const CreateKey: React.FC<CreateKeyProps> = ({ const CreateKey: React.FC<CreateKeyProps> = ({
userID, userID,
teamID,
userRole, userRole,
accessToken, accessToken,
data, data,
@ -36,7 +38,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
const [form] = Form.useForm(); const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
const [apiKey, setApiKey] = useState(null); const [apiKey, setApiKey] = useState(null);
const handleOk = () => { const handleOk = () => {
setIsModalVisible(false); setIsModalVisible(false);
form.resetFields(); form.resetFields();
@ -89,7 +90,10 @@ const CreateKey: React.FC<CreateKeyProps> = ({
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label="Team ID" name="team_id"> <Form.Item label="Team ID" name="team_id">
<Input placeholder="ai_team" /> <Input
placeholder="ai_team"
defaultValue={teamID ? teamID : ""}
/>
</Form.Item> </Form.Item>
<Form.Item label="Models" name="models"> <Form.Item label="Models" name="models">
<Select <Select

View file

@ -3,29 +3,33 @@ import { Typography } from "antd";
import { Select, SelectItem } from "@tremor/react"; import { Select, SelectItem } from "@tremor/react";
interface DashboardTeamProps { interface DashboardTeamProps {
teams: string[] | null; teams: Object[] | null;
setSelectedTeam: React.Dispatch<React.SetStateAction<any | null>>;
} }
const DashboardTeam: React.FC<DashboardTeamProps> = ({ teams }) => { const DashboardTeam: React.FC<DashboardTeamProps> = ({
teams,
setSelectedTeam,
}) => {
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
const [value, setValue] = useState(""); const [value, setValue] = useState("");
console.log(`received teams ${teams}`);
return ( return (
<div className="mt-10"> <div className="mt-10">
<Title level={4}>Default Team</Title> <Title level={4}>Default Team</Title>
<Paragraph> <Paragraph>
If you belong to multiple teams, this setting controls which If you belong to multiple teams, this setting controls which team is
organization is used by default when creating new API Keys. used by default when creating new API Keys.
</Paragraph> </Paragraph>
{teams && teams.length > 0 ? ( {teams && teams.length > 0 ? (
<Select <Select defaultValue="0">
id="distance" {teams.map((team: any, index) => (
value={value} <SelectItem
onValueChange={setValue} value={String(index)}
className="mt-2" onClick={() => setSelectedTeam(team)}
> >
{teams.map((model) => ( {team["team_alias"]}
<SelectItem value="model">{model}</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
) : ( ) : (

View file

@ -41,9 +41,11 @@ const Sidebar: React.FC<SidebarProps> = ({
Users Users
</Menu.Item> </Menu.Item>
) : null} ) : null}
{userRole == "Admin" ? (
<Menu.Item key="6" onClick={() => setPage("teams")}> <Menu.Item key="6" onClick={() => setPage("teams")}>
Teams Teams
</Menu.Item> </Menu.Item>
) : null}
</Menu> </Menu>
</Sider> </Sider>
</Layout> </Layout>

View file

@ -196,6 +196,7 @@ export const userInfoCall = async (
} }
const data = await response.json(); const data = await response.json();
console.log("API Response:", data);
message.info("Received user data"); message.info("Received user data");
return data; return data;
// Handle success - you might want to update some state or UI based on the created key // Handle success - you might want to update some state or UI based on the created key
@ -503,14 +504,23 @@ export const teamCreateCall = async (
} }
}; };
export const teamUpdateCall = async ( export interface Member {
role: string;
user_id: string | null;
user_email: string | null;
}
export const teamMemberAddCall = async (
accessToken: string, accessToken: string,
formValues: Record<string, any> // Assuming formValues is an object teamId: string,
formValues: Member // Assuming formValues is an object
) => { ) => {
try { try {
console.log("Form Values in teamCreateCall:", formValues); // Log the form values before making the API call console.log("Form Values in teamMemberAddCall:", formValues); // Log the form values before making the API call
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/update` : `/team/update`; const url = proxyBaseUrl
? `${proxyBaseUrl}/team/member_add`
: `/team/member_add`;
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@ -518,7 +528,8 @@ export const teamUpdateCall = async (
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
...formValues, // Include formValues in the request body team_id: teamId,
member: formValues, // Include formValues in the request body
}), }),
}); });

View file

@ -22,19 +22,32 @@ import {
Icon, Icon,
Button, Button,
Col, Col,
Text,
Grid, Grid,
} from "@tremor/react"; } from "@tremor/react";
import { CogIcon } from "@heroicons/react/outline"; import { CogIcon } from "@heroicons/react/outline";
interface TeamProps { interface TeamProps {
teams: string[] | null; teams: any[] | null;
searchParams: any; searchParams: any;
accessToken: string | null;
setTeams: React.Dispatch<React.SetStateAction<Object[] | null>>;
} }
import { teamCreateCall, teamMemberAddCall, Member } from "./networking";
const Team: React.FC<TeamProps> = ({ teams, searchParams }) => { const Team: React.FC<TeamProps> = ({
teams,
searchParams,
accessToken,
setTeams,
}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [memberForm] = Form.useForm(); const [memberForm] = Form.useForm();
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [selectedTeam, setSelectedTeam] = useState<null | any>(
teams ? teams[0] : null
);
const [isTeamModalVisible, setIsTeamModalVisible] = useState(false); const [isTeamModalVisible, setIsTeamModalVisible] = useState(false);
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false); const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false);
const handleOk = () => { const handleOk = () => {
@ -59,7 +72,17 @@ const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
const handleCreate = async (formValues: Record<string, any>) => { const handleCreate = async (formValues: Record<string, any>) => {
try { try {
console.log("reaches here"); if (accessToken != null) {
message.info("Making API Call");
const response: any = await teamCreateCall(accessToken, formValues);
if (teams !== null) {
setTeams([...teams, response]);
} else {
setTeams([response]);
}
console.log(`response for team create call: ${response}`);
setIsTeamModalVisible(false);
}
} catch (error) { } catch (error) {
console.error("Error creating the key:", error); console.error("Error creating the key:", error);
} }
@ -67,7 +90,36 @@ const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
const handleMemberCreate = async (formValues: Record<string, any>) => { const handleMemberCreate = async (formValues: Record<string, any>) => {
try { try {
console.log("reaches here"); if (accessToken != null && teams != null) {
message.info("Making API Call");
const user_role: Member = {
role: "user",
user_email: formValues.user_email,
user_id: null,
};
const response: any = await teamMemberAddCall(
accessToken,
selectedTeam["team_id"],
user_role
);
console.log(`response for team create call: ${response["data"]}`);
// Checking if the team exists in the list and updating or adding accordingly
const foundIndex = teams.findIndex((team) => {
console.log(
`team.team_id=${team.team_id}; response.data.team_id=${response.data.team_id}`
);
return team.team_id === response.data.team_id;
});
console.log(`foundIndex: ${foundIndex}`);
if (foundIndex !== -1) {
// If the team is found, update it
const updatedTeams = [...teams]; // Copy the current state
updatedTeams[foundIndex] = response.data; // Update the specific team
setTeams(updatedTeams); // Set the new state
setSelectedTeam(response.data);
}
setIsAddMemberModalVisible(false);
}
} catch (error) { } catch (error) {
console.error("Error creating the key:", error); console.error("Error creating the key:", error);
} }
@ -90,24 +142,28 @@ const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
</TableHead> </TableHead>
<TableBody> <TableBody>
{teams && teams.length > 0
? teams.map((team: any) => (
<TableRow> <TableRow>
<TableCell>Wilhelm Tell</TableCell> <TableCell>{team["team_alias"]}</TableCell>
<TableCell className="text-right">1</TableCell> <TableCell>{team["spend"]}</TableCell>
<TableCell>Uri, Schwyz, Unterwalden</TableCell> <TableCell>
<TableCell>National Hero</TableCell> {team["max_budget"] ? team["max_budget"] : "No limit"}
</TableRow> </TableCell>
<TableRow> <TableCell>
<TableCell>The Witcher</TableCell> <Text>
<TableCell className="text-right">129</TableCell> TPM Limit:{" "}
<TableCell>Kaedwen</TableCell> {team.tpm_limit ? team.tpm_limit : "Unlimited"}{" "}
<TableCell>Legend</TableCell> <br></br> RPM Limit:{" "}
</TableRow> {team.rpm_limit ? team.rpm_limit : "Unlimited"}
<TableRow> </Text>
<TableCell>Mizutsune</TableCell> </TableCell>
<TableCell className="text-right">82</TableCell> <TableCell>
<TableCell>Japan</TableCell> <Icon icon={CogIcon} size="sm" />
<TableCell>N/A</TableCell> </TableCell>
</TableRow> </TableRow>
))
: null}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
@ -180,14 +236,16 @@ const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
members you see. members you see.
</Paragraph> </Paragraph>
{teams && teams.length > 0 ? ( {teams && teams.length > 0 ? (
<Select <Select defaultValue="0">
id="distance" {teams.map((team: any, index) => (
value={value} <SelectItem
onValueChange={setValue} value={String(index)}
className="mt-2" onClick={() => {
setSelectedTeam(team);
}}
> >
{teams.map((model) => ( {team["team_alias"]}
<SelectItem value="model">{model}</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
) : ( ) : (
@ -208,27 +266,23 @@ const Team: React.FC<TeamProps> = ({ teams, searchParams }) => {
</TableHead> </TableHead>
<TableBody> <TableBody>
{selectedTeam
? selectedTeam["members_with_roles"].map((member: any) => (
<TableRow> <TableRow>
<TableCell>Wilhelm Tell</TableCell> <TableCell>
<TableCell>Uri, Schwyz, Unterwalden</TableCell> {member["user_email"]
<TableCell> ? member["user_email"]
<Icon icon={CogIcon} size="sm" /> : member["user_id"]
</TableCell> ? member["user_id"]
</TableRow> : null}
<TableRow> </TableCell>
<TableCell>The Witcher</TableCell> <TableCell>{member["role"]}</TableCell>
<TableCell>Kaedwen</TableCell>
<TableCell>
<Icon icon={CogIcon} size="sm" />
</TableCell>
</TableRow>
<TableRow>
<TableCell>Mizutsune</TableCell>
<TableCell>Japan</TableCell>
<TableCell> <TableCell>
<Icon icon={CogIcon} size="sm" /> <Icon icon={CogIcon} size="sm" />
</TableCell> </TableCell>
</TableRow> </TableRow>
))
: null}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>

View file

@ -25,10 +25,10 @@ interface UserDashboardProps {
userID: string | null; userID: string | null;
userRole: string | null; userRole: string | null;
userEmail: string | null; userEmail: string | null;
teams: string[] | null; teams: any[] | null;
setUserRole: React.Dispatch<React.SetStateAction<string>>; setUserRole: React.Dispatch<React.SetStateAction<string>>;
setUserEmail: React.Dispatch<React.SetStateAction<string | null>>; setUserEmail: React.Dispatch<React.SetStateAction<string | null>>;
setTeams: React.Dispatch<React.SetStateAction<string[] | null>>; setTeams: React.Dispatch<React.SetStateAction<Object[] | null>>;
} }
const UserDashboard: React.FC<UserDashboardProps> = ({ const UserDashboard: React.FC<UserDashboardProps> = ({
@ -53,6 +53,9 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
const token = searchParams.get("token"); const token = searchParams.get("token");
const [accessToken, setAccessToken] = useState<string | null>(null); const [accessToken, setAccessToken] = useState<string | null>(null);
const [userModels, setUserModels] = useState<string[]>([]); const [userModels, setUserModels] = useState<string[]>([]);
const [selectedTeam, setSelectedTeam] = useState<any | null>(
teams ? teams[0] : null
);
// check if window is not undefined // check if window is not undefined
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.addEventListener("beforeunload", function () { window.addEventListener("beforeunload", function () {
@ -119,7 +122,7 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
console.log( console.log(
`received teams in user dashboard: ${Object.keys( `received teams in user dashboard: ${Object.keys(
response response
)}; team type: ${Array.isArray(response.teams)}` )}; team values: ${Object.entries(response.teams)}`
); );
setUserSpendData(response["user_info"]); setUserSpendData(response["user_info"]);
setData(response["keys"]); // Assuming this is the correct path to your data setData(response["keys"]); // Assuming this is the correct path to your data
@ -196,13 +199,14 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
/> />
<CreateKey <CreateKey
userID={userID} userID={userID}
teamID={selectedTeam ? selectedTeam["team_id"] : null}
userRole={userRole} userRole={userRole}
userModels={userModels} userModels={userModels}
accessToken={accessToken} accessToken={accessToken}
data={data} data={data}
setData={setData} setData={setData}
/> />
<DashboardTeam teams={teams} /> <DashboardTeam teams={teams} setSelectedTeam={setSelectedTeam} />
</Col> </Col>
</Grid> </Grid>
</div> </div>