Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
55 changes: 23 additions & 32 deletions web-admin/src/features/organizations/users/AddUsersDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,27 @@
newRole: string,
isSuperUser: boolean = false,
) {
try {
await $addOrganizationMemberUser.mutateAsync({
organization: organization,
data: {
email: newEmail,
role: newRole,
superuserForceAccess: isSuperUser,
},
});

await queryClient.invalidateQueries({
queryKey:
getAdminServiceListOrganizationMemberUsersQueryKey(organization),
});

await queryClient.invalidateQueries({
queryKey: getAdminServiceListOrganizationInvitesQueryKey(organization),
});
await $addOrganizationMemberUser.mutateAsync({
organization: organization,
data: {
email: newEmail,
role: newRole,
superuserForceAccess: isSuperUser,
},
});

email = "";
role = "";
isSuperUser = false;
} catch (error) {
console.error("Error adding user to organization", error);
throw error;
}
await queryClient.invalidateQueries({
queryKey:
getAdminServiceListOrganizationMemberUsersQueryKey(organization),
});

await queryClient.invalidateQueries({
queryKey: getAdminServiceListOrganizationInvitesQueryKey(organization),
});

email = "";
role = "";
isSuperUser = false;
}

const formId = "add-user-form";
Expand Down Expand Up @@ -139,12 +134,6 @@
// Show error notification if any invites failed
if (failed.length > 0) {
failedInvites = failed; // Store failed emails
eventBus.emit("notification", {
type: "error",
message: `Failed to invite ${failed.length} ${
failed.length === 1 ? "person" : "people"
}`,
});
}

