rendering accounts for admin screen

This commit is contained in:
balzack 2025-02-14 22:02:08 -08:00
parent 72f2ab4b88
commit 49142bdad8
10 changed files with 198 additions and 25 deletions

View File

@ -7,11 +7,53 @@ export const styles = StyleSheet.create({
height: '100%',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
},
largeTitle: {
fontSize: 20,
flexGrow: 1,
paddingLeft: 16,
},
smallTitle: {
fontSize: 20,
textAlign: 'center',
flexGrow: 1,
},
header: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: 48,
},
line: {
width: '100%',
},
members: {
width: '100%',
flexGrow: 1,
},
empty: {
width: '100%',
flexGrow: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
label: {
fontSize: 18,
color: Colors.placeholder,
},
card: {
width: '100%',
height: 48,
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 16,
borderBottomWidth: 1,
},
icon: {
backgroundColor: 'transparent',
},
});

View File

@ -1,14 +1,69 @@
import React from 'react';
import {SafeAreaView, Image, View, Pressable} from 'react-native';
import {Text} from 'react-native-paper';
import React, {useState} from 'react';
import {FlatList, SafeAreaView, Image, View, Pressable} from 'react-native';
import {Text, IconButton, Divider, Surface, useTheme} from 'react-native-paper';
import {useAccounts} from './useAccounts.hook';
import {styles} from './Accounts.styled';
import { Card } from '../card/Card';
import { Colors } from '../constants/Colors';
export function Accounts({ setup }: { setup: ()=>void }) {
const { state, actions } = useAccounts();
const theme = useTheme();
const [loading, setLoading] = useState('');
export function Accounts() {
return (
<View style={styles.accounts}>
<View style={styles.header}>
<Text>ACCOUNTS</Text>
</View>
{ state.layout === 'large' && (
<View style={styles.header}>
<Text style={styles.largeTitle}>{ state.strings.accounts }</Text>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={()=>{}} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="cog-outline" onPress={setup} />
</View>
)}
{ state.layout === 'small' && (
<View style={styles.header}>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={()=>{}} />
<Text style={styles.smallTitle}>{ state.strings.accounts }</Text>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
</View>
)}
<Divider style={styles.line} bold={true} />
{state.members.length !== 0 && (
<FlatList
style={styles.members}
data={state.members}
initialNumToRender={32}
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
const options = [
<IconButton key="disable" style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="lock-open-variant-outline" onPress={()=>{}} />,
<IconButton key="reset" style={styles.icon} loading={loading} iconColor={Colors.pending} mode="contained" icon="account-cancel-outline" onPress={()=>{}} />,
<IconButton key="remove" style={styles.icon} loading={loading} iconColor={Colors.offsync} mode="contained" icon="trash-can-outline" onPress={()=>{}} />
];
return (
<Card
containerStyle={{
...styles.card,
borderColor: theme.colors.outlineVariant,
}}
imageUrl={item.imageUrl}
name={item.storageUsed > 1048576 ? `${item.handle} [${Math.floor(item.storageUsed / 1048576)}MB]` : item.handle}
handle={item.guid}
node={item.node}
placeholder={state.strings.name}
select={()=>{}}
actions={options}
/>
);
}} />
)}
{state.members.length === 0 && (
<View style={styles.empty}>
<Text style={styles.label}>{ state.strings.noAccounts }</Text>
</View>
)}
<Divider style={styles.line} bold={true} />
</View>
);
}

View File

@ -0,0 +1,45 @@
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[],
});
const updateState = (value: any) => {
setState(s => ({...s, ...value}));
};
const sync = async () => {
try {
const service = app.state.service;
const members = await service.getMembers();
updateState({ members });
} catch (err) {
console.log(err);
}
}
useEffect(() => {
const { layout, strings} = display.state;
updateState({ layout, strings});
}, [display.state]);
useEffect(() => {
if (app.state.service) {
sync();
}
}, [app.state.service]);
const actions = {
};
return {state, actions};
}

View File

