forked from sk1982/actaeon
add card deletion
This commit is contained in:
parent
619c8bf2cd
commit
18271a7d69
@ -12,9 +12,9 @@ import { getGlobalConfig } from '@/config';
|
||||
import { UserPayload } from '@/types/user';
|
||||
import { DB } from '@/types/db';
|
||||
|
||||
export const getCards = async (user: UserPayload) => {
|
||||
export const getCards = async (user: number) => {
|
||||
return db.selectFrom('aime_card')
|
||||
.where('user', '=', user.id)
|
||||
.where('user', '=', user)
|
||||
.selectAll()
|
||||
.execute();
|
||||
};
|
||||
@ -65,11 +65,26 @@ export const adminAddCardToUser = async (user: number, code: string): Promise<Ac
|
||||
return { data: await getUsers() };
|
||||
};
|
||||
|
||||
export const deleteCard = async (user: number, id: number) => {
|
||||
const requestingUser = await requireUser();
|
||||
|
||||
if (requestingUser.id !== user)
|
||||
requirePermission(requestingUser.permissions, UserPermissions.USERMOD);
|
||||
|
||||
await db.deleteFrom('aime_card')
|
||||
.where('id', '=', id)
|
||||
.where('user', '=', user)
|
||||
.executeTakeFirst();
|
||||
|
||||
revalidatePath('/settings', 'page');
|
||||
revalidatePath('/admin/users', 'page');
|
||||
};
|
||||
|
||||
export const userAddCard = async (code: string): Promise<ActionResult<{ card: DB['aime_card'] }>> => {
|
||||
const user = await requireUser();
|
||||
|
||||
if (!hasPermission(user.permissions, UserPermissions.USERMOD)) {
|
||||
const cards = await getCards(user);
|
||||
const cards = await getCards(user.id);
|
||||
|
||||
if (!getGlobalConfig('allow_user_add_card'))
|
||||
return { error: true, message: 'You do not have permissions to add a card' };
|
||||
|
@ -12,7 +12,7 @@ import { hasPermission } from '@/helpers/permissions';
|
||||
import { AimeCard } from '@/components/aime-card';
|
||||
import { useErrorModal } from '@/components/error-modal';
|
||||
import { AdminUser } from '@/data/user';
|
||||
import { adminAddCardToUser } from '@/actions/card';
|
||||
import { adminAddCardToUser, deleteCard } from '@/actions/card';
|
||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { useConfirmModal } from '@/components/confirm-modal';
|
||||
import Link from 'next/link';
|
||||
@ -75,7 +75,7 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
||||
onSelectionChange={s => typeof s !== 'string' && setOpenUsers(s)}
|
||||
className="my-1 border-b sm:border-b-0 border-divider sm:bg-content1 sm:rounded-lg sm:px-4 overflow-hidden">
|
||||
|
||||
{users.map(userEntry => (<AccordionItem key={userEntry.uuid}
|
||||
{users.map(userEntry => (<AccordionItem key={userEntry.uuid ?? userEntry.id}
|
||||
id={userEntry.uuid ?? undefined} indicator={({ isOpen }) => <Tooltip content="Show cards">
|
||||
<div className="flex items-center">
|
||||
<CreditCardIcon className="h-6 w-6 mr-1" />
|
||||
@ -142,6 +142,14 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
||||
<section className="flex sm:p-4">
|
||||
<div className="flex-grow flex flex-wrap items-center justify-center gap-2">
|
||||
{userEntry.cards.map(c => <AimeCard key={c.access_code}
|
||||
canDelete
|
||||
onDelete={() => {
|
||||
deleteCard(c.user!, c.id!);
|
||||
setUsers(u => u.map(u => u.id === userEntry.id ? {
|
||||
...u,
|
||||
cards: u.cards.filter(card => card.id !== c.id)
|
||||
} : u));
|
||||
}}
|
||||
card={{
|
||||
...c,
|
||||
created_date: new Date(c.created_date!),
|
||||
|
@ -11,7 +11,7 @@ import { UserPermissions } from '@/types/permissions';
|
||||
import { hasPermission } from '@/helpers/permissions';
|
||||
import { useErrorModal } from '@/components/error-modal';
|
||||
import { useState } from 'react';
|
||||
import { userAddCard } from '@/actions/card';
|
||||
import { deleteCard, userAddCard } from '@/actions/card';
|
||||
|
||||
export type CardsProps = {
|
||||
cards: DB['aime_card'][],
|
||||
@ -21,7 +21,7 @@ export type CardsProps = {
|
||||
|
||||
export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps) => {
|
||||
const prompt = usePromptModal();
|
||||
const user = useUser();
|
||||
const user = useUser({ required: true });
|
||||
const setError = useErrorModal();
|
||||
const [cards, setCards] = useState(initialCards);
|
||||
|
||||
@ -29,7 +29,7 @@ export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps)
|
||||
<header className="text-2xl font-semibold flex px-4 h-16 items-center">
|
||||
Cards
|
||||
|
||||
{(hasPermission(user?.permissions, UserPermissions.USERMOD) ||
|
||||
{(hasPermission(user.permissions, UserPermissions.USERMOD) ||
|
||||
(canAddCard && cards.length < (maxCard ?? Infinity))) && <Tooltip content="Add card">
|
||||
<Button isIconOnly className="ml-auto" onPress={() => {
|
||||
promptAccessCode(prompt, 'Enter an access code for this card', code => {
|
||||
@ -47,7 +47,10 @@ export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps)
|
||||
</header>
|
||||
<Divider className="mb-4 hidden sm:block" />
|
||||
<div className="px-1 sm:px-4 sm:pb-4 flex flex-wrap items-center justify-center gap-4">
|
||||
{cards.map(c => <AimeCard key={c.id} card={c} className="w-full" />)}
|
||||
{cards.map(c => <AimeCard canDelete key={c.id} card={c} className="w-full" onDelete={() => {
|
||||
deleteCard(user.id, c.id);
|
||||
setCards(cards => cards.filter(card => card.id !== c.id));
|
||||
}} />)}
|
||||
</div>
|
||||
</section>);
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ import { getGlobalConfig } from '@/config';
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const user = await requireUser();
|
||||
const cards = await getCards(user);
|
||||
const cards = await getCards(user.id);
|
||||
|
||||
return (<div className="w-full flex items-center justify-center">
|
||||
<div className="w-full max-w-full sm:max-w-5xl flex flex-col gap-2 2xl:max-w-screen-4xl 2xl:grid grid-cols-12">
|
||||
|
@ -5,22 +5,27 @@ import { useState } from 'react';
|
||||
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||||
import { Button, Tooltip } from '@nextui-org/react';
|
||||
import { useUser } from '@/helpers/use-user';
|
||||
import { TbHammer, TbHammerOff, TbLock, TbLockOpen } from 'react-icons/tb';
|
||||
import { TbHammer, TbHammerOff, TbLock, TbLockOpen, TbTrashX } from 'react-icons/tb';
|
||||
import { hasPermission } from '@/helpers/permissions';
|
||||
import { UserPermissions } from '@/types/permissions';
|
||||
import { banUnbanCard, lockUnlockCard } from '@/actions/card';
|
||||
import { useConfirmModal } from './confirm-modal';
|
||||
|
||||
type AimeCardProps = {
|
||||
card: DB['aime_card'],
|
||||
className?: string,
|
||||
canDelete?: boolean,
|
||||
onDelete?: () => void
|
||||
};
|
||||
|
||||
export const AimeCard = ({ className, card }: AimeCardProps) => {
|
||||
export const AimeCard = ({ className, card, canDelete, onDelete }: AimeCardProps) => {
|
||||
const [showCode, setShowCode] = useState(false);
|
||||
const user = useUser();
|
||||
const canBan = hasPermission(user?.permissions, UserPermissions.USERMOD);
|
||||
const [locked, setLocked] = useState(!!card.is_locked ?? false);
|
||||
const [banned, setBanned] = useState(!!card.is_banned ?? true);
|
||||
const confirm = useConfirmModal();
|
||||
|
||||
const formatOptions = {
|
||||
year: '2-digit',
|
||||
month: 'numeric',
|
||||
@ -73,9 +78,16 @@ export const AimeCard = ({ className, card }: AimeCardProps) => {
|
||||
</>}
|
||||
</div>
|
||||
|
||||
<div className="text-sm sm:text-medium">
|
||||
<div className="text-sm sm:text-medium flex items-end">
|
||||
{card.last_login_date ? `Last Used ${card.last_login_date.toLocaleTimeString(undefined, formatOptions)}` :
|
||||
'Never Used'}
|
||||
|
||||
{canDelete && <Tooltip content={<span className="text-danger">Delete this card</span>}>
|
||||
<Button className="ml-auto" isIconOnly color="danger" variant="faded" onPress={() => confirm('Do you want to delete this card?',
|
||||
() => onDelete?.())}>
|
||||
<TbTrashX className="w-7 h-7" />
|
||||
</Button>
|
||||
</Tooltip>}
|
||||
</div>
|
||||
|
||||
{(locked || banned) && <div className="absolute flex items-center justify-center w-full left-0 top-[60%] backdrop-blur h-12 sm:h-16 bg-gray-600/50 font-bold sm:text-2xl">
|
||||
|
@ -88,7 +88,8 @@ export const getUsers = async () => {
|
||||
] as const)
|
||||
.execute();
|
||||
|
||||
return parseJsonResult(res, ['cards']);
|
||||
const data = parseJsonResult(res, ['cards']);
|
||||
return data.map(d => ({ ...d, cards: d.cards.filter(c => c.id !== null) }));
|
||||
};
|
||||
|
||||
export type AdminUser = Awaited<ReturnType<typeof getUsers>>[number];
|
||||
|
Loading…
Reference in New Issue
Block a user