mirror of
https://github.com/balzack/databag.git
synced 2025-03-13 09:00:06 +00:00
adding token modals for admin config
This commit is contained in:
parent
c4a6796740
commit
4d1a8d1516
@ -79,3 +79,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
.prompt {
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.copy {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
color: var(--mantine-color-green-2);
|
||||
}
|
||||
|
||||
.control {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,115 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import classes from './Accounts.module.css'
|
||||
import { useAccounts } from './useAccounts.hook'
|
||||
import { Modal, Divider, Text, ActionIcon } from '@mantine/core'
|
||||
import { IconUserPlus, IconUserCheck, IconReload, IconSettings, IconLockOpen2, IconUserCancel, IconTrash } from '@tabler/icons-react'
|
||||
import { Modal, Divider, Text, ActionIcon, Button } from '@mantine/core'
|
||||
import { IconUserPlus, IconUserCheck, IconCopy, IconCheck, IconReload, IconSettings, IconLockOpen2, IconUserCancel, IconTrash } from '@tabler/icons-react'
|
||||
import { Card } from '../card/Card'
|
||||
import { Colors } from '../constants/Colors';
|
||||
import { modals } from '@mantine/modals'
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
|
||||
export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
const { state, actions } = useAccounts();
|
||||
const [failed, setFailed] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [blocking, setBlocking] = useState(null as null | number);
|
||||
const [removing, setRemoving] = useState(null as null | number);
|
||||
const [accessing, setAccessing] = useState(null as null | number);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [accessOpened, { open: accessOpen, close: accessClose }] = useDisclosure(false)
|
||||
const [addOpened, { open: addOpen, close: addClose }] = useDisclosure(false)
|
||||
const [tokenCopy, setTokenCopy] = useState(false);
|
||||
const [linkCopy, setLinkCopy] = useState(false);
|
||||
const [token, setToken] = useState('');
|
||||
const link = `${window.location.origin}/#/create?add=${token}`;
|
||||
|
||||
useEffect(() => {
|
||||
actions.reload();
|
||||
}, []);
|
||||
|
||||
const loadAccounts = async () => {
|
||||
if (!loading) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await actions.reload();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const copyToken = async () => {
|
||||
if (!tokenCopy) {
|
||||
try {
|
||||
navigator.clipboard.writeText(token)
|
||||
setTokenCopy(true);
|
||||
setTimeout(() => {
|
||||
setTokenCopy(false);
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const copyLink = async () => {
|
||||
if (!linkCopy) {
|
||||
try {
|
||||
navigator.clipboard.writeText(link)
|
||||
setLinkCopy(true);
|
||||
setTimeout(() => {
|
||||
setLinkCopy(false);
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const showError = () => {
|
||||
modals.openConfirmModal({
|
||||
title: state.strings.operationFailed,
|
||||
withCloseButton: true,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: <Text>{state.strings.tryAgain}</Text>,
|
||||
cancelProps: { display: 'none' },
|
||||
confirmProps: { display: 'none' },
|
||||
})
|
||||
}
|
||||
|
||||
const addAccount = async () => {
|
||||
if (!adding) {
|
||||
setAdding(true);
|
||||
try {
|
||||
const access = await actions.addAccount();
|
||||
setToken(access);
|
||||
addOpen();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
showError();
|
||||
}
|
||||
setAdding(false);
|
||||
}
|
||||
}
|
||||
|
||||
const accessAccount = async (accountId: number) => {
|
||||
if (!accessing) {
|
||||
setAccessing(accountId);
|
||||
try {
|
||||
const access = await actions.accessAccount(accountId);
|
||||
setToken(access);
|
||||
accessOpen();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
showError();
|
||||
}
|
||||
setAccessing(null);
|
||||
}
|
||||
}
|
||||
|
||||
const blockAccount = async (accountId: number, block: boolean) => {
|
||||
if (!blocking) {
|
||||
setBlocking(accountId);
|
||||
@ -22,15 +117,42 @@ export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
await actions.blockAccount(accountId, block);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setFailed(true);
|
||||
showError();
|
||||
}
|
||||
setBlocking(null);
|
||||
}
|
||||
}
|
||||
|
||||
const removeAccount = (accountId: number) => {
|
||||
modals.openConfirmModal({
|
||||
title: state.strings.confirmDelete,
|
||||
withCloseButton: false,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: <Text>{ state.strings.areSure }</Text>,
|
||||
labels: { confirm: state.strings.remove, cancel: state.strings.cancel },
|
||||
onConfirm: async () => {
|
||||
if (!removing) {
|
||||
setRemoving(accountId);
|
||||
try {
|
||||
await actions.removeAccount(accountId);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
showError();
|
||||
}
|
||||
setRemoving(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const members = state.members.map((member, idx) => {
|
||||
const options = [
|
||||
<ActionIcon key="acess" className={classes.action} variant="light" onClick={actions.reload} loading={false}><IconLockOpen2 /></ActionIcon>,
|
||||
<ActionIcon key="acess" className={classes.action} variant="light" loading={removing === member.accountId} onClick={() => accessAccount(member.accountId)}>
|
||||
<IconLockOpen2 />
|
||||
</ActionIcon>,
|
||||
<ActionIcon key="block" className={classes.action} variant="light" loading={blocking === member.accountId} color={Colors.pending} onClick={() => blockAccount(member.accountId, !member.disabled)}>
|
||||
{ member.disabled && (
|
||||
<IconUserCheck />
|
||||
@ -39,7 +161,7 @@ export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
<IconUserCancel />
|
||||
)}
|
||||
</ActionIcon>,
|
||||
<ActionIcon key="remove" className={classes.action} variant="light" onClick={actions.reload} loading={false} color={Colors.offsync}><IconTrash /></ActionIcon>,
|
||||
<ActionIcon key="remove" className={classes.action} variant="light" loading={removing === member.accountId} color={Colors.offsync} onClick={() => removeAccount(member.accountId)}><IconTrash /></ActionIcon>,
|
||||
];
|
||||
|
||||
return (
|
||||
@ -52,7 +174,7 @@ export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
<div className={classes.content}>
|
||||
<div className={classes.header}>
|
||||
{ state.layout !== 'large' && (
|
||||
<ActionIcon className={classes.action} variant="light" onClick={actions.reload} loading={state.loading}>
|
||||
<ActionIcon className={classes.action} variant="light" onClick={actions.reload} loading={loading}>
|
||||
<IconReload />
|
||||
</ActionIcon>
|
||||
)}
|
||||
@ -64,7 +186,7 @@ export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
<IconReload />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon className={classes.action} variant="light" onClick={()=>{}}>
|
||||
<ActionIcon className={classes.action} variant="light" onClick={addAccount}>
|
||||
<IconUserPlus />
|
||||
</ActionIcon>
|
||||
{ state.layout === 'large' && (
|
||||
@ -77,6 +199,44 @@ export function Accounts({ openSetup }: { openSetup: ()=>void }) {
|
||||
{ members }
|
||||
</div>
|
||||
</div>
|
||||
<Modal title={state.strings.addingTitle} size="lg" opened={addOpened} onClose={addClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
|
||||
<div className={classes.modal}>
|
||||
<Text className={classes.prompt}>{state.strings.addingLink}:</Text>
|
||||
<div className={classes.copy}>
|
||||
<Text className={classes.value}>{ link }</Text>
|
||||
{linkCopy && <IconCheck size="16" />}
|
||||
{!linkCopy && <IconCopy size="16" className={classes.icon} onClick={copyLink} />}
|
||||
</div>
|
||||
<Text className={classes.prompt}>{state.strings.addingToken}:</Text>
|
||||
<div className={classes.copy}>
|
||||
<Text className={classes.value}>{ token }</Text>
|
||||
{tokenCopy && <IconCheck size="16" />}
|
||||
{!tokenCopy && <IconCopy size="16" className={classes.icon} onClick={copyToken} />}
|
||||
</div>
|
||||
<div className={classes.control}>
|
||||
<Button onClick={addClose}>{ state.strings.close }</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal title={state.strings.accessingTitle} size="lg" opened={accessOpened} onClose={accessClose} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
|
||||
<div className={classes.modal}>
|
||||
<Text className={classes.prompt}>{state.strings.accessingLink}:</Text>
|
||||
<div className={classes.copy}>
|
||||
<Text className={classes.value}>{ link }</Text>
|
||||
{linkCopy && <IconCheck size="16" />}
|
||||
{!linkCopy && <IconCopy size="16" className={classes.icon} onClick={copyLink} />}
|
||||
</div>
|
||||
<Text className={classes.prompt}>{state.strings.accessingToken}:</Text>
|
||||
<div className={classes.copy}>
|
||||
<Text className={classes.value}>{ token }</Text>
|
||||
{tokenCopy && <IconCheck size="16" />}
|
||||
{!tokenCopy && <IconCopy size="16" className={classes.icon} onClick={copyToken} />}
|
||||
</div>
|
||||
<div className={classes.control}>
|
||||
<Button onClick={addClose}>{ state.strings.close }</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ export function Setup() {
|
||||
<Text className={classes.label}>{state.strings.keyType}:</Text>
|
||||
<Radio.Group name="keyType" className={classes.radio} value={state.setup?.keyType} onChange={actions.setKeyType}>
|
||||
<Group mt="xs">
|
||||
<Radio disabled={state.loading} value="RSA_2048" label="RSA2048" />
|
||||
<Radio disabled={state.loading} value="RSA_4096" label="RSA4096" />
|
||||
<Radio disabled={state.loading} value="RSA2048" label="RSA2048" />
|
||||
<Radio disabled={state.loading} value="RSA4096" label="RSA4096" />
|
||||
</Group>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
@ -153,7 +153,7 @@ export function useSetup() {
|
||||
}
|
||||
},
|
||||
setKeyType: (type: string) => {
|
||||
const keyType = type === 'RSA_2048' ? KeyType.RSA_2048 : KeyType.RSA_4096;
|
||||
const keyType = type === 'RSA2048' ? KeyType.RSA_2048 : KeyType.RSA_4096;
|
||||
if (setup.current) {
|
||||
setup.current.keyType = keyType;
|
||||
updateState({ setup: setup.current });
|
||||
@ -231,6 +231,8 @@ export function useSetup() {
|
||||
},
|
||||
setEnableService: (iceService: boolean) => {
|
||||
if (setup.current) {
|
||||
const iceUrl = iceService ? 'https://rtc.live.cloudflare.com/v1/turn/keys/%%TURN_KEY_ID%%/credentials/generate' : '';
|
||||
setup.current.iceUrl = iceUrl;
|
||||
setup.current.iceService = iceService ? ICEService.Cloudflare : ICEService.Default;
|
||||
updateState({ setup: setup.current });
|
||||
save();
|
||||
|
Loading…
Reference in New Issue
Block a user