mirror of
https://github.com/MeshEnvy/mesh-forge.git
synced 2026-08-02 06:53:14 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { useMutation } from "convex/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "../../convex/_generated/api";
|
||||
|
||||
interface ProfileEditorProps {
|
||||
initialData?: any;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function ProfileEditor({
|
||||
initialData,
|
||||
onSave,
|
||||
onCancel,
|
||||
}: ProfileEditorProps) {
|
||||
const createProfile = useMutation(api.profiles.create);
|
||||
const updateProfile = useMutation(api.profiles.update);
|
||||
|
||||
const { register, handleSubmit, setValue, watch } = useForm({
|
||||
defaultValues: initialData || {
|
||||
name: "",
|
||||
targets: [],
|
||||
config: {
|
||||
MESHTASTIC_EXCLUDE_MQTT: false,
|
||||
MESHTASTIC_EXCLUDE_AUDIO: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const targets = watch("targets");
|
||||
|
||||
const toggleTarget = (target: string) => {
|
||||
const current = targets || [];
|
||||
if (current.includes(target)) {
|
||||
setValue(
|
||||
"targets",
|
||||
current.filter((t: string) => t !== target),
|
||||
);
|
||||
} else {
|
||||
setValue("targets", [...current, target]);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
if (initialData?._id) {
|
||||
await updateProfile({ id: initialData._id, ...data });
|
||||
} else {
|
||||
await createProfile(data);
|
||||
}
|
||||
onSave();
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="space-y-6 bg-slate-900 p-6 rounded-lg border border-slate-800"
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Profile Name</label>
|
||||
<Input
|
||||
{...register("name")}
|
||||
className="bg-slate-950 border-slate-800"
|
||||
placeholder="e.g. Solar Repeater"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Targets</label>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
{["tbeam", "rak4631", "heltec_v3", "pico"].map((t) => (
|
||||
<div key={t} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={t}
|
||||
checked={targets?.includes(t)}
|
||||
onCheckedChange={() => toggleTarget(t)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={t}
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{t}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">
|
||||
Configuration Flags
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="no_mqtt"
|
||||
checked={watch("config.MESHTASTIC_EXCLUDE_MQTT")}
|
||||
onCheckedChange={(checked) =>
|
||||
setValue("config.MESHTASTIC_EXCLUDE_MQTT", checked)
|
||||
}
|
||||
/>
|
||||
<label htmlFor="no_mqtt">Exclude MQTT</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="no_audio"
|
||||
checked={watch("config.MESHTASTIC_EXCLUDE_AUDIO")}
|
||||
onCheckedChange={(checked) =>
|
||||
setValue("config.MESHTASTIC_EXCLUDE_AUDIO", checked)
|
||||
}
|
||||
/>
|
||||
<label htmlFor="no_audio">Exclude Audio</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button type="button" variant="ghost" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Save Profile</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("grid place-content-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox };
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
Reference in New Issue
Block a user