fix: resolve lint warnings/errors and apply formatting (#1024)
* fix: resolve lint warnings/errors and apply formatting Fix 10 oxlint issues (2 errors, 8 warnings): - Remove unused catch parameters in Security.tsx and ImportDialog.tsx - Remove stray expression in Generator.tsx - Add eslint-disable for debounced useCallback in FilterControl.tsx - Remove unnecessary deps (resolveDB, store) in bindStoreToDevice.ts - Prefix unused variant param in AppSidebar.tsx - Memoize tabs arrays in DeviceConfig, RadioConfig, ModuleConfig - Fix channels type in RadioConfig TabItem Also applies oxfmt formatting across all files. * updating lock file * update protobuf package * fix: regenerate pnpm-lock.yaml and exclude jsr protobufs from minimumReleaseAge * prevented http card from always being polled * updated pnpm config fiile * updated actions * removed biome config and lint system leftovers * updating protobuf package
This commit is contained in:
@@ -1,19 +1,11 @@
|
||||
import { SupportBadge } from "@app/components/Badge/SupportedBadge.tsx";
|
||||
import { Switch } from "@app/components/UI/Switch.tsx";
|
||||
import type {
|
||||
ConnectionType,
|
||||
NewConnection,
|
||||
} from "@app/core/stores/deviceStore/types.ts";
|
||||
import type { ConnectionType, NewConnection } from "@app/core/stores/deviceStore/types.ts";
|
||||
import { testHttpReachable } from "@app/pages/Connections/utils";
|
||||
import { Button } from "@components/UI/Button.tsx";
|
||||
import { Input } from "@components/UI/Input.tsx";
|
||||
import { Label } from "@components/UI/Label.tsx";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@components/UI/Tabs.tsx";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/UI/Tabs.tsx";
|
||||
import { Link } from "@components/UI/Typography/Link.tsx";
|
||||
import {
|
||||
type BrowserFeature,
|
||||
@@ -45,9 +37,7 @@ type DialogState = {
|
||||
protocol: "http" | "https";
|
||||
url: string;
|
||||
testStatus: TestingStatus;
|
||||
btSelected:
|
||||
| { id: string; name?: string; device?: BluetoothDevice }
|
||||
| undefined;
|
||||
btSelected: { id: string; name?: string; device?: BluetoothDevice } | undefined;
|
||||
serialSelected: { vendorId?: number; productId?: number } | undefined;
|
||||
};
|
||||
|
||||
@@ -60,9 +50,7 @@ type DialogAction =
|
||||
| { type: "SET_TEST_STATUS"; payload: TestingStatus }
|
||||
| {
|
||||
type: "SET_BT_SELECTED";
|
||||
payload:
|
||||
| { id: string; name?: string; device?: BluetoothDevice }
|
||||
| undefined;
|
||||
payload: { id: string; name?: string; device?: BluetoothDevice } | undefined;
|
||||
}
|
||||
| {
|
||||
type: "SET_SERIAL_SELECTED";
|
||||
@@ -82,30 +70,27 @@ type Pane = {
|
||||
build: () => NewConnection | null;
|
||||
};
|
||||
|
||||
const featureErrors: Record<BrowserFeature, { href: string; i18nKey: string }> =
|
||||
{
|
||||
"Web Bluetooth": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility",
|
||||
i18nKey: "addConnection.validation.requiresWebBluetooth",
|
||||
},
|
||||
"Web Serial": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility",
|
||||
i18nKey: "addConnection.validation.requiresWebSerial",
|
||||
},
|
||||
"Secure Context": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts",
|
||||
i18nKey: "addConnection.validation.requiresSecureContext",
|
||||
},
|
||||
};
|
||||
const featureErrors: Record<BrowserFeature, { href: string; i18nKey: string }> = {
|
||||
"Web Bluetooth": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility",
|
||||
i18nKey: "addConnection.validation.requiresWebBluetooth",
|
||||
},
|
||||
"Web Serial": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility",
|
||||
i18nKey: "addConnection.validation.requiresWebSerial",
|
||||
},
|
||||
"Secure Context": {
|
||||
href: "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts",
|
||||
i18nKey: "addConnection.validation.requiresSecureContext",
|
||||
},
|
||||
};
|
||||
|
||||
const FeatureErrorMessage = ({ missingFeatures, tabId }: FeatureErrorProps) => {
|
||||
if (missingFeatures.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const browserFeatures = missingFeatures.filter(
|
||||
(feature) => feature !== "Secure Context",
|
||||
);
|
||||
const browserFeatures = missingFeatures.filter((feature) => feature !== "Secure Context");
|
||||
const needsSecureContext = missingFeatures.includes("Secure Context");
|
||||
|
||||
const needsFeature =
|
||||
@@ -167,22 +152,17 @@ const initialState: DialogState = {
|
||||
btSelected: undefined,
|
||||
serialSelected: undefined,
|
||||
};
|
||||
export const createInitialDialogState = (
|
||||
overrides?: Partial<DialogState>,
|
||||
): DialogState => {
|
||||
export const createInitialDialogState = (overrides?: Partial<DialogState>): DialogState => {
|
||||
return { ...initialState, ...(overrides ?? {}) };
|
||||
};
|
||||
|
||||
export const dialogStateInitializer = (
|
||||
overrides?: Partial<DialogState>,
|
||||
): DialogState => createInitialDialogState(overrides);
|
||||
export const dialogStateInitializer = (overrides?: Partial<DialogState>): DialogState =>
|
||||
createInitialDialogState(overrides);
|
||||
|
||||
function dialogReducer(state: DialogState, action: DialogAction): DialogState {
|
||||
switch (action.type) {
|
||||
case "RESET":
|
||||
return createInitialDialogState(
|
||||
action.payload?.isHTTPS ? { protocol: "https" } : {},
|
||||
);
|
||||
return createInitialDialogState(action.payload?.isHTTPS ? { protocol: "https" } : {});
|
||||
case "SET_TAB":
|
||||
return { ...state, tab: action.payload };
|
||||
case "SET_NAME":
|
||||
@@ -223,22 +203,13 @@ function PickerRow({
|
||||
<div className="grid gap-2">
|
||||
<Label>{label}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="subtle"
|
||||
className="gap-2"
|
||||
onClick={onPick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button variant="subtle" className="gap-2" onClick={onPick} disabled={disabled}>
|
||||
<MousePointerClick className="h-4 w-4" />
|
||||
{buttonText}
|
||||
</Button>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400 truncate">
|
||||
{display}
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400 truncate">{display}</div>
|
||||
</div>
|
||||
{helper ? (
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">{helper}</p>
|
||||
) : null}
|
||||
{helper ? <p className="text-xs text-slate-500 dark:text-slate-400">{helper}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -267,10 +238,8 @@ export default function AddConnectionDialog({
|
||||
const { unsupported } = useBrowserFeatureDetection();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const bluetoothSupported =
|
||||
typeof navigator !== "undefined" && "bluetooth" in navigator;
|
||||
const serialSupported =
|
||||
typeof navigator !== "undefined" && "serial" in navigator;
|
||||
const bluetoothSupported = typeof navigator !== "undefined" && "bluetooth" in navigator;
|
||||
const serialSupported = typeof navigator !== "undefined" && "serial" in navigator;
|
||||
const isURLHTTPS = isHTTPS;
|
||||
|
||||
const reset = useCallback(() => {
|
||||
@@ -299,9 +268,7 @@ export default function AddConnectionDialog({
|
||||
if (!bluetoothSupported) {
|
||||
toast({
|
||||
title: t("addConnection.bluetoothConnection.notSupported.title"),
|
||||
description: t(
|
||||
"addConnection.bluetoothConnection.notSupported.description",
|
||||
),
|
||||
description: t("addConnection.bluetoothConnection.notSupported.description"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -336,9 +303,7 @@ export default function AddConnectionDialog({
|
||||
if (!serialSupported) {
|
||||
toast({
|
||||
title: t("addConnection.serialConnection.notSupported.title"),
|
||||
description: t(
|
||||
"addConnection.serialConnection.notSupported.description",
|
||||
),
|
||||
description: t("addConnection.serialConnection.notSupported.description"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -370,9 +335,7 @@ export default function AddConnectionDialog({
|
||||
}
|
||||
toast({
|
||||
title: t("addConnection.serialConnection.portSelected.title"),
|
||||
description: t(
|
||||
"addConnection.serialConnection.portSelected.description",
|
||||
),
|
||||
description: t("addConnection.serialConnection.portSelected.description"),
|
||||
});
|
||||
} catch (err) {
|
||||
makeToastErrorHandler("Serial")(err);
|
||||
@@ -397,9 +360,7 @@ export default function AddConnectionDialog({
|
||||
dispatch({ type: "SET_TEST_STATUS", payload: "failure" });
|
||||
toast({
|
||||
title: t("addConnection.httpConnection.connectionTest.failure.title"),
|
||||
description: t(
|
||||
"addConnection.httpConnection.connectionTest.failure.description",
|
||||
),
|
||||
description: t("addConnection.httpConnection.connectionTest.failure.description"),
|
||||
});
|
||||
}
|
||||
}, [state.protocol, state.url, toast, t]);
|
||||
@@ -410,9 +371,7 @@ export default function AddConnectionDialog({
|
||||
placeholder: t("addConnection.httpConnection.namePlaceholder"),
|
||||
children: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Label htmlFor="url">
|
||||
{t("addConnection.httpConnection.heading")}
|
||||
</Label>
|
||||
<Label htmlFor="url">{t("addConnection.httpConnection.heading")}</Label>
|
||||
|
||||
<Input
|
||||
id={"url"}
|
||||
@@ -448,23 +407,19 @@ export default function AddConnectionDialog({
|
||||
className="gap-2"
|
||||
onClick={handleTestHttp}
|
||||
disabled={
|
||||
urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`)
|
||||
.success === false || state.testStatus === "testing"
|
||||
urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`).success === false ||
|
||||
state.testStatus === "testing"
|
||||
}
|
||||
>
|
||||
{state.testStatus === "testing" ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t(
|
||||
"addConnection.httpConnection.connectionTest.button.loading",
|
||||
)}
|
||||
{t("addConnection.httpConnection.connectionTest.button.loading")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MousePointerClick className="h-4 w-4" />
|
||||
{t(
|
||||
"addConnection.httpConnection.connectionTest.button.label",
|
||||
)}
|
||||
{t("addConnection.httpConnection.connectionTest.button.label")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -477,9 +432,7 @@ export default function AddConnectionDialog({
|
||||
{state.testStatus === "failure" && (
|
||||
<div className="flex items-center gap-1 text-sm text-red-600 dark:text-red-400">
|
||||
<XCircle className="h-4 w-4" />
|
||||
{t(
|
||||
"addConnection.httpConnection.connectionTest.notReachable",
|
||||
)}
|
||||
{t("addConnection.httpConnection.connectionTest.notReachable")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -489,8 +442,8 @@ export default function AddConnectionDialog({
|
||||
</div>
|
||||
),
|
||||
validate: () =>
|
||||
urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`)
|
||||
.success === true && state.testStatus === "success",
|
||||
urlOrIpv4Schema.safeParse(`${state.protocol}://${state.url}`).success === true &&
|
||||
state.testStatus === "success",
|
||||
build: () => ({
|
||||
type: "http",
|
||||
name: state.name.trim(),
|
||||
@@ -503,12 +456,8 @@ export default function AddConnectionDialog({
|
||||
<>
|
||||
<SupportBadge
|
||||
supported={bluetoothSupported}
|
||||
labelSupported={t(
|
||||
"addConnection.bluetoothConnection.supported.title",
|
||||
)}
|
||||
labelUnsupported={t(
|
||||
"addConnection.bluetoothConnection.notSupported.title",
|
||||
)}
|
||||
labelSupported={t("addConnection.bluetoothConnection.supported.title")}
|
||||
labelUnsupported={t("addConnection.bluetoothConnection.notSupported.title")}
|
||||
/>
|
||||
<PickerRow
|
||||
label={t("addConnection.bluetoothConnection.device")}
|
||||
@@ -522,10 +471,7 @@ export default function AddConnectionDialog({
|
||||
}
|
||||
helper={t("addConnection.bluetoothConnection.helperText")}
|
||||
/>
|
||||
<FeatureErrorMessage
|
||||
missingFeatures={unsupported}
|
||||
tabId="bluetooth"
|
||||
/>
|
||||
<FeatureErrorMessage missingFeatures={unsupported} tabId="bluetooth" />
|
||||
</>
|
||||
),
|
||||
validate: () => state.name.trim().length > 0 && !!state.btSelected,
|
||||
@@ -543,12 +489,8 @@ export default function AddConnectionDialog({
|
||||
<>
|
||||
<SupportBadge
|
||||
supported={serialSupported}
|
||||
labelSupported={t(
|
||||
"addConnection.serialConnection.supported.title",
|
||||
)}
|
||||
labelUnsupported={t(
|
||||
"addConnection.serialConnection.notSupported.title",
|
||||
)}
|
||||
labelSupported={t("addConnection.serialConnection.supported.title")}
|
||||
labelUnsupported={t("addConnection.serialConnection.notSupported.title")}
|
||||
/>
|
||||
<PickerRow
|
||||
label={t("addConnection.serialConnection.port")}
|
||||
@@ -558,10 +500,8 @@ export default function AddConnectionDialog({
|
||||
display={
|
||||
state.serialSelected
|
||||
? t("addConnection.serialConnection.deviceName", {
|
||||
vendorId:
|
||||
state.serialSelected.vendorId?.toString(16) ?? "?",
|
||||
productId:
|
||||
state.serialSelected.productId?.toString(16) ?? "?",
|
||||
vendorId: state.serialSelected.vendorId?.toString(16) ?? "?",
|
||||
productId: state.serialSelected.productId?.toString(16) ?? "?",
|
||||
})
|
||||
: t("addConnection.serialConnection.notSelected")
|
||||
}
|
||||
@@ -571,8 +511,7 @@ export default function AddConnectionDialog({
|
||||
</>
|
||||
),
|
||||
validate: () =>
|
||||
state.name.trim().length > 0 &&
|
||||
(!!state.serialSelected || !serialSupported),
|
||||
state.name.trim().length > 0 && (!!state.serialSelected || !serialSupported),
|
||||
build: () => ({
|
||||
type: "serial",
|
||||
name: state.name.trim(),
|
||||
@@ -598,8 +537,7 @@ export default function AddConnectionDialog({
|
||||
const canCreate = useMemo(() => currentPane.validate(), [currentPane]);
|
||||
|
||||
const submit =
|
||||
(fn: (p: NewConnection, device?: BluetoothDevice) => Promise<void>) =>
|
||||
async () => {
|
||||
(fn: (p: NewConnection, device?: BluetoothDevice) => Promise<void>) => async () => {
|
||||
if (!canCreate) {
|
||||
return;
|
||||
}
|
||||
@@ -608,8 +546,7 @@ export default function AddConnectionDialog({
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
const btDevice =
|
||||
state.tab === "bluetooth" ? state.btSelected?.device : undefined;
|
||||
const btDevice = state.tab === "bluetooth" ? state.btSelected?.device : undefined;
|
||||
await fn(payload, btDevice);
|
||||
};
|
||||
|
||||
@@ -625,9 +562,7 @@ export default function AddConnectionDialog({
|
||||
>
|
||||
<Tabs
|
||||
value={state.tab}
|
||||
onValueChange={(v) =>
|
||||
dispatch({ type: "SET_TAB", payload: v as TabKey })
|
||||
}
|
||||
onValueChange={(v) => dispatch({ type: "SET_TAB", payload: v as TabKey })}
|
||||
>
|
||||
<TabsList className="grid grid-cols-3">
|
||||
{TAB_META.map(({ key, label, Icon }) => (
|
||||
@@ -647,20 +582,14 @@ export default function AddConnectionDialog({
|
||||
<Input
|
||||
id={`name-${state.tab}`}
|
||||
value={state.name}
|
||||
onChange={(evt) =>
|
||||
dispatch({ type: "SET_NAME", payload: evt.target.value })
|
||||
}
|
||||
onChange={(evt) => dispatch({ type: "SET_NAME", payload: evt.target.value })}
|
||||
placeholder={currentPane.placeholder}
|
||||
/>
|
||||
</div>
|
||||
{PANES[key].children()}
|
||||
<div className="flex justify-end">
|
||||
<div className="inline-flex rounded-md shadow-sm overflow-hidden border">
|
||||
<Button
|
||||
onClick={submit(onSave)}
|
||||
disabled={!canCreate}
|
||||
className="rounded-none"
|
||||
>
|
||||
<Button onClick={submit(onSave)} disabled={!canCreate} className="rounded-none">
|
||||
{t("button.saveConnection")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -28,18 +28,13 @@ export const urlOrIpv4Schema = z
|
||||
}
|
||||
|
||||
// IPv4 pattern
|
||||
const ipv4Regex =
|
||||
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
||||
const ipv4Regex = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
||||
// Domain pattern (e.g. example.com, meshtastic.local)
|
||||
const domainRegex = /^(?!-)(?:[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,}$/;
|
||||
// Local domain (e.g. meshtastic.local)
|
||||
const localDomainRegex = /^(?!-)[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.local$/;
|
||||
|
||||
return (
|
||||
ipv4Regex.test(host) ||
|
||||
domainRegex.test(host) ||
|
||||
localDomainRegex.test(host)
|
||||
);
|
||||
return ipv4Regex.test(host) || domainRegex.test(host) || localDomainRegex.test(host);
|
||||
}, "Must be a valid IPv4 address or domain name with optional port (10-65535)")
|
||||
.transform((val) => {
|
||||
return /^https?:\/\//i.test(val) ? val : `http://${val}`;
|
||||
|
||||
+1
-3
@@ -47,9 +47,7 @@ describe("ClearAllStoresDialog", () => {
|
||||
|
||||
it("calls clearAllStores and navigates to '/' when confirm is clicked", () => {
|
||||
render(<ClearAllStoresDialog open onOpenChange={mockOnOpenChange} />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Clear all local storage" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Clear all local storage" }));
|
||||
|
||||
expect(mockClearAllStores).toHaveBeenCalledTimes(1);
|
||||
expect(assignedHref).toBe("/"); // forced reload target
|
||||
|
||||
@@ -7,10 +7,7 @@ export interface ClearAllStoresDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const ClearAllStoresDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ClearAllStoresDialogProps) => {
|
||||
export const ClearAllStoresDialog = ({ open, onOpenChange }: ClearAllStoresDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
|
||||
const handleClearAllStores = () => {
|
||||
|
||||
+1
-4
@@ -14,10 +14,7 @@ export interface ClientNotificationDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const ClientNotificationDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ClientNotificationDialogProps) => {
|
||||
export const ClientNotificationDialog = ({ open, onOpenChange }: ClientNotificationDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { getClientNotification, removeClientNotification } = useDevice();
|
||||
|
||||
|
||||
+3
-9
@@ -46,19 +46,13 @@ describe("DeleteMessagesDialog", () => {
|
||||
it("renders the dialog when open is true", () => {
|
||||
render(<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />);
|
||||
expect(screen.getByText("Clear All Messages")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/This action will clear all message history./),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/This action will clear all message history./)).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Clear Messages" }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Clear Messages" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render the dialog when open is false", () => {
|
||||
render(
|
||||
<DeleteMessagesDialog open={false} onOpenChange={mockOnOpenChange} />,
|
||||
);
|
||||
render(<DeleteMessagesDialog open={false} onOpenChange={mockOnOpenChange} />);
|
||||
expect(screen.queryByText("Clear All Messages")).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ export interface DeleteMessagesDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const DeleteMessagesDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: DeleteMessagesDialogProps) => {
|
||||
export const DeleteMessagesDialog = ({ open, onOpenChange }: DeleteMessagesDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const messageStore = useMessages();
|
||||
|
||||
|
||||
+1
-3
@@ -33,9 +33,7 @@ describe("FactoryResetConfigDialog", () => {
|
||||
it("calls factoryResetConfig and then closes the dialog on confirm", async () => {
|
||||
render(<FactoryResetConfigDialog open onOpenChange={mockOnOpenChange} />);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Factory Reset Config" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Factory Reset Config" }));
|
||||
|
||||
expect(mockFactoryReset).toHaveBeenCalledTimes(1);
|
||||
|
||||
|
||||
+1
-4
@@ -8,10 +8,7 @@ export interface FactoryResetConfigDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const FactoryResetConfigDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: FactoryResetConfigDialogProps) => {
|
||||
export const FactoryResetConfigDialog = ({ open, onOpenChange }: FactoryResetConfigDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection } = useDevice();
|
||||
|
||||
|
||||
+1
-3
@@ -61,9 +61,7 @@ describe("FactoryResetDeviceDialog", () => {
|
||||
);
|
||||
|
||||
render(<FactoryResetDeviceDialog open onOpenChange={mockOnOpenChange} />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Factory Reset Device" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Factory Reset Device" }));
|
||||
|
||||
// Called immediately
|
||||
expect(mockFactoryResetDevice).toHaveBeenCalledTimes(1);
|
||||
|
||||
+2
-10
@@ -1,10 +1,5 @@
|
||||
import { toast } from "@core/hooks/useToast.ts";
|
||||
import {
|
||||
useDevice,
|
||||
useDeviceStore,
|
||||
useMessageStore,
|
||||
useNodeDBStore,
|
||||
} from "@core/stores";
|
||||
import { useDevice, useDeviceStore, useMessageStore, useNodeDBStore } from "@core/stores";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DialogWrapper } from "../DialogWrapper.tsx";
|
||||
|
||||
@@ -13,10 +8,7 @@ export interface FactoryResetDeviceDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const FactoryResetDeviceDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: FactoryResetDeviceDialogProps) => {
|
||||
export const FactoryResetDeviceDialog = ({ open, onOpenChange }: FactoryResetDeviceDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection, id } = useDevice();
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
try {
|
||||
const channelsUrl = new URL(importDialogInput);
|
||||
if (
|
||||
(channelsUrl.hostname !== "meshtastic.org" &&
|
||||
channelsUrl.pathname !== "/e/") ||
|
||||
(channelsUrl.hostname !== "meshtastic.org" && channelsUrl.pathname !== "/e/") ||
|
||||
!channelsUrl.hash
|
||||
) {
|
||||
throw t("import.error.invalidUrl");
|
||||
@@ -56,11 +55,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
|
||||
const encodedChannelConfig = channelsUrl.hash.substring(1);
|
||||
const paddedString = encodedChannelConfig
|
||||
.padEnd(
|
||||
encodedChannelConfig.length +
|
||||
((4 - (encodedChannelConfig.length % 4)) % 4),
|
||||
"=",
|
||||
)
|
||||
.padEnd(encodedChannelConfig.length + ((4 - (encodedChannelConfig.length % 4)) % 4), "=")
|
||||
.replace(/-/g, "+")
|
||||
.replace(/_/g, "/");
|
||||
|
||||
@@ -75,43 +70,35 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
setImportIndex(newImportChannelArray);
|
||||
setUpdateConfig(newChannelSet?.loraConfig !== undefined);
|
||||
setValidUrl(true);
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
setValidUrl(false);
|
||||
setChannelSet(undefined);
|
||||
}
|
||||
}, [importDialogInput, t]);
|
||||
|
||||
const apply = () => {
|
||||
channelSet?.settings.forEach(
|
||||
(ch: Protobuf.Channel.ChannelSettings, index: number) => {
|
||||
if (importIndex[index] === -1) {
|
||||
return;
|
||||
}
|
||||
channelSet?.settings.forEach((ch: Protobuf.Channel.ChannelSettings, index: number) => {
|
||||
if (importIndex[index] === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = create(Protobuf.Channel.ChannelSchema, {
|
||||
index: importIndex[index],
|
||||
role:
|
||||
importIndex[index] === 0
|
||||
? Protobuf.Channel.Channel_Role.PRIMARY
|
||||
: Protobuf.Channel.Channel_Role.SECONDARY,
|
||||
settings: ch,
|
||||
});
|
||||
const payload = create(Protobuf.Channel.ChannelSchema, {
|
||||
index: importIndex[index],
|
||||
role:
|
||||
importIndex[index] === 0
|
||||
? Protobuf.Channel.Channel_Role.PRIMARY
|
||||
: Protobuf.Channel.Channel_Role.SECONDARY,
|
||||
settings: ch,
|
||||
});
|
||||
|
||||
if (
|
||||
!deepCompareConfig(
|
||||
channels.get(importIndex[index] ?? 0),
|
||||
payload,
|
||||
true,
|
||||
)
|
||||
) {
|
||||
setChange(
|
||||
{ type: "channel", index: importIndex[index] ?? 0 },
|
||||
payload,
|
||||
channels.get(importIndex[index] ?? 0),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!deepCompareConfig(channels.get(importIndex[index] ?? 0), payload, true)) {
|
||||
setChange(
|
||||
{ type: "channel", index: importIndex[index] ?? 0 },
|
||||
payload,
|
||||
channels.get(importIndex[index] ?? 0),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (channelSet?.loraConfig && updateConfig) {
|
||||
const payload = {
|
||||
@@ -146,23 +133,14 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("import.title")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans
|
||||
i18nKey={"import.description"}
|
||||
components={{ italic: <i />, br: <br /> }}
|
||||
/>
|
||||
<Trans i18nKey={"import.description"} components={{ italic: <i />, br: <br /> }} />
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>{t("import.channelSetUrl")}</Label>
|
||||
<Input
|
||||
value={importDialogInput}
|
||||
variant={
|
||||
importDialogInput === ""
|
||||
? "default"
|
||||
: validUrl
|
||||
? "dirty"
|
||||
: "invalid"
|
||||
}
|
||||
variant={importDialogInput === "" ? "default" : validUrl ? "dirty" : "invalid"}
|
||||
onChange={(e) => {
|
||||
setImportDialogInput(e.target.value);
|
||||
}}
|
||||
@@ -191,10 +169,7 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
<span className="flex-1">{t("import.channelSlot")}</span>
|
||||
</div>
|
||||
{channelSet?.settings.map((channel, index) => (
|
||||
<div
|
||||
className="flex items-center"
|
||||
key={`channel_${channel.id}_${index}`}
|
||||
>
|
||||
<div className="flex items-center" key={`channel_${channel.id}_${index}`}>
|
||||
<Label className="flex-1">
|
||||
{channel.name.length
|
||||
? channel.name
|
||||
@@ -214,14 +189,10 @@ export const ImportDialog = ({ open, onOpenChange }: ImportDialogProps) => {
|
||||
disabled={importIndex.includes(i) && index !== i}
|
||||
value={i.toString()}
|
||||
>
|
||||
{i === 0
|
||||
? t("import.primary")
|
||||
: `${t("import.channelPrefix")}${i}`}
|
||||
{i === 0 ? t("import.primary") : `${t("import.channelPrefix")}${i}`}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value="-1">
|
||||
{t("import.doNotImport")}
|
||||
</SelectItem>
|
||||
<SelectItem value="-1">{t("import.doNotImport")}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -27,13 +27,10 @@ export const LocationResponseDialog = ({
|
||||
|
||||
const from = getNode(location?.from ?? 0);
|
||||
const longName =
|
||||
from?.user?.longName ??
|
||||
(from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName"));
|
||||
from?.user?.longName ?? (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName"));
|
||||
const shortName =
|
||||
from?.user?.shortName ??
|
||||
(from
|
||||
? `${numberToHexUnpadded(from?.num).substring(0, 4)}`
|
||||
: t("unknown.shortName"));
|
||||
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : t("unknown.shortName"));
|
||||
|
||||
const position = location?.data;
|
||||
|
||||
@@ -70,23 +67,18 @@ export const LocationResponseDialog = ({
|
||||
rel="noreferrer"
|
||||
>
|
||||
{" "}
|
||||
{position.latitudeI ?? 0 / 1e7},{" "}
|
||||
{position.longitudeI ?? 0 / 1e7}
|
||||
{position.latitudeI ?? 0 / 1e7}, {position.longitudeI ?? 0 / 1e7}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
{t("locationResponse.altitude")} {position.altitude}
|
||||
{(position.altitude ?? 0) < 1
|
||||
? t("unit.meter.one")
|
||||
: t("unit.meter.plural")}
|
||||
{(position.altitude ?? 0) < 1 ? t("unit.meter.one") : t("unit.meter.plural")}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
// Optional: Show a message if coordinates are not available
|
||||
<p className="text-textPrimary">
|
||||
{t("locationResponse.noCoordinates")}
|
||||
</p>
|
||||
<p className="text-textPrimary">{t("locationResponse.noCoordinates")}</p>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
|
||||
@@ -18,11 +18,7 @@ export interface ManagedModeDialogProps {
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export const ManagedModeDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
}: ManagedModeDialogProps) => {
|
||||
export const ManagedModeDialog = ({ open, onOpenChange, onSubmit }: ManagedModeDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const [confirmState, setConfirmState] = useState(false);
|
||||
|
||||
@@ -47,9 +43,7 @@ export const ManagedModeDialog = ({
|
||||
onChange={() => setConfirmState(!confirmState)}
|
||||
name="confirmUnderstanding"
|
||||
>
|
||||
<p className="dark:text-white pt-1">
|
||||
{t("managedMode.confirmUnderstanding")}
|
||||
</p>
|
||||
<p className="dark:text-white pt-1">{t("managedMode.confirmUnderstanding")}</p>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
||||
@@ -8,12 +8,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@components/UI/Dialog.tsx";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@components/UI/Tabs.tsx";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/UI/Tabs.tsx";
|
||||
import {
|
||||
type BrowserFeature,
|
||||
useBrowserFeatureDetection,
|
||||
@@ -63,9 +58,7 @@ const ErrorMessage = ({ missingFeatures, tabId }: FeatureErrorProps) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const browserFeatures = missingFeatures.filter(
|
||||
(feature) => feature !== "Secure Context",
|
||||
);
|
||||
const browserFeatures = missingFeatures.filter((feature) => feature !== "Secure Context");
|
||||
const needsSecureContext = missingFeatures.includes("Secure Context");
|
||||
|
||||
const needsFeature =
|
||||
@@ -133,17 +126,13 @@ export const NewDeviceDialog = ({ open, onOpenChange }: NewDeviceProps) => {
|
||||
id: "BLE",
|
||||
label: t("newDeviceDialog.tabBluetooth"),
|
||||
element: BLE,
|
||||
isDisabled:
|
||||
unsupported.includes("Web Bluetooth") ||
|
||||
unsupported.includes("Secure Context"),
|
||||
isDisabled: unsupported.includes("Web Bluetooth") || unsupported.includes("Secure Context"),
|
||||
},
|
||||
{
|
||||
id: "Serial",
|
||||
label: t("newDeviceDialog.tabSerial"),
|
||||
element: Serial,
|
||||
isDisabled:
|
||||
unsupported.includes("Web Serial") ||
|
||||
unsupported.includes("Secure Context"),
|
||||
isDisabled: unsupported.includes("Web Serial") || unsupported.includes("Secure Context"),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -50,10 +50,7 @@ export interface NodeDetailsDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const NodeDetailsDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: NodeDetailsDialogProps) => {
|
||||
export const NodeDetailsDialog = ({ open, onOpenChange }: NodeDetailsDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { setDialogOpen, connection } = useDevice();
|
||||
const { getNode } = useNodeDB();
|
||||
@@ -64,12 +61,8 @@ export const NodeDetailsDialog = ({
|
||||
|
||||
const node = getNode(nodeNumDetails);
|
||||
|
||||
const [isFavoriteState, setIsFavoriteState] = useState<boolean>(
|
||||
node?.isFavorite ?? false,
|
||||
);
|
||||
const [isIgnoredState, setIsIgnoredState] = useState<boolean>(
|
||||
node?.isIgnored ?? false,
|
||||
);
|
||||
const [isFavoriteState, setIsFavoriteState] = useState<boolean>(node?.isFavorite ?? false);
|
||||
const [isIgnoredState, setIsIgnoredState] = useState<boolean>(node?.isIgnored ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!node) {
|
||||
@@ -168,8 +161,7 @@ export const NodeDetailsDialog = ({
|
||||
key: "batteryLevel",
|
||||
label: t("nodeDetails.batteryLevel"),
|
||||
value: node.deviceMetrics?.batteryLevel,
|
||||
format: (val: number) =>
|
||||
val === 101 ? t("batteryStatus.pluggedIn") : `${val.toFixed(2)}%`,
|
||||
format: (val: number) => (val === 101 ? t("batteryStatus.pluggedIn") : `${val.toFixed(2)}%`),
|
||||
},
|
||||
{
|
||||
key: "voltage",
|
||||
@@ -202,27 +194,17 @@ export const NodeDetailsDialog = ({
|
||||
<DialogFooter>
|
||||
<div className="w-full ">
|
||||
<div className="flex flex-row flex-wrap space-y-1">
|
||||
<Button
|
||||
className="mr-1"
|
||||
name="message"
|
||||
onClick={handleDirectMessage}
|
||||
>
|
||||
<Button className="mr-1" name="message" onClick={handleDirectMessage}>
|
||||
<MessageSquareIcon className="mr-2" />
|
||||
{t("nodeDetails.message")}
|
||||
</Button>
|
||||
<Button
|
||||
className="mr-1"
|
||||
name="traceRoute"
|
||||
onClick={handleTraceroute}
|
||||
>
|
||||
<Button className="mr-1" name="traceRoute" onClick={handleTraceroute}>
|
||||
<WaypointsIcon className="mr-2" />
|
||||
{t("nodeDetails.traceRoute")}
|
||||
</Button>
|
||||
<Button className="mr-1" onClick={handleToggleFavorite}>
|
||||
<StarIcon
|
||||
className={cn(
|
||||
isFavoriteState ? " fill-yellow-400 stroke-yellow-400" : "",
|
||||
)}
|
||||
className={cn(isFavoriteState ? " fill-yellow-400 stroke-yellow-400" : "")}
|
||||
/>
|
||||
</Button>
|
||||
<div className="flex flex-1 justify-start" />
|
||||
@@ -243,9 +225,7 @@ export const NodeDetailsDialog = ({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
|
||||
{isIgnoredState
|
||||
? t("nodeDetails.unignoreNode")
|
||||
: t("nodeDetails.ignoreNode")}
|
||||
{isIgnoredState ? t("nodeDetails.unignoreNode") : t("nodeDetails.ignoreNode")}
|
||||
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -275,9 +255,7 @@ export const NodeDetailsDialog = ({
|
||||
<div className="flex flex-col flex-wrap space-x-1 space-y-1">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="w-full bg-slate-100 text-slate-900 dark:text-slate-100 dark:bg-slate-800 p-3 rounded-lg">
|
||||
<p className="text-lg font-semibold">
|
||||
{t("nodeDetails.details")}
|
||||
</p>
|
||||
<p className="text-lg font-semibold">{t("nodeDetails.details")}</p>
|
||||
<table className="table-fixed w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -291,9 +269,10 @@ export const NodeDetailsDialog = ({
|
||||
<tr>
|
||||
<td>{t("nodeDetails.role")}</td>
|
||||
<td>
|
||||
{Protobuf.Config.Config_DeviceConfig_Role[
|
||||
node.user?.role ?? 0
|
||||
]?.replace(/_/g, " ")}
|
||||
{Protobuf.Config.Config_DeviceConfig_Role[node.user?.role ?? 0]?.replace(
|
||||
/_/g,
|
||||
" ",
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -312,44 +291,35 @@ export const NodeDetailsDialog = ({
|
||||
<td>{t("nodeDetails.hardware")}</td>
|
||||
<td>
|
||||
{(
|
||||
Protobuf.Mesh.HardwareModel[
|
||||
node.user?.hwModel ?? 0
|
||||
] ?? t("unknown.shortName")
|
||||
Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0] ??
|
||||
t("unknown.shortName")
|
||||
).replace(/_/g, " ")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("nodeDetails.messageable")}</td>
|
||||
<td>
|
||||
{node.user?.isUnmessagable ? t("no") : t("yes")}
|
||||
</td>
|
||||
<td>{node.user?.isUnmessagable ? t("no") : t("yes")}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DeviceImage
|
||||
className="w-40 p-2 rounded-lg border-4 border-slate-200 dark:border-slate-800"
|
||||
deviceType={
|
||||
Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0] ??
|
||||
"UNKNOWN"
|
||||
}
|
||||
deviceType={Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0] ?? "UNKNOWN"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={sectionClassName}>
|
||||
<p className="text-lg font-semibold">
|
||||
{t("nodeDetails.security")}
|
||||
</p>
|
||||
<p className="text-lg font-semibold">{t("nodeDetails.security")}</p>
|
||||
<table className="table-auto w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="pr-2">{t("nodeDetails.publicKey")}</td>
|
||||
<td>
|
||||
<pre className="text-xs pt-0.5">
|
||||
{node.user?.publicKey &&
|
||||
node.user?.publicKey.length > 0
|
||||
{node.user?.publicKey && node.user?.publicKey.length > 0
|
||||
? fromByteArray(node.user.publicKey)
|
||||
: t("unknown.longName")}
|
||||
</pre>
|
||||
@@ -368,9 +338,7 @@ export const NodeDetailsDialog = ({
|
||||
</div>
|
||||
|
||||
<div className={sectionClassName}>
|
||||
<p className="text-lg font-semibold">
|
||||
{t("nodeDetails.position")}
|
||||
</p>
|
||||
<p className="text-lg font-semibold">{t("nodeDetails.position")}</p>
|
||||
|
||||
{node.position ? (
|
||||
<table className="table-auto w-full">
|
||||
@@ -387,8 +355,7 @@ export const NodeDetailsDialog = ({
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{node.position.latitudeI / 1e7},{" "}
|
||||
{node.position.longitudeI / 1e7}
|
||||
{node.position.latitudeI / 1e7}, {node.position.longitudeI / 1e7}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -407,11 +374,7 @@ export const NodeDetailsDialog = ({
|
||||
) : (
|
||||
<p>{t("unknown.longName")}</p>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleRequestPosition}
|
||||
name="requestPosition"
|
||||
className="mt-2"
|
||||
>
|
||||
<Button onClick={handleRequestPosition} name="requestPosition" className="mt-2">
|
||||
<MapPinnedIcon className="mr-2" />
|
||||
{t("nodeDetails.requestPosition")}
|
||||
</Button>
|
||||
@@ -436,9 +399,7 @@ export const NodeDetailsDialog = ({
|
||||
<tr>
|
||||
<td>{t("nodeDetails.uptime")}</td>
|
||||
<td>
|
||||
<Uptime
|
||||
seconds={node.deviceMetrics.uptimeSeconds}
|
||||
/>
|
||||
<Uptime seconds={node.deviceMetrics.uptimeSeconds} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@@ -457,9 +418,7 @@ export const NodeDetailsDialog = ({
|
||||
</p>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="overflow-x-scroll">
|
||||
<pre className="text-xs w-full">
|
||||
{JSON.stringify(node, null, 2)}
|
||||
</pre>
|
||||
<pre className="text-xs w-full">{JSON.stringify(node, null, 2)}</pre>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
@@ -19,25 +19,19 @@ export interface PkiBackupDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const PkiBackupDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: PkiBackupDialogProps) => {
|
||||
export const PkiBackupDialog = ({ open, onOpenChange }: PkiBackupDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { config, setDialogOpen } = useDevice();
|
||||
const { getMyNode } = useNodeDB();
|
||||
const privateKey = config.security?.privateKey;
|
||||
const publicKey = config.security?.publicKey;
|
||||
|
||||
const decodeKeyData = React.useCallback(
|
||||
(key: Uint8Array<ArrayBufferLike>) => {
|
||||
if (!key) {
|
||||
return "";
|
||||
}
|
||||
return fromByteArray(key ?? new Uint8Array(0));
|
||||
},
|
||||
[],
|
||||
);
|
||||
const decodeKeyData = React.useCallback((key: Uint8Array<ArrayBufferLike>) => {
|
||||
if (!key) {
|
||||
return "";
|
||||
}
|
||||
return fromByteArray(key ?? new Uint8Array(0));
|
||||
}, []);
|
||||
|
||||
const closeDialog = React.useCallback(() => {
|
||||
setDialogOpen("pkiBackup", false);
|
||||
@@ -133,9 +127,7 @@ export const PkiBackupDialog = ({
|
||||
<DialogTitle>{t("pkiBackup.title")}</DialogTitle>
|
||||
<DialogDescription>{t("pkiBackup.secureBackup")}</DialogDescription>
|
||||
<DialogDescription>
|
||||
<span className="font-bold break-before-auto">
|
||||
{t("pkiBackup.loseKeysWarning")}
|
||||
</span>
|
||||
<span className="font-bold break-before-auto">{t("pkiBackup.loseKeysWarning")}</span>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="mt-6">
|
||||
|
||||
@@ -46,11 +46,7 @@ export const PkiRegenerateDialog = ({
|
||||
<DialogDescription>{dialogText.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="destructive"
|
||||
name="regenerate"
|
||||
onClick={() => onSubmit()}
|
||||
>
|
||||
<Button variant="destructive" name="regenerate" onClick={() => onSubmit()}>
|
||||
{dialogText.button}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -24,12 +24,7 @@ export interface QRDialogProps {
|
||||
channels: Map<Types.ChannelNumber, Protobuf.Channel.Channel>;
|
||||
}
|
||||
|
||||
export const QRDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
loraConfig,
|
||||
channels,
|
||||
}: QRDialogProps) => {
|
||||
export const QRDialog = ({ open, onOpenChange, loraConfig, channels }: QRDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]);
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
|
||||
@@ -49,16 +44,12 @@ export const QRDialog = ({
|
||||
settings: channelsToEncode,
|
||||
}),
|
||||
);
|
||||
const base64 = fromByteArray(
|
||||
toBinary(Protobuf.AppOnly.ChannelSetSchema, encoded),
|
||||
)
|
||||
const base64 = fromByteArray(toBinary(Protobuf.AppOnly.ChannelSetSchema, encoded))
|
||||
.replace(/=/g, "")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_");
|
||||
|
||||
setQrCodeUrl(
|
||||
`https://meshtastic.org/e/${qrCodeAdd ? "?add=true" : ""}#${base64}`,
|
||||
);
|
||||
setQrCodeUrl(`https://meshtastic.org/e/${qrCodeAdd ? "?add=true" : ""}#${base64}`);
|
||||
}, [allChannels, selectedChannels, qrCodeAdd, loraConfig]);
|
||||
|
||||
return (
|
||||
@@ -89,14 +80,9 @@ export const QRDialog = ({
|
||||
checked={selectedChannels.includes(channel.index)}
|
||||
onChange={() => {
|
||||
if (selectedChannels.includes(channel.index)) {
|
||||
setSelectedChannels(
|
||||
selectedChannels.filter((c) => c !== channel.index),
|
||||
);
|
||||
setSelectedChannels(selectedChannels.filter((c) => c !== channel.index));
|
||||
} else {
|
||||
setSelectedChannels([
|
||||
...selectedChannels,
|
||||
channel.index,
|
||||
]);
|
||||
setSelectedChannels([...selectedChannels, channel.index]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import type {
|
||||
ButtonHTMLAttributes,
|
||||
ClassAttributes,
|
||||
InputHTMLAttributes,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import type { ButtonHTMLAttributes, ClassAttributes, InputHTMLAttributes, ReactNode } from "react";
|
||||
import type { JSX } from "react/jsx-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { RebootDialog } from "./RebootDialog.tsx";
|
||||
@@ -54,16 +49,10 @@ vi.mock("@components/UI/Input.tsx", async () => {
|
||||
vi.mock("@components/UI/Dialog.tsx", () => {
|
||||
return {
|
||||
Dialog: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
DialogContent: ({ children }: { children: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
DialogHeader: ({ children }: { children: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
DialogContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
DialogHeader: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
DialogTitle: ({ children }: { children: ReactNode }) => <h1>{children}</h1>,
|
||||
DialogDescription: ({ children }: { children: ReactNode }) => (
|
||||
<p>{children}</p>
|
||||
),
|
||||
DialogDescription: ({ children }: { children: ReactNode }) => <p>{children}</p>,
|
||||
DialogClose: () => null,
|
||||
};
|
||||
});
|
||||
@@ -81,12 +70,8 @@ describe("RebootDialog", () => {
|
||||
it("renders dialog with default input value", () => {
|
||||
render(<RebootDialog open onOpenChange={() => {}} />);
|
||||
expect(screen.getByPlaceholderText(/enter delay/i)).toHaveValue(5);
|
||||
expect(
|
||||
screen.getByRole("heading", { name: /reboot device/i, level: 1 }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /reboot now/i }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: /reboot device/i, level: 1 })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /reboot now/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls correct reboot function based on OTA checkbox state", () => {
|
||||
@@ -199,8 +184,6 @@ describe("RebootDialog", () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: /cancel/i }));
|
||||
});
|
||||
expect(rebootMock).toHaveBeenCalledWith(-1);
|
||||
expect(
|
||||
screen.queryByText(/reboot has been scheduled/i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/reboot has been scheduled/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,11 +96,7 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => {
|
||||
<Separator />
|
||||
{!isScheduled ? (
|
||||
<>
|
||||
<Checkbox
|
||||
checked={isOTA}
|
||||
onChange={(checked) => setIsOTA(checked)}
|
||||
className="px-2"
|
||||
>
|
||||
<Checkbox checked={isOTA} onChange={(checked) => setIsOTA(checked)} className="px-2">
|
||||
{t("reboot.ota")}
|
||||
</Checkbox>
|
||||
<div className="flex gap-2 px-2 items-center relative">
|
||||
@@ -137,9 +133,7 @@ export const RebootDialog = ({ open, onOpenChange }: RebootDialogProps) => {
|
||||
) : (
|
||||
<div className="px-2">
|
||||
<div className="pb-6 pt-2 text-center">
|
||||
<Label className=" text-gray-700 dark:text-gray-300 ">
|
||||
{t("reboot.scheduled")}
|
||||
</Label>
|
||||
<Label className=" text-gray-700 dark:text-gray-300 ">{t("reboot.scheduled")}</Label>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
CurrentDeviceContext,
|
||||
useDeviceStore,
|
||||
useMessageStore,
|
||||
} from "@core/stores";
|
||||
import { CurrentDeviceContext, useDeviceStore, useMessageStore } from "@core/stores";
|
||||
import { render } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
||||
import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
|
||||
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
|
||||
|
||||
vi.mock("@core/stores", async () => {
|
||||
const actual = (await vi.importActual(
|
||||
"@core/stores",
|
||||
)) as typeof import("@core/stores");
|
||||
const actual = (await vi.importActual("@core/stores")) as typeof import("@core/stores");
|
||||
return {
|
||||
...actual,
|
||||
useMessageStore: vi.fn(),
|
||||
|
||||
@@ -16,10 +16,7 @@ export interface RefreshKeysDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const RefreshKeysDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: RefreshKeysDialogProps) => {
|
||||
export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { activeChat } = useMessages();
|
||||
const { nodeErrors, getNode } = useNodeDB();
|
||||
@@ -48,10 +45,7 @@ export const RefreshKeysDialog = ({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-w-8 flex flex-col gap-2"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogContent className="max-w-8 flex flex-col gap-2" aria-describedby={undefined}>
|
||||
<DialogClose onClick={handleCloseDialog} />
|
||||
<DialogHeader>
|
||||
<DialogTitle>{text.title}</DialogTitle>
|
||||
@@ -60,30 +54,17 @@ export const RefreshKeysDialog = ({
|
||||
<ul className="mt-2">
|
||||
<li className="flex place-items-center gap-2 items-start">
|
||||
<div className="p-2 bg-slate-500 rounded-lg mt-1">
|
||||
<LockKeyholeOpenIcon
|
||||
size={30}
|
||||
className="text-white justify-center"
|
||||
/>
|
||||
<LockKeyholeOpenIcon size={30} className="text-white justify-center" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>
|
||||
<p className="font-bold mb-0.5">
|
||||
{t("refreshKeys.label.acceptNewKeys")}
|
||||
</p>
|
||||
<p className="font-bold mb-0.5">{t("refreshKeys.label.acceptNewKeys")}</p>
|
||||
<p>{t("refreshKeys.description.acceptNewKeys")}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
name="requestNewKeys"
|
||||
onClick={handleNodeRemove}
|
||||
>
|
||||
<Button variant="default" name="requestNewKeys" onClick={handleNodeRemove}>
|
||||
{t("button.requestNewKeys")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
name="dismiss"
|
||||
onClick={handleCloseDialog}
|
||||
>
|
||||
<Button variant="outline" name="dismiss" onClick={handleCloseDialog}>
|
||||
{t("button.dismiss")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,10 +8,7 @@ export interface RemoveNodeDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const RemoveNodeDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: RemoveNodeDialogProps) => {
|
||||
export const RemoveNodeDialog = ({ open, onOpenChange }: RemoveNodeDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection } = useDevice();
|
||||
const { getNode, removeNode } = useNodeDB();
|
||||
|
||||
@@ -48,9 +48,7 @@ describe("ResetNodeDbDialog", () => {
|
||||
);
|
||||
|
||||
render(<ResetNodeDbDialog open onOpenChange={mockOnOpenChange} />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Reset Node Database" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Reset Node Database" }));
|
||||
|
||||
// Called immediately
|
||||
expect(mockResetNodes).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -8,10 +8,7 @@ export interface ResetNodeDbDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const ResetNodeDbDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ResetNodeDbDialogProps) => {
|
||||
export const ResetNodeDbDialog = ({ open, onOpenChange }: ResetNodeDbDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const { connection } = useDevice();
|
||||
const { removeAllNodeErrors, removeAllNodes } = useNodeDB();
|
||||
|
||||
@@ -32,13 +32,10 @@ export const TracerouteResponseDialog = ({
|
||||
const snrBack = (traceroute?.data.snrBack ?? []).map((snr) => snr / 4);
|
||||
const from = getNode(traceroute?.to ?? 0); // The origin of the traceroute = the "to" node of the mesh packet
|
||||
const fromLongName =
|
||||
from?.user?.longName ??
|
||||
(from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName"));
|
||||
from?.user?.longName ?? (from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName"));
|
||||
const fromShortName =
|
||||
from?.user?.shortName ??
|
||||
(from
|
||||
? `${numberToHexUnpadded(from?.num).substring(0, 4)}`
|
||||
: t("unknown.shortName"));
|
||||
(from ? `${numberToHexUnpadded(from?.num).substring(0, 4)}` : t("unknown.shortName"));
|
||||
|
||||
const toUser = getNode(traceroute?.from ?? 0); // The destination of the traceroute = the "from" node of the mesh packet
|
||||
|
||||
|
||||
@@ -20,16 +20,12 @@ export interface RouterRoleDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const UnsafeRolesDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: RouterRoleDialogProps) => {
|
||||
export const UnsafeRolesDialog = ({ open, onOpenChange }: RouterRoleDialogProps) => {
|
||||
const { t } = useTranslation("dialog");
|
||||
const [confirmState, setConfirmState] = useState(false);
|
||||
const { setDialogOpen } = useDevice();
|
||||
|
||||
const deviceRoleLink =
|
||||
"https://meshtastic.org/docs/configuration/radio/device/";
|
||||
const deviceRoleLink = "https://meshtastic.org/docs/configuration/radio/device/";
|
||||
const choosingTheRightDeviceRoleLink =
|
||||
"https://meshtastic.org/blog/choosing-the-right-device-role/";
|
||||
|
||||
@@ -63,17 +59,11 @@ export const UnsafeRolesDialog = ({
|
||||
onChange={() => setConfirmState(!confirmState)}
|
||||
name="confirmUnderstanding"
|
||||
>
|
||||
<span className="dark:text-white">
|
||||
{t("unsafeRoles.confirmUnderstanding")}
|
||||
</span>
|
||||
<span className="dark:text-white">{t("unsafeRoles.confirmUnderstanding")}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<DialogFooter className="mt-6">
|
||||
<Button
|
||||
variant="default"
|
||||
name="dismiss"
|
||||
onClick={() => handleCloseDialog("dismiss")}
|
||||
>
|
||||
<Button variant="default" name="dismiss" onClick={() => handleCloseDialog("dismiss")}>
|
||||
{t("button.dismiss")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
+14
-52
@@ -4,20 +4,11 @@ import {
|
||||
} from "@components/Dialog/UnsafeRolesDialog/useUnsafeRolesDialog.ts";
|
||||
import { eventBus } from "@core/utils/eventBus.ts";
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
type Mock,
|
||||
vi,
|
||||
} from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
|
||||
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock("@tanstack/react-router", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("@tanstack/react-router")>();
|
||||
const actual = await importOriginal<typeof import("@tanstack/react-router")>();
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: () => mockNavigate,
|
||||
@@ -65,10 +56,7 @@ describe("useUnsafeRolesDialog", () => {
|
||||
|
||||
result.current.handleCloseDialog();
|
||||
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
|
||||
"unsafeRoles",
|
||||
false,
|
||||
);
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith("unsafeRoles", false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,8 +65,7 @@ describe("useUnsafeRolesDialog", () => {
|
||||
const { result } = renderUnsafeRolesHook();
|
||||
const safeRole = "SAFE_ROLE";
|
||||
|
||||
const validationResult =
|
||||
await result.current.validateRoleSelection(safeRole);
|
||||
const validationResult = await result.current.validateRoleSelection(safeRole);
|
||||
|
||||
expect(validationResult).toBe(true);
|
||||
expect(mockDevice.setDialogOpen).not.toHaveBeenCalled();
|
||||
@@ -87,61 +74,40 @@ describe("useUnsafeRolesDialog", () => {
|
||||
it("should open dialog for unsafe roles and resolve with true when confirmed", async () => {
|
||||
const { result } = renderUnsafeRolesHook();
|
||||
|
||||
const validationPromise = result.current.validateRoleSelection(
|
||||
UNSAFE_ROLES[0]!,
|
||||
);
|
||||
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[0]!);
|
||||
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
|
||||
"unsafeRoles",
|
||||
true,
|
||||
);
|
||||
expect(eventBus.on).toHaveBeenCalledWith(
|
||||
"dialog:unsafeRoles",
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith("unsafeRoles", true);
|
||||
expect(eventBus.on).toHaveBeenCalledWith("dialog:unsafeRoles", expect.any(Function));
|
||||
|
||||
const onHandler = (eventBus.on as Mock).mock.calls[0]![1];
|
||||
onHandler({ action: "confirm" });
|
||||
const validationResult = await validationPromise;
|
||||
|
||||
expect(validationResult).toBe(true);
|
||||
expect(eventBus.off).toHaveBeenCalledWith(
|
||||
"dialog:unsafeRoles",
|
||||
onHandler,
|
||||
);
|
||||
expect(eventBus.off).toHaveBeenCalledWith("dialog:unsafeRoles", onHandler);
|
||||
});
|
||||
|
||||
it("should resolve with false when user dismisses the dialog", async () => {
|
||||
const { result } = renderUnsafeRolesHook();
|
||||
const validationPromise = result.current.validateRoleSelection(
|
||||
UNSAFE_ROLES[0]!,
|
||||
);
|
||||
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[0]!);
|
||||
const onHandler = (eventBus.on as Mock).mock.calls[0]![1];
|
||||
onHandler({ action: "dismiss" });
|
||||
|
||||
const validationResult = await validationPromise;
|
||||
expect(validationResult).toBe(false);
|
||||
expect(eventBus.off).toHaveBeenCalledWith(
|
||||
"dialog:unsafeRoles",
|
||||
onHandler,
|
||||
);
|
||||
expect(eventBus.off).toHaveBeenCalledWith("dialog:unsafeRoles", onHandler);
|
||||
});
|
||||
|
||||
it("should clean up event listener after response", async () => {
|
||||
const { result } = renderUnsafeRolesHook();
|
||||
|
||||
const validationPromise = result.current.validateRoleSelection(
|
||||
UNSAFE_ROLES[1]!,
|
||||
);
|
||||
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[1]!);
|
||||
const onHandler = (eventBus.on as Mock).mock.calls[0]![1];
|
||||
|
||||
onHandler({ action: "confirm" });
|
||||
await validationPromise;
|
||||
|
||||
expect(eventBus.off).toHaveBeenCalledWith(
|
||||
"dialog:unsafeRoles",
|
||||
onHandler,
|
||||
);
|
||||
expect(eventBus.off).toHaveBeenCalledWith("dialog:unsafeRoles", onHandler);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,13 +118,9 @@ describe("useUnsafeRolesDialog", () => {
|
||||
mockDevice.setDialogOpen.mockClear();
|
||||
(eventBus.on as Mock).mockClear();
|
||||
|
||||
const validationPromise =
|
||||
result.current.validateRoleSelection(unsafeRole);
|
||||
const validationPromise = result.current.validateRoleSelection(unsafeRole);
|
||||
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
|
||||
"unsafeRoles",
|
||||
true,
|
||||
);
|
||||
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith("unsafeRoles", true);
|
||||
|
||||
const onHandler = (eventBus.on as Mock).mock.calls[0]![1];
|
||||
onHandler({ action: "confirm" });
|
||||
|
||||
@@ -21,11 +21,7 @@ export const useUnsafeRolesDialog = () => {
|
||||
setDialogOpen("unsafeRoles", true);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const handleResponse = ({
|
||||
action,
|
||||
}: {
|
||||
action: "confirm" | "dismiss";
|
||||
}) => {
|
||||
const handleResponse = ({ action }: { action: "confirm" | "dismiss" }) => {
|
||||
eventBus.off("dialog:unsafeRoles", handleResponse);
|
||||
resolve(action === "confirm");
|
||||
};
|
||||
|
||||
@@ -62,9 +62,7 @@ export interface UseDialogReturn {
|
||||
}
|
||||
|
||||
export const useDialog = (initialState?: DialogState): UseDialogReturn => {
|
||||
const [state, setState] = useState<DialogState>(
|
||||
initialState ?? { isOpen: false },
|
||||
);
|
||||
const [state, setState] = useState<DialogState>(initialState ?? { isOpen: false });
|
||||
|
||||
const openDialog = useCallback((config: DialogConfig) => {
|
||||
setState({ isOpen: true, config });
|
||||
@@ -97,10 +95,7 @@ export const useDialog = (initialState?: DialogState): UseDialogReturn => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getDefaultTexts = (
|
||||
type: DialogType,
|
||||
t: ReturnType<typeof useTranslation>["t"],
|
||||
) => {
|
||||
export const getDefaultTexts = (type: DialogType, t: ReturnType<typeof useTranslation>["t"]) => {
|
||||
switch (type) {
|
||||
case "confirm":
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user