wip manage user dialog
This commit is contained in:
parent
2e5cdd7776
commit
06b6bbefd4
|
@ -4,9 +4,10 @@ import { fetchAllUsers } from "@/lib/actions";
|
|||
import GhostMessage from "@/lib/components/ui/GhostMessage";
|
||||
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
|
||||
import CreateUserButton from "@/lib/components/ui/admin/CreateUserButton";
|
||||
import ManageUserButton from "@/lib/components/ui/admin/ManageUserButton";
|
||||
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||
import { sha256sum } from "@/lib/util";
|
||||
import { Avatar, Button, Card, Flex, Heading, Table, Text, TextField } from "@radix-ui/themes";
|
||||
import { isAdmin, sha256sum } from "@/lib/util";
|
||||
import { Avatar, Badge, Button, Card, Flex, Heading, Table, Text, TextField } from "@radix-ui/themes";
|
||||
import { SearchIcon, UserRoundXIcon } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
|
@ -63,10 +64,11 @@ export default function Users() {
|
|||
fallback={email.slice(0, 1) || "@"}
|
||||
/>
|
||||
<Text size='2'>{email}</Text>
|
||||
{isAdmin(email) && <Badge variant="surface">Admin</Badge>}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
<Table.Cell justify='end'>
|
||||
<Button variant="soft">Manage</Button>
|
||||
<ManageUserButton email={email} />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
|
|
|
@ -31,6 +31,14 @@ export async function fetchOwnAliases() {
|
|||
return await getUserAliases(session.user.email);
|
||||
}
|
||||
|
||||
export async function fetchUserAliases(email: string) {
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||
|
||||
return await getUserAliases(email);
|
||||
}
|
||||
|
||||
export async function fetchAllAliases() {
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||
|
|
32
src/lib/components/ui/GenericConfirmationDialog.tsx
Normal file
32
src/lib/components/ui/GenericConfirmationDialog.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Button, Dialog, DialogDescription, DialogTitle, Flex } from "@radix-ui/themes";
|
||||
|
||||
export default function GenericConfirmationDialog({
|
||||
title, description, labelConfirm, action, children,
|
||||
}: {
|
||||
title: React.ReactNode,
|
||||
description: React.ReactNode,
|
||||
labelConfirm?: string,
|
||||
action?: () => any,
|
||||
children: React.ReactNode,
|
||||
}) {
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
{children}
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
|
||||
<Flex gap="3" mt="4" justify="end">
|
||||
<Dialog.Close>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Close>
|
||||
<Button variant="solid" onClick={action}>{labelConfirm ?? "Confirm"}</Button>
|
||||
</Dialog.Close>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
109
src/lib/components/ui/admin/ManageUserButton.tsx
Normal file
109
src/lib/components/ui/admin/ManageUserButton.tsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
"use client";
|
||||
|
||||
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||
import { AliasEntry } from "@/lib/db";
|
||||
import { isAdmin, sha256sum } from "@/lib/util";
|
||||
import { Avatar, Badge, Button, Card, Code, Dialog, Flex, Grid, Heading, Table, Text } from "@radix-ui/themes";
|
||||
import { useEffect, useState } from "react";
|
||||
import GhostMessage from "../GhostMessage";
|
||||
import { BananaIcon } from "lucide-react";
|
||||
import LoadingSpinner from "../LoadingSpinner";
|
||||
import { approveAlias, deleteAlias, fetchUserAliases } from "@/lib/actions";
|
||||
import GenericConfirmationDialog from "../GenericConfirmationDialog";
|
||||
|
||||
export default function ManageUserButton({ email }: { email: string }) {
|
||||
const [aliases, setAliases] = useState<AliasEntry[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserAliases(email).then(setAliases);
|
||||
}, [email]);
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
<Button variant="soft">Manage</Button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<Dialog.Title>
|
||||
Manage user
|
||||
</Dialog.Title>
|
||||
|
||||
<Grid gap="4">
|
||||
<Card className="h-fit">
|
||||
<Flex direction="row" gap="3" align="center">
|
||||
<Avatar
|
||||
size="4"
|
||||
src={`https://gravatar.com/avatar/${sha256sum(email)}?d=${GRAVATAR_DEFAULT}`}
|
||||
fallback={email.slice(0, 1) || "@"}
|
||||
/>
|
||||
<Flex direction="column">
|
||||
<Text>{email}</Text>
|
||||
<Text size="2" weight="light">{isAdmin(email) ? "Administrator" : "User"}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
<Card className="h-fit min-w-40">
|
||||
<Heading size="3" mb="4">Aliases</Heading>
|
||||
|
||||
{
|
||||
aliases
|
||||
? aliases.length > 0
|
||||
? <Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell justify="start">Alias</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell justify="end">Options</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
<Table.Body>
|
||||
{
|
||||
aliases.map((alias) => (
|
||||
<Table.Row key={alias.id}>
|
||||
<Table.Cell justify="start">
|
||||
{alias.alias} {alias.pending && <Badge variant="soft" ml="3">Pending</Badge>}
|
||||
</Table.Cell>
|
||||
<Table.Cell justify="end">
|
||||
<Flex gap="3" justify="end">
|
||||
{
|
||||
alias.pending &&
|
||||
<GenericConfirmationDialog
|
||||
title="Approve alias"
|
||||
description={<>Do you want to approve <Code>{alias.address}</Code>'s alias request for <Code>{alias.alias}</Code>?</>}
|
||||
labelConfirm="Approve"
|
||||
action={async () => {
|
||||
await approveAlias(alias.alias);
|
||||
setAliases(aliases.map((a) => a.id == alias.id ? { ...alias, pending: false } : a));
|
||||
}}
|
||||
>
|
||||
<Button size="1" variant="surface">Approve</Button>
|
||||
</GenericConfirmationDialog>
|
||||
}
|
||||
<GenericConfirmationDialog
|
||||
title="Delete alias"
|
||||
description={<>Are you sure you want to delete <Code>{alias.alias}</Code>?</>}
|
||||
labelConfirm="Delete"
|
||||
action={async () => {
|
||||
await deleteAlias(alias.alias);
|
||||
setAliases(aliases.filter((a) => a.id != alias.id));
|
||||
}}
|
||||
>
|
||||
<Button size="1" variant="soft">Delete</Button>
|
||||
</GenericConfirmationDialog>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))
|
||||
}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
: <GhostMessage icon={<BananaIcon />} header="No aliases" message="The user does not have any aliases" />
|
||||
: <GhostMessage icon={<LoadingSpinner />} header="Loading" />
|
||||
}
|
||||
</Card>
|
||||
</Grid>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
|
@ -7,8 +7,9 @@ export function sha256sum(input: any) {
|
|||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
export function isAdmin(session: Session | null) {
|
||||
return session?.user?.email == "lea@amogus.cloud"; // todo
|
||||
export function isAdmin(session: Session | string | null) {
|
||||
let email = typeof session == 'string' ? session : session?.user?.email;
|
||||
return email == "lea@amogus.cloud"; // todo
|
||||
}
|
||||
|
||||
export function aliasesNeedApproval(session: Session | null) {
|
||||
|
|
Loading…
Reference in a new issue