rendering admin component headers

This commit is contained in:
balzack 2025-02-19 22:42:38 -08:00
parent e90b7aa4b3
commit ec6becaf9c
9 changed files with 597 additions and 12 deletions

View File

@ -0,0 +1,45 @@
.accounts {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
.content {
width: 100%;
max-width: 650px;
.header {
height: 48px;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.leftTitle {
flex-grow: 1;
display: flex;
justify-content: flex-start;
}
.centerTitle {
flex-grow: 1;
display: flex;
justify-content: center;
}
.title {
padding-left: 16px;
padding-right: 16px;
font-size: 20px;
}
.area {
flex-grow: 1;
}
}
.line {
width: 100%;
}
}
}

View File

@ -1,4 +1,21 @@
import classes from './Accounts.module.css'
import { useAccounts } from './useAccounts.hook'
import { Modal, Divider, Text, ActionIcon } from '@mantine/core'
export function Accounts({ openSetup }: { openSetup: ()=>void }) {
return <div onClick={openSetup}>ACCOUNTS</div>
const { state, actions } = useAccounts();
return (
<div className={classes.accounts}>
<div className={classes.content}>
<div className={classes.header}>
<div className={state.layout === 'large' ? classes.leftTitle : classes.centerTitle}>
<Text className={classes.title}>{ state.strings.accounts }</Text>
</div>
</div>
<Divider className={classes.line} />
</div>
</div>
);
}

View File

@ -0,0 +1,59 @@
import {useEffect, useState, useContext, useRef} from 'react';
import {AppContext} from '../context/AppContext';
import {DisplayContext} from '../context/DisplayContext';
import {ContextType} from '../context/ContextType';
import type { Member } from 'databag-client-sdk';
export function useAccounts() {
const app = useContext(AppContext);
const display = useContext(DisplayContext);
const [state, setState] = useState({
layout: '',
strings: {},
members: [] as Member[],
loading: false,
});
const updateState = (value: any) => {
setState(s => ({...s, ...value}));
};
const sync = async () => {
if (!state.loading) {
try {
updateState({ loading: true });
const service = app.state.service;
const members = await service.getMembers();
updateState({ members, loading: false });
} catch (err) {
console.log(err);
updateState({ loading: false });
}
}
}
useEffect(() => {
const { layout, strings} = display.state;
updateState({ layout, strings});
}, [display.state]);
const actions = {
reload: sync,
addAccount: async () => {
return await app.state.service.createMemberAccess();
},
accessAccount: async (accountId: number) => {
return await app.state.service.resetMemberAccess(accountId);
},
blockAccount: async (accountId: number, flag: boolean) => {
await app.state.service.blockMember(accountId, flag);
await sync();
},
removeAccount: async (accountId: number) => {
await app.state.service.removeMember(accountId);
await sync();
},
};
return {state, actions};
}

View File

