Browse Source

pnpm check:fix

pull/381/head
Tilen Komel 1 year ago
parent
commit
891a1a9503
  1. 2
      rsbuild.config.ts
  2. 2
      src/App.tsx
  3. 2
      src/components/Dialog/DialogManager.tsx
  4. 38
      src/components/Dialog/NewDeviceDialog.tsx
  5. 8
      src/components/Form/FormPasswordGenerator.tsx
  6. 63
      src/components/PageComponents/Channel.tsx
  7. 16
      src/components/PageComponents/Config/Bluetooth.tsx
  8. 10
      src/components/PageComponents/Config/Security.tsx
  9. 5
      src/components/PageComponents/Connect/Serial.tsx
  10. 6
      src/components/PageComponents/Map/NodeDetail.tsx
  11. 2
      src/components/PageComponents/Messages/Message.tsx
  12. 3
      src/components/Toaster.tsx
  13. 2
      src/components/UI/Button.tsx
  14. 62
      src/components/UI/Toast.tsx
  15. 5
      src/components/UI/Typography/Link.tsx
  16. 18
      src/core/hooks/useBrowserFeatureDetection.ts
  17. 77
      src/core/hooks/useKeyBackupReminder.tsx
  18. 36
      src/core/hooks/useToast.ts
  19. 9
      src/pages/Map.tsx
  20. 2
      tsconfig.json

2
rsbuild.config.ts

@ -1,6 +1,6 @@
import { execSync } from "node:child_process";
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { execSync } from "node:child_process";
let hash = "";

2
src/App.tsx

@ -4,6 +4,7 @@ import { CommandPalette } from "@components/CommandPalette.tsx";
import { DeviceSelector } from "@components/DeviceSelector.tsx";
import { DialogManager } from "@components/Dialog/DialogManager";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
import { KeyBackupReminder } from "@components/KeyBackupReminder";
import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx";
import { ThemeController } from "@components/generic/ThemeController.tsx";
@ -11,7 +12,6 @@ import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Dashboard } from "@pages/Dashboard/index.tsx";
import { MapProvider } from "react-map-gl";
import { KeyBackupReminder } from "@components/KeyBackupReminder";
export const App = (): JSX.Element => {
const { getDevice } = useDeviceStore();

2
src/components/Dialog/DialogManager.tsx

@ -1,11 +1,11 @@
import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog";
import { QRDialog } from "@components/Dialog/QRDialog.tsx";
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog";
export const DialogManager = (): JSX.Element => {
const { channels, config, dialog, setDialogOpen } = useDevice();

38
src/components/Dialog/NewDeviceDialog.tsx

@ -11,7 +11,6 @@ import {
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { AlertCircle, InfoIcon, } from "lucide-react";
import {
Tabs,
TabsContent,
@ -19,8 +18,9 @@ import {
TabsTrigger,
} from "@components/UI/Tabs.tsx";
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
import { Link } from "../UI/Typography/Link";
import { AlertCircle, InfoIcon } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { Link } from "../UI/Typography/Link";
export interface TabElementProps {
closeDialog: () => void;
@ -50,26 +50,25 @@ const links: { [key: string]: string } = {
"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts",
};
const listFormatter = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction'
const listFormatter = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
const ErrorMessage = ({ missingFeatures }: 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 formatFeatureList = (features: string[]) => {
const parts = listFormatter.formatToParts(features);
return parts.map((part) => {
if (part.type === 'element') {
if (part.type === "element") {
return (
<Link
key={part.value}
href={links[part.value]}
>
<Link key={part.value} href={links[part.value]}>
{part.value}
</Link>
);
@ -94,12 +93,8 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
<>
{browserFeatures.length > 0 && " Additionally, it"}
{browserFeatures.length === 0 && "This application"} requires a{" "}
<Link
href={links["Secure Context"]}
>
secure context
</Link>
. Please connect using HTTPS or localhost.
<Link href={links["Secure Context"]}>secure context</Link>.
Please connect using HTTPS or localhost.
</>
)}
</p>
@ -146,10 +141,7 @@ export const NewDeviceDialog = ({
<Tabs defaultValue="HTTP">
<TabsList>
{tabs.map((tab) => (
<TabsTrigger
key={tab.label}
value={tab.label}
>
<TabsTrigger key={tab.label} value={tab.label}>
{tab.label}
</TabsTrigger>
))}
@ -157,7 +149,9 @@ export const NewDeviceDialog = ({
{tabs.map((tab) => (
<TabsContent key={tab.label} value={tab.label}>
<fieldset disabled={tab.isDisabled}>
{tab.isDisabled ? <ErrorMessage missingFeatures={unsupported} /> : null}
{tab.isDisabled ? (
<ErrorMessage missingFeatures={unsupported} />
) : null}
<tab.element closeDialog={() => onOpenChange(false)} />
</fieldset>
</TabsContent>

8
src/components/Form/FormPasswordGenerator.tsx

@ -2,12 +2,12 @@ import type {
BaseFormBuilderProps,
GenericFormElementProps,
} from "@components/Form/DynamicForm.tsx";
import type { ButtonVariant } from "@components/UI/Button";
import { Generator } from "@components/UI/Generator.tsx";
import { Eye, EyeOff } from "lucide-react";
import type { ChangeEventHandler, MouseEventHandler } from "react";
import { useState } from "react";
import { Controller, type FieldValues } from "react-hook-form";
import type { ButtonVariant } from "@components/UI/Button";
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
type: "passwordGenerator";
@ -44,9 +44,9 @@ export function PasswordGenerator<T extends FieldValues>({
action={
field.hide
? {
icon: passwordShown ? EyeOff : Eye,
onClick: togglePasswordVisiblity,
}
icon: passwordShown ? EyeOff : Eye,
onClick: togglePasswordVisiblity,
}
: undefined
}
devicePSKBitCount={field.devicePSKBitCount}

63
src/components/PageComponents/Channel.tsx

@ -23,7 +23,8 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
channel?.settings?.psk.length ?? 16,
);
const [validationText, setValidationText] = useState<string>();
const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(false);
const [preSharedDialogOpen, setPreSharedDialogOpen] =
useState<boolean>(false);
const onSubmit = (data: ChannelValidation) => {
const channel = new Protobuf.Channel.Channel({
@ -99,12 +100,13 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
psk: pass,
positionEnabled:
channel?.settings?.moduleSettings?.positionPrecision !==
undefined &&
undefined &&
channel?.settings?.moduleSettings?.positionPrecision > 0,
preciseLocation:
channel?.settings?.moduleSettings?.positionPrecision === 32,
positionPrecision:
channel?.settings?.moduleSettings?.positionPrecision === undefined
channel?.settings?.moduleSettings?.positionPrecision ===
undefined
? 10
: channel?.settings?.moduleSettings?.positionPrecision,
},
@ -133,12 +135,19 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
type: "passwordGenerator",
name: "settings.psk",
label: "Pre-Shared Key",
description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)",
description:
"Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)",
validationText: validationText,
devicePSKBitCount: bitCount ?? 0,
inputChange: inputChangeEvent,
selectChange: selectChangeEvent,
actionButtons: [{ text: 'Generate', variant: 'success', onClick: preSharedClickEvent }],
actionButtons: [
{
text: "Generate",
variant: "success",
onClick: preSharedClickEvent,
},
],
hide: true,
properties: {
value: pass,
@ -185,29 +194,29 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
enumValue:
config.display?.units === 0
? {
"Within 23 km": 10,
"Within 12 km": 11,
"Within 5.8 km": 12,
"Within 2.9 km": 13,
"Within 1.5 km": 14,
"Within 700 m": 15,
"Within 350 m": 16,
"Within 200 m": 17,
"Within 90 m": 18,
"Within 50 m": 19,
}
"Within 23 km": 10,
"Within 12 km": 11,
"Within 5.8 km": 12,
"Within 2.9 km": 13,
"Within 1.5 km": 14,
"Within 700 m": 15,
"Within 350 m": 16,
"Within 200 m": 17,
"Within 90 m": 18,
"Within 50 m": 19,
}
: {
"Within 15 miles": 10,
"Within 7.3 miles": 11,
"Within 3.6 miles": 12,
"Within 1.8 miles": 13,
"Within 0.9 miles": 14,
"Within 0.5 miles": 15,
"Within 0.2 miles": 16,
"Within 600 feet": 17,
"Within 300 feet": 18,
"Within 150 feet": 19,
},
"Within 15 miles": 10,
"Within 7.3 miles": 11,
"Within 3.6 miles": 12,
"Within 1.8 miles": 13,
"Within 0.9 miles": 14,
"Within 0.5 miles": 15,
"Within 0.2 miles": 16,
"Within 600 feet": 17,
"Within 300 feet": 18,
"Within 150 feet": 19,
},
},
},
],

16
src/components/PageComponents/Config/Bluetooth.tsx

@ -6,20 +6,16 @@ import { useState } from "react";
export const Bluetooth = (): JSX.Element => {
const { config, setWorkingConfig } = useDevice();
const [bluetoothValidationText, setBluetoothValidationText] = useState<string>();
const [bluetoothValidationText, setBluetoothValidationText] =
useState<string>();
const bluetoothPinChangeEvent = (
e: React.ChangeEvent<HTMLInputElement>,
) => {
if (e.target.value[0] == "0")
{
const bluetoothPinChangeEvent = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value[0] == "0") {
setBluetoothValidationText("Bluetooth Pin cannot start with 0.");
}
else
{
} else {
setBluetoothValidationText("");
}
}
};
const onSubmit = (data: BluetoothValidation) => {
setWorkingConfig(

10
src/components/PageComponents/Config/Security.tsx

@ -12,7 +12,8 @@ import { Eye, EyeOff } from "lucide-react";
import { useState } from "react";
export const Security = (): JSX.Element => {
const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice();
const { config, nodes, hardware, setWorkingConfig, setDialogOpen } =
useDevice();
const [privateKey, setPrivateKey] = useState<string>(
fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
@ -31,7 +32,8 @@ export const Security = (): JSX.Element => {
);
const [adminKeyValidationText, setAdminKeyValidationText] =
useState<string>();
const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState<boolean>(false);
const [privateKeyDialogOpen, setPrivateKeyDialogOpen] =
useState<boolean>(false);
const onSubmit = (data: SecurityValidation) => {
if (privateKeyValidationText || adminKeyValidationText) return;
@ -76,7 +78,7 @@ export const Security = (): JSX.Element => {
const pkiBackupClickEvent = () => {
setDialogOpen("pkiBackup", true);
}
};
const pkiRegenerate = () => {
const privateKey = getX25519PrivateKey();
@ -202,7 +204,7 @@ export const Security = (): JSX.Element => {
name: "isManaged",
label: "Managed",
description:
'If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.',
"If true, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless a suitable Remote Admin node has been setup, and the public key stored in the field below.",
},
{
type: "text",

5
src/components/PageComponents/Connect/Serial.tsx

@ -58,8 +58,9 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
await onConnect(port);
}}
>
{`# ${index} - ${usbVendorId ?? "UNK"} - ${usbProductId ?? "UNK"
}`}
{`# ${index} - ${usbVendorId ?? "UNK"} - ${
usbProductId ?? "UNK"
}`}
</Button>
);
})}

6
src/components/PageComponents/Map/NodeDetail.tsx

@ -1,11 +1,12 @@
import { Mono } from "@components/generic/Mono.tsx";
import { Separator } from "@app/components/UI/Seperator";
import { H5 } from "@app/components/UI/Typography/H5.tsx";
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
import { Separator } from "@app/components/UI/Seperator";
import { Mono } from "@components/generic/Mono.tsx";
import { TimeAgo } from "@components/generic/Table/tmp/TimeAgo.tsx";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf } from "@meshtastic/js";
import type { Protobuf as ProtobufType } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import {
BatteryChargingIcon,
BatteryFullIcon,
@ -17,7 +18,6 @@ import {
MountainSnow,
Star,
} from "lucide-react";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
export interface NodeDetailProps {
node: ProtobufType.Mesh.NodeInfo;

2
src/components/PageComponents/Messages/Message.tsx

@ -45,7 +45,7 @@ export const Message = ({
{sender?.user?.longName ?? "UNK"}
</span>
<span className="mt-1 font-mono text-xs text-textSecondary">
{message.rxTime.toLocaleDateString()}
{message.rxTime.toLocaleDateString()}
</span>
<span className="mt-1 font-mono text-xs text-textSecondary">
{message.rxTime.toLocaleTimeString(undefined, {

3
src/components/Toaster.tsx

@ -17,7 +17,6 @@ export function Toaster() {
<Toast
key={id}
{...props}
duration={duration}
className="flex flex-col gap-4"
>
@ -32,4 +31,4 @@ export function Toaster() {
<ToastViewport />
</ToastProvider>
);
}
}

2
src/components/UI/Button.tsx

@ -39,7 +39,7 @@ export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { }
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {

62
src/components/UI/Toast.tsx

@ -1,11 +1,11 @@
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from 'lucide-react'
import * as ToastPrimitives from "@radix-ui/react-toast";
import { type VariantProps, cva } from "class-variance-authority";
import { X } from "lucide-react";
import * as React from "react";
import { cn } from "@core/utils/cn"
import { cn } from "@core/utils/cn";
const ToastProvider = ToastPrimitives.Provider
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
@ -15,33 +15,34 @@ const ToastViewport = React.forwardRef<
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-24 sm:right-6 sm:top-auto sm:flex-col md:max-w-[420px]",
className
className,
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50",
default:
"border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50",
destructive:
"group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50"
"group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50",
},
},
defaultVariants: {
variant: "default",
},
}
)
},
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
@ -49,9 +50,9 @@ const Toast = React.forwardRef<
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
);
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
@ -61,12 +62,12 @@ const ToastAction = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
className,
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
@ -76,15 +77,15 @@ const ToastClose = React.forwardRef<
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-slate-400 dark:hover:text-slate-50",
className
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
@ -95,8 +96,8 @@ const ToastTitle = React.forwardRef<
className={cn("text-sm font-semibold", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
@ -107,12 +108,12 @@ const ToastDescription = React.forwardRef<
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
@ -124,5 +125,4 @@ export {
ToastDescription,
ToastClose,
ToastAction,
}
};

5
src/components/UI/Typography/Link.tsx

@ -11,7 +11,10 @@ export const Link = ({ href, children, className }: LinkProps): JSX.Element => (
href={href}
target={"_blank"}
rel="noopener noreferrer"
className={cn("font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50", className)}
className={cn(
"font-medium text-slate-900 underline underline-offset-4 dark:text-slate-50",
className,
)}
>
{children}
</a>

18
src/core/hooks/useBrowserFeatureDetection.ts

@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { useMemo } from "react";
export type BrowserFeature = 'Web Bluetooth' | 'Web Serial' | 'Secure Context';
export type BrowserFeature = "Web Bluetooth" | "Web Serial" | "Secure Context";
interface BrowserSupport {
supported: BrowserFeature[];
@ -10,9 +10,13 @@ interface BrowserSupport {
export function useBrowserFeatureDetection(): BrowserSupport {
const support = useMemo(() => {
const features: [BrowserFeature, boolean][] = [
['Web Bluetooth', !!navigator?.bluetooth],
['Web Serial', !!navigator?.serial],
['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost']
["Web Bluetooth", !!navigator?.bluetooth],
["Web Serial", !!navigator?.serial],
[
"Secure Context",
window.location.protocol === "https:" ||
window.location.hostname === "localhost",
],
];
return features.reduce<BrowserSupport>(
@ -21,9 +25,9 @@ export function useBrowserFeatureDetection(): BrowserSupport {
list.push(feature);
return acc;
},
{ supported: [], unsupported: [] }
{ supported: [], unsupported: [] },
);
}, []);
return support;
}
}

77
src/core/hooks/useKeyBackupReminder.tsx

@ -17,11 +17,11 @@ interface ReminderState {
lastShown: string;
}
const TOAST_APPEAR_DELAY = 10_000 // 10 seconds;
const TOAST_DURATION = 30_000 // 30 seconds;:
const TOAST_APPEAR_DELAY = 10_000; // 10 seconds;
const TOAST_DURATION = 30_000; // 30 seconds;:
// remind user in 1 year to backup keys again, if they accept the reminder;
const ON_ACCEPT_REMINDER_DAYS = 365
const ON_ACCEPT_REMINDER_DAYS = 365;
function isReminderExpired(lastShown: string): boolean {
const lastShownDate = new Date(lastShown);
@ -35,13 +35,14 @@ export function useBackupReminder({
reminderInDays = 7,
enabled,
message,
onAccept = () => { },
onAccept = () => {},
cookieOptions,
}: UseBackupReminderOptions) {
const { toast } = useToast();
const toastShownRef = useRef(false);
const { value: reminderCookie, setCookie } =
useCookie<ReminderState>("key_backup_reminder");
const { value: reminderCookie, setCookie } = useCookie<ReminderState>(
"key_backup_reminder",
);
const suppressReminder = useCallback(
(days: number) => {
@ -69,39 +70,37 @@ export function useBackupReminder({
toastShownRef.current = true;
const { dismiss } = toast(
{
title: "Backup Reminder",
duration: TOAST_DURATION,
delay: TOAST_APPEAR_DELAY,
description: message,
action: (
<div className="flex gap-2">
<Button
type="button"
variant="default"
onClick={() => {
onAccept();
dismiss();
suppressReminder(ON_ACCEPT_REMINDER_DAYS);
}}
>
Back up now
</Button>
<Button
type="button"
variant="outline"
onClick={() => {
dismiss();
suppressReminder(reminderInDays);
}}
>
Remind me in {reminderInDays} days
</Button>
</div>
),
},
);
const { dismiss } = toast({
title: "Backup Reminder",
duration: TOAST_DURATION,
delay: TOAST_APPEAR_DELAY,
description: message,
action: (
<div className="flex gap-2">
<Button
type="button"
variant="default"
onClick={() => {
onAccept();
dismiss();
suppressReminder(ON_ACCEPT_REMINDER_DAYS);
}}
>
Back up now
</Button>
<Button
type="button"
variant="outline"
onClick={() => {
dismiss();
suppressReminder(reminderInDays);
}}
>
Remind me in {reminderInDays} days
</Button>
</div>
),
});
return () => {
if (!toastShownRef.current) {

36
src/core/hooks/useToast.ts

@ -31,21 +31,21 @@ type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
interface State {
toasts: ToasterToast[];
@ -81,7 +81,7 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
};
@ -103,10 +103,10 @@ export const reducer = (state: State, action: Action): State => {
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
...t,
open: false,
}
: t,
),
};
}
@ -193,4 +193,4 @@ function useToast() {
};
}
export { toast, useToast };
export { toast, useToast };

9
src/pages/Map.tsx

@ -1,5 +1,5 @@
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
import { cn } from "@app/core/utils/cn.ts";
import { PageLayout } from "@components/PageLayout.tsx";
import { Sidebar } from "@components/Sidebar.tsx";
@ -8,6 +8,7 @@ import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Hashicon } from "@emeraldpay/hashicon-react";
import type { Protobuf } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { bbox, lineString } from "@turf/turf";
import {
@ -19,7 +20,6 @@ import {
import { useCallback, useEffect, useState } from "react";
import { AttributionControl, Marker, Popup, useMap } from "react-map-gl";
import MapGl from "react-map-gl/maplibre";
import { Protobuf } from "@meshtastic/js";
export const MapPage = (): JSX.Element => {
const { nodes, waypoints } = useDevice();
@ -148,7 +148,10 @@ export const MapPage = (): JSX.Element => {
}}
>
<AttributionControl
style={{ background: darkMode ? "#ffffff" : "", color: darkMode ? "black" : "" }}
style={{
background: darkMode ? "#ffffff" : "",
color: darkMode ? "black" : "",
}}
/>
{waypoints.map((wp) => (
<Marker

2
tsconfig.json

@ -35,6 +35,6 @@
],
"strictPropertyInitialization": false,
"experimentalDecorators": true,
"allowImportingTsExtensions": true,
"allowImportingTsExtensions": true
}
}

Loading…
Cancel
Save