refactor: streamline package.json and tailwind.config.ts
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 1m56s

- Simplified dependencies in package.json by removing unused packages and organizing existing ones.
- Updated tailwind.config.ts for better readability and maintainability, ensuring consistent formatting and structure.
This commit is contained in:
2025-06-27 13:12:29 +05:00
parent 71fc914bde
commit 9e0d2d277b
37 changed files with 10199 additions and 9669 deletions

View File

@ -1,3 +1,9 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
"extends": [
"next/core-web-vitals",
"next/typescript"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn"
}
}

View File

@ -1,6 +1,6 @@
import { authOptions } from "@/app/auth";
import { DevicesTable } from "@/components/devices-table";
import DeviceFilter from "@/components/devices/device-filter";
import DynamicFilter from "@/components/generic-filter";
import AddDeviceDialogForm from "@/components/user/add-device-dialog";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
@ -29,7 +29,31 @@ export default async function Devices({
id="user-filters"
className=" pb-4 gap-4 flex sm:flex-row flex-col items-start justify-endO"
>
<DeviceFilter />
{/* <DeviceFilter /> */}
<DynamicFilter
description="Filter devices by name, MAC address, or vendor."
title="Device Filter"
inputs={[
{
name: "name",
label: "Device Name",
type: "string",
placeholder: "Enter device name",
},
{
name: "mac",
label: "MAC Address",
type: "string",
placeholder: "Enter MAC address",
},
{
name: "vendor",
label: "Vendor",
type: "string",
placeholder: "Enter vendor name",
}
]}
/>
</div>
<Suspense key={query || page} fallback={<DevicesTableSkeleton />}>
<DevicesTable parentalControl={false} searchParams={searchParams} />

View File

@ -0,0 +1,452 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; // Assuming shadcn Label
import { cn } from "@/lib/utils";
import { ListFilter, Loader2, X } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState, useTransition } from "react";
import { RadioGroup, RadioGroupItem } from "./ui/radio-group";
interface CheckboxGroupProps {
label: string;
options: Array<{ value: string; label: string }>;
selectedValues: string[];
onChange: (values: string[]) => void;
name: string;
}
const CheckboxGroup: React.FC<CheckboxGroupProps> = ({
label,
options,
selectedValues,
onChange,
name,
}) => {
const handleCheckedChange = (value: string, checked: boolean) => {
if (checked) {
onChange([...selectedValues, value]);
} else {
onChange(selectedValues.filter((v) => v !== value));
}
};
return (
<div className="flex flex-col gap-2 p-2 border rounded-md">
<Label className="font-semibold text-sm">{label}</Label>
<div className="flex flex-col gap-2">
{options.map((option) => (
<div key={`${name}-${option.value}`} className="flex items-center space-x-2">
<Checkbox
id={`${name}-${option.value}`}
checked={selectedValues.includes(option.value)}
onCheckedChange={(checked) =>
handleCheckedChange(option.value, !!checked)
}
/>
<Label htmlFor={`${name}-${option.value}`}>{option.label}</Label>
</div>
))}
</div>
</div>
);
};
type FilterOption = { value: string; label: string };
type FilterInputConfig<TKey extends string> =
| {
name: TKey;
label: string;
placeholder: string;
type: "string" | "number";
}
| {
name: TKey;
label: string;
type: "checkbox-group";
options: FilterOption[];
serialize?: (values: string[]) => string;
deserialize?: (urlValue: string) => string[];
}
| {
name: TKey;
label: string;
type: "radio-group";
options: FilterOption[];
};
// Utility type to extract the config for a specific key from the array
type GetConfigForKey<TKey, TConfigArray extends FilterInputConfig<string>[]> =
TConfigArray extends ReadonlyArray<infer U>
? U extends FilterInputConfig<string> & { name: TKey }
? U
: never
: never;
// This type maps filter keys to their appropriate value types (string or string[])
type FilterValues<
TFilterKeys extends string,
TInputs extends FilterInputConfig<string>[],
> = {
[K in TFilterKeys]: GetConfigForKey<K, TInputs> extends { type: "checkbox-group" }
? string[]
: GetConfigForKey<K, TInputs> extends { type: "radio-group" }
? string
: string;
};
// Default serialization/deserialization for checkbox-group if not provided
const defaultSerialize = (values: string[]) => values.join(",");
const defaultDeserialize = (urlValue: string) =>
urlValue ? urlValue.split(",") : [];
// --- Component Props ---
interface DynamicFilterProps<
TFilterKeys extends string,
TInputs extends FilterInputConfig<TFilterKeys>[],
> {
inputs: TInputs;
title?: string;
description?: string;
}
// --- Main DynamicFilter Component ---
export default function DynamicFilter<
TFilterKeys extends string,
TInputs extends FilterInputConfig<TFilterKeys>[],
>({
inputs,
title = "Filters",
description = "Select your desired filters here",
}: DynamicFilterProps<TFilterKeys, TInputs>) {
const { replace } = useRouter();
const [isOpen, setIsOpen] = useState(false);
const [disabled, startTransition] = useTransition();
const searchParams = useSearchParams();
const pathname = usePathname();
// Use a ref or memoized value for URLSearchParams to avoid re-creations
// and ensure it's always based on the latest searchParams.
const currentParams = useMemo(
() => new URLSearchParams(searchParams.toString()),
[searchParams],
);
// Initialize local state for input values based on URL
const initialInputState = useMemo(() => {
const initialState: Partial<FilterValues<TFilterKeys, TInputs>> = {};
for (const input of inputs) {
const urlValue = searchParams.get(input.name);
if (input.type === "checkbox-group") {
const deserialize = input.deserialize || defaultDeserialize;
(initialState as FilterValues<TFilterKeys, TInputs>)[input.name] = deserialize(urlValue || "") as FilterValues<TFilterKeys, TInputs>[typeof input.name];
} else {
(initialState as FilterValues<TFilterKeys, TInputs>)[input.name] = (urlValue || "") as FilterValues<TFilterKeys, TInputs>[typeof input.name];
}
}
return initialState as FilterValues<TFilterKeys, TInputs>;
}, [inputs, searchParams]); // Re-initialize if inputs config or URL searchParams change
const [inputValues, setInputValues] = useState<
FilterValues<TFilterKeys, TInputs>
>(initialInputState);
// Update local state if URL searchParams change while drawer is closed
// This is important if filters are applied externally or on page load
useEffect(() => {
if (!isOpen) {
setInputValues(initialInputState);
}
}, [isOpen, initialInputState]);
// Handler for text/number input changes
const handleTextOrNumberInputChange =
(name: TFilterKeys) => (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValues((prev) => ({
...prev,
[name]: e.target.value,
}));
};
// Handler for checkbox group changes
const handleCheckboxGroupChange =
(name: TFilterKeys) => (values: string[]) => {
setInputValues((prev) => ({
...prev,
[name]: values,
}));
};
// Handles applying all filters
const handleApplyFilters = () => {
const newParams = new URLSearchParams(currentParams.toString()); // Start fresh with current params
for (const input of inputs) {
const value = inputValues[input.name];
if (input.type === "checkbox-group") {
const serialize = input.serialize || defaultSerialize;
const serializedValue = serialize(value as string[]);
if (serializedValue) {
newParams.set(input.name, serializedValue);
} else {
newParams.delete(input.name);
}
} else {
// String/Number inputs
if (value) {
newParams.set(input.name, value as string);
} else {
newParams.delete(input.name);
}
}
}
newParams.set("page", "1"); // Always reset page on filter apply
startTransition(() => {
replace(`${pathname}?${newParams.toString()}`);
setIsOpen(false); // Close the drawer after applying filters
});
};
// Handles clearing all filters
const handleClearFilters = () => {
// Reset local input values
const clearedInputState: Partial<FilterValues<TFilterKeys, TInputs>> = {};
for (const input of inputs) {
if (input.type === "checkbox-group") {
(clearedInputState as FilterValues<TFilterKeys, TInputs>)[input.name as TFilterKeys] = [] as any;
} else {
(clearedInputState as FilterValues<TFilterKeys, TInputs>)[input.name as TFilterKeys] = "" as any;
}
}
setInputValues(clearedInputState as FilterValues<TFilterKeys, TInputs>);
startTransition(() => {
replace(pathname); // Navigate to base path
setIsOpen(false); // Close the drawer
});
};
// Dynamically constructs applied filters for badges
const appliedFilters = useMemo(() => {
const filters: Array<{
key: TFilterKeys;
value: string | string[];
label: string;
config: FilterInputConfig<TFilterKeys>;
}> = [];
for (const input of inputs) {
const urlValue = searchParams.get(input.name);
if (urlValue) {
if (input.type === "checkbox-group") {
const deserialize = input.deserialize || defaultDeserialize;
const deserialized = deserialize(urlValue);
if (deserialized.length > 0) {
filters.push({
key: input.name,
value: deserialized,
label: input.label,
config: input,
});
}
} else {
filters.push({
key: input.name,
value: urlValue,
label: input.label,
config: input,
});
}
}
}
return filters;
}, [searchParams, inputs]);
// Dynamic `prettyPrintFilter` for badges
const prettyPrintFilter = (
key: TFilterKeys,
value: string | string[],
config: FilterInputConfig<TFilterKeys>,
) => {
if (config.type === "checkbox-group") {
const labels = (value as string[])
.map(
(v) => config.options.find((opt) => opt.value === v)?.label || v,
)
.join(", ");
return (
<p>
{config.label.replace(/%20/g, " ")}: <span className="text-muted-foreground">{labels}</span>
</p>
);
}
return (
<p>
{config.label}: <span className="text-muted-foreground">{value}</span>
</p>
);
};
// Handles removing an individual filter
const handleRemoveFilter = (keyToRemove: TFilterKeys) => {
const newParams = new URLSearchParams(currentParams.toString());
newParams.delete(keyToRemove);
newParams.set("page", "1"); // Reset page after removing a filter
// Clear the specific input's local state
const inputConfig = inputs.find((input) => input.name === keyToRemove);
setInputValues((prev) => ({
...prev,
[keyToRemove]: inputConfig?.type === "checkbox-group" ? [] : "",
}));
startTransition(() => {
replace(`${pathname}?${newParams.toString()}`);
});
};
return (
<div className="flex flex-col items-start justify-start gap-2 w-full">
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerTrigger asChild>
<Button
className="w-full sm:w-48 flex items-end justify-between"
onClick={() => setIsOpen(!isOpen)}
variant="outline"
>
Filter
<ListFilter />
</Button>
</DrawerTrigger>
<DrawerContent>
<div className="mx-auto w-full max-w-3xl">
<DrawerHeader>
<DrawerTitle>{title}</DrawerTitle>
<DrawerDescription asChild>
<div>{description}</div>
</DrawerDescription>
</DrawerHeader>
<div className="grid sm:grid-cols-3 gap-4 p-4">
{inputs.map((input) => {
switch (input.type) {
case "string":
case "number":
return (
<Input
key={input.name}
placeholder={input.placeholder}
value={inputValues[input.name] as string}
onChange={handleTextOrNumberInputChange(input.name)}
type={input.type}
/>
);
case "checkbox-group":
return (
<CheckboxGroup
key={input.name}
name={input.name}
label={input.label}
options={input.options}
selectedValues={inputValues[input.name] as string[]}
onChange={handleCheckboxGroupChange(input.name)}
/>
);
case "radio-group":
return (
<div key={input.name} className="flex flex-col gap-2 p-2 border rounded-md">
<Label className="font-semibold text-sm">{input.label}</Label>
<RadioGroup
value={inputValues[input.name] as string}
onValueChange={(value) =>
setInputValues((prev) => ({
...prev,
[input.name]: value,
}))
}
>
{input.options.map((option) => (
<div key={`${input.name}-${option.value}`} className="flex items-center space-x-2">
<RadioGroupItem value={option.value} id={`${input.name}-${option.value}`} />
<Label htmlFor={`${input.name}-${option.value}`}>{option.label}</Label>
</div>
))}
</RadioGroup>
</div>
);
default:
return null;
}
})}
</div>
<DrawerFooter className="max-w-sm mx-auto">
<Button onClick={handleApplyFilters} disabled={disabled}>
{disabled ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Applying...
</>
) : (
<>Apply Filters</>
)}
</Button>
<Button variant="secondary" onClick={handleClearFilters}>
Clear Filters
</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</div>
</DrawerContent>
</Drawer>
<div className="flex gap-2 w-fit flex-wrap">
{appliedFilters.map((filter) => (
<Badge
aria-disabled={disabled}
variant={"outline"}
className={cn(
"flex p-2 gap-2 items-center justify-between",
{ "opacity-50 pointer-events-none": disabled },
)}
key={filter.key}
>
<span className="text-md">
{prettyPrintFilter(filter.key, filter.value, filter.config)}
</span>
{disabled ? (
<Loader2 className="animate-spin" size={16} />
) : (
<X
className="bg-sarLinkOrange/50 rounded-full p-1 hover:cursor-pointer hover:ring ring-sarLinkOrange"
size={16}
onClick={() => handleRemoveFilter(filter.key)}
>
Remove
</X>
)}
</Badge>
))}
</div>
</div>
);
}

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { Accordion as AccordionPrimitive } from "radix-ui"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,6 +1,6 @@
"use client"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
import * as React from "react"
import { buttonVariants } from "@/components/ui/button"

