feat: add team member role change & removal, add uptime server stats, etc

This commit is contained in:
2024-11-14 16:28:44 +00:00
parent b574f83e74
commit 33dda374c7
25 changed files with 566 additions and 110 deletions
@@ -0,0 +1,75 @@
import Icons from "@/components/ui/icons";
import Modal from "@/components/ui/modal";
import { useZForm } from "@/hooks/useZForm";
import { createDisclosure } from "@/lib/utils";
import React from "react";
import { ScrollView, XStack } from "tamagui";
import FormField from "@/components/ui/form";
import { ErrorAlert } from "@/components/ui/alert";
import Button from "@/components/ui/button";
import {
SetRoleSchema,
setRoleSchema,
teamMemberRoles,
} from "../schema/team-form";
import { SelectField } from "@/components/ui/select";
import { useSetRoleMutation } from "../hooks/query";
export const changeRoleModal = createDisclosure<SetRoleSchema>();
const ChangeRoleForm = () => {
const { data } = changeRoleModal.use();
const form = useZForm(setRoleSchema, data);
const setRole = useSetRoleMutation(data?.teamId || "");
const onSubmit = form.handleSubmit((values) => {
setRole.mutate(values, {
onSuccess: () => {
changeRoleModal.onClose();
form.reset();
},
});
});
return (
<Modal
disclosure={changeRoleModal}
title="Change Role"
description="Change team member role."
maxHeight={280}
>
<ScrollView contentContainerStyle={{ padding: "$4", pt: 0, gap: "$4" }}>
<ErrorAlert error={setRole.error} />
<FormField label="Role">
<SelectField
items={teamMemberRoles}
form={form}
name="role"
placeholder="Select Role..."
/>
</FormField>
</ScrollView>
<XStack p="$4" gap="$4">
<Button
flex={1}
onPress={changeRoleModal.onClose}
bg="$colorTransparent"
>
Cancel
</Button>
<Button
flex={1}
icon={<Icons name="account-plus" size={18} />}
onPress={onSubmit}
isLoading={setRole.isPending}
>
Update Role
</Button>
</XStack>
</Modal>
);
};
export default ChangeRoleForm;
+38 -4
View File
@@ -3,6 +3,10 @@ import { Avatar, Button, ListItem, View, YGroup } from "tamagui";
import MenuButton from "@/components/ui/menu-button";
import Icons from "@/components/ui/icons";
import SearchInput from "@/components/ui/search-input";
import { useTeamId } from "@/stores/auth";
import { changeRoleModal } from "./change-role-form";
import { useRemoveMemberMutation } from "../hooks/query";
import { showDialog } from "@/hooks/useDialog";
type Props = {
members?: any[];
@@ -10,7 +14,17 @@ type Props = {
};
const MemberList = ({ members, allowWrite }: Props) => {
const teamId = useTeamId();
const [search, setSearch] = useState("");
const remove = useRemoveMemberMutation(teamId);
const onRemove = (member: any) => {
showDialog({
title: "Remove Member",
description: "Are you sure you want to remove this member?",
onConfirm: () => remove.mutate(member.userId),
});
};
const memberList = useMemo(() => {
let items = members || [];
@@ -51,7 +65,12 @@ const MemberList = ({ members, allowWrite }: Props) => {
</Avatar>
}
iconAfter={
allowWrite ? <MemberActionButton member={member} /> : undefined
allowWrite ? (
<MemberActionButton
member={member}
onRemove={() => onRemove(member)}
/>
) : undefined
}
/>
</YGroup.Item>
@@ -63,9 +82,10 @@ const MemberList = ({ members, allowWrite }: Props) => {
type MemberActionButtonProps = {
member: any;
onRemove: () => void;
};
const MemberActionButton = ({ member }: MemberActionButtonProps) => (
const MemberActionButton = ({ member, onRemove }: MemberActionButtonProps) => (
<MenuButton
size="$1"
placement="bottom-end"
@@ -77,10 +97,24 @@ const MemberActionButton = ({ member }: MemberActionButtonProps) => (
/>
}
>
<MenuButton.Item icon={<Icons name="account-key" size={16} />}>
<MenuButton.Item
icon={<Icons name="account-key" size={16} />}
onPress={() =>
changeRoleModal.onOpen({
teamId: member.teamId,
userId: member.userId,
role: member.role,
})
}
>
Change Role
</MenuButton.Item>
<MenuButton.Item color="$red10" icon={<Icons name="trash-can" size={16} />}>
<MenuButton.Item
color="$red10"
icon={<Icons name="trash-can" size={16} />}
onPress={onRemove}
>
Remove Member
</MenuButton.Item>
</MenuButton>
+29 -4
View File
@@ -1,6 +1,10 @@
import api from "@/lib/api";
import { useMutation, useQuery } from "@tanstack/react-query";
import { InviteSchema, TeamFormSchema } from "../schema/team-form";
import {
InviteSchema,
SetRoleSchema,
TeamFormSchema,
} from "../schema/team-form";
import queryClient from "@/lib/queryClient";
import { setTeam, useTeamId } from "@/stores/auth";
import { router } from "expo-router";
@@ -28,7 +32,6 @@ export const useSaveTeam = () => {
? api(`/teams/${body.id}`, { method: "PUT", body })
: api(`/teams`, { method: "POST", body });
},
onError: (e) => console.error(e),
onSuccess: (res, body) => {
queryClient.invalidateQueries({ queryKey: ["teams"] });
@@ -43,9 +46,31 @@ export const useSaveTeam = () => {
export const useInviteMutation = (teamId: string | null) => {
return useMutation({
mutationFn: async (body: InviteSchema) => {
return api(`/teams/${teamId}/invite`, { method: "POST", body });
return api(`/teams/${teamId}/members`, { method: "POST", body });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["teams", teamId] });
},
});
};
export const useSetRoleMutation = (teamId: string | null) => {
return useMutation({
mutationFn: async (body: SetRoleSchema) => {
const url = `/teams/${teamId}/members/${body.userId}/role`;
return api(url, { method: "PUT", body });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["teams", teamId] });
},
});
};
export const useRemoveMemberMutation = (teamId: string | null) => {
return useMutation({
mutationFn: async (id: string) => {
return api(`/teams/${teamId}/members/${id}`, { method: "DELETE" });
},
onError: (e) => console.error(e),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["teams", teamId] });
},
+2
View File
@@ -20,6 +20,7 @@ import tamaguiConfig from "@/tamagui.config";
import MemberList from "./components/member-list";
import { useUser } from "@/hooks/useUser";
import InviteForm, { inviteFormModal } from "./components/invite-form";
import ChangeRoleForm from "./components/change-role-form";
export default function TeamPage() {
const teamId = useTeamId();
@@ -71,6 +72,7 @@ export default function TeamPage() {
<MemberList members={data?.members} allowWrite={canWrite} />
<InviteForm />
<ChangeRoleForm />
</ScrollView>
</>
);
+13 -1
View File
@@ -9,10 +9,12 @@ export const teamFormSchema = z.object({
export type TeamFormSchema = z.infer<typeof teamFormSchema>;
const teamRoles = ["owner", "admin", "member"] as const;
export const inviteSchema = z.object({
teamId: z.string().ulid(),
username: z.string().min(1, { message: "Username/email is required" }),
role: z.enum(["owner", "admin", "member"], {
role: z.enum(teamRoles, {
errorMap: () => ({ message: "Role is required" }),
}),
});
@@ -24,3 +26,13 @@ export const teamMemberRoles: SelectItem[] = [
];
export type InviteSchema = z.infer<typeof inviteSchema>;
export const setRoleSchema = z.object({
teamId: z.string().ulid(),
userId: z.string().ulid(),
role: z.enum(teamRoles, {
errorMap: () => ({ message: "Role is required" }),
}),
});
export type SetRoleSchema = z.infer<typeof setRoleSchema>;