// Close dialog after showing notifications
Expand Down Expand Up @@ -220,7 +209,9 @@
</MultiInput>
{#if failedInvites.length > 0}
<div class="text-sm text-red-500 py-2">
Failed to invite {failedInvites.join(", ")}
{failedInvites.length === 1
? `${failedInvites[0]} is already a member of this organization`
: `${failedInvites.join(", ")} are already members of this organization`}
</div>
{/if}
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import Avatar from "@rilldata/web-common/components/avatar/Avatar.svelte";
import Chip from "@rilldata/web-common/components/chip/core/Chip.svelte";
import { getRandomBgColor } from "@rilldata/web-common/features/themes/color-config";
import { cn } from "@rilldata/web-common/lib/shadcn";

Expand All @@ -10,6 +11,7 @@
export let pendingAcceptance: boolean = false;
export let shape: "circle" | "square" = "circle";
export let count: number = 0;
export let role: string | null = null;

function getInitials(name: string) {
return name.charAt(0).toUpperCase();
Expand All @@ -35,11 +37,16 @@
</div>
{/if}
<div class="flex flex-col text-left">
<span class="text-sm font-medium text-gray-900">
<span class="text-sm font-medium text-gray-900 flex flex-row gap-x-1">
{name}
<span class="text-gray-500 font-normal">
{isCurrentUser ? "(You)" : ""}
</span>
{#if role === "guest"}
<Chip type="amber" label="Guest" compact readOnly>
<svelte:fragment slot="body">Guest</svelte:fragment>
</Chip>
{/if}
</span>
{#if pendingAcceptance || email}
<span class="text-xs text-gray-500">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@rilldata/web-common/components/alert-dialog/index.js";
import Button from "@rilldata/web-common/components/button/Button.svelte";

export let open = false;
export let email: string;
export let newRole: string;
export let onUpgrade: (email: string, role: string) => void;

async function handleUpgrade() {
try {
onUpgrade(email, newRole);
open = false;
} catch (error) {
console.error("Failed to upgrade user role:", error);
}
}
</script>

<AlertDialog bind:open>
<AlertDialogTrigger asChild>
<div class="hidden"></div>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Upgrade guest to {newRole}?</AlertDialogTitle>
<AlertDialogDescription>
<div class="mt-1">
Upgrading a guest to {newRole} will grant this user access to all open
projects in the organization. Would you like to upgrade this guest user
to {newRole}?
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<Button
type="plain"
on:click={() => {
open = false;
}}>Cancel</Button
>
<Button type="primary" on:click={handleUpgrade}>Yes, upgrade</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
64 changes: 64 additions & 0 deletions web-admin/src/features/organizations/users/OrgUsersFilters.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts">
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import CaretUpIcon from "@rilldata/web-common/components/icons/CaretUpIcon.svelte";
import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte";

export let filterSelection: "all" | "members" | "guests" | "pending" = "all";

let isDropdownOpen = false;
</script>

<DropdownMenu.Root bind:open={isDropdownOpen}>
<DropdownMenu.Trigger
class="min-w-[210px] flex flex-row justify-between gap-1 items-center rounded-sm border border-gray-300 {isDropdownOpen
? 'bg-slate-200'
: 'hover:bg-slate-100'} px-2 py-1"
>
<span class="capitalize"
>{filterSelection === "all" ? "All users" : filterSelection}</span
>
{#if isDropdownOpen}
<CaretUpIcon size="12px" />
{:else}
<CaretDownIcon size="12px" />
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start" class="w-[210px]">
<DropdownMenu.CheckboxItem
class="font-normal flex items-center"
checked={filterSelection === "all"}
on:click={() => {
filterSelection = "all";
}}
>
<span>All users</span>
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
class="font-normal flex items-center"
checked={filterSelection === "members"}
on:click={() => {
filterSelection = "members";
}}
>
<span>Members</span>
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
class="font-normal flex items-center"
checked={filterSelection === "guests"}
on:click={() => {
filterSelection = "guests";
}}
>
<span>Guests</span>
</DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem
class="font-normal flex items-center"
checked={filterSelection === "pending"}
on:click={() => {
filterSelection = "pending";
}}
>
<span>Pending invites</span>
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
22 changes: 21 additions & 1 deletion web-admin/src/features/organizations/users/OrgUsersTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
InfiniteData,
InfiniteQueryObserverResult,
} from "@tanstack/svelte-query";
import { ExternalLinkIcon } from "lucide-svelte";

interface OrgUser extends V1OrganizationMemberUser, V1UserInvite {
invitedBy?: string;
Expand All @@ -43,6 +44,7 @@
RpcStatus
>;
export let currentUserEmail: string;
export let currentUserRole: string;

const ROW_HEIGHT = 69;
const OVERSCAN = 5;
Expand Down Expand Up @@ -72,19 +74,21 @@
pendingAcceptance: Boolean(row.original.invitedBy),
isCurrentUser: row.original.userEmail === currentUserEmail,
photoUrl: row.original.userPhotoUrl,
role: row.original.roleName,
}),
meta: {
widthPercent: 5,
},
},
{
accessorKey: "roleName",
header: "Role",
header: "Organization Role",
cell: ({ row }) =>
flexRender(OrgUsersTableRoleCell, {
email: row.original.userEmail,
role: row.original.roleName,
isCurrentUser: row.original.userEmail === currentUserEmail,
currentUserRole: currentUserRole,
}),
meta: {
widthPercent: 5,
Expand All @@ -98,7 +102,9 @@
cell: ({ row }) =>
flexRender(OrgUsersTableActionsCell, {
email: row.original.userEmail,
role: row.original.roleName,
isCurrentUser: row.original.userEmail === currentUserEmail,
currentUserRole: currentUserRole,
}),
meta: {
widthPercent: 0,
Expand Down Expand Up @@ -205,6 +211,20 @@
header.getContext(),
)}
/>
{#if header.column.id === "roleName"}
<a
href="https://docs.rilldata.com/manage/roles-permissions#organization-level-permissions"
target="_blank"
rel="noopener noreferrer"
class="hover:text-gray-700"
>
<ExternalLinkIcon
class="text-gray-500"
size="11px"
strokeWidth={2}
/>
</a>
{/if}
{#if header.column.getIsSorted().toString() === "asc"}
<span>
<ArrowDown flip size="12px" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@
import { page } from "$app/stores";

export let email: string;
export let role: string;
export let isCurrentUser: boolean;
export let currentUserRole: string;

let isDropdownOpen = false;
let isRemoveConfirmOpen = false;

$: organization = $page.params.organization;
$: isAdmin = currentUserRole === "admin";
$: isEditor = currentUserRole === "editor";
$: canManageUser =
!isCurrentUser &&
(isAdmin ||
(isEditor &&
(role === "editor" || role === "viewer" || role === "guest")));

const queryClient = useQueryClient();
const removeOrganizationMemberUser =
Expand Down Expand Up @@ -81,7 +90,7 @@
}
</script>

{#if !isCurrentUser}
{#if canManageUser}
<DropdownMenu.Root bind:open={isDropdownOpen}>
<DropdownMenu.Trigger class="flex-none">
<IconButton rounded active={isDropdownOpen}>
Expand Down
Loading
Loading