Switch to Vite + improvements
This commit is contained in:
+5
-2
@@ -13,6 +13,7 @@ import {
|
||||
addChannel,
|
||||
addMessage,
|
||||
addNode,
|
||||
addPosition,
|
||||
addUser,
|
||||
setDeviceStatus,
|
||||
setLastMeshInterraction,
|
||||
@@ -82,9 +83,11 @@ const App = (): JSX.Element => {
|
||||
});
|
||||
|
||||
connection.onUserPacket.subscribe((user) => {
|
||||
console.log('got user packet');
|
||||
dispatch(addUser(user));
|
||||
});
|
||||
|
||||
dispatch(addUser(user.data));
|
||||
connection.onPositionPacket.subscribe((position) => {
|
||||
dispatch(addPosition(position));
|
||||
});
|
||||
|
||||
connection.onNodeInfoPacket.subscribe(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import Avatar from 'boring-avatars';
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const MessageBar = (): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ready = useAppSelector((state) => state.meshtastic.ready);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const [currentMessage, setCurrentMessage] = React.useState('');
|
||||
const [destinationNode, setDestinationNode] =
|
||||
@@ -52,8 +53,11 @@ export const MessageBar = (): JSX.Element => {
|
||||
...nodes
|
||||
.filter((node) => node.num !== myNodeInfo.myNodeNum)
|
||||
.map((node) => {
|
||||
const user = users.filter(
|
||||
(user) => user.packet.from === node.num,
|
||||
)[0]?.data;
|
||||
return {
|
||||
name: node.user?.shortName ?? node.num,
|
||||
name: user ? user.shortName : node.num,
|
||||
value: node.num,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||
|
||||
|
||||
@@ -67,49 +67,3 @@ export const Button = ({
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// import React from 'react';
|
||||
|
||||
// type DefaultButtonProps = JSX.IntrinsicElements['button'];
|
||||
|
||||
// export interface ButtonProps extends DefaultButtonProps {
|
||||
// icon?: JSX.Element;
|
||||
// circle?: boolean;
|
||||
// active?: boolean;
|
||||
// border?: boolean;
|
||||
// confirmAction?: () => void;
|
||||
// rightIcon?: React.ReactNode;
|
||||
// leftIcon?: React.ReactNode;
|
||||
// nested?: boolean;
|
||||
// }
|
||||
|
||||
// export const Button = ({
|
||||
// rightIcon,
|
||||
// leftIcon,
|
||||
// children,
|
||||
// nested,
|
||||
// ...props
|
||||
// }: ButtonProps): JSX.Element => {
|
||||
// return (
|
||||
// <button
|
||||
// className={`select-none flex hover:bg-gray-300 dark:hover:bg-gray-500 rounded-md cursor-pointer active:scale-95 dark:text-white ${
|
||||
// nested
|
||||
// ? 'hover:bg-gray-300 dark:hover:bg-gray-500 dark:bg-gray-600 bg-gray-200'
|
||||
// : 'dark:bg-gray-700 bg-gray-200'
|
||||
// }`}
|
||||
// {...props}
|
||||
// >
|
||||
// {leftIcon && (
|
||||
// <div className="flex py-1 bg-gray-00 rounded-l-md">
|
||||
// <div className="mx-2 my-auto">{leftIcon}</div>
|
||||
// </div>
|
||||
// )}
|
||||
// <div className="flex px-4 py-2 space-x-2 leading-4">{children}</div>
|
||||
// {rightIcon && (
|
||||
// <div className="flex py-2 bg-gray-200 rounded-r-md">
|
||||
// <div className="mx-2 my-auto">{rightIcon}</div>
|
||||
// </div>
|
||||
// )}
|
||||
// </button>
|
||||
// );
|
||||
// };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface CoverProps {
|
||||
content: JSX.Element;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { Blur } from '@components/generic/Blur';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaulButtonProps = JSX.IntrinsicElements['button'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
type DefaultDivProps = JSX.IntrinsicElements['div'];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface StatCardProps {
|
||||
title: string;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
? 'border-red-500'
|
||||
: props.disabled
|
||||
? 'border-gray-200'
|
||||
: ' focus-within:border-primary hover:border-primary'
|
||||
: 'focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary'
|
||||
}`}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface LabelProps {
|
||||
error?: string;
|
||||
@@ -21,7 +21,7 @@ export const InputWrapper = ({
|
||||
? 'border-red-500'
|
||||
: disabled
|
||||
? 'border-gray-200'
|
||||
: ' focus-within:border-primary hover:border-primary'
|
||||
: ' focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface LabelProps {
|
||||
label: string;
|
||||
|
||||
@@ -29,8 +29,8 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
<InputWrapper>
|
||||
<select
|
||||
ref={ref}
|
||||
className={`w-full bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
||||
small ? 'py-1 mx-1' : 'h-10 mx-2'
|
||||
className={`w-full rounded-md bg-white dark:bg-transparent focus:outline-none focus:border-primary ${
|
||||
small ? 'p-1' : 'h-10 px-2'
|
||||
}`}
|
||||
disabled={
|
||||
props.disabled
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export const Logo = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
|
||||
import { Drawer } from '@components/generic/Drawer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
FiGrid,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiWifi, FiWifiOff } from 'react-icons/fi';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiMoon, FiSun } from 'react-icons/fi';
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.ReloadPrompt-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.ReloadPrompt-toast {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #8885;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
text-align: left;
|
||||
box-shadow: 3px 4px 5px 0 #8885;
|
||||
background-color: white;
|
||||
}
|
||||
.ReloadPrompt-toast-message {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.ReloadPrompt-toast-button {
|
||||
border: 1px solid #8885;
|
||||
outline: none;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import './ReloadPrompt.css';
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
import type React from 'react';
|
||||
|
||||
import { useRegisterSW } from 'virtual:pwa-register/react';
|
||||
|
||||
const ReloadPrompt = (): JSX.Element => {
|
||||
const {
|
||||
offlineReady: [offlineReady, setOfflineReady],
|
||||
needRefresh: [needRefresh, setNeedRefresh],
|
||||
updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
onRegistered(r) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
console.log(`SW Registered:`, r);
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.log('SW registration error', error);
|
||||
},
|
||||
});
|
||||
|
||||
const close = (): void => {
|
||||
setOfflineReady(false);
|
||||
setNeedRefresh(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ReloadPrompt-container">
|
||||
{(offlineReady || needRefresh) && (
|
||||
<div className="ReloadPrompt-toast">
|
||||
<div className="ReloadPrompt-message">
|
||||
{offlineReady ? (
|
||||
<span>App ready to work offline</span>
|
||||
) : (
|
||||
<span>
|
||||
New content available, click on reload button to update.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{needRefresh && (
|
||||
<button
|
||||
className="ReloadPrompt-toast-button"
|
||||
onClick={(): Promise<void> => updateServiceWorker(true)}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="ReloadPrompt-toast-button"
|
||||
onClick={(): void => close()}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReloadPrompt;
|
||||
@@ -5,8 +5,7 @@ import { FiXCircle } from 'react-icons/fi';
|
||||
import { useBreakpoint } from '@app/hooks/breakpoint';
|
||||
import { Drawer } from '@components/generic/Drawer';
|
||||
import { IconButton } from '@components/generic/IconButton';
|
||||
import type { SidebarItemProps } from '@components/generic/SidebarItem';
|
||||
import { SidebarItem } from '@components/generic/SidebarItem';
|
||||
import { SidebarItem, SidebarItemProps } from '@components/generic/SidebarItem';
|
||||
import { Tab } from '@headlessui/react';
|
||||
|
||||
export interface PageLayoutProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
export interface PrimaryTemplateProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -22,8 +22,7 @@ interface MeshtasticState {
|
||||
ready: boolean;
|
||||
myNodeInfo: Protobuf.MyNodeInfo;
|
||||
myNode: Protobuf.NodeInfo;
|
||||
users: Protobuf.User[];
|
||||
myUser: Protobuf.User;
|
||||
users: Types.UserPacket[];
|
||||
positionPackets: Types.PositionPacket[];
|
||||
nodes: Protobuf.NodeInfo[];
|
||||
channels: Protobuf.Channel[];
|
||||
@@ -41,7 +40,6 @@ const initialState: MeshtasticState = {
|
||||
myNodeInfo: Protobuf.MyNodeInfo.create(),
|
||||
myNode: Protobuf.NodeInfo.create(),
|
||||
users: [],
|
||||
myUser: Protobuf.User.create(),
|
||||
positionPackets: [],
|
||||
nodes: [],
|
||||
channels: [],
|
||||
@@ -69,21 +67,22 @@ export const meshtasticSlice = createSlice({
|
||||
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
|
||||
state.myNodeInfo = action.payload;
|
||||
},
|
||||
addUser: (state, action: PayloadAction<Protobuf.User>) => {
|
||||
if (action.payload.id === state.myNode.user?.id) {
|
||||
state.myUser = action.payload;
|
||||
}
|
||||
addUser: (state, action: PayloadAction<Types.UserPacket>) => {
|
||||
if (
|
||||
state.users.findIndex((user) => user.id === action.payload.id) !== -1
|
||||
state.users.findIndex(
|
||||
(user) => user.data.id === action.payload.data.id,
|
||||
) !== -1
|
||||
) {
|
||||
state.users = state.users.map((user) => {
|
||||
return user.id === action.payload.id ? action.payload : user;
|
||||
return user.data.id === action.payload.data.id
|
||||
? action.payload
|
||||
: user;
|
||||
});
|
||||
} else {
|
||||
state.users.push(action.payload);
|
||||
}
|
||||
},
|
||||
addPositionPacket: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
addPosition: (state, action: PayloadAction<Types.PositionPacket>) => {
|
||||
state.positionPackets.push(action.payload);
|
||||
},
|
||||
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
|
||||
@@ -161,7 +160,7 @@ export const {
|
||||
setReady,
|
||||
setMyNodeInfo,
|
||||
addUser,
|
||||
addPositionPacket,
|
||||
addPosition,
|
||||
addNode,
|
||||
addChannel,
|
||||
setPreferences,
|
||||
|
||||
+2
-6
@@ -10,20 +10,16 @@ import { RouteProvider } from '@core/router';
|
||||
import { store } from '@core/store';
|
||||
|
||||
import App from './App';
|
||||
import ReloadPrompt from './components/pwa/ReloadPrompt';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<RouteProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<ReloadPrompt />
|
||||
</Provider>
|
||||
</RouteProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
|
||||
// Learn more: https://www.snowpack.dev/#hot-module-replacement
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiHash } from 'react-icons/fi';
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAppSelector } from '../hooks/redux';
|
||||
export const Messages = (): JSX.Element => {
|
||||
const messages = useAppSelector((state) => state.meshtastic.messages);
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
const channels = useAppSelector((state) => state.meshtastic.channels);
|
||||
|
||||
return (
|
||||
@@ -49,8 +50,9 @@ export const Messages = (): JSX.Element => {
|
||||
ack={message.ack}
|
||||
rxTime={new Date()}
|
||||
senderName={
|
||||
nodes.find((node) => node.num === message.message.packet.from)
|
||||
?.user?.longName ?? 'UNK'
|
||||
users.find(
|
||||
(user) => user.packet.from === message.message.packet.from,
|
||||
)?.data.longName ?? 'UNK'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import Avatar from 'boring-avatars';
|
||||
|
||||
@@ -10,21 +10,22 @@ import { Node } from './Node';
|
||||
|
||||
export const Nodes = (): JSX.Element => {
|
||||
const nodes = useAppSelector((state) => state.meshtastic.nodes);
|
||||
|
||||
const users = useAppSelector((state) => state.meshtastic.users);
|
||||
return (
|
||||
<PageLayout
|
||||
title="Nodes"
|
||||
emptyMessage="No nodes discovered yet..."
|
||||
sidebarItems={nodes.map((node) => {
|
||||
const user = users.find((user) => user.packet.from === node.num)?.data;
|
||||
return {
|
||||
title: node.user?.longName ?? node.num.toString(),
|
||||
description: node.user?.hwModel
|
||||
? Protobuf.HardwareModel[node.user.hwModel]
|
||||
title: user ? user.longName : node.num.toString(),
|
||||
description: user
|
||||
? Protobuf.HardwareModel[user.hwModel]
|
||||
: 'Unknown Hardware',
|
||||
icon: (
|
||||
<Avatar
|
||||
size={30}
|
||||
name={node.user?.longName ?? node.num.toString()}
|
||||
name={user ? user.longName : node.num.toString()}
|
||||
variant="beam"
|
||||
colors={['#213435', '#46685B', '#648A64', '#A6B985', '#E1E3AC']}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'react-json-pretty/themes/acai.css';
|
||||
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiMenu, FiTerminal } from 'react-icons/fi';
|
||||
import JSONPretty from 'react-json-pretty';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import { useAppSelector } from '@app/hooks/redux';
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { Checkbox } from '@components/generic/form/Checkbox';
|
||||
import { Input } from '@components/generic/form/Input';
|
||||
@@ -21,9 +22,15 @@ export interface NodeProps {
|
||||
}
|
||||
|
||||
export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||
(user) => user.packet.from === node.num,
|
||||
)?.data;
|
||||
const position = useAppSelector(
|
||||
(state) => state.meshtastic.positionPackets,
|
||||
).find((position) => position.packet.from === node.num)?.data;
|
||||
return (
|
||||
<PrimaryTemplate
|
||||
title={node.user?.longName ?? node.num.toString()}
|
||||
title={user ? user.longName : node.num.toString()}
|
||||
tagline="Node"
|
||||
button={
|
||||
<IconButton
|
||||
@@ -47,7 +54,7 @@ export const Node = ({ navOpen, setNavOpen, node }: NodeProps): JSX.Element => {
|
||||
description={new Date(node.lastHeard * 1000).toLocaleString()}
|
||||
>
|
||||
<div className="p-10">
|
||||
<JSONPretty data={node.position} />
|
||||
<JSONPretty data={position} />
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { Card } from '@components/generic/Card';
|
||||
import { PrimaryTemplate } from '@components/templates/PrimaryTemplate';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
// import { DefaultExtensionType, defaultStyles, FileIcon } from 'react-file-icon';
|
||||
import { FiMenu, FiTrash, FiUploadCloud } from 'react-icons/fi';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { FiFileText, FiRss } from 'react-icons/fi';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -27,7 +27,10 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const [debug, setDebug] = React.useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const user = useAppSelector((state) => state.meshtastic.myUser);
|
||||
const myNodeInfo = useAppSelector((state) => state.meshtastic.myNodeInfo);
|
||||
const user = useAppSelector((state) => state.meshtastic.users).find(
|
||||
(user) => user.packet.from === myNodeInfo.myNodeNum,
|
||||
);
|
||||
const { register, handleSubmit, formState } = useForm<{
|
||||
longName: string;
|
||||
shortName: string;
|
||||
@@ -35,17 +38,19 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
team: Protobuf.Team;
|
||||
}>({
|
||||
defaultValues: {
|
||||
longName: user.longName,
|
||||
shortName: user.shortName,
|
||||
isLicensed: user.isLicensed,
|
||||
team: user.team,
|
||||
longName: user?.data.longName,
|
||||
shortName: user?.data.shortName,
|
||||
isLicensed: user?.data.isLicensed,
|
||||
team: user?.data.team,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
void connection.setOwner({ ...user, ...data });
|
||||
// TODO: can be remove once getUser is implemented
|
||||
dispatch(addUser({ ...user, ...data }));
|
||||
if (user) {
|
||||
void connection.setOwner({ ...user.data, ...data });
|
||||
dispatch(addUser({ ...user, ...{ data: { ...user.data, ...data } } }));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -92,10 +97,14 @@ export const Device = ({ navOpen, setNavOpen }: DeviceProps): JSX.Element => {
|
||||
<Cover enabled={debug} content={<JSONPretty data={user} />} />
|
||||
<div className="w-full max-w-3xl p-10 md:max-w-xl">
|
||||
<form className="space-y-2" onSubmit={onSubmit}>
|
||||
<Input label={'Device ID'} value={user.id} disabled />
|
||||
<Input label={'Device ID'} value={user?.data.id} disabled />
|
||||
<Input
|
||||
label={'Hardware'}
|
||||
value={Protobuf.HardwareModel[user.hwModel]}
|
||||
value={
|
||||
Protobuf.HardwareModel[
|
||||
user?.data.hwModel ?? Protobuf.HardwareModel.UNSET
|
||||
]
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
<Input label={'Device Name'} {...register('longName')} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import {
|
||||
FiLayers,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import type React from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FiMenu, FiSave } from 'react-icons/fi';
|
||||
|
||||
Reference in New Issue
Block a user