@ -15,6 +15,10 @@ export const en = {
noAccess: 'No Access',
connecting: 'Connecting',
setup: 'Setup',
accounts: 'Accounts',
noAccounts: 'No Accounts',
membership: 'Membership',
channelGuest: 'Topic Guest',
channelHost: 'Topic Host',
@ -164,10 +168,16 @@ export const en = {
confirmDelete: 'Deleting Account',
areSure: 'Are you sure you want to delete the account?',
addingTitle: 'Adding Account',
addingLink: 'Use the following link can be used to create an account',
addingToken: 'Use the following token can be used to create an account from the login screen',
accessingTitle: 'Accessing Account',
accessingLink: 'Use the following link to access the specified account',
accessingToken: 'Use the following token to access the specified account from the login screen',
mb: 'MB',
gb: 'GB',
copied: 'Copied',
accounts: 'Accounts',
accessAccount: 'Access Account',
browserLink: 'Browser Link',
mobileToken: 'Mobile Token',
@ -298,6 +308,17 @@ export const fr = {
noAccess: 'Pas d\'Accès',
connecting: 'Démarrage de la Connexion',
setup: 'Installation',
accounts: 'Comptes',
noAccounts: 'Aucun Compte',
addingTitle: 'Ajout d\'un Compte',
addingLink: 'Utilisez le lien suivant pour créer un compte',
addingToken: 'Utilisez le jeton suivant pour créer un compte depuis l\'écran de connexion',
accessingTitle: 'Accéder au Compte',
accessingLink: 'Utilisez le lien suivant pour accéder au compte spécifié',
accessingToken: 'Utilisez le jeton suivant pour accéder au compte spécifié depuis l\'écran de connexion',
membership: 'Adhésion',
channelHost: 'Hôte du Sujet',
channelGuest: 'Invité du Sujet',
@ -467,7 +488,6 @@ export const fr = {
mb: 'Mo',
gb: 'Go',
copied: 'Copié',
accounts: 'Comptes',
accessAccount: 'Accéder au Compte',
browserLink: 'Lien du Navigateur',
mobileToken: 'Code Mobile',
@ -576,6 +596,17 @@ export const sp = {
reportTopicPrompt: '¿Estás seguro de que deseas reportar este tema para revisión del administrador?',
connecting: 'Conexión Inicial',
setup: 'Configuración',
accounts: 'Cuentas',
noAccounts: 'No hay cuentas',
addingTitle: 'Añadiendo cuenta',
addingLink: 'Utilice el siguiente enlace para crear una cuenta',
addingToken: 'Utilice el siguiente token para crear una cuenta desde la pantalla de inicio de sesión',
accessingTitle: 'Accediendo a la cuenta',
accessingLink: 'Utilice el siguiente enlace para acceder a la cuenta especificada',
accessingToken: 'Utilice el siguiente token para acceder a la cuenta especificada desde la pantalla de inicio de sesión',
noAccess: 'Sin Acceso',
membership: 'Afiliación',
channelHost: 'Anfitrión del Tema',
@ -746,7 +777,6 @@ export const sp = {
mb: 'MB',
gb: 'GB',
copied: 'Copiado',
accounts: 'Cuentas',
accessAccount: 'Acceder a la Cuenta',
browserLink: 'Enlace del Navegador',
mobileToken: 'Código Móvil',
@ -854,6 +884,17 @@ export const pt = {
reportTopicPrompt: 'Tem certeza de que deseja denunciar este tópico para revisão do administrador?',
connecting: 'Iniciando Conexão',
setup: 'configurar',
accounts: 'Contas',
noAccounts: 'Sem Contas',
addingTitle: 'Adicionando conta',
addingLink: 'Use o seguinte link para criar uma conta',
addingToken: 'Use o seguinte token para criar uma conta a partir da tela de login',
accessingTitle: 'Acessando conta',
accessingLink: 'Use o seguinte link para acessar a conta especificada',
accessingToken: 'Use o seguinte token para acessar a conta especificada a partir da tela de login',
noAccess: 'Sem Acesso',
membership: 'Associação',
channelHost: 'Anfitrião do Tópico',
@ -1024,7 +1065,6 @@ export const pt = {
mb: 'MB',
gb: 'GB',
copied: 'Copiado',
accounts: 'Contas',
accessAccount: 'Acessar conta',
browserLink: 'Link do navegador',
mobileToken: 'Token móvel',
@ -1133,6 +1173,17 @@ export const de = {
reportTopicPrompt: 'Sind Sie sicher, dass Sie dieses Thema zur Überprüfung durch den Administrator melden möchten?',
connecting: 'Startverbindung',
setup: 'Aufstellen',
accounts: 'Konten',
noAccounts: 'Keine Konten',
addingTitle: 'Konto hinzufügen',
addingLink: 'Verwenden Sie den folgenden Link, um ein Konto zu erstellen',
addingToken: 'Verwenden Sie das folgende Token, um ein Konto vom Anmeldebildschirm aus zu erstellen',
accessingTitle: 'Kontozugriff',
accessingLink: 'Verwenden Sie den folgenden Link, um auf das angegebene Konto zuzugreifen',
accessingToken: 'Verwenden Sie das folgende Token, um vom Anmeldebildschirm aus auf das angegebene Konto zuzugreifen',
noAccess: 'Kein Zugriff',
channelHost: 'Themenhost',
channelGuest: 'Thema Gast',
@ -1302,7 +1353,6 @@ export const de = {
mb: 'MB',
gb: 'GB',
copied: 'Kopiert',
accounts: 'Konten',
accessAccount: 'Kontozugriff',
browserLink: 'Browser-Link',
mobileToken: 'Mobilcode',
@ -1411,6 +1461,17 @@ export const ru = {
reportTopicPrompt: 'Вы уверены, что хотите отправить эту тему на рассмотрение администратору?',
connecting: 'начало соединения',
setup: 'настраивать',
accounts: 'Учетные записи',
noAccounts: 'Нет учетных записей',
addingTitle: 'Добавление аккаунта',
addingLink: 'Используйте следующую ссылку для создания аккаунта',
addingToken: 'Используйте следующий токен для создания аккаунта с экрана входа',
accessingTitle: 'Доступ к аккаунту',
accessingLink: 'Используйте следующую ссылку для доступа к указанному аккаунту',
accessingToken: 'Используйте следующий токен для доступа к указанному аккаунту с экрана входа',
noAccess: 'Нет доступа',
membership: 'Членство',
channelHost: 'Ведущий темы',
@ -1580,7 +1641,6 @@ export const ru = {
mb: 'МБ',
gb: 'ГБ',
copied: 'Скопировано',
accounts: 'Аккаунты',
accessAccount: 'Доступ к аккаунту',
browserLink: 'Ссылка на браузер',
mobileToken: 'Мобильный токен',

View File

@ -1,2 +1,91 @@
.service {
position: relative;
width: 100%;
height: 100%;
.body {
display: flex;
width: 100%;
height: calc(100% - 48px);
position: absolute;
top: 0;
left: 0;
flex-direction: column;
}
.show {
display: flex;
flex-grow: 1;
height: 10%;
position: relative;
}
.hide {
display: none;
}
.display {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
.screen {
width: 100%;
height: 100%;
background-color: var(--mantine-color-surface-1);
overflow: auto;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.tabs {
position: absolute;
bottom: 0;
width: 100%;
display: flex;
flex-direction: row;
.activeTabItem {
flex: 1;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--mantine-color-tab-2);
color: var(--mantine-color-dbgreen-1);
padding-bottom: 8px;
padding-top: 8px;
cursor: pointer;
}
.idleTabItem {
flex: 1;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--mantine-color-tab-1);
color: var(--mantine-color-dbgreen-0);
padding-bottom: 8px;
padding-top: 8px;
cursor: pointer;
&:hover {
background: var(--mantine-color-tab-3);
}
}
.tabDivider {
width: 2px;
}
.tabIcon {
width: 32px;
height: 32px;
}
}
}

View File

@ -15,7 +15,7 @@ export function Service() {
const [setup, { open: openSetup, close: closeSetup }] = useDisclosure(false)
return (
<div className={classes.session}>
<div className={classes.service}>
{state.layout === 'small' && (
<>
<div className={classes.body}>
@ -57,9 +57,7 @@ export function Service() {
)}
{state.layout === 'large' && (
<div className={classes.display}>
<div style={classes.accounts}>
<Accounts openSetup={openSetup} />
</div>
<Accounts openSetup={openSetup} />
<Drawer opened={setup} onClose={closeSetup} withCloseButton={false} size="sm" padding="0" position="right">
<div style={{ height: '100vh' }}>
<Setup />

View File

@ -0,0 +1,39 @@
.accounts {
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
.content {
width: 100%;
max-width: 650px;
.header {
height: 48px;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.centerTitle {
flex-grow: 1;
display: flex;
justify-content: center;
}
.title {
padding-left: 16px;
padding-right: 16px;
font-size: 20px;
}
.area {
flex-grow: 1;
}
}
.line {
width: 100%;
}
}
}

View File

@ -1,4 +1,21 @@
import classes from './Setup.module.css'
import { useSetup } from './useSetup.hook'
import { Modal, Divider, Text, ActionIcon } from '@mantine/core'
export function Setup() {
return <div>SETUP</div>
const { state, actions } = useSetup();
return (
<div className={classes.accounts}>
<div className={classes.content}>
<div className={classes.header}>
<div className={classes.centerTitle}>
<Text className={classes.title}>{ state.strings.setup }</Text>
</div>
</div>
<Divider className={classes.line} />
</div>
</div>
);
}

View File

@ -0,0 +1,261 @@
import {useEffect, useState, useContext, useRef} from 'react';
import {AppContext} from '../context/AppContext';
import {DisplayContext} from '../context/DisplayContext';
import {ContextType} from '../context/ContextType';
import type { Setup } from 'databag-client-sdk';
const DEBOUNCE_MS = 2000;
const DELAY_MS = 1000;
export function useSetup() {
const updated = useRef(false);
const loading = useRef(false);
const debounce = useRef(null);
const setup = useRef(null as null | Setup);
const app = useContext(AppContext);
const display = useContext(DisplayContext);
const [state, setState] = useState({
layout: '',
strings: {},
loading: true,
updating: false,
error: false,
accountStorage: '',
setup: null as null | Setup,
mfaEnabled: false,
mfaCode: '',
mfaMessage: '',
confirmingMFAuth: false,
confirmMFAuthText: '',
confirmMFAuthImage: '',
});
const updateState = (value: any) => {
setState(s => ({...s, ...value}));
};
const sync = async () => {
while (loading.current) {
try {
const service = app.state.service;
const mfaEnabled = await service.checkMFAuth();
setup.current = await service.getSetup();
loading.current = false;
const storage = Math.floor(setup.current.accountStorage / 1073741824);
updateState({ setup: setup.current, mfaEnabled, accountStorage: storage.toString(), loading: false });
} catch (err) {
console.log(err);
await new Promise((r) => setTimeout(r, DELAY_MS));
}
}
}
const save = () => {
updated.current = true;
updateState({ updating: true });
clearTimeout(debounce.current);
debounce.current = setTimeout(async () => {
updated.current = false;
try {
const service = app.state.service;
await service.setSetup(setup.current);
if (updated.current) {
save()
} else {
updateState({ updating: false });
}
} catch (err) {
console.log(err);
updateState({ error: true });
}
}, DEBOUNCE_MS);
}
useEffect(() => {
const { layout, strings} = display.state;
updateState({ layout, strings});
}, [display.state]);
useEffect(() => {
if (app.state.service) {
loading.current = true;
sync();
return () => {
loading.current = false;
}
}
}, []);
const actions = {
clearError: () => {
updateState({ error: false });
},
enableMFAuth: async () => {
try {
const service = app.state.service;
const { text, image } = await service.enableMFAuth();
updateState({ confirmingMFAuth: true, mfaCode: '', mfaMessage: '', confirmMFAuthText: text, confirmMFAuthImage: image });
} catch (err) {
console.log(err);
updateState({ error: true });
}
},
disableMFAuth: async () => {
try {
const service = app.state.service;
await service.disableMFAuth();
updateState({ mfaEnabled: false });
} catch (err) {
console.log(err);
updateState({ error: true });
}
},
confirmMFAuth: async () => {
try {
const service = app.state.service;
await service.confirmMFAuth(state.mfaCode);
updateState({ confirmingMFAuth: false, mfaEnabled: true });
} catch (err) {
if (err.message === '401') {
updateState({ mfaMessage: state.strings.mfaError });
} else if (err.message === '429') {
updateState({ mfaMessage: state.strings.mfaDisabled });
} else {
updateState({ mfaMessage: state.strings.error });
}
}
},
cancelMFAuth: () => {
updateState({ confirmingMFAuth: false });
},
setMFAuthCode: (mfaCode: string) => {
updateState({ mfaCode });
},
setDomain: (domain: string) => {
if (setup.current) {
setup.current.domain = domain;
updateState({ setup: setup.current });
save();
}
},
setAccountStorage: (accountStorage: string) => {
if (setup.current) {
const storage = parseInt(accountStorage) * 1073741824;
if (storage >= 0) {
setup.current.accountStorage = storage;
updateState({ setup: setup.current, accountStorage });
} else {
setup.current.accountStorage = 0;
updateState({ setup: setup.current, accountStorage: 0 });
}
save();
}
},
setKeyType: (keyType: string) => {
if (setup.current) {
setup.current.keyType = keyType;
updateState({ setup: setup.current });
save();
}
},
setEnableOpenAccess: (enableOpenAccess: boolean) => {
if (setup.current) {
setup.current.enableOpenAccess = enableOpenAccess;
updateState({ setup: setup.current });
save();
}
},
setOpenAccessLimit: (openAccessLimit: string) => {
if (setup.current) {
const limit = parseInt(openAccessLimit);
if (limit >= 0) {
setup.current.openAccessLimit = limit;
updateState({ setup: setup.current, openAccessLimit });
} else {
setup.current.openAccessLimit = 0;
updateState({ setup: setup.current, openAccessLimit: 0 });
}
save();
}
},
setPushSupported: (pushSupported: boolean) => {
if (setup.current) {
setup.current.pushSupported = pushSupported;
updateState({ setup: setup.current });
save();
}
},
setAllowUnsealed: (allowUnsealed: boolean) => {
if (setup.current) {
setup.current.allowUnsealed = allowUnsealed;
updateState({ setup: setup.current });
save();
}
},
setEnableImage: (enableImage: boolean) => {
if (setup.current) {
setup.current.enableImage = enableImage;
updateState({ setup: setup.current });
save();
}
},
setEnableAudio: (enableAudio: boolean) => {
if (setup.current) {
setup.current.enableAudio = enableAudio;
updateState({ setup: setup.current });
save();
}
},
setEnableVideo: (enableVideo: boolean) => {
if (setup.current) {
setup.current.enableVideo = enableVideo;
updateState({ setup: setup.current });
save();
}
},
setEnableBinary: (enableBinary: boolean) => {
if (setup.current) {
setup.current.enableBinary = enableBinary;
updateState({ setup: setup.current });
save();
}
},
setEnableIce: (enableIce: boolean) => {
if (setup.current) {
setup.current.enableIce = enableIce;
updateState({ setup: setup.current });
save();
}
},
setEnableService: (enableService: boolean) => {
if (setup.current) {
setup.current.enableService = enableService;
updateState({ setup: setup.current });
save();
}
},
setIceUrl: (iceUrl: string) => {
if (setup.current) {
setup.current.iceUrl = iceUrl;
updateState({ setup: setup.current });
save();
}
},
setIceUsername: (iceUsername: string) => {
if (setup.current) {
setup.current.iceUsername = iceUsername;
updateState({ setup: setup.current });
save();
}
},
setIcePassword: (icePassword: string) => {
if (setup.current) {
setup.current.icePassword = icePassword;
updateState({ setup: setup.current });
save();
}
},
};
return {state, actions};
}