diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index b0762db..f199cdd 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -12,6 +12,7 @@ import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQu import { ShipmentToasts } from "@/components/frontend/logistics/ShipmentToasts"; import { GameTickRealtime } from "@/components/frontend/realtime/GameTickRealtime"; import { LandingPage } from "@/components/frontend/LandingPage"; +import { Toaster } from "@/components/ui/sonner"; export const metadata: Metadata = { description: "A gamified platform for Polaris Task Force, an Arma 3 unit.", @@ -91,6 +92,7 @@ export default async function RootLayout(props: { children: React.ReactNode }) { )} {user && } {isLogistics && } + ); diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..6eb2a05 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", + outline: + "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + link: "text-primary underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..f5a7e43 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import { CheckIcon } from "lucide-react" +import { Checkbox as CheckboxPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx new file mode 100644 index 0000000..fe17589 --- /dev/null +++ b/src/components/ui/sonner.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { Toaster as Sonner } from "sonner"; + +type ToasterProps = React.ComponentProps; + +const Toaster = ({ ...props }: ToasterProps) => { + return ; +}; + +export { Toaster }; diff --git a/src/hooks/useNow.ts b/src/hooks/useNow.ts new file mode 100644 index 0000000..ef6eda7 --- /dev/null +++ b/src/hooks/useNow.ts @@ -0,0 +1,14 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export function useNow(intervalMs = 1000): number { + const [now, setNow] = useState(() => Date.now()); + + useEffect(() => { + const interval = setInterval(() => setNow(Date.now()), intervalMs); + return () => clearInterval(interval); + }, [intervalMs]); + + return now; +}