hook up config for users to add cards
This commit is contained in:
parent
28ae102c02
commit
619c8bf2cd
@ -3,15 +3,16 @@
|
||||
import { requireUser } from '@/actions/auth';
|
||||
import { db } from '@/db';
|
||||
import { UserPermissions } from '@/types/permissions';
|
||||
import { requirePermission } from '@/helpers/permissions';
|
||||
import { hasPermission, requirePermission } from '@/helpers/permissions';
|
||||
import { addCardToUser } from '@/data/card';
|
||||
import { AdminUser, getUsers } from '@/data/user';
|
||||
import { ActionResult } from '@/types/action-result';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { getGlobalConfig } from '@/config';
|
||||
import { UserPayload } from '@/types/user';
|
||||
import { DB } from '@/types/db';
|
||||
|
||||
export const getCards = async () => {
|
||||
const user = await requireUser();
|
||||
|
||||
export const getCards = async (user: UserPayload) => {
|
||||
return db.selectFrom('aime_card')
|
||||
.where('user', '=', user.id)
|
||||
.selectAll()
|
||||
@ -63,3 +64,36 @@ export const adminAddCardToUser = async (user: number, code: string): Promise<Ac
|
||||
|
||||
return { data: await getUsers() };
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (!getGlobalConfig('allow_user_add_card'))
|
||||
return { error: true, message: 'You do not have permissions to add a card' };
|
||||
|
||||
if (cards.length >= (getGlobalConfig('user_max_card') ?? Infinity))
|
||||
return { error: true, message: 'You cannot add a card because you have reached your max card count' };
|
||||
}
|
||||
|
||||
const res = await addCardToUser(user.id, code);
|
||||
|
||||
if (res.error)
|
||||
return res;
|
||||
|
||||
revalidatePath('/settings', 'page');
|
||||
revalidatePath('/admin/users', 'page');
|
||||
|
||||
return {
|
||||
card: {
|
||||
id: res.id,
|
||||
access_code: code,
|
||||
user: user.id,
|
||||
created_date: new Date(),
|
||||
is_locked: 0,
|
||||
is_banned: 0,
|
||||
last_login_date: null
|
||||
}};
|
||||
};
|
||||
|
@ -7,8 +7,6 @@ import { USER_PERMISSION_NAMES, UserPermissions } from '@/types/permissions';
|
||||
import { Button, Divider, Tooltip, Input, Accordion, AccordionItem, Spacer } from '@nextui-org/react';
|
||||
import { ChevronDownIcon, CreditCardIcon, PencilSquareIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||
import { usePromptModal } from '@/components/prompt-modal';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import { generateAccessCode } from '@/helpers/access-code';
|
||||
import { useUser } from '@/helpers/use-user';
|
||||
import { hasPermission } from '@/helpers/permissions';
|
||||
import { AimeCard } from '@/components/aime-card';
|
||||
@ -19,6 +17,7 @@ import { TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { useConfirmModal } from '@/components/confirm-modal';
|
||||
import Link from 'next/link';
|
||||
import { PermissionIcon } from '@/components/permission-icon';
|
||||
import { promptAccessCode } from '@/components/prompt-access-code';
|
||||
|
||||
const FORMAT = {
|
||||
month: 'numeric',
|
||||
@ -42,33 +41,13 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
||||
setOpenUsers(new Set([window.location.hash.slice(1)]));
|
||||
}, []);
|
||||
|
||||
const promptAccessCode = (message: string, onConfirm: (val: string) => void) => {
|
||||
prompt({
|
||||
size: '2xl',
|
||||
title: 'Enter Access Code', content: (val, setVal) => <>
|
||||
{ message }
|
||||
<div className="flex overflow-hidden rounded-lg">
|
||||
<Input label="Access Code" inputMode="numeric" size="sm" type="text" maxLength={24} radius="none"
|
||||
classNames={{ input: `[font-feature-settings:"fwid"] text-xs sm:text-sm` }}
|
||||
value={val.match(/.{1,4}/g)?.join('-') ?? ''}
|
||||
onValueChange={v => setVal(v.replace(/\D/g, ''))} />
|
||||
<Tooltip content="Generate Random Code">
|
||||
<Button isIconOnly color="primary" size="lg" radius="none" onPress={() => setVal(generateAccessCode())}>
|
||||
<ArrowPathIcon className="h-7" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
}, v => onConfirm(v.replace(/\D/g, '')));
|
||||
}
|
||||
|
||||
return (<main className="max-w-5xl mx-auto w-full">
|
||||
<header className="p-4 font-semibold text-2xl flex items-center">
|
||||
Users
|
||||
|
||||
<Tooltip content="Create new user">
|
||||
<Button isIconOnly className="ml-auto"
|
||||
onPress={() => promptAccessCode('Enter an access code to create this user', code => {
|
||||
onPress={() => promptAccessCode(prompt, 'Enter an access code to create this user', code => {
|
||||
createUserWithAccessCode(code)
|
||||
.then(res => {
|
||||
if (res.error)
|
||||
@ -172,7 +151,7 @@ export const AdminUserList = ({ users: initialUsers }: { users: AdminUser[]; })
|
||||
}} />)}
|
||||
</div>
|
||||
<Tooltip content="Add new card to this user">
|
||||
<Button isIconOnly onPress={() => promptAccessCode('Enter an access code to add',
|
||||
<Button isIconOnly onPress={() => promptAccessCode(prompt, 'Enter an access code to add',
|
||||
code => adminAddCardToUser(userEntry.id, code).then(res => {
|
||||
if (res.error)
|
||||
return setError(res.message);
|
||||
|
53
src/app/(with-header)/settings/cards.tsx
Normal file
53
src/app/(with-header)/settings/cards.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import { DB } from '@/types/db';
|
||||
import { AimeCard } from '@/components/aime-card';
|
||||
import { Button, Divider, Tooltip } from '@nextui-org/react';
|
||||
import { PlusIcon } from '@heroicons/react/24/outline';
|
||||
import { usePromptModal } from '@/components/prompt-modal';
|
||||
import { promptAccessCode } from '@/components/prompt-access-code';
|
||||
import { useUser } from '@/helpers/use-user';
|
||||
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';
|
||||
|
||||
export type CardsProps = {
|
||||
cards: DB['aime_card'][],
|
||||
canAddCard: boolean,
|
||||
maxCard: number | null
|
||||
};
|
||||
|
||||
export const Cards = ({ cards: initialCards, canAddCard, maxCard }: CardsProps) => {
|
||||
const prompt = usePromptModal();
|
||||
const user = useUser();
|
||||
const setError = useErrorModal();
|
||||
const [cards, setCards] = useState(initialCards);
|
||||
|
||||
return (<section className="w-full rounded-lg sm:bg-content1 sm:shadow-lg">
|
||||
<header className="text-2xl font-semibold flex px-4 h-16 items-center">
|
||||
Cards
|
||||
|
||||
{(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 => {
|
||||
userAddCard(code)
|
||||
.then(res => {
|
||||
if (res.error)
|
||||
return setError(res.message);
|
||||
setCards(c => [...c, res.card]);
|
||||
})
|
||||
});
|
||||
}}>
|
||||
<PlusIcon className="h-3/4" />
|
||||
</Button>
|
||||
</Tooltip>}
|
||||
</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" />)}
|
||||
</div>
|
||||
</section>);
|
||||
};
|
@ -1,26 +1,24 @@
|
||||
import { getCards } from '@/actions/card';
|
||||
import { Divider } from '@nextui-org/react';
|
||||
import { AimeCard } from '@/components/aime-card';
|
||||
import { UserSettings } from './user-settings';
|
||||
import { Cards } from './cards';
|
||||
import { requireUser } from '@/actions/auth';
|
||||
import { getGlobalConfig } from '@/config';
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const card = await getCards();
|
||||
|
||||
const user = await requireUser();
|
||||
const cards = await getCards(user);
|
||||
|
||||
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-2">
|
||||
<div>
|
||||
<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">
|
||||
<div className="col-span-3">
|
||||
<UserSettings />
|
||||
</div>
|
||||
|
||||
<Divider className="block sm:hidden mt-2" />
|
||||
|
||||
<div className="w-full rounded-lg sm:bg-content1 sm:shadow-lg">
|
||||
<div className="text-2xl font-semibold p-4">Cards</div>
|
||||
<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">
|
||||
{card.map(c => <AimeCard key={c.id} card={c} className="w-full" />)}
|
||||
</div>
|
||||
<div className="col-span-9">
|
||||
<Cards cards={cards} canAddCard={getGlobalConfig('allow_user_add_card')} maxCard={getGlobalConfig('user_max_card') ?? Infinity} />
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
24
src/components/prompt-access-code.tsx
Normal file
24
src/components/prompt-access-code.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { generateAccessCode } from '@/helpers/access-code';
|
||||
import { PromptCallback } from './prompt-modal';
|
||||
import { Button, Input, Tooltip } from '@nextui-org/react';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export const promptAccessCode = (prompt: PromptCallback, message: string, onConfirm: (val: string) => void) => {
|
||||
prompt({
|
||||
size: '2xl',
|
||||
title: 'Enter Access Code', content: (val, setVal) => <>
|
||||
{message}
|
||||
<div className="flex overflow-hidden rounded-lg">
|
||||
<Input label="Access Code" inputMode="numeric" size="sm" type="text" maxLength={24} radius="none"
|
||||
classNames={{ input: `[font-feature-settings:"fwid"] text-xs sm:text-sm` }}
|
||||
value={val.match(/.{1,4}/g)?.join('-') ?? ''}
|
||||
onValueChange={v => setVal(v.replace(/\D/g, ''))} />
|
||||
<Tooltip content="Generate Random Code">
|
||||
<Button isIconOnly color="primary" size="lg" radius="none" onPress={() => setVal(generateAccessCode())}>
|
||||
<ArrowPathIcon className="h-7" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
}, v => onConfirm(v.replace(/\D/g, '')));
|
||||
};
|
@ -6,7 +6,7 @@ import { useHashNavigation } from '@/helpers/use-hash-navigation';
|
||||
type PromptOptions = { title: string, size?: ModalProps['size'] } &
|
||||
(({ message: string, content?: never } & Partial<Pick<InputProps, 'type' | 'name' | 'label' | 'placeholder'>>) |
|
||||
{ content: (value: string, setValue: (v: string) => void) => ReactNode, message?: never });
|
||||
type PromptCallback = (options: PromptOptions, onConfirm: (val: string) => void, onCancel?: () => void) => void;
|
||||
export type PromptCallback = (options: PromptOptions, onConfirm: (val: string) => void, onCancel?: () => void) => void;
|
||||
const PromptContext = createContext<PromptCallback>(() => {});
|
||||
|
||||
export const PromptProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user