View File

@ -1,6 +1,6 @@
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
import { AspectRatio as AspectRatioPrimitive } from "radix-ui"
const AspectRatio = AspectRatioPrimitive.Root

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { Avatar as AvatarPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,5 +1,5 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { Slot as SlotPrimitive } from "radix-ui"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
@ -45,7 +45,7 @@ const BreadcrumbLink = React.forwardRef<
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
const Comp = asChild ? SlotPrimitive.Slot : "a"
return (
<Comp

View File

@ -1,4 +1,4 @@
import { Slot } from "@radix-ui/react-slot"
import { Slot as SlotPrimitive } from "radix-ui"
import { type VariantProps, cva } from "class-variance-authority"
import * as React from "react"
@ -45,7 +45,7 @@ function Button({
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? SlotPrimitive.Slot : "button"
return (
<Comp

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Checkbox as CheckboxPrimitive } from "radix-ui"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,6 +1,6 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
const Collapsible = CollapsiblePrimitive.Root

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { type DialogProps } from "radix-ui"
import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { ContextMenu as ContextMenuPrimitive } from "radix-ui"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Dialog as DialogPrimitive } from "radix-ui"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,8 +1,8 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui"
import {
Controller,
FormProvider,
@ -104,13 +104,13 @@ const FormLabel = React.forwardRef<
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
React.ElementRef<typeof SlotPrimitive.Slot>,
React.ComponentPropsWithoutRef<typeof SlotPrimitive.Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
<SlotPrimitive.Slot
ref={ref}
id={formItemId}
aria-describedby={

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { HoverCard as HoverCardPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Label as LabelPrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Menubar as MenubarPrimitive } from "radix-ui"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,5 +1,5 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { Progress as ProgressPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
import { Circle } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Select as SelectPrimitive } from "radix-ui"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { Dialog as SheetPrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { Slot as SlotPrimitive } from "radix-ui"
import { VariantProps, cva } from "class-variance-authority"
import { PanelLeft } from "lucide-react"
@ -442,7 +442,7 @@ const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div"
const Comp = asChild ? SlotPrimitive.Slot : "div"
return (
<Comp
@ -463,7 +463,7 @@ const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? SlotPrimitive.Slot : "button"
return (
<Comp
@ -563,7 +563,7 @@ const SidebarMenuButton = React.forwardRef<
},
ref
) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? SlotPrimitive.Slot : "button"
const { isMobile, state } = useSidebar()
const button = (
@ -609,7 +609,7 @@ const SidebarMenuAction = React.forwardRef<
showOnHover?: boolean
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? SlotPrimitive.Slot : "button"
return (
<Comp
@ -723,7 +723,7 @@ const SidebarMenuSubButton = React.forwardRef<
isActive?: boolean
}
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
const Comp = asChild ? SlotPrimitive.Slot : "a"
return (
<Comp

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { Slider as SliderPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { Switch as SwitchPrimitives } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { Toggle as TogglePrimitive } from "radix-ui"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

View File

@ -1,7 +1,7 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { Tooltip as TooltipPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"

18966
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +1,60 @@
{
"name": "sarlink-portal",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@faker-js/faker": "^9.3.0",
"@hookform/resolvers": "^5.1.1",
"@pyncz/tailwind-mask-image": "^2.0.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-menubar": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.61.4",
"axios": "^1.8.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"jotai": "2.8.0",
"lucide-react": "^0.523.0",
"millify": "^6.1.0",
"moment": "^2.30.1",
"motion": "^12.15.0",
"next": "15.3.3",
"next-auth": "^4.24.11",
"next-themes": "^0.4.6",
"nextjs-toploader": "^3.7.15",
"nuqs": "^2.4.3",
"react": "19.1.0",
"react-aria-components": "^1.5.0",
"react-day-picker": "^9.7.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.58.1",
"react-phone-number-input": "^3.4.9",
"react-resizable-panels": "^3.0.3",
"recharts": "^3.0.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.25.67"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/node": "^22.10.2",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.2",
"eslint": "^9.17.0",
"eslint-config-next": "15.1.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss-motion": "^1.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
"name": "sarlink-portal",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@faker-js/faker": "^9.3.0",
"@hookform/resolvers": "^5.1.1",
"@pyncz/tailwind-mask-image": "^2.0.0",
"@tanstack/react-query": "^5.61.4",
"axios": "^1.8.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"jotai": "2.8.0",
"lucide-react": "^0.523.0",
"millify": "^6.1.0",
"moment": "^2.30.1",
"motion": "^12.15.0",
"next": "15.3.3",
"next-auth": "^4.24.11",
"next-themes": "^0.4.6",
"nextjs-toploader": "^3.7.15",
"nuqs": "^2.4.3",
"radix-ui": "^1.4.2",
"react": "19.1.0",
"react-aria-components": "^1.5.0",
"react-day-picker": "^9.7.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.58.1",
"react-phone-number-input": "^3.4.9",
"react-resizable-panels": "^3.0.3",
"recharts": "^3.0.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"zod": "^3.25.67"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.2",
"eslint": "^9.17.0",
"eslint-config-next": "15.1.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss-motion": "^1.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}

View File

@ -9,101 +9,101 @@ export default {
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
sarLinkOrange: '#f49b5b',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
fontFamily: {
sans: [
'var(--font-barlow)'
],
mono: [
'var(--font-mono)'
]
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
extend: {
colors: {
sarLinkOrange: '#f49b5b',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
fontFamily: {
sans: [
'var(--font-barlow)'
],
mono: [
'var(--font-mono)'
]
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [
tailwindcssAnimate,
tailwindcssMotion,
require("@pyncz/tailwind-mask-image"),
require("tailwindcss-animate")
],
require("tailwindcss-animate")
],
} satisfies Config;