@ -17,6 +17,9 @@ export const en = {
noAccess: 'No Access',
connecting: 'Connecting',
accounts: 'Accounts',
noAccounts: 'No Accounts',
membership: 'Membership',
channelGuest: 'Topic Guest',
channelHost: 'Topic Host',
@ -300,6 +303,9 @@ export const fr = {
noAccess: 'Pas d\'Accès',
connecting: 'Démarrage de la Connexion',
accounts: 'Comptes',
noAccounts: 'Aucun Compte',
membership: 'Adhésion',
channelHost: 'Hôte du Sujet',
channelGuest: 'Invité du Sujet',
@ -578,6 +584,9 @@ export const sp = {
reportTopicPrompt: '¿Estás seguro de que deseas reportar este tema para revisión del administrador?',
connecting: 'Conexión Inicial',
accounts: 'Cuentas',
noAccounts: 'No hay cuentas',
noAccess: 'Sin Acceso',
membership: 'Afiliación',
channelHost: 'Anfitrión del Tema',
@ -856,6 +865,9 @@ export const pt = {
reportTopicPrompt: 'Tem certeza de que deseja denunciar este tópico para revisão do administrador?',
connecting: 'Iniciando Conexão',
accounts: 'Contas',
noAccounts: 'Sem Contas',
noAccess: 'Sem Acesso',
membership: 'Associação',
channelHost: 'Anfitrião do Tópico',
@ -1135,6 +1147,9 @@ export const de = {
reportTopicPrompt: 'Sind Sie sicher, dass Sie dieses Thema zur Überprüfung durch den Administrator melden möchten?',
connecting: 'Startverbindung',
accounts: 'Konten',
noAccounts: 'Keine Konten',
noAccess: 'Kein Zugriff',
channelHost: 'Themenhost',
channelGuest: 'Thema Gast',
@ -1413,6 +1428,9 @@ export const ru = {
reportTopicPrompt: 'Вы уверены, что хотите отправить эту тему на рассмотрение администратору?',
connecting: 'начало соединения',
accounts: 'Учетные записи',
noAccounts: 'Нет учетных записей',
noAccess: 'Нет доступа',
membership: 'Членство',
channelHost: 'Ведущий темы',

View File

@ -88,6 +88,10 @@ export const styles = StyleSheet.create({
width: '100%',
flexGrow: 1,
flexShrink: 1,
minHeight: 0,
height: '1%',
overflow: 'hidden',
position: 'relative',
},
tabs: {
flexShrink: 0,

View File

@ -19,10 +19,10 @@ export function Service() {
const showSetup = {display: tab === 'setup' ? 'flex' : 'none'};
return (
<View style={styles.service}>
<SafeAreaView style={styles.service}>
{state.layout !== 'large' && (
<Surface elevation={3}>
<SafeAreaView style={styles.full}>
<View>
<View style={styles.full}>
<View style={styles.screen}>
<View
style={{
@ -85,8 +85,8 @@ export function Service() {
)}
</View>
</View>
</SafeAreaView>
</Surface>
</View>
</View>
)}
{state.layout === 'large' && (
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
@ -95,7 +95,7 @@ export function Service() {
</View>
</NavigationContainer>
)}
</View>
</SafeAreaView>
);
}
@ -130,13 +130,7 @@ function SetupScreen() {
function AccountScreen({setup}) {
return (
<View style={styles.frame}>
<Accounts />
<IconButton
mode="contained"
icon={'cog'}
size={28}
onPress={setup.openDrawer}
/>
<Accounts setup={setup.openDrawer} />
</View>
);
}

View File

@ -169,6 +169,9 @@ export interface Service {
removeMember(accountId: number): Promise<void>;
getSetup(): Promise<Setup>;
setSetup(setup: Setup): Promise<void>;
enableMFAuth(): Promise<{ image: string, text: string }>;
confirmMFAuth(code: string): Promise<void>;
disableMFAuth(): Promise<void>;
}
export interface Contributor {

View File

@ -225,6 +225,7 @@ export type ProfileEntity = {
};
export type AccountEntity = {
acconutId: number;
guid: string;
handle: string;
name: string;
@ -235,6 +236,7 @@ export type AccountEntity = {
seal?: string;
version: string;
node: string;
storageUsed: string;
};
export const defaultProfileEntity = {

View File

@ -1,6 +1,9 @@
import type { Service } from './api';
import type { Member, Setup } from './types';
import { type AccountEntity, avatar } from './entities';
import type { Logging } from './logging';
import { getMembers } from './net/getMembers';
import { getMemberImageUrl } from './net/getMemberImageUrl';
import { getAdminMFAuth } from './net/getAdminMFAuth';
import { setAdminMFAuth } from './net/setAdminMFAuth';
import { addAdminMFAuth } from './net/addAdminMFAuth';
@ -32,7 +35,13 @@ export class ServiceModule implements Service {
public async removeMember(accountId: number): Promise<void> {}
public async getMembers(): Promise<Member[]> {
return [];
const { node, secure, token } = this;
const accounts = await getMembers(node, secure, token);
return accounts.map(account => {
const { accountId, guid, handle, name, imageSet, revision, disabled, storageUsed } = account;
const imageUrl = imageSet ? getMemberImageUrl(node, secure, token, accountId, revision) : avatar;
return { accountId, guid, handle, name, imageUrl, storageUsed };
});
}
public async getSetup(): Promise<Setup> {
@ -59,18 +68,18 @@ export class ServiceModule implements Service {
public async setSetup(config: Setup): Promise<void> {}
public async enableMFA(): Promise<{ secretImage: string, secretText: string}> {
public async enableMFAuth(): Promise<{ image: string, text: string}> {
const { node, secure, token } = this;
const { secretImage, secretText } = await addAdminMFAuth(node, secure, token);
return { secretImage, secretText };
}
public async disableMFA(): Promise<void> {
public async disableMFAuth(): Promise<void> {
const { node, secure, token } = this;
await removeAdminMFAuth(node, secure, token);
}
public async confirmMFA(code: string): Promise<void> {
public async confirmMFAuth(code: string): Promise<void> {
const { node, secure, token } = this;
await setAdminMFAuth(node, secure, token, code);
}

View File

@ -217,8 +217,9 @@ export type Member = {
name: string;
description: string;
location: string;
imageSet: boolean;
imageUrl: string;
disabled: boolean;
storageUsed: number,
};
export type Setup = {