Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions apps/start/src/components/settings/members/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { TooltipComplete } from '@/components/tooltip-complete';
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { useTRPC } from '@/integrations/trpc/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import type { ColumnDef } from '@tanstack/react-table';
import { toast } from 'sonner';

import { ColumnCreatedAt } from '@/components/column-created-at';
import { Badge } from '@/components/ui/badge';
import { createActionColumn } from '@/components/ui/data-table/data-table-helpers';
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { useTRPC } from '@/integrations/trpc/react';
import { pushModal } from '@/modals';
import type { IServiceMember } from '@openpanel/db';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouteContext } from '@tanstack/react-router';
import type { ColumnDef } from '@tanstack/react-table';
import { toast } from 'sonner';

export function useColumns() {
const { session } = useRouteContext({ strict: false });
const currentUserId = session?.userId;

const columns: ColumnDef<IServiceMember>[] = [
{
accessorKey: 'user',
Expand Down Expand Up @@ -87,6 +89,7 @@ export function useColumns() {
createActionColumn(({ row }) => {
const queryClient = useQueryClient();
const trpc = useTRPC();
const isSelf = !!currentUserId && row.original.userId === currentUserId;
const revoke = useMutation(
trpc.organization.removeMember.mutationOptions({
onSuccess() {
Expand All @@ -107,13 +110,15 @@ export function useColumns() {

return (
<>
<DropdownMenuItem
onClick={() => {
pushModal('EditMember', row.original);
}}
>
Edit access
</DropdownMenuItem>
{!isSelf && (
<DropdownMenuItem
onClick={() => {
pushModal('EditMember', row.original);
}}
>
Edit member
</DropdownMenuItem>
)}
<DropdownMenuItem
className="text-destructive"
onClick={() => {
Expand Down
66 changes: 48 additions & 18 deletions apps/start/src/modals/edit-member.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ButtonContainer } from '@/components/button-container';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { ProjectAccessGrants } from '@/components/settings/project-access-grants';
import { useTRPC } from '@/integrations/trpc/react';
import { handleError } from '@/integrations/trpc/react';
Expand All @@ -13,6 +15,8 @@ import type { IProjectAccessGrant } from '@openpanel/validation';
import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container';

type OrgRole = 'org:admin' | 'org:member';

type EditMemberProps = IServiceMember;

export default function EditMember(member: EditMemberProps) {
Expand All @@ -27,20 +31,23 @@ export default function EditMember(member: EditMemberProps) {
})) ?? [];

const [access, setAccess] = useState<IProjectAccessGrant[]>(toGrants);
const [role, setRole] = useState<OrgRole>(
member.role === 'org:admin' ? 'org:admin' : 'org:member',
);

const projectsQuery = useQuery(
trpc.project.list.queryOptions({ organizationId: member.organizationId }),
);

const mutation = useMutation(
trpc.organization.updateMemberAccess.mutationOptions({
trpc.organization.updateMember.mutationOptions({
onError(error) {
handleError(error);
setAccess(toGrants());
setRole(member.role === 'org:admin' ? 'org:admin' : 'org:member');
},
onSuccess() {
toast.success('Access updated');
// Refresh members list so access column reflects changes
toast.success('Member updated');
queryClient.invalidateQueries(trpc.organization.members.pathFilter());
popModal();
},
Expand All @@ -49,19 +56,38 @@ export default function EditMember(member: EditMemberProps) {

const projects = projectsQuery.data ?? [];

const memberName = member.user
? [member.user.firstName, member.user.lastName].filter(Boolean).join(' ')
: null;

return (
<ModalContent>
<ModalHeader
title={
member.user
? `Edit access for ${[member.user.firstName, member.user.lastName]
.filter(Boolean)
.join(' ')}`
: 'Edit member access'
}
/>
<ModalHeader title={memberName ? `Edit ${memberName}` : 'Edit member'} />

<div className="col gap-4">
<div>
<Label id="edit-member-role-label">Organization role</Label>
<RadioGroup
aria-labelledby="edit-member-role-label"
value={role}
onValueChange={(value) => setRole(value as OrgRole)}
className="mt-2 flex gap-4"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="org:member" id="edit-member-role" />
<Label className="mb-0" htmlFor="edit-member-role">
Member
</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="org:admin" id="edit-admin-role" />
<Label className="mb-0" htmlFor="edit-admin-role">
Admin
</Label>
</div>
</RadioGroup>
</div>

<ProjectAccessGrants
value={access}
onChange={setAccess}
Expand All @@ -73,14 +99,18 @@ export default function EditMember(member: EditMemberProps) {
Cancel
</Button>
<Button
onClick={() =>
onClick={() => {
if (!member.user) {
return;
}
mutation.mutate({
userId: member.user!.id,
userId: member.user.id,
organizationId: member.organizationId,
access: access,
})
}
disabled={mutation.isPending}
role,
access,
});
Comment thread
grootbro marked this conversation as resolved.
}}
disabled={mutation.isPending || !member.user}
>
Save
</Button>
Expand Down
Loading