Apply and Upgrade Eslint (#288)

* Upgrade ESLINT and fix issues

* feat: add frontend linting job to GitHub Actions workflow

* Move UI linting to separate file

* More linting fixes

* Remove pull_request trigger from UI linting workflow

* Update UI linting workflow

* Rename frontend-lint workflow to ui-lint for clarity
This commit is contained in:
Adam Shiervani 2025-03-25 11:56:24 +01:00 committed by GitHub
parent 9d511d7f58
commit 3b711db781
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
86 changed files with 1627 additions and 1231 deletions

View File

@ -19,12 +19,12 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: v21.1.0 node-version: v21.1.0
cache: 'npm' cache: "npm"
cache-dependency-path: '**/package-lock.json' cache-dependency-path: "**/package-lock.json"
- name: Set up Golang - name: Set up Golang
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: '1.24.0' go-version: "1.24.0"
- name: Build frontend - name: Build frontend
run: | run: |
make frontend make frontend

34
.github/workflows/ui-lint.yml vendored Normal file
View File

@ -0,0 +1,34 @@
---
name: ui-lint
on:
push:
paths:
- "ui/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/ui-lint.yml"
permissions:
contents: read
jobs:
ui-lint:
name: UI Lint
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: v21.1.0
cache: "npm"
cache-dependency-path: "ui/package-lock.json"
- name: Install dependencies
run: |
cd ui
npm ci
- name: Lint UI
run: |
cd ui
npm run lint

View File

@ -8,6 +8,8 @@ module.exports = {
"plugin:react-hooks/recommended", "plugin:react-hooks/recommended",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react/jsx-runtime", "plugin:react/jsx-runtime",
"plugin:import/recommended",
"prettier",
], ],
ignorePatterns: ["dist", ".eslintrc.cjs", "tailwind.config.js", "postcss.config.js"], ignorePatterns: ["dist", ".eslintrc.cjs", "tailwind.config.js", "postcss.config.js"],
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
@ -20,5 +22,45 @@ module.exports = {
}, },
rules: { rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }], "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"import/order": [
"error",
{
/**
* @description
*
* This keeps imports separate from one another, ensuring that imports are separated
* by their relative groups. As you move through the groups, imports become closer
* to the current file.
*
* @example
* ```
* import fs from 'fs';
*
* import package from 'npm-package';
*
* import xyz from '~/project-file';
*
* import index from '../';
*
* import sibling from './foo';
* ```
*/
groups: ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "always",
},
],
},
settings: {
"import/resolver": {
alias: {
map: [
["@components", "./src/components"],
["@routes", "./src/routes"],
["@assets", "./src/assets"],
["@", "./src"],
],
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
},
},
}, },
}; };

View File

@ -5,11 +5,7 @@
"useTabs": false, "useTabs": false,
"arrowParens": "avoid", "arrowParens": "avoid",
"singleQuote": false, "singleQuote": false,
"plugins": [ "plugins": ["prettier-plugin-tailwindcss"],
"prettier-plugin-tailwindcss" "tailwindFunctions": ["clsx"],
],
"tailwindFunctions": [
"clsx"
],
"printWidth": 90 "printWidth": 90
} }

1955
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,13 @@
"build:device": "tsc && vite build --mode=device --emptyOutDir", "build:device": "tsc && vite build --mode=device --emptyOutDir",
"build:staging": "tsc && vite build --mode=cloud-staging", "build:staging": "tsc && vite build --mode=cloud-staging",
"build:prod": "tsc && vite build --mode=cloud-production", "build:prod": "tsc && vite build --mode=cloud-production",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint './src/**/*.{ts,tsx}'",
"lint:fix": "eslint './src/**/*.{ts,tsx}' --fix",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.0", "@headlessui/react": "^2.2.0",
"@headlessui/tailwindcss": "^0.2.2", "@headlessui/tailwindcss": "^0.2.1",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"@xterm/addon-clipboard": "^0.1.0", "@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
@ -27,46 +28,49 @@
"@xterm/addon-webgl": "^0.18.0", "@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"cva": "^1.0.0-beta.1", "cva": "^1.0.0-beta.1",
"eslint-import-resolver-alias": "^1.1.2",
"focus-trap-react": "^10.2.3", "focus-trap-react": "^10.2.3",
"framer-motion": "^11.15.0", "framer-motion": "^11.15.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"mini-svg-data-uri": "^1.4.4", "mini-svg-data-uri": "^1.4.4",
"motion": "^12.4.7",
"react": "^18.2.0", "react": "^18.2.0",
"react-animate-height": "^3.2.3", "react-animate-height": "^3.2.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.4.1",
"react-icons": "^5.5.0", "react-icons": "^5.4.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-simple-keyboard": "^3.7.112", "react-simple-keyboard": "^3.7.112",
"react-xtermjs": "^1.0.9", "react-xtermjs": "^1.0.9",
"recharts": "^2.15.1", "recharts": "^2.15.0",
"semver": "^7.7.1",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
"usehooks-ts": "^3.1.1", "usehooks-ts": "^3.1.0",
"validator": "^13.12.0", "validator": "^13.12.0",
"xterm": "^5.3.0",
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.15",
"@types/react": "^18.2.66", "@types/react": "^18.2.66",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/semver": "^7.5.8",
"@types/validator": "^13.12.2", "@types/validator": "^13.12.2",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^8.25.0",
"@vitejs/plugin-react-swc": "^3.8.0", "@vitejs/plugin-react-swc": "^3.7.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^8.57.0", "eslint": "^8.20.0",
"eslint-plugin-react": "^7.34.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-react": "^7.37.4",
"postcss": "^8.5.3", "eslint-plugin-react-hooks": "^5.1.0",
"prettier": "^3.5.2", "eslint-plugin-react-refresh": "^0.4.19",
"prettier-plugin-tailwindcss": "^0.5.13", "postcss": "^8.4.49",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "^5.7.3", "typescript": "^5.7.2",
"vite": "^5.2.0", "vite": "^5.2.0",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^5.1.4"
} }
} }

View File

@ -1,3 +1,10 @@
import { MdOutlineContentPasteGo } from "react-icons/md";
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
import { FaKeyboard } from "react-icons/fa6";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { Fragment, useCallback, useRef } from "react";
import { CommandLineIcon } from "@heroicons/react/20/solid";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { import {
useHidStore, useHidStore,
@ -5,19 +12,13 @@ import {
useSettingsStore, useSettingsStore,
useUiStore, useUiStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { MdOutlineContentPasteGo } from "react-icons/md";
import Container from "@components/Container"; import Container from "@components/Container";
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import PasteModal from "@/components/popovers/PasteModal"; import PasteModal from "@/components/popovers/PasteModal";
import { FaKeyboard } from "react-icons/fa6";
import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index"; import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import MountPopopover from "@/components/popovers/MountPopover";
import MountPopopover from "./popovers/MountPopover"; import ExtensionPopover from "@/components/popovers/ExtensionPopover";
import { Fragment, useCallback, useRef } from "react"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import { CommandLineIcon } from "@heroicons/react/20/solid";
import ExtensionPopover from "./popovers/ExtensionPopover";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
export default function Actionbar({ export default function Actionbar({
requestFullscreen, requestFullscreen,

View File

@ -1,21 +1,22 @@
import { useLocation, useNavigation, useSearchParams } from "react-router-dom";
import { Button, LinkButton } from "@components/Button"; import { Button, LinkButton } from "@components/Button";
import { GoogleIcon } from "@components/Icons"; import { GoogleIcon } from "@components/Icons";
import SimpleNavbar from "@components/SimpleNavbar"; import SimpleNavbar from "@components/SimpleNavbar";
import Container from "@components/Container"; import Container from "@components/Container";
import { useLocation, useNavigation, useSearchParams } from "react-router-dom";
import Fieldset from "@components/Fieldset"; import Fieldset from "@components/Fieldset";
import GridBackground from "@components/GridBackground"; import GridBackground from "@components/GridBackground";
import StepCounter from "@components/StepCounter"; import StepCounter from "@components/StepCounter";
import { CLOUD_API } from "@/ui.config"; import { CLOUD_API } from "@/ui.config";
type AuthLayoutProps = { interface AuthLayoutProps {
title: string; title: string;
description: string; description: string;
action: string; action: string;
cta: string; cta: string;
ctaHref: string; ctaHref: string;
showCounter?: boolean; showCounter?: boolean;
}; }
export default function AuthLayout({ export default function AuthLayout({
title, title,
@ -46,8 +47,8 @@ export default function AuthLayout({
} }
/> />
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="isolate flex h-full w-full items-center justify-center">
<div className="max-w-2xl -mt-16 space-y-8"> <div className="-mt-16 max-w-2xl space-y-8">
{showCounter ? ( {showCounter ? (
<div className="text-center"> <div className="text-center">
<StepCounter currStepIdx={0} nSteps={2} /> <StepCounter currStepIdx={0} nSteps={2} />
@ -61,11 +62,8 @@ export default function AuthLayout({
</div> </div>
<Fieldset className="space-y-12"> <Fieldset className="space-y-12">
<div className="max-w-sm mx-auto space-y-4"> <div className="mx-auto max-w-sm space-y-4">
<form <form action={`${CLOUD_API}/oidc/google`} method="POST">
action={`${CLOUD_API}/oidc/google`}
method="POST"
>
{/*This could be the KVM ID*/} {/*This could be the KVM ID*/}
{deviceId ? ( {deviceId ? (
<input type="hidden" name="deviceId" value={deviceId} /> <input type="hidden" name="deviceId" value={deviceId} />

View File

@ -1,8 +1,9 @@
import React from "react"; import React from "react";
import { FetcherWithComponents, Link, LinkProps, useNavigation } from "react-router-dom";
import ExtLink from "@/components/ExtLink"; import ExtLink from "@/components/ExtLink";
import LoadingSpinner from "@/components/LoadingSpinner"; import LoadingSpinner from "@/components/LoadingSpinner";
import { cva, cx } from "@/cva.config"; import { cva, cx } from "@/cva.config";
import { FetcherWithComponents, Link, LinkProps, useNavigation } from "react-router-dom";
const sizes = { const sizes = {
XS: "h-[28px] px-2 text-xs", XS: "h-[28px] px-2 text-xs",
@ -101,7 +102,7 @@ const iconVariants = cva({
}, },
}); });
type ButtonContentPropsType = { interface ButtonContentPropsType {
text?: string | React.ReactNode; text?: string | React.ReactNode;
LeadingIcon?: React.FC<{ className: string | undefined }> | null; LeadingIcon?: React.FC<{ className: string | undefined }> | null;
TrailingIcon?: React.FC<{ className: string | undefined }> | null; TrailingIcon?: React.FC<{ className: string | undefined }> | null;
@ -111,7 +112,7 @@ type ButtonContentPropsType = {
size: keyof typeof sizes; size: keyof typeof sizes;
theme: keyof typeof themes; theme: keyof typeof themes;
loading?: boolean; loading?: boolean;
}; }
function ButtonContent(props: ButtonContentPropsType) { function ButtonContent(props: ButtonContentPropsType) {
const { text, LeadingIcon, TrailingIcon, fullWidth, className, textAlign, loading } = const { text, LeadingIcon, TrailingIcon, fullWidth, className, textAlign, loading } =

View File

@ -1,10 +1,11 @@
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
type CardPropsType = { interface CardPropsType {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
}; }
export const GridCard = ({ export const GridCard = ({
children, children,

View File

@ -1,10 +1,10 @@
import React from "react"; import React from "react";
type Props = { interface Props {
headline: string; headline: string;
description?: string | React.ReactNode; description?: string | React.ReactNode;
Button?: React.ReactNode; Button?: React.ReactNode;
}; }
export const CardHeader = ({ headline, description, Button }: Props) => { export const CardHeader = ({ headline, description, Button }: Props) => {
return ( return (

View File

@ -1,7 +1,8 @@
import type { Ref } from "react"; import type { Ref } from "react";
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import FieldLabel from "@/components/FieldLabel";
import clsx from "clsx"; import clsx from "clsx";
import FieldLabel from "@/components/FieldLabel";
import { cva, cx } from "@/cva.config"; import { cva, cx } from "@/cva.config";
const sizes = { const sizes = {
@ -52,7 +53,7 @@ type CheckboxWithLabelProps = React.ComponentProps<typeof FieldLabel> &
const CheckboxWithLabel = forwardRef<HTMLInputElement, CheckboxWithLabelProps>( const CheckboxWithLabel = forwardRef<HTMLInputElement, CheckboxWithLabelProps>(
function CheckboxWithLabel( function CheckboxWithLabel(
{ label, id, description, as, fullWidth, readOnly, ...props }, { label, id, description, fullWidth, readOnly, ...props },
ref: Ref<HTMLInputElement>, ref: Ref<HTMLInputElement>,
) { ) {
return ( return (

View File

@ -1,4 +1,6 @@
/* eslint-disable react-refresh/only-export-components */
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
function Container({ children, className }: { children: ReactNode; className?: string }) { function Container({ children, className }: { children: ReactNode; className?: string }) {

View File

@ -1,8 +1,8 @@
import Card from "@components/Card"; import Card from "@components/Card";
export type CustomTooltipProps = { export interface CustomTooltipProps {
payload: { payload: { date: number; stat: number }; unit: string }[]; payload: { payload: { date: number; stat: number }; unit: string }[];
}; }
export default function CustomTooltip({ payload }: CustomTooltipProps) { export default function CustomTooltip({ payload }: CustomTooltipProps) {
if (payload?.length) { if (payload?.length) {

View File

@ -1,14 +1,16 @@
import { GridCard } from "@/components/Card";
import React from "react"; import React from "react";
import { GridCard } from "@/components/Card";
import { cx } from "../cva.config"; import { cx } from "../cva.config";
type Props = { interface Props {
IconElm?: React.FC<any>; IconElm?: React.FC<{ className: string | undefined }>;
headline: string; headline: string;
description?: string | React.ReactNode; description?: string | React.ReactNode;
BtnElm?: React.ReactNode; BtnElm?: React.ReactNode;
className?: string; className?: string;
}; }
export default function EmptyCard({ export default function EmptyCard({
IconElm, IconElm,
@ -27,10 +29,16 @@ export default function EmptyCard({
> >
<div className="max-w-[90%] space-y-1.5 text-center md:max-w-[60%]"> <div className="max-w-[90%] space-y-1.5 text-center md:max-w-[60%]">
<div className="space-y-2"> <div className="space-y-2">
{IconElm && <IconElm className="w-6 h-6 mx-auto text-blue-600 dark:text-blue-400" />} {IconElm && (
<h4 className="text-base font-bold leading-none text-black dark:text-white">{headline}</h4> <IconElm className="mx-auto h-6 w-6 text-blue-600 dark:text-blue-400" />
)}
<h4 className="text-base font-bold leading-none text-black dark:text-white">
{headline}
</h4>
</div> </div>
<p className="mx-auto text-sm text-slate-600 dark:text-slate-400">{description}</p> <p className="mx-auto text-sm text-slate-600 dark:text-slate-400">
{description}
</p>
</div> </div>
{BtnElm} {BtnElm}
</div> </div>

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
export default function ExtLink({ export default function ExtLink({

View File

@ -1,4 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { useFeatureFlag } from "../hooks/useFeatureFlag"; import { useFeatureFlag } from "../hooks/useFeatureFlag";
export function FeatureFlag({ export function FeatureFlag({

View File

@ -1,13 +1,14 @@
import React from "react"; import React from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
type Props = { interface Props {
label: string | React.ReactNode; label: string | React.ReactNode;
id?: string; id?: string;
as?: "label" | "span"; as?: "label" | "span";
description?: string | React.ReactNode | null; description?: string | React.ReactNode | null;
disabled?: boolean; disabled?: boolean;
}; }
export default function FieldLabel({ export default function FieldLabel({
label, label,
id, id,

View File

@ -9,7 +9,7 @@ export default function Fieldset({
disabled, disabled,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
fetcher?: FetcherWithComponents<any>; fetcher?: FetcherWithComponents<unknown>;
className?: string; className?: string;
disabled?: boolean; disabled?: boolean;
}) { }) {

View File

@ -2,19 +2,22 @@ import { Fragment, useCallback } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ArrowLeftEndOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/16/solid"; import { ArrowLeftEndOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/16/solid";
import { Menu, MenuButton } from "@headlessui/react"; import { Menu, MenuButton } from "@headlessui/react";
import { LuMonitorSmartphone } from "react-icons/lu";
import Container from "@/components/Container"; import Container from "@/components/Container";
import Card from "@/components/Card"; import Card from "@/components/Card";
import { LuMonitorSmartphone } from "react-icons/lu";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { useHidStore, useRTCStore, useUserStore } from "@/hooks/stores"; import { useHidStore, useRTCStore, useUserStore } from "@/hooks/stores";
import LogoBlueIcon from "@/assets/logo-blue.svg"; import LogoBlueIcon from "@/assets/logo-blue.svg";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
import USBStateStatus from "@components/USBStateStatus"; import USBStateStatus from "@components/USBStateStatus";
import PeerConnectionStatusCard from "@components/PeerConnectionStatusCard"; import PeerConnectionStatusCard from "@components/PeerConnectionStatusCard";
import { CLOUD_API, DEVICE_API } from "@/ui.config";
import api from "../api"; import api from "../api";
import { isOnDevice } from "../main"; import { isOnDevice } from "../main";
import { Button, LinkButton } from "./Button"; import { Button, LinkButton } from "./Button";
import { CLOUD_API, DEVICE_API } from "@/ui.config";
interface NavbarProps { interface NavbarProps {
isLoggedIn: boolean; isLoggedIn: boolean;

View File

@ -1,3 +1,5 @@
import { useEffect } from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { import {
useHidStore, useHidStore,
@ -6,7 +8,6 @@ import {
useSettingsStore, useSettingsStore,
useVideoStore, useVideoStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { useEffect } from "react";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
export default function InfoBar() { export default function InfoBar() {

View File

@ -1,7 +1,8 @@
import type { Ref } from "react"; import type { Ref } from "react";
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import FieldLabel from "@/components/FieldLabel";
import clsx from "clsx"; import clsx from "clsx";
import FieldLabel from "@/components/FieldLabel";
import Card from "@/components/Card"; import Card from "@/components/Card";
import { cva } from "@/cva.config"; import { cva } from "@/cva.config";
@ -84,7 +85,7 @@ const InputFieldWithLabel = forwardRef<HTMLInputElement, InputFieldWithLabelProp
{(label || description) && ( {(label || description) && (
<FieldLabel label={label} id={id} description={description} /> <FieldLabel label={label} id={id} description={description} />
)} )}
<InputField ref={ref as any} id={id} {...props} /> <InputField ref={ref as never} id={id} {...props} />
</div> </div>
); );
}, },

View File

@ -1,10 +1,11 @@
import { Button, LinkButton } from "@components/Button";
import Card from "@components/Card";
import { MdConnectWithoutContact } from "react-icons/md"; import { MdConnectWithoutContact } from "react-icons/md";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { LuEllipsisVertical } from "react-icons/lu"; import { LuEllipsisVertical } from "react-icons/lu";
import Card from "@components/Card";
import { Button, LinkButton } from "@components/Button";
function getRelativeTimeString(date: Date | number, lang = navigator.language): string { function getRelativeTimeString(date: Date | number, lang = navigator.language): string {
// Allow dates or times to be passed // Allow dates or times to be passed
const timeMs = typeof date === "number" ? date : date.getTime(); const timeMs = typeof date === "number" ? date : date.getTime();

View File

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react"; import { Dialog, DialogBackdrop, DialogPanel } from "@headlessui/react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
const Modal = React.memo(function Modal({ const Modal = React.memo(function Modal({

View File

@ -1,4 +1,5 @@
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import EmptyCard from "@/components/EmptyCard"; import EmptyCard from "@/components/EmptyCard";
export default function NotFoundPage() { export default function NotFoundPage() {

View File

@ -13,11 +13,9 @@ const PeerConnectionStatusMap = {
export type PeerConnections = keyof typeof PeerConnectionStatusMap; export type PeerConnections = keyof typeof PeerConnectionStatusMap;
type StatusProps = { type StatusProps = Record<PeerConnections, {
[key in PeerConnections]: {
statusIndicatorClassName: string; statusIndicatorClassName: string;
}; }>;
};
export default function PeerConnectionStatusCard({ export default function PeerConnectionStatusCard({
state, state,

View File

@ -1,9 +1,12 @@
import React from "react"; import React from "react";
import FieldLabel from "@/components/FieldLabel";
import clsx from "clsx"; import clsx from "clsx";
import Card from "./Card";
import FieldLabel from "@/components/FieldLabel";
import { cva } from "@/cva.config"; import { cva } from "@/cva.config";
import Card from "./Card";
type SelectMenuProps = Pick< type SelectMenuProps = Pick<
JSX.IntrinsicElements["select"], JSX.IntrinsicElements["select"],
"disabled" | "onChange" | "name" | "value" "disabled" | "onChange" | "name" | "value"

View File

@ -1,10 +1,11 @@
import Container from "@/components/Container";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import React from "react"; import React from "react";
import Container from "@/components/Container";
import LogoBlueIcon from "@/assets/logo-blue.png"; import LogoBlueIcon from "@/assets/logo-blue.png";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
type Props = { logoHref?: string; actionElement?: React.ReactNode }; interface Props { logoHref?: string; actionElement?: React.ReactNode }
export default function SimpleNavbar({ logoHref, actionElement }: Props) { export default function SimpleNavbar({ logoHref, actionElement }: Props) {
return ( return (

View File

@ -9,6 +9,7 @@ import {
XAxis, XAxis,
YAxis, YAxis,
} from "recharts"; } from "recharts";
import CustomTooltip, { CustomTooltipProps } from "@components/CustomTooltip"; import CustomTooltip, { CustomTooltipProps } from "@components/CustomTooltip";
export default function StatChart({ export default function StatChart({

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
interface Props { interface Props {

View File

@ -1,12 +1,13 @@
import { CheckIcon } from "@heroicons/react/16/solid"; import { CheckIcon } from "@heroicons/react/16/solid";
import { cva, cx } from "@/cva.config"; import { cva, cx } from "@/cva.config";
import Card from "@/components/Card"; import Card from "@/components/Card";
type Props = { interface Props {
nSteps: number; nSteps: number;
currStepIdx: number; currStepIdx: number;
size?: keyof typeof sizes; size?: keyof typeof sizes;
}; }
const sizes = { const sizes = {
SM: "text-xs leading-[12px]", SM: "text-xs leading-[12px]",

View File

@ -1,8 +1,5 @@
import "react-simple-keyboard/build/css/index.css"; import "react-simple-keyboard/build/css/index.css";
import { AvailableTerminalTypes, useUiStore } from "@/hooks/stores";
import { Button } from "./Button";
import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { ChevronDownIcon } from "@heroicons/react/16/solid";
import { cx } from "@/cva.config";
import { useEffect } from "react"; import { useEffect } from "react";
import { useXTerm } from "react-xtermjs"; import { useXTerm } from "react-xtermjs";
import { FitAddon } from "@xterm/addon-fit"; import { FitAddon } from "@xterm/addon-fit";
@ -11,6 +8,11 @@ import { WebglAddon } from "@xterm/addon-webgl";
import { Unicode11Addon } from "@xterm/addon-unicode11"; import { Unicode11Addon } from "@xterm/addon-unicode11";
import { ClipboardAddon } from "@xterm/addon-clipboard"; import { ClipboardAddon } from "@xterm/addon-clipboard";
import { cx } from "@/cva.config";
import { AvailableTerminalTypes, useUiStore } from "@/hooks/stores";
import { Button } from "./Button";
const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2"); const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2");
// Terminal theme configuration // Terminal theme configuration

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import FieldLabel from "@/components/FieldLabel";
import clsx from "clsx"; import clsx from "clsx";
import FieldLabel from "@/components/FieldLabel";
import { FieldError } from "@/components/InputField"; import { FieldError } from "@/components/InputField";
import Card from "@/components/Card"; import Card from "@/components/Card";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";

View File

@ -1,23 +1,20 @@
import React from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import KeyboardAndMouseConnectedIcon from "@/assets/keyboard-and-mouse-connected.png"; import KeyboardAndMouseConnectedIcon from "@/assets/keyboard-and-mouse-connected.png";
import React from "react";
import LoadingSpinner from "@components/LoadingSpinner"; import LoadingSpinner from "@components/LoadingSpinner";
import StatusCard from "@components/StatusCards"; import StatusCard from "@components/StatusCards";
import { HidState } from "@/hooks/stores"; import { HidState } from "@/hooks/stores";
type USBStates = HidState["usbState"]; type USBStates = HidState["usbState"];
type StatusProps = { type StatusProps = Record<USBStates, {
[key in USBStates]: {
icon: React.FC<{ className: string | undefined }>; icon: React.FC<{ className: string | undefined }>;
iconClassName: string; iconClassName: string;
statusIndicatorClassName: string; statusIndicatorClassName: string;
}; }>;
};
const USBStateMap: { const USBStateMap: Record<USBStates, string> = {
[key in USBStates]: string;
} = {
configured: "Connected", configured: "Connected",
attached: "Connecting", attached: "Connecting",
addressed: "Connecting", addressed: "Connecting",

View File

@ -1,8 +1,10 @@
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { Button } from "./Button"; import { Button } from "./Button";
import { GridCard } from "./Card"; import { GridCard } from "./Card";
import LoadingSpinner from "./LoadingSpinner"; import LoadingSpinner from "./LoadingSpinner";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
export default function UpdateInProgressStatusCard() { export default function UpdateInProgressStatusCard() {
const { navigateTo } = useDeviceUiNavigation(); const { navigateTo } = useDeviceUiNavigation();

View File

@ -1,9 +1,9 @@
import { useCallback } from "react"; import { useCallback , useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useJsonRpc } from "../hooks/useJsonRpc"; import { useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications"; import notifications from "../notifications";
import { SettingsItem } from "../routes/devices.$id.settings"; import { SettingsItem } from "../routes/devices.$id.settings";
import Checkbox from "./Checkbox"; import Checkbox from "./Checkbox";
import { Button } from "./Button"; import { Button } from "./Button";
import { SelectMenuBasic } from "./SelectMenuBasic"; import { SelectMenuBasic } from "./SelectMenuBasic";

View File

@ -1,14 +1,14 @@
import { useMemo } from "react"; import { useMemo , useCallback , useEffect, useState } from "react";
import { useCallback } from "react";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { InputFieldWithLabel } from "./InputField";
import { useEffect, useState } from "react";
import { UsbConfigState } from "../hooks/stores"; import { UsbConfigState } from "../hooks/stores";
import { useJsonRpc } from "../hooks/useJsonRpc"; import { useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications"; import notifications from "../notifications";
import { SettingsItem } from "../routes/devices.$id.settings"; import { SettingsItem } from "../routes/devices.$id.settings";
import { InputFieldWithLabel } from "./InputField";
import { SelectMenuBasic } from "./SelectMenuBasic"; import { SelectMenuBasic } from "./SelectMenuBasic";
import Fieldset from "./Fieldset"; import Fieldset from "./Fieldset";

View File

@ -1,11 +1,12 @@
import React from "react"; import React from "react";
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid"; import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
import { ArrowRightIcon } from "@heroicons/react/16/solid"; import { ArrowRightIcon } from "@heroicons/react/16/solid";
import { motion, AnimatePresence } from "framer-motion";
import { LuPlay } from "react-icons/lu";
import { Button, LinkButton } from "@components/Button"; import { Button, LinkButton } from "@components/Button";
import LoadingSpinner from "@components/LoadingSpinner"; import LoadingSpinner from "@components/LoadingSpinner";
import { GridCard } from "@components/Card"; import { GridCard } from "@components/Card";
import { motion, AnimatePresence } from "motion/react";
import { LuPlay } from "react-icons/lu";
interface OverlayContentProps { interface OverlayContentProps {
children: React.ReactNode; children: React.ReactNode;

View File

@ -1,11 +1,15 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import Keyboard from "react-simple-keyboard"; import Keyboard from "react-simple-keyboard";
import { Button } from "@components/Button";
import Card from "@components/Card";
import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { ChevronDownIcon } from "@heroicons/react/16/solid";
import { motion, AnimatePresence } from "framer-motion";
import Card from "@components/Card";
// eslint-disable-next-line import/order
import { Button } from "@components/Button";
import "react-simple-keyboard/build/css/index.css"; import "react-simple-keyboard/build/css/index.css";
import { useHidStore, useUiStore } from "@/hooks/stores"; import { useHidStore, useUiStore } from "@/hooks/stores";
import { motion, AnimatePresence } from "motion/react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
import useKeyboard from "@/hooks/useKeyboard"; import useKeyboard from "@/hooks/useKeyboard";

View File

@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
useDeviceSettingsStore, useDeviceSettingsStore,
useHidStore, useHidStore,
@ -15,9 +16,13 @@ import Actionbar from "@components/ActionBar";
import InfoBar from "@components/InfoBar"; import InfoBar from "@components/InfoBar";
import useKeyboard from "@/hooks/useKeyboard"; import useKeyboard from "@/hooks/useKeyboard";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { HDMIErrorOverlay, NoAutoplayPermissionsOverlay } from "./VideoOverlay";
import { ConnectionErrorOverlay } from "./VideoOverlay"; import {
import { LoadingOverlay } from "./VideoOverlay"; HDMIErrorOverlay,
NoAutoplayPermissionsOverlay,
ConnectionErrorOverlay,
LoadingOverlay,
} from "./VideoOverlay";
export default function WebRTCVideo() { export default function WebRTCVideo() {
// Video and stream related refs and states // Video and stream related refs and states
@ -82,13 +87,13 @@ export default function WebRTCVideo() {
const onVideoPlaying = useCallback(() => { const onVideoPlaying = useCallback(() => {
setIsPlaying(true); setIsPlaying(true);
videoElm.current && updateVideoSizeStore(videoElm.current); if (videoElm.current) updateVideoSizeStore(videoElm.current);
}, [updateVideoSizeStore]); }, [updateVideoSizeStore]);
// On mount, get the video size // On mount, get the video size
useEffect( useEffect(
function updateVideoSizeOnMount() { function updateVideoSizeOnMount() {
videoElm.current && updateVideoSizeStore(videoElm.current); if (videoElm.current) updateVideoSizeStore(videoElm.current);
}, },
[setVideoClientSize, updateVideoSizeStore, setVideoSize], [setVideoClientSize, updateVideoSizeStore, setVideoSize],
); );

View File

@ -1,11 +1,13 @@
import { Button } from "@components/Button";
import { LuHardDrive, LuPower, LuRotateCcw } from "react-icons/lu"; import { LuHardDrive, LuPower, LuRotateCcw } from "react-icons/lu";
import { useEffect, useState } from "react";
import { Button } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useEffect, useState } from "react";
import notifications from "@/notifications"; import notifications from "@/notifications";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useJsonRpc } from "../../hooks/useJsonRpc"; import { useJsonRpc } from "../../hooks/useJsonRpc";
import LoadingSpinner from "../LoadingSpinner";
const LONG_PRESS_DURATION = 3000; // 3 seconds for long press const LONG_PRESS_DURATION = 3000; // 3 seconds for long press
@ -102,11 +104,11 @@ export function ATXPowerControl() {
{atxState === null ? ( {atxState === null ? (
<Card className="flex h-[120px] items-center justify-center p-3"> <Card className="flex h-[120px] items-center justify-center p-3">
<LoadingSpinner className="w-6 h-6 text-blue-500 dark:text-blue-400" /> <LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
</Card> </Card>
) : ( ) : (
<Card className="h-[120px] animate-fadeIn opacity-0"> <Card className="h-[120px] animate-fadeIn opacity-0">
<div className="p-3 space-y-4"> <div className="space-y-4 p-3">
{/* Control Buttons */} {/* Control Buttons */}
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button

View File

@ -1,12 +1,13 @@
import { Button } from "@components/Button";
import { LuPower } from "react-icons/lu"; import { LuPower } from "react-icons/lu";
import { useCallback, useEffect, useState } from "react";
import { Button } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import FieldLabel from "../FieldLabel";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useCallback, useEffect, useState } from "react";
import notifications from "@/notifications"; import notifications from "@/notifications";
import LoadingSpinner from "../LoadingSpinner"; import FieldLabel from "@components/FieldLabel";
import LoadingSpinner from "@components/LoadingSpinner";
interface DCPowerState { interface DCPowerState {
isOn: boolean; isOn: boolean;
@ -59,11 +60,11 @@ export function DCPowerControl() {
{powerState === null ? ( {powerState === null ? (
<Card className="flex h-[160px] justify-center p-3"> <Card className="flex h-[160px] justify-center p-3">
<LoadingSpinner className="w-6 h-6 text-blue-500 dark:text-blue-400" /> <LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
</Card> </Card>
) : ( ) : (
<Card className="h-[160px] animate-fadeIn opacity-0"> <Card className="h-[160px] animate-fadeIn opacity-0">
<div className="p-3 space-y-4"> <div className="space-y-4 p-3">
{/* Power Controls */} {/* Power Controls */}
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button

View File

@ -1,12 +1,13 @@
import { Button } from "@components/Button";
import { LuTerminal } from "react-icons/lu"; import { LuTerminal } from "react-icons/lu";
import { useEffect, useState } from "react";
import { Button } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SelectMenuBasic } from "../SelectMenuBasic";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useEffect, useState } from "react";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { useUiStore } from "@/hooks/stores"; import { useUiStore } from "@/hooks/stores";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
interface SerialSettings { interface SerialSettings {
baudRate: string; baudRate: string;

View File

@ -1,19 +1,20 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { LuPower, LuTerminal, LuPlugZap } from "react-icons/lu";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import Card, { GridCard } from "@components/Card"; import Card, { GridCard } from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { Button } from "../Button";
import { LuPower, LuTerminal, LuPlugZap } from "react-icons/lu";
import { ATXPowerControl } from "@components/extensions/ATXPowerControl"; import { ATXPowerControl } from "@components/extensions/ATXPowerControl";
import { DCPowerControl } from "@components/extensions/DCPowerControl"; import { DCPowerControl } from "@components/extensions/DCPowerControl";
import { SerialConsole } from "@components/extensions/SerialConsole"; import { SerialConsole } from "@components/extensions/SerialConsole";
import notifications from "../../notifications"; import { Button } from "@components/Button";
import notifications from "@/notifications";
interface Extension { interface Extension {
id: string; id: string;
name: string; name: string;
description: string; description: string;
icon: any; icon: React.ElementType;
} }
const AVAILABLE_EXTENSIONS: Extension[] = [ const AVAILABLE_EXTENSIONS: Extension[] = [
@ -58,7 +59,9 @@ export default function ExtensionPopover() {
const handleSetActiveExtension = (extension: Extension | null) => { const handleSetActiveExtension = (extension: Extension | null) => {
send("setActiveExtension", { extensionId: extension?.id || "" }, resp => { send("setActiveExtension", { extensionId: extension?.id || "" }, resp => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(`Failed to set active extension: ${resp.error.data || "Unknown error"}`); notifications.error(
`Failed to set active extension: ${resp.error.data || "Unknown error"}`,
);
return; return;
} }
setActiveExtension(extension); setActiveExtension(extension);
@ -80,7 +83,7 @@ export default function ExtensionPopover() {
return ( return (
<GridCard> <GridCard>
<div className="p-4 py-3 space-y-4"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-headerBody">
<div className="space-y-4"> <div className="space-y-4">
{activeExtension ? ( {activeExtension ? (
@ -89,7 +92,7 @@ export default function ExtensionPopover() {
{renderActiveExtension()} {renderActiveExtension()}
<div <div
className="flex items-center justify-end space-x-2 opacity-0 animate-fadeIn" className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.2s", animationDelay: "0.2s",
@ -110,7 +113,7 @@ export default function ExtensionPopover() {
title="Extensions" title="Extensions"
description="Load and manage your extensions" description="Load and manage your extensions"
/> />
<Card className="opacity-0 animate-fadeIn"> <Card className="animate-fadeIn opacity-0">
<div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30"> <div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30">
{AVAILABLE_EXTENSIONS.map(extension => ( {AVAILABLE_EXTENSIONS.map(extension => (
<div <div

View File

@ -1,11 +1,6 @@
import { Button } from "@components/Button";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import Card, { GridCard } from "@components/Card";
import { PlusCircleIcon } from "@heroicons/react/20/solid"; import { PlusCircleIcon } from "@heroicons/react/20/solid";
import { useMemo, forwardRef, useEffect, useCallback } from "react"; import { useMemo, forwardRef, useEffect, useCallback } from "react";
import { formatters } from "@/utils";
import { RemoteVirtualMediaState, useMountMediaStore, useRTCStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { import {
LuArrowUpFromLine, LuArrowUpFromLine,
LuCheckCheck, LuCheckCheck,
@ -13,11 +8,17 @@ import {
LuPlus, LuPlus,
LuRadioReceiver, LuRadioReceiver,
} from "react-icons/lu"; } from "react-icons/lu";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "../../notifications";
import { useClose } from "@headlessui/react"; import { useClose } from "@headlessui/react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { Button } from "@components/Button";
import Card, { GridCard } from "@components/Card";
import { formatters } from "@/utils";
import { RemoteVirtualMediaState, useMountMediaStore, useRTCStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import notifications from "@/notifications";
const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => { const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
const diskDataChannelStats = useRTCStore(state => state.diskDataChannelStats); const diskDataChannelStats = useRTCStore(state => state.diskDataChannelStats);
@ -194,7 +195,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
<GridCard> <GridCard>
<div className="space-y-4 p-4 py-3"> <div className="space-y-4 p-4 py-3">
<div ref={ref} className="grid h-full grid-rows-headerBody"> <div ref={ref} className="grid h-full grid-rows-headerBody">
<div className="h-full space-y-4 "> <div className="h-full space-y-4">
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader
title="Virtual Media" title="Virtual Media"

View File

@ -1,15 +1,16 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { LuCornerDownLeft } from "react-icons/lu";
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
import { useClose } from "@headlessui/react";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { GridCard } from "@components/Card"; import { GridCard } from "@components/Card";
import { TextAreaWithLabel } from "@components/TextArea"; import { TextAreaWithLabel } from "@components/TextArea";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores"; import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores";
import notifications from "../../notifications";
import { useCallback, useEffect, useRef, useState } from "react";
import { LuCornerDownLeft } from "react-icons/lu";
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
import { useClose } from "@headlessui/react";
import { chars, keys, modifiers } from "@/keyboardMappings"; import { chars, keys, modifiers } from "@/keyboardMappings";
import notifications from "@/notifications";
const hidKeyboardPayload = (keys: number[], modifier: number) => { const hidKeyboardPayload = (keys: number[], modifier: number) => {
return { keys, modifier }; return { keys, modifier };
@ -59,6 +60,7 @@ export default function PasteModal() {
}); });
} }
} catch (error) { } catch (error) {
console.error(error);
notifications.error("Failed to paste text"); notifications.error("Failed to paste text");
} }
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode]); }, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode]);
@ -71,7 +73,7 @@ export default function PasteModal() {
return ( return (
<GridCard> <GridCard>
<div className="p-4 py-3 space-y-4"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-headerBody">
<div className="h-full space-y-4"> <div className="h-full space-y-4">
<div className="space-y-4"> <div className="space-y-4">
@ -81,7 +83,7 @@ export default function PasteModal() {
/> />
<div <div
className="space-y-2 opacity-0 animate-fadeIn" className="animate-fadeIn space-y-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.1s", animationDelay: "0.1s",
@ -120,8 +122,8 @@ export default function PasteModal() {
/> />
{invalidChars.length > 0 && ( {invalidChars.length > 0 && (
<div className="flex items-center mt-2 gap-x-2"> <div className="mt-2 flex items-center gap-x-2">
<ExclamationCircleIcon className="w-4 h-4 text-red-500 dark:text-red-400" /> <ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
<span className="text-xs text-red-500 dark:text-red-400"> <span className="text-xs text-red-500 dark:text-red-400">
The following characters won&apos;t be pasted:{" "} The following characters won&apos;t be pasted:{" "}
{invalidChars.join(", ")} {invalidChars.join(", ")}
@ -135,7 +137,7 @@ export default function PasteModal() {
</div> </div>
</div> </div>
<div <div
className="flex items-center justify-end opacity-0 animate-fadeIn gap-x-2" className="flex animate-fadeIn items-center justify-end gap-x-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.2s", animationDelay: "0.2s",

View File

@ -1,8 +1,8 @@
import { InputFieldWithLabel } from "@components/InputField";
import { useState, useRef } from "react"; import { useState, useRef } from "react";
import { LuPlus } from "react-icons/lu"; import { LuPlus, LuArrowLeft } from "react-icons/lu";
import { Button } from "../../Button";
import { LuArrowLeft } from "react-icons/lu"; import { InputFieldWithLabel } from "@/components/InputField";
import { Button } from "@/components/Button";
interface AddDeviceFormProps { interface AddDeviceFormProps {
onAddDevice: (name: string, macAddress: string) => void; onAddDevice: (name: string, macAddress: string) => void;
@ -26,7 +26,7 @@ export default function AddDeviceForm({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div <div
className="space-y-4 opacity-0 animate-fadeIn" className="animate-fadeIn space-y-4 opacity-0"
style={{ style={{
animationDuration: "0.5s", animationDuration: "0.5s",
animationFillMode: "forwards", animationFillMode: "forwards",
@ -73,7 +73,7 @@ export default function AddDeviceForm({
/> />
</div> </div>
<div <div
className="flex items-center justify-end space-x-2 opacity-0 animate-fadeIn" className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.2s", animationDelay: "0.2s",

View File

@ -1,8 +1,9 @@
import { Button } from "@components/Button";
import Card from "@components/Card";
import { FieldError } from "@components/InputField";
import { LuPlus, LuSend, LuTrash2 } from "react-icons/lu"; import { LuPlus, LuSend, LuTrash2 } from "react-icons/lu";
import { Button } from "@/components/Button";
import Card from "@/components/Card";
import { FieldError } from "@/components/InputField";
export interface StoredDevice { export interface StoredDevice {
name: string; name: string;
macAddress: string; macAddress: string;
@ -27,12 +28,14 @@ export default function DeviceList({
}: DeviceListProps) { }: DeviceListProps) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Card className="opacity-0 animate-fadeIn"> <Card className="animate-fadeIn opacity-0">
<div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30"> <div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30">
{storedDevices.map((device, index) => ( {storedDevices.map((device, index) => (
<div key={index} className="flex items-center justify-between p-3 gap-x-2"> <div key={index} className="flex items-center justify-between gap-x-2 p-3">
<div className="space-y-0.5"> <div className="space-y-0.5">
<p className="text-sm font-semibold leading-none text-slate-900 dark:text-slate-100">{device?.name}</p> <p className="text-sm font-semibold leading-none text-slate-900 dark:text-slate-100">
{device?.name}
</p>
<p className="text-sm text-slate-600 dark:text-slate-400"> <p className="text-sm text-slate-600 dark:text-slate-400">
{device.macAddress?.toLowerCase()} {device.macAddress?.toLowerCase()}
</p> </p>
@ -60,18 +63,13 @@ export default function DeviceList({
</div> </div>
</Card> </Card>
<div <div
className="flex items-center justify-end space-x-2 opacity-0 animate-fadeIn" className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.2s", animationDelay: "0.2s",
}} }}
> >
<Button <Button size="SM" theme="blank" text="Close" onClick={onCancelWakeOnLanModal} />
size="SM"
theme="blank"
text="Close"
onClick={onCancelWakeOnLanModal}
/>
<Button <Button
size="SM" size="SM"
theme="primary" theme="primary"

View File

@ -1,7 +1,8 @@
import Card from "@components/Card";
import { PlusCircleIcon } from "@heroicons/react/16/solid"; import { PlusCircleIcon } from "@heroicons/react/16/solid";
import { LuPlus } from "react-icons/lu"; import { LuPlus } from "react-icons/lu";
import { Button } from "../../Button";
import Card from "@/components/Card";
import { Button } from "@/components/Button";
export default function EmptyStateCard({ export default function EmptyStateCard({
onCancelWakeOnLanModal, onCancelWakeOnLanModal,
@ -11,15 +12,15 @@ export default function EmptyStateCard({
setShowAddForm: (show: boolean) => void; setShowAddForm: (show: boolean) => void;
}) { }) {
return ( return (
<div className="space-y-4 select-none"> <div className="select-none space-y-4">
<Card className="opacity-0 animate-fadeIn"> <Card className="animate-fadeIn opacity-0">
<div className="flex items-center justify-center py-8 text-center"> <div className="flex items-center justify-center py-8 text-center">
<div className="space-y-3"> <div className="space-y-3">
<div className="space-y-1"> <div className="space-y-1">
<div className="inline-block"> <div className="inline-block">
<Card> <Card>
<div className="p-1"> <div className="p-1">
<PlusCircleIcon className="w-4 h-4 text-blue-700 shrink-0 dark:text-white" /> <PlusCircleIcon className="h-4 w-4 shrink-0 text-blue-700 dark:text-white" />
</div> </div>
</Card> </Card>
</div> </div>
@ -34,7 +35,7 @@ export default function EmptyStateCard({
</div> </div>
</Card> </Card>
<div <div
className="flex items-center justify-end space-x-2 opacity-0 animate-fadeIn" className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{ style={{
animationDuration: "0.7s", animationDuration: "0.7s",
animationDelay: "0.2s", animationDelay: "0.2s",

View File

@ -1,10 +1,12 @@
import { useCallback, useEffect, useState } from "react";
import { useClose } from "@headlessui/react";
import { GridCard } from "@components/Card"; import { GridCard } from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useRTCStore, useUiStore } from "@/hooks/stores"; import { useRTCStore, useUiStore } from "@/hooks/stores";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { useCallback, useEffect, useState } from "react";
import { useClose } from "@headlessui/react";
import EmptyStateCard from "./EmptyStateCard"; import EmptyStateCard from "./EmptyStateCard";
import DeviceList, { StoredDevice } from "./DeviceList"; import DeviceList, { StoredDevice } from "./DeviceList";
import AddDeviceForm from "./AddDeviceForm"; import AddDeviceForm from "./AddDeviceForm";
@ -99,7 +101,7 @@ export default function WakeOnLanModal() {
return ( return (
<GridCard> <GridCard>
<div className="p-4 py-3 space-y-4"> <div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-headerBody"> <div className="grid h-full grid-rows-headerBody">
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader

View File

@ -1,9 +1,10 @@
import SidebarHeader from "@components/SidebarHeader";
import { GridCard } from "@components/Card";
import { useRTCStore, useUiStore } from "@/hooks/stores";
import StatChart from "@components/StatChart";
import { useInterval } from "usehooks-ts"; import { useInterval } from "usehooks-ts";
import SidebarHeader from "@/components/SidebarHeader";
import { GridCard } from "@/components/Card";
import { useRTCStore, useUiStore } from "@/hooks/stores";
import StatChart from "@/components/StatChart";
function createChartArray<T, K extends keyof T>( function createChartArray<T, K extends keyof T>(
stream: Map<number, T>, stream: Map<number, T>,
metric: K, metric: K,
@ -120,7 +121,7 @@ export default function ConnectionStatsSidebar() {
<GridCard> <GridCard>
<div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500"> <div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500">
{inboundRtpStats.size === 0 ? ( {inboundRtpStats.size === 0 ? (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-slate-700">Waiting for data...</p> <p className="text-slate-700">Waiting for data...</p>
</div> </div>
) : isMetricSupported(inboundRtpStats, "packetsLost") ? ( ) : isMetricSupported(inboundRtpStats, "packetsLost") ? (
@ -130,7 +131,7 @@ export default function ConnectionStatsSidebar() {
unit=" packets" unit=" packets"
/> />
) : ( ) : (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-black">Metric not supported</p> <p className="text-black">Metric not supported</p>
</div> </div>
)} )}
@ -149,7 +150,7 @@ export default function ConnectionStatsSidebar() {
<GridCard> <GridCard>
<div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500"> <div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500">
{inboundRtpStats.size === 0 ? ( {inboundRtpStats.size === 0 ? (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-slate-700">Waiting for data...</p> <p className="text-slate-700">Waiting for data...</p>
</div> </div>
) : isMetricSupported(candidatePairStats, "currentRoundTripTime") ? ( ) : isMetricSupported(candidatePairStats, "currentRoundTripTime") ? (
@ -167,7 +168,7 @@ export default function ConnectionStatsSidebar() {
unit=" ms" unit=" ms"
/> />
) : ( ) : (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-black">Metric not supported</p> <p className="text-black">Metric not supported</p>
</div> </div>
)} )}
@ -186,7 +187,7 @@ export default function ConnectionStatsSidebar() {
<GridCard> <GridCard>
<div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500"> <div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500">
{inboundRtpStats.size === 0 ? ( {inboundRtpStats.size === 0 ? (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-slate-700">Waiting for data...</p> <p className="text-slate-700">Waiting for data...</p>
</div> </div>
) : ( ) : (
@ -216,7 +217,7 @@ export default function ConnectionStatsSidebar() {
<GridCard> <GridCard>
<div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500"> <div className="flex h-[127px] w-full items-center justify-center text-sm text-slate-500">
{inboundRtpStats.size === 0 ? ( {inboundRtpStats.size === 0 ? (
<div className="flex flex-col items-center space-y-1 "> <div className="flex flex-col items-center space-y-1">
<p className="text-slate-700">Waiting for data...</p> <p className="text-slate-700">Waiting for data...</p>
</div> </div>
) : ( ) : (

View File

@ -1,7 +1,8 @@
import { useNavigate, useParams, NavigateOptions } from "react-router-dom"; import { useNavigate, useParams, NavigateOptions } from "react-router-dom";
import { isOnDevice } from "../main";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { isOnDevice } from "../main";
/** /**
* Generates the correct path based on whether the app is running on device or in cloud mode * Generates the correct path based on whether the app is running on device or in cloud mode
* *

View File

@ -1,5 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
import { FeatureFlagContext } from "../providers/FeatureFlagProvider";
import { FeatureFlagContext } from "@/providers/FeatureFlagContext";
export const useFeatureFlag = (minAppVersion: string) => { export const useFeatureFlag = (minAppVersion: string) => {
const context = useContext(FeatureFlagContext); const context = useContext(FeatureFlagContext);

View File

@ -1,4 +1,5 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useRTCStore } from "@/hooks/stores"; import { useRTCStore } from "@/hooks/stores";
export interface JsonRpcRequest { export interface JsonRpcRequest {
@ -56,7 +57,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
// The "API" can also "request" data from the client // The "API" can also "request" data from the client
// If the payload has a method, it's a request // If the payload has a method, it's a request
if ("method" in payload) { if ("method" in payload) {
onRequest && onRequest(payload); if (onRequest) onRequest(payload);
return; return;
} }

View File

@ -1,4 +1,5 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { useHidStore, useRTCStore } from "@/hooks/stores"; import { useHidStore, useRTCStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";

View File

@ -1,19 +1,18 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import type { RefObject } from "react"; import type { RefObject } from "react";
import { useIsMounted } from "./useIsMounted"; import { useIsMounted } from "./useIsMounted";
/** The size of the observed element. */ /** The size of the observed element. */
type Size = { interface Size {
/** The width of the observed element. */ /** The width of the observed element. */
width: number | undefined; width: number | undefined;
/** The height of the observed element. */ /** The height of the observed element. */
height: number | undefined; height: number | undefined;
}; }
/** The options for the ResizeObserver. */ /** The options for the ResizeObserver. */
type UseResizeObserverOptions<T extends HTMLElement = HTMLElement> = { interface UseResizeObserverOptions<T extends HTMLElement = HTMLElement> {
/** The ref of the element to observe. */ /** The ref of the element to observe. */
ref: RefObject<T>; ref: RefObject<T>;
/** /**
@ -26,7 +25,7 @@ type UseResizeObserverOptions<T extends HTMLElement = HTMLElement> = {
* @default 'content-box' * @default 'content-box'
*/ */
box?: "border-box" | "content-box" | "device-pixel-content-box"; box?: "border-box" | "content-box" | "device-pixel-content-box";
}; }
const initialSize: Size = { const initialSize: Size = {
width: undefined, width: undefined,
@ -102,7 +101,7 @@ export function useResizeObserver<T extends HTMLElement = HTMLElement>(
return () => { return () => {
observer.disconnect(); observer.disconnect();
}; };
}, [box, ref.current, isMounted]); }, [box, isMounted, ref]);
return { width, height }; return { width, height };
} }
@ -127,6 +126,6 @@ function extractSize(
return Array.isArray(entry[box]) return Array.isArray(entry[box])
? entry[box][0][sizeType] ? entry[box][0][sizeType]
: // @ts-ignore Support Firefox's non-standard behavior : // @ts-expect-error Support Firefox's non-standard behavior
(entry[box][sizeType] as number); (entry[box][sizeType] as number);
} }

View File

@ -1,5 +1,4 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import Root from "./root";
import "./index.css"; import "./index.css";
import { import {
createBrowserRouter, createBrowserRouter,
@ -8,19 +7,22 @@ import {
RouterProvider, RouterProvider,
useRouteError, useRouteError,
} from "react-router-dom"; } from "react-router-dom";
import DeviceRoute, { LocalDevice } from "@routes/devices.$id";
import DevicesRoute, { loader as DeviceListLoader } from "@routes/devices";
import SetupRoute from "@routes/devices.$id.setup";
import LoginRoute from "@routes/login";
import SignupRoute from "@routes/signup";
import AdoptRoute from "@routes/adopt";
import DeviceIdRename from "@routes/devices.$id.rename";
import DevicesIdDeregister from "@routes/devices.$id.deregister";
import NotFoundPage from "@components/NotFoundPage";
import EmptyCard from "@components/EmptyCard";
import { ExclamationTriangleIcon } from "@heroicons/react/16/solid"; import { ExclamationTriangleIcon } from "@heroicons/react/16/solid";
import EmptyCard from "@components/EmptyCard";
import NotFoundPage from "@components/NotFoundPage";
import DevicesIdDeregister from "@routes/devices.$id.deregister";
import DeviceIdRename from "@routes/devices.$id.rename";
import AdoptRoute from "@routes/adopt";
import SignupRoute from "@routes/signup";
import LoginRoute from "@routes/login";
import SetupRoute from "@routes/devices.$id.setup";
import DevicesRoute, { loader as DeviceListLoader } from "@routes/devices";
import DeviceRoute, { LocalDevice } from "@routes/devices.$id";
import Card from "@components/Card"; import Card from "@components/Card";
import DevicesAlreadyAdopted from "@routes/devices.already-adopted"; import DevicesAlreadyAdopted from "@routes/devices.already-adopted";
import Root from "./root";
import Notifications from "./notifications"; import Notifications from "./notifications";
import LoginLocalRoute from "./routes/login-local"; import LoginLocalRoute from "./routes/login-local";
import WelcomeLocalModeRoute from "./routes/welcome-local.mode"; import WelcomeLocalModeRoute from "./routes/welcome-local.mode";

View File

@ -1,8 +1,9 @@
import toast, { Toast, Toaster, useToasterStore } from "react-hot-toast"; import toast, { Toast, Toaster, useToasterStore } from "react-hot-toast";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid";
import Card from "@/components/Card"; import Card from "@/components/Card";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid";
interface NotificationOptions { interface NotificationOptions {
duration?: number; duration?: number;

View File

@ -0,0 +1,10 @@
import { createContext } from "react";
import { FeatureFlagContextType } from "./FeatureFlagProvider";
// Create the context
export const FeatureFlagContext = createContext<FeatureFlagContextType>({
appVersion: null,
isFeatureEnabled: () => false,
});

View File

@ -1,17 +1,12 @@
import { createContext } from "react";
import semver from "semver"; import semver from "semver";
interface FeatureFlagContextType { import { FeatureFlagContext } from "./FeatureFlagContext";
export interface FeatureFlagContextType {
appVersion: string | null; appVersion: string | null;
isFeatureEnabled: (minVersion: string) => boolean; isFeatureEnabled: (minVersion: string) => boolean;
} }
// Create the context
export const FeatureFlagContext = createContext<FeatureFlagContextType>({
appVersion: null,
isFeatureEnabled: () => false,
});
// Provider component that fetches version and provides context // Provider component that fetches version and provides context
export const FeatureFlagProvider = ({ export const FeatureFlagProvider = ({
children, children,

View File

@ -1,7 +1,9 @@
import { LoaderFunctionArgs, redirect } from "react-router-dom"; import { LoaderFunctionArgs, redirect } from "react-router-dom";
import api from "../api";
import { DEVICE_API } from "@/ui.config"; import { DEVICE_API } from "@/ui.config";
import api from "../api";
export interface CloudState { export interface CloudState {
connected: boolean; connected: boolean;
url: string; url: string;

View File

@ -6,6 +6,8 @@ import {
useActionData, useActionData,
useLoaderData, useLoaderData,
} from "react-router-dom"; } from "react-router-dom";
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
import { Button, LinkButton } from "@components/Button"; import { Button, LinkButton } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { CardHeader } from "@components/CardHeader"; import { CardHeader } from "@components/CardHeader";
@ -13,7 +15,6 @@ import DashboardNavbar from "@components/Header";
import { User } from "@/hooks/stores"; import { User } from "@/hooks/stores";
import { checkAuth } from "@/main"; import { checkAuth } from "@/main";
import Fieldset from "@components/Fieldset"; import Fieldset from "@components/Fieldset";
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
import { CLOUD_API } from "@/ui.config"; import { CLOUD_API } from "@/ui.config";
interface LoaderData { interface LoaderData {
@ -36,6 +37,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
return { message: "There was an error renaming your device. Please try again." }; return { message: "There was an error renaming your device. Please try again." };
} }
} catch (e) { } catch (e) {
console.error(e);
return { message: "There was an error renaming your device. Please try again." }; return { message: "There was an error renaming your device. Please try again." };
} }

View File

@ -1,15 +1,4 @@
import Card, { GridCard } from "@/components/Card";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@components/Button";
import LogoBlueIcon from "@/assets/logo-blue.svg";
import LogoWhiteIcon from "@/assets/logo-white.svg";
import {
MountMediaState,
RemoteVirtualMediaState,
useMountMediaStore,
useRTCStore,
} from "../hooks/stores";
import { cx } from "../cva.config";
import { import {
LuGlobe, LuGlobe,
LuLink, LuLink,
@ -18,8 +7,15 @@ import {
LuCheck, LuCheck,
LuUpload, LuUpload,
} from "react-icons/lu"; } from "react-icons/lu";
import { PlusCircleIcon , ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { TrashIcon } from "@heroicons/react/16/solid";
import { useNavigate } from "react-router-dom";
import Card, { GridCard } from "@/components/Card";
import { Button } from "@components/Button";
import LogoBlueIcon from "@/assets/logo-blue.svg";
import LogoWhiteIcon from "@/assets/logo-white.svg";
import { formatters } from "@/utils"; import { formatters } from "@/utils";
import { PlusCircleIcon } from "@heroicons/react/20/solid";
import AutoHeight from "@components/AutoHeight"; import AutoHeight from "@components/AutoHeight";
import { InputFieldWithLabel } from "@/components/InputField"; import { InputFieldWithLabel } from "@/components/InputField";
import DebianIcon from "@/assets/debian-icon.png"; import DebianIcon from "@/assets/debian-icon.png";
@ -28,14 +24,20 @@ import FedoraIcon from "@/assets/fedora-icon.png";
import OpenSUSEIcon from "@/assets/opensuse-icon.png"; import OpenSUSEIcon from "@/assets/opensuse-icon.png";
import ArchIcon from "@/assets/arch-icon.png"; import ArchIcon from "@/assets/arch-icon.png";
import NetBootIcon from "@/assets/netboot-icon.svg"; import NetBootIcon from "@/assets/netboot-icon.svg";
import { TrashIcon } from "@heroicons/react/16/solid";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import notifications from "../notifications";
import Fieldset from "@/components/Fieldset"; import Fieldset from "@/components/Fieldset";
import { isOnDevice } from "../main";
import { DEVICE_API } from "@/ui.config"; import { DEVICE_API } from "@/ui.config";
import { useNavigate } from "react-router-dom";
import { useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications";
import { isOnDevice } from "../main";
import { cx } from "../cva.config";
import {
MountMediaState,
RemoteVirtualMediaState,
useMountMediaStore,
useRTCStore,
} from "../hooks/stores";
export default function MountRoute() { export default function MountRoute() {
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -1,4 +1,5 @@
import { useNavigate, useOutletContext } from "react-router-dom"; import { useNavigate, useOutletContext } from "react-router-dom";
import { GridCard } from "@/components/Card"; import { GridCard } from "@/components/Card";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import LogoBlue from "@/assets/logo-blue.svg"; import LogoBlue from "@/assets/logo-blue.svg";

View File

@ -6,8 +6,9 @@ import {
useActionData, useActionData,
useLoaderData, useLoaderData,
} from "react-router-dom"; } from "react-router-dom";
import { Button, LinkButton } from "@components/Button";
import { ChevronLeftIcon } from "@heroicons/react/16/solid"; import { ChevronLeftIcon } from "@heroicons/react/16/solid";
import { Button, LinkButton } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { CardHeader } from "@components/CardHeader"; import { CardHeader } from "@components/CardHeader";
import { InputFieldWithLabel } from "@components/InputField"; import { InputFieldWithLabel } from "@components/InputField";
@ -15,9 +16,10 @@ import DashboardNavbar from "@components/Header";
import { User } from "@/hooks/stores"; import { User } from "@/hooks/stores";
import { checkAuth } from "@/main"; import { checkAuth } from "@/main";
import Fieldset from "@components/Fieldset"; import Fieldset from "@components/Fieldset";
import api from "../api";
import { CLOUD_API } from "@/ui.config"; import { CLOUD_API } from "@/ui.config";
import api from "../api";
interface LoaderData { interface LoaderData {
device: { id: string; name: string; user: { googleId: string } }; device: { id: string; name: string; user: { googleId: string } };
user: User; user: User;
@ -39,6 +41,7 @@ const action = async ({ params, request }: ActionFunctionArgs) => {
return { message: "There was an error renaming your device. Please try again." }; return { message: "There was an error renaming your device. Please try again." };
} }
} catch (e) { } catch (e) {
console.error(e);
return { message: "There was an error renaming your device. Please try again." }; return { message: "There was an error renaming your device. Please try again." };
} }
@ -80,9 +83,9 @@ export default function DeviceIdRename() {
picture={user?.picture} picture={user?.picture}
/> />
<div className="w-full h-full"> <div className="h-full w-full">
<div className="mt-4"> <div className="mt-4">
<div className="w-full h-full px-4 mx-auto space-y-6 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl"> <div className="mx-auto h-full w-full space-y-6 px-4 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl">
<div className="space-y-4"> <div className="space-y-4">
<LinkButton <LinkButton
size="SM" size="SM"
@ -100,7 +103,7 @@ export default function DeviceIdRename() {
<Fieldset> <Fieldset>
<Form method="POST" className="max-w-sm space-y-4"> <Form method="POST" className="max-w-sm space-y-4">
<div className="relative group"> <div className="group relative">
<InputFieldWithLabel <InputFieldWithLabel
label="New device name" label="New device name"
type="text" type="text"

View File

@ -1,4 +1,5 @@
import { LoaderFunctionArgs, redirect } from "react-router-dom"; import { LoaderFunctionArgs, redirect } from "react-router-dom";
import { getDeviceUiPath } from "../hooks/useAppNavigation"; import { getDeviceUiPath } from "../hooks/useAppNavigation";
export function loader({ params }: LoaderFunctionArgs) { export function loader({ params }: LoaderFunctionArgs) {

View File

@ -1,20 +1,22 @@
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SettingsItem } from "./devices.$id.settings";
import { useLoaderData, useNavigate } from "react-router-dom"; import { useLoaderData, useNavigate } from "react-router-dom";
import { Button, LinkButton } from "../components/Button";
import { DEVICE_API } from "../ui.config";
import api from "../api";
import { LocalDevice } from "./devices.$id";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { GridCard } from "../components/Card";
import { ShieldCheckIcon } from "@heroicons/react/24/outline"; import { ShieldCheckIcon } from "@heroicons/react/24/outline";
import notifications from "../notifications";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { InputFieldWithLabel } from "../components/InputField"; import api from "@/api";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SettingsSectionHeader } from "../components/SettingsSectionHeader"; import { GridCard } from "@/components/Card";
import { isOnDevice } from "../main"; import { Button, LinkButton } from "@/components/Button";
import { InputFieldWithLabel } from "@/components/InputField";
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
import { SettingsSectionHeader } from "@/components/SettingsSectionHeader";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import notifications from "@/notifications";
import { DEVICE_API } from "@/ui.config";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { isOnDevice } from "@/main";
import { LocalDevice } from "./devices.$id";
import { SettingsItem } from "./devices.$id.settings";
import { CloudState } from "./adopt"; import { CloudState } from "./adopt";
export const loader = async () => { export const loader = async () => {

View File

@ -1,9 +1,10 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useLocation, useRevalidator } from "react-router-dom";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { InputFieldWithLabel } from "@/components/InputField"; import { InputFieldWithLabel } from "@/components/InputField";
import api from "@/api"; import api from "@/api";
import { useLocalAuthModalStore } from "@/hooks/stores"; import { useLocalAuthModalStore } from "@/hooks/stores";
import { useLocation, useRevalidator } from "react-router-dom";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
export default function SecurityAccessLocalAuthRoute() { export default function SecurityAccessLocalAuthRoute() {
@ -53,6 +54,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
setError(data.error || "An error occurred while setting the password"); setError(data.error || "An error occurred while setting the password");
} }
} catch (error) { } catch (error) {
console.error(error);
setError("An error occurred while setting the password"); setError("An error occurred while setting the password");
} }
}; };
@ -92,6 +94,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
setError(data.error || "An error occurred while changing the password"); setError(data.error || "An error occurred while changing the password");
} }
} catch (error) { } catch (error) {
console.error(error);
setError("An error occurred while changing the password"); setError("An error occurred while changing the password");
} }
}; };
@ -113,6 +116,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
setError(data.error || "An error occurred while disabling the password"); setError(data.error || "An error occurred while disabling the password");
} }
} catch (error) { } catch (error) {
console.error(error);
setError("An error occurred while disabling the password"); setError("An error occurred while disabling the password");
} }
}; };

View File

@ -1,16 +1,19 @@
import { SettingsItem } from "./devices.$id.settings";
import { useCallback, useState, useEffect } from "react";
import { GridCard } from "@components/Card";
import { SettingsPageHeader } from "../components/SettingsPageheader"; import { SettingsPageHeader } from "../components/SettingsPageheader";
import Checkbox from "../components/Checkbox"; import Checkbox from "../components/Checkbox";
import { useJsonRpc } from "../hooks/useJsonRpc"; import { useJsonRpc } from "../hooks/useJsonRpc";
import { useCallback, useState, useEffect } from "react";
import notifications from "../notifications"; import notifications from "../notifications";
import { TextAreaWithLabel } from "../components/TextArea"; import { TextAreaWithLabel } from "../components/TextArea";
import { isOnDevice } from "../main"; import { isOnDevice } from "../main";
import { Button } from "../components/Button"; import { Button } from "../components/Button";
import { useSettingsStore } from "../hooks/stores"; import { useSettingsStore } from "../hooks/stores";
import { GridCard } from "@components/Card";
import { SettingsItem } from "./devices.$id.settings";
export default function SettingsAdvancedRoute() { export default function SettingsAdvancedRoute() {
const [send] = useJsonRpc(); const [send] = useJsonRpc();

View File

@ -1,6 +1,8 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { SettingsPageHeader } from "../components/SettingsPageheader"; import { SettingsPageHeader } from "../components/SettingsPageheader";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsItem } from "./devices.$id.settings"; import { SettingsItem } from "./devices.$id.settings";
export default function SettingsAppearanceRoute() { export default function SettingsAppearanceRoute() {

View File

@ -1,15 +1,17 @@
import { SettingsPageHeader } from "../components/SettingsPageheader";
import { SettingsItem } from "./devices.$id.settings"; import { useState , useEffect } from "react";
import { useState } from "react";
import { useEffect } from "react";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { SettingsPageHeader } from "../components/SettingsPageheader";
import { Button } from "../components/Button"; import { Button } from "../components/Button";
import notifications from "../notifications"; import notifications from "../notifications";
import Checkbox from "../components/Checkbox"; import Checkbox from "../components/Checkbox";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation"; import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { useDeviceStore } from "../hooks/stores"; import { useDeviceStore } from "../hooks/stores";
import { SettingsItem } from "./devices.$id.settings";
export default function SettingsGeneralRoute() { export default function SettingsGeneralRoute() {
const [send] = useJsonRpc(); const [send] = useJsonRpc();
const { navigateTo } = useDeviceUiNavigation(); const { navigateTo } = useDeviceUiNavigation();

View File

@ -1,11 +1,12 @@
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import Card from "@/components/Card";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import Card from "@/components/Card";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { UpdateState, useDeviceStore, useUpdateStore } from "@/hooks/stores"; import { UpdateState, useDeviceStore, useUpdateStore } from "@/hooks/stores";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import LoadingSpinner from "@/components/LoadingSpinner"; import LoadingSpinner from "@/components/LoadingSpinner";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";

View File

@ -1,13 +1,14 @@
import { useEffect } from "react";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SettingsItem } from "@routes/devices.$id.settings"; import { SettingsItem } from "@routes/devices.$id.settings";
import { BacklightSettings, useSettingsStore } from "@/hooks/stores"; import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
import { useEffect } from "react";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
import notifications from "../notifications"; import notifications from "../notifications";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { UsbInfoSetting } from "../components/UsbInfoSetting"; import { UsbInfoSetting } from "../components/UsbInfoSetting";
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
import { FeatureFlag } from "../components/FeatureFlag"; import { FeatureFlag } from "../components/FeatureFlag";
export default function SettingsHardwareRoute() { export default function SettingsHardwareRoute() {

View File

@ -1,3 +1,6 @@
import { CheckCircleIcon } from "@heroicons/react/16/solid";
import { useCallback, useEffect, useState } from "react";
import MouseIcon from "@/assets/mouse-icon.svg"; import MouseIcon from "@/assets/mouse-icon.svg";
import PointingFinger from "@/assets/pointing-finger.svg"; import PointingFinger from "@/assets/pointing-finger.svg";
import { GridCard } from "@/components/Card"; import { GridCard } from "@/components/Card";
@ -6,11 +9,11 @@ import { useDeviceSettingsStore, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import { CheckCircleIcon } from "@heroicons/react/16/solid";
import { useCallback, useEffect, useState } from "react";
import { FeatureFlag } from "../components/FeatureFlag"; import { FeatureFlag } from "../components/FeatureFlag";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { useFeatureFlag } from "../hooks/useFeatureFlag"; import { useFeatureFlag } from "../hooks/useFeatureFlag";
import { SettingsItem } from "./devices.$id.settings"; import { SettingsItem } from "./devices.$id.settings";
type ScrollSensitivity = "low" | "default" | "high"; type ScrollSensitivity = "low" | "default" | "high";

View File

@ -1,5 +1,4 @@
import { NavLink, Outlet, useLocation } from "react-router-dom"; import { NavLink, Outlet, useLocation } from "react-router-dom";
import Card from "@/components/Card";
import { import {
LuSettings, LuSettings,
LuKeyboard, LuKeyboard,
@ -10,8 +9,11 @@ import {
LuArrowLeft, LuArrowLeft,
LuPalette, LuPalette,
} from "react-icons/lu"; } from "react-icons/lu";
import { LinkButton } from "../components/Button";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import Card from "@/components/Card";
import { LinkButton } from "../components/Button";
import { cx } from "../cva.config"; import { cx } from "../cva.config";
import { useUiStore } from "../hooks/stores"; import { useUiStore } from "../hooks/stores";
import useKeyboard from "../hooks/useKeyboard"; import useKeyboard from "../hooks/useKeyboard";

View File

@ -1,12 +1,14 @@
import { SettingsPageHeader } from "@components/SettingsPageheader"; import { useState, useEffect } from "react";
import { SettingsItem } from "./devices.$id.settings";
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { TextAreaWithLabel } from "@/components/TextArea"; import { TextAreaWithLabel } from "@/components/TextArea";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useState, useEffect } from "react"; import { SettingsPageHeader } from "@components/SettingsPageheader";
import notifications from "../notifications"; import notifications from "../notifications";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsItem } from "./devices.$id.settings";
const defaultEdid = const defaultEdid =
"00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b";
const edids = [ const edids = [

View File

@ -1,8 +1,3 @@
import SimpleNavbar from "@components/SimpleNavbar";
import GridBackground from "@components/GridBackground";
import Container from "@components/Container";
import StepCounter from "@components/StepCounter";
import Fieldset from "@components/Fieldset";
import { import {
ActionFunctionArgs, ActionFunctionArgs,
Form, Form,
@ -12,12 +7,19 @@ import {
useParams, useParams,
useSearchParams, useSearchParams,
} from "react-router-dom"; } from "react-router-dom";
import SimpleNavbar from "@components/SimpleNavbar";
import GridBackground from "@components/GridBackground";
import Container from "@components/Container";
import StepCounter from "@components/StepCounter";
import Fieldset from "@components/Fieldset";
import { InputFieldWithLabel } from "@components/InputField"; import { InputFieldWithLabel } from "@components/InputField";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { checkAuth } from "@/main"; import { checkAuth } from "@/main";
import api from "../api";
import { CLOUD_API } from "@/ui.config"; import { CLOUD_API } from "@/ui.config";
import api from "../api";
const loader = async ({ params }: LoaderFunctionArgs) => { const loader = async ({ params }: LoaderFunctionArgs) => {
await checkAuth(); await checkAuth();
const res = await fetch(`${CLOUD_API}/devices/${params.id}`, { const res = await fetch(`${CLOUD_API}/devices/${params.id}`, {

View File

@ -1,4 +1,20 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import {
LoaderFunctionArgs,
Outlet,
Params,
redirect,
useLoaderData,
useLocation,
useNavigate,
useOutlet,
useParams,
useSearchParams,
} from "react-router-dom";
import { useInterval } from "usehooks-ts";
import FocusTrap from "focus-trap-react";
import { motion, AnimatePresence } from "framer-motion";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { import {
DeviceSettingsState, DeviceSettingsState,
@ -16,36 +32,23 @@ import {
VideoState, VideoState,
} from "@/hooks/stores"; } from "@/hooks/stores";
import WebRTCVideo from "@components/WebRTCVideo"; import WebRTCVideo from "@components/WebRTCVideo";
import {
LoaderFunctionArgs,
Outlet,
Params,
redirect,
useLoaderData,
useLocation,
useNavigate,
useOutlet,
useParams,
useSearchParams,
} from "react-router-dom";
import { checkAuth, isInCloud, isOnDevice } from "@/main"; import { checkAuth, isInCloud, isOnDevice } from "@/main";
import DashboardNavbar from "@components/Header"; import DashboardNavbar from "@components/Header";
import { useInterval } from "usehooks-ts";
import ConnectionStatsSidebar from "@/components/sidebar/connectionStats"; import ConnectionStatsSidebar from "@/components/sidebar/connectionStats";
import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc"; import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc";
import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard";
import api from "../api";
import { DeviceStatus } from "./welcome-local";
import FocusTrap from "focus-trap-react";
import Terminal from "@components/Terminal"; import Terminal from "@components/Terminal";
import { CLOUD_API, DEVICE_API } from "@/ui.config"; import { CLOUD_API, DEVICE_API } from "@/ui.config";
import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard";
import api from "../api";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { motion, AnimatePresence } from "motion/react";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation"; import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { FeatureFlagProvider } from "../providers/FeatureFlagProvider"; import { FeatureFlagProvider } from "../providers/FeatureFlagProvider";
import { SystemVersionInfo } from "./devices.$id.settings.general.update";
import notifications from "../notifications"; import notifications from "../notifications";
import { SystemVersionInfo } from "./devices.$id.settings.general.update";
import { DeviceStatus } from "./welcome-local";
interface LocalLoaderResp { interface LocalLoaderResp {
authMode: "password" | "noPassword" | null; authMode: "password" | "noPassword" | null;
} }

View File

@ -1,4 +1,6 @@
import { useLoaderData, useRevalidator } from "react-router-dom"; import { useLoaderData, useRevalidator } from "react-router-dom";
import { LuMonitorSmartphone } from "react-icons/lu";
import { ArrowRightIcon } from "@heroicons/react/16/solid";
import DashboardNavbar from "@components/Header"; import DashboardNavbar from "@components/Header";
import { LinkButton } from "@components/Button"; import { LinkButton } from "@components/Button";
@ -7,8 +9,6 @@ import useInterval from "@/hooks/useInterval";
import { checkAuth } from "@/main"; import { checkAuth } from "@/main";
import { User } from "@/hooks/stores"; import { User } from "@/hooks/stores";
import EmptyCard from "@components/EmptyCard"; import EmptyCard from "@components/EmptyCard";
import { LuMonitorSmartphone } from "react-icons/lu";
import { ArrowRightIcon } from "@heroicons/react/16/solid";
import { CLOUD_API } from "@/ui.config"; import { CLOUD_API } from "@/ui.config";
interface LoaderData { interface LoaderData {
@ -49,8 +49,8 @@ export default function DevicesRoute() {
/> />
<div className="flex h-full overflow-hidden"> <div className="flex h-full overflow-hidden">
<div className="w-full h-full px-4 mx-auto space-y-6 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl"> <div className="mx-auto h-full w-full space-y-6 px-4 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl">
<div className="flex items-center justify-between pb-4 mt-8 border-b border-b-slate-800/20 dark:border-b-slate-300/20"> <div className="mt-8 flex items-center justify-between border-b border-b-slate-800/20 pb-4 dark:border-b-slate-300/20">
<div> <div>
<h1 className="text-xl font-bold text-black dark:text-white"> <h1 className="text-xl font-bold text-black dark:text-white">
Cloud KVMs Cloud KVMs

View File

@ -1,19 +1,22 @@
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { useState } from "react";
import { LuEye, LuEyeOff } from "react-icons/lu";
import SimpleNavbar from "@components/SimpleNavbar"; import SimpleNavbar from "@components/SimpleNavbar";
import GridBackground from "@components/GridBackground"; import GridBackground from "@components/GridBackground";
import Container from "@components/Container"; import Container from "@components/Container";
import Fieldset from "@components/Fieldset"; import Fieldset from "@components/Fieldset";
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { InputFieldWithLabel } from "@components/InputField"; import { InputFieldWithLabel } from "@components/InputField";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { useState } from "react";
import { LuEye, LuEyeOff } from "react-icons/lu";
import LogoBlueIcon from "@/assets/logo-blue.png"; import LogoBlueIcon from "@/assets/logo-blue.png";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
import api from "../api";
import { DeviceStatus } from "./welcome-local";
import ExtLink from "../components/ExtLink";
import { DEVICE_API } from "@/ui.config"; import { DEVICE_API } from "@/ui.config";
import api from "../api";
import ExtLink from "../components/ExtLink";
import { DeviceStatus } from "./welcome-local";
const loader = async () => { const loader = async () => {
const res = await api const res = await api
.GET(`${DEVICE_API}/device/status`) .GET(`${DEVICE_API}/device/status`)
@ -31,12 +34,9 @@ const action = async ({ request }: ActionFunctionArgs) => {
const password = formData.get("password"); const password = formData.get("password");
try { try {
const response = await api.POST( const response = await api.POST(`${DEVICE_API}/auth/login-local`, {
`${DEVICE_API}/auth/login-local`, password,
{ });
password,
},
);
if (response.ok) { if (response.ok) {
return redirect("/"); return redirect("/");
@ -44,6 +44,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
return { error: "Invalid password" }; return { error: "Invalid password" };
} }
} catch (error) { } catch (error) {
console.error(error);
return { error: "An error occurred while logging in" }; return { error: "An error occurred while logging in" };
} }
}; };
@ -58,22 +59,28 @@ export default function LoginLocalRoute() {
<div className="grid min-h-screen grid-rows-layout"> <div className="grid min-h-screen grid-rows-layout">
<SimpleNavbar /> <SimpleNavbar />
<Container> <Container>
<div className="flex items-center justify-center w-full h-full isolate"> <div className="isolate flex h-full w-full items-center justify-center">
<div className="max-w-2xl -mt-32 space-y-8"> <div className="-mt-32 max-w-2xl space-y-8">
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" /> <img
src={LogoWhiteIcon}
alt=""
className="-ml-4 hidden h-[32px] dark:block"
/>
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" /> <img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
</div> </div>
<div className="space-y-2 text-center"> <div className="space-y-2 text-center">
<h1 className="text-4xl font-semibold text-black dark:text-white">Welcome back to JetKVM</h1> <h1 className="text-4xl font-semibold text-black dark:text-white">
Welcome back to JetKVM
</h1>
<p className="font-medium text-slate-600 dark:text-slate-400"> <p className="font-medium text-slate-600 dark:text-slate-400">
Enter your password to access your JetKVM. Enter your password to access your JetKVM.
</p> </p>
</div> </div>
<Fieldset className="space-y-12"> <Fieldset className="space-y-12">
<Form method="POST" className="max-w-sm mx-auto space-y-4"> <Form method="POST" className="mx-auto max-w-sm space-y-4">
<div className="space-y-4"> <div className="space-y-4">
<InputFieldWithLabel <InputFieldWithLabel
label="Password" label="Password"
@ -88,14 +95,14 @@ export default function LoginLocalRoute() {
onClick={() => setShowPassword(false)} onClick={() => setShowPassword(false)}
className="pointer-events-auto" className="pointer-events-auto"
> >
<LuEye className="w-4 h-4 cursor-pointer text-slate-500 dark:text-slate-400" /> <LuEye className="h-4 w-4 cursor-pointer text-slate-500 dark:text-slate-400" />
</div> </div>
) : ( ) : (
<div <div
onClick={() => setShowPassword(true)} onClick={() => setShowPassword(true)}
className="pointer-events-auto" className="pointer-events-auto"
> >
<LuEyeOff className="w-4 h-4 cursor-pointer text-slate-500 dark:text-slate-400" /> <LuEyeOff className="h-4 w-4 cursor-pointer text-slate-500 dark:text-slate-400" />
</div> </div>
) )
} }
@ -111,7 +118,7 @@ export default function LoginLocalRoute() {
textAlign="center" textAlign="center"
/> />
<div className="flex justify-start mt-4 text-xs text-slate-500 dark:text-slate-400"> <div className="mt-4 flex justify-start text-xs text-slate-500 dark:text-slate-400">
<ExtLink <ExtLink
href="https://jetkvm.com/docs/networking/local-access#reset-password" href="https://jetkvm.com/docs/networking/local-access#reset-password"
className="hover:underline" className="hover:underline"

View File

@ -1,6 +1,7 @@
import AuthLayout from "@components/AuthLayout";
import { useLocation, useSearchParams } from "react-router-dom"; import { useLocation, useSearchParams } from "react-router-dom";
import AuthLayout from "@components/AuthLayout";
export default function LoginRoute() { export default function LoginRoute() {
const [sq] = useSearchParams(); const [sq] = useSearchParams();
const location = useLocation(); const location = useLocation();

View File

@ -1,6 +1,7 @@
import AuthLayout from "@components/AuthLayout";
import { useLocation, useSearchParams } from "react-router-dom"; import { useLocation, useSearchParams } from "react-router-dom";
import AuthLayout from "@components/AuthLayout";
export default function SignupRoute() { export default function SignupRoute() {
const [sq] = useSearchParams(); const [sq] = useSearchParams();
const location = useLocation(); const location = useLocation();

View File

@ -1,15 +1,19 @@
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { useState } from "react";
import GridBackground from "@components/GridBackground"; import GridBackground from "@components/GridBackground";
import Container from "@components/Container"; import Container from "@components/Container";
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { useState } from "react";
import { GridCard } from "../components/Card";
import LogoBlueIcon from "@/assets/logo-blue.png"; import LogoBlueIcon from "@/assets/logo-blue.png";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
import { DEVICE_API } from "@/ui.config";
import { GridCard } from "../components/Card";
import { cx } from "../cva.config"; import { cx } from "../cva.config";
import api from "../api"; import api from "../api";
import { DeviceStatus } from "./welcome-local"; import { DeviceStatus } from "./welcome-local";
import { DEVICE_API } from "@/ui.config";
const loader = async () => { const loader = async () => {
const res = await api const res = await api

View File

@ -1,17 +1,20 @@
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { useState, useRef, useEffect } from "react";
import { LuEye, LuEyeOff } from "react-icons/lu";
import GridBackground from "@components/GridBackground"; import GridBackground from "@components/GridBackground";
import Container from "@components/Container"; import Container from "@components/Container";
import Fieldset from "@components/Fieldset"; import Fieldset from "@components/Fieldset";
import { ActionFunctionArgs, Form, redirect, useActionData } from "react-router-dom";
import { InputFieldWithLabel } from "@components/InputField"; import { InputFieldWithLabel } from "@components/InputField";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { useState, useRef, useEffect } from "react";
import { LuEye, LuEyeOff } from "react-icons/lu";
import LogoBlueIcon from "@/assets/logo-blue.png"; import LogoBlueIcon from "@/assets/logo-blue.png";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
import api from "../api";
import { DeviceStatus } from "./welcome-local";
import { DEVICE_API } from "@/ui.config"; import { DEVICE_API } from "@/ui.config";
import api from "../api";
import { DeviceStatus } from "./welcome-local";
const loader = async () => { const loader = async () => {
const res = await api const res = await api
.GET(`${DEVICE_API}/device/status`) .GET(`${DEVICE_API}/device/status`)

View File

@ -1,4 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { cx } from "cva";
import { redirect } from "react-router-dom";
import GridBackground from "@components/GridBackground"; import GridBackground from "@components/GridBackground";
import Container from "@components/Container"; import Container from "@components/Container";
import { LinkButton } from "@components/Button"; import { LinkButton } from "@components/Button";
@ -6,11 +9,12 @@ import LogoBlueIcon from "@/assets/logo-blue.png";
import LogoWhiteIcon from "@/assets/logo-white.svg"; import LogoWhiteIcon from "@/assets/logo-white.svg";
import DeviceImage from "@/assets/jetkvm-device-still.png"; import DeviceImage from "@/assets/jetkvm-device-still.png";
import LogoMark from "@/assets/logo-mark.png"; import LogoMark from "@/assets/logo-mark.png";
import { cx } from "cva";
import api from "../api";
import { redirect } from "react-router-dom";
import { DEVICE_API } from "@/ui.config"; import { DEVICE_API } from "@/ui.config";
import api from "../api";
export interface DeviceStatus { export interface DeviceStatus {
isSetup: boolean; isSetup: boolean;
} }

View File

@ -51,8 +51,8 @@ export const formatters = {
]; ];
let duration = (date.valueOf() - new Date().valueOf()) / 1000; let duration = (date.valueOf() - new Date().valueOf()) / 1000;
for (let i = 0; i < DIVISIONS.length; i++) {
const division = DIVISIONS[i]; for (const division of DIVISIONS) {
if (Math.abs(duration) < division.amount) { if (Math.abs(duration) < division.amount) {
return relativeTimeFormat.format(Math.round(duration), division.name); return relativeTimeFormat.format(Math.round(duration), division.name);
} }
@ -61,7 +61,7 @@ export const formatters = {
}, },
price: (price: number | bigint | string, options?: Intl.NumberFormatOptions) => { price: (price: number | bigint | string, options?: Intl.NumberFormatOptions) => {
let opts: Intl.NumberFormatOptions = { const opts: Intl.NumberFormatOptions = {
style: "currency", style: "currency",
currency: "USD", currency: "USD",
...(options || {}), ...(options || {}),