diff --git a/.gitignore b/.gitignore index 595d2acb..489a07e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ # belong in git's global ignore instead: # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` +# Ignore all .DS_Store files +*.DS_Store + # Ignore bundler config. /.bundle diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 33838439..0773790d 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,6 +1,27 @@ # frozen_string_literal: true -class DashboardController < InertiaController +class DashboardController < ApplicationController + skip_before_action :authenticate + inertia_config default_render: true + + # Override the inertia_share for this controller to handle unauthenticated users + inertia_share flash: -> { flash.to_hash }, + auth: -> { + if Current.user + { + user: Current.user.as_json(only: %i[id name email verified created_at updated_at]), + session: Current.session.as_json(only: %i[id]) + } + else + { + user: nil, + session: nil + } + end + } + def index + # Dashboard renders with or without authentication + render inertia: 'dashboard/productivity-enhanced' end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index c0036d73..c7789ff7 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -13,9 +13,9 @@ def create @session = user.sessions.create! cookies.signed.permanent[:session_token] = {value: @session.id, httponly: true} - redirect_to dashboard_path, notice: "Signed in successfully" + redirect_to dashboard_url, notice: "Signed in successfully" else - redirect_to sign_in_path, alert: "That email or password is incorrect" + redirect_to sign_in_url, alert: "That email or password is incorrect" end end diff --git a/app/frontend/components/app-header.tsx b/app/frontend/components/app-header.tsx index a301bacb..a1564ee3 100644 --- a/app/frontend/components/app-header.tsx +++ b/app/frontend/components/app-header.tsx @@ -213,9 +213,9 @@ export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) { diff --git a/app/frontend/components/nav-user.tsx b/app/frontend/components/nav-user.tsx index cf51f5cd..541f353d 100644 --- a/app/frontend/components/nav-user.tsx +++ b/app/frontend/components/nav-user.tsx @@ -22,6 +22,17 @@ export function NavUser() { const { state } = useSidebar() const isMobile = useIsMobile() + // Create a default guest user when not authenticated + const user = auth.user || { + id: 0, + name: "Guest User", + email: "guest@example.com", + avatar: null, + verified: false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + } + return ( @@ -31,7 +42,7 @@ export function NavUser() { size="lg" className="text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent group" > - + @@ -42,7 +53,7 @@ export function NavUser() { isMobile ? "bottom" : state === "collapsed" ? "left" : "bottom" } > - + diff --git a/app/frontend/components/ui/accordion.tsx b/app/frontend/components/ui/accordion.tsx new file mode 100644 index 00000000..51682c27 --- /dev/null +++ b/app/frontend/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } diff --git a/app/frontend/components/ui/alert-dialog.tsx b/app/frontend/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..1036d19a --- /dev/null +++ b/app/frontend/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" +import type * as React from "react" + +import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +} \ No newline at end of file diff --git a/app/frontend/components/ui/alert.tsx b/app/frontend/components/ui/alert.tsx new file mode 100644 index 00000000..49c4123e --- /dev/null +++ b/app/frontend/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import { type VariantProps, cva } from "class-variance-authority" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertDescription, AlertTitle } diff --git a/app/frontend/components/ui/calendar.tsx b/app/frontend/components/ui/calendar.tsx new file mode 100644 index 00000000..ea1a11af --- /dev/null +++ b/app/frontend/components/ui/calendar.tsx @@ -0,0 +1,214 @@ +"use client" + +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "lucide-react" +import * as React from "react" +import type { DayButton} from "react-day-picker"; +import { DayPicker, getDefaultClassNames } from "react-day-picker" + +import { Button, buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = "label", + buttonVariant = "ghost", + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps["variant"] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => + date.toLocaleString("default", { month: "short" }), + ...formatters, + }} + classNames={{ + root: cn("w-fit", defaultClassNames.root), + months: cn( + "flex gap-4 flex-col md:flex-row relative", + defaultClassNames.months + ), + month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + nav: cn( + "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + defaultClassNames.nav + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_previous + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + defaultClassNames.button_next + ), + month_caption: cn( + "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + defaultClassNames.month_caption + ), + dropdowns: cn( + "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + defaultClassNames.dropdowns + ), + dropdown_root: cn( + "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", + defaultClassNames.dropdown_root + ), + dropdown: cn( + "absolute bg-popover inset-0 opacity-0", + defaultClassNames.dropdown + ), + caption_label: cn( + "select-none font-medium", + captionLayout === "label" + ? "text-sm" + : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", + defaultClassNames.caption_label + ), + table: "w-full border-collapse", + weekdays: cn("flex", defaultClassNames.weekdays), + weekday: cn( + "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + defaultClassNames.weekday + ), + week: cn("flex w-full mt-2", defaultClassNames.week), + week_number_header: cn( + "select-none w-(--cell-size)", + defaultClassNames.week_number_header + ), + week_number: cn( + "text-[0.8rem] select-none text-muted-foreground", + defaultClassNames.week_number + ), + day: cn( + "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", + defaultClassNames.day + ), + range_start: cn( + "rounded-l-md bg-accent", + defaultClassNames.range_start + ), + range_middle: cn("rounded-none", defaultClassNames.range_middle), + range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), + today: cn( + "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", + defaultClassNames.today + ), + outside: cn( + "text-muted-foreground aria-selected:text-muted-foreground", + defaultClassNames.outside + ), + disabled: cn( + "text-muted-foreground opacity-50", + defaultClassNames.disabled + ), + hidden: cn("invisible", defaultClassNames.hidden), + ...classNames, + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
+ ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === "left") { + return ( + + ) + } + + if (orientation === "right") { + return ( + + ) + } + + return ( + + ) + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( + + ) +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon", + ...props +}: React.ComponentProps) { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +} + +export { + Carousel, + type CarouselApi, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} \ No newline at end of file diff --git a/app/frontend/components/ui/collapsible.tsx b/app/frontend/components/ui/collapsible.tsx index 0ab55687..96bd0dc2 100644 --- a/app/frontend/components/ui/collapsible.tsx +++ b/app/frontend/components/ui/collapsible.tsx @@ -1,4 +1,7 @@ +"use client" + import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" +import type * as React from "react" function Collapsible({ ...props diff --git a/app/frontend/components/ui/command.tsx b/app/frontend/components/ui/command.tsx new file mode 100644 index 00000000..d35d7fcf --- /dev/null +++ b/app/frontend/components/ui/command.tsx @@ -0,0 +1,179 @@ +"use client" + +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" +import type * as React from "react" + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { cn } from "@/lib/utils" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} diff --git a/app/frontend/components/ui/context-menu.tsx b/app/frontend/components/ui/context-menu.tsx new file mode 100644 index 00000000..487aa76d --- /dev/null +++ b/app/frontend/components/ui/context-menu.tsx @@ -0,0 +1,252 @@ +"use client" + +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + ContextMenu, + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuGroup, + ContextMenuItem, + ContextMenuLabel, + ContextMenuPortal, + ContextMenuRadioGroup, + ContextMenuRadioItem, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} \ No newline at end of file diff --git a/app/frontend/components/ui/drawer.tsx b/app/frontend/components/ui/drawer.tsx new file mode 100644 index 00000000..3b62efdb --- /dev/null +++ b/app/frontend/components/ui/drawer.tsx @@ -0,0 +1,63 @@ +"use client" + +import type * as React from "react" + +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" + +// Drawer components using Sheet as base +function Drawer({ ...props }: React.ComponentProps) { + return +} + +function DrawerTrigger({ ...props }: React.ComponentProps) { + return +} + +function DrawerClose({ ...props }: React.ComponentProps) { + return +} + +function DrawerContent({ + side = "bottom", + ...props +}: React.ComponentProps) { + return +} + +function DrawerHeader({ ...props }: React.ComponentProps) { + return +} + +function DrawerFooter({ ...props }: React.ComponentProps) { + return +} + +function DrawerTitle({ ...props }: React.ComponentProps) { + return +} + +function DrawerDescription({ + ...props +}: React.ComponentProps) { + return +} + +export { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} \ No newline at end of file diff --git a/app/frontend/components/ui/hover-card.tsx b/app/frontend/components/ui/hover-card.tsx new file mode 100644 index 00000000..7eedd60e --- /dev/null +++ b/app/frontend/components/ui/hover-card.tsx @@ -0,0 +1,44 @@ +"use client" + +import * as HoverCardPrimitive from "@radix-ui/react-hover-card" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function HoverCard({ + ...props +}: React.ComponentProps) { + return +} + +function HoverCardTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function HoverCardContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { HoverCard, HoverCardContent, HoverCardTrigger } diff --git a/app/frontend/components/ui/popover.tsx b/app/frontend/components/ui/popover.tsx new file mode 100644 index 00000000..5decb803 --- /dev/null +++ b/app/frontend/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as PopoverPrimitive from "@radix-ui/react-popover" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } \ No newline at end of file diff --git a/app/frontend/components/ui/progress.tsx b/app/frontend/components/ui/progress.tsx new file mode 100644 index 00000000..04908574 --- /dev/null +++ b/app/frontend/components/ui/progress.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as ProgressPrimitive from "@radix-ui/react-progress" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/app/frontend/components/ui/radio-group.tsx b/app/frontend/components/ui/radio-group.tsx new file mode 100644 index 00000000..02f76362 --- /dev/null +++ b/app/frontend/components/ui/radio-group.tsx @@ -0,0 +1,45 @@ +"use client" + +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { CircleIcon } from "lucide-react" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function RadioGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function RadioGroupItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { RadioGroup, RadioGroupItem } \ No newline at end of file diff --git a/app/frontend/components/ui/slider.tsx b/app/frontend/components/ui/slider.tsx new file mode 100644 index 00000000..726f57ef --- /dev/null +++ b/app/frontend/components/ui/slider.tsx @@ -0,0 +1,63 @@ +"use client" + +import * as SliderPrimitive from "@radix-ui/react-slider" +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Slider({ + className, + defaultValue, + value, + min = 0, + max = 100, + ...props +}: React.ComponentProps) { + const _values = React.useMemo( + () => + Array.isArray(value) + ? value + : Array.isArray(defaultValue) + ? defaultValue + : [min, max], + [value, defaultValue, min, max], + ) + + return ( + + + + + {Array.from({ length: _values.length }, (_, index) => ( + + ))} + + ) +} + +export { Slider } diff --git a/app/frontend/components/ui/switch.tsx b/app/frontend/components/ui/switch.tsx new file mode 100644 index 00000000..24c84866 --- /dev/null +++ b/app/frontend/components/ui/switch.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as SwitchPrimitive from "@radix-ui/react-switch" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function Switch({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Switch } diff --git a/app/frontend/components/ui/tabs.tsx b/app/frontend/components/ui/tabs.tsx new file mode 100644 index 00000000..d69dce03 --- /dev/null +++ b/app/frontend/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as TabsPrimitive from "@radix-ui/react-tabs" +import type * as React from "react" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsContent, TabsList, TabsTrigger } diff --git a/app/frontend/components/user-info.tsx b/app/frontend/components/user-info.tsx index 2ccb3814..05e783a1 100644 --- a/app/frontend/components/user-info.tsx +++ b/app/frontend/components/user-info.tsx @@ -14,7 +14,7 @@ export function UserInfo({ return ( <> - + {user.avatar && } {getInitials(user.name)} diff --git a/app/frontend/components/user-menu-content.tsx b/app/frontend/components/user-menu-content.tsx index 9047c2c2..5800325c 100644 --- a/app/frontend/components/user-menu-content.tsx +++ b/app/frontend/components/user-menu-content.tsx @@ -30,6 +30,24 @@ export function UserMenuContent({ auth }: UserMenuContentProps) { router.flushAll() } + // Handle guest users + if (!user || !session) { + return ( + <> + +
+ +
+
+ + + + Sign in to access settings + + + ) + } + return ( <> diff --git a/app/frontend/data/dashboard-mock-data.ts b/app/frontend/data/dashboard-mock-data.ts new file mode 100644 index 00000000..0473f4b7 --- /dev/null +++ b/app/frontend/data/dashboard-mock-data.ts @@ -0,0 +1,359 @@ +export interface Task { + id: string + title: string + description: string + status: "todo" | "in-progress" | "done" + priority: "low" | "medium" | "high" | "urgent" + dueDate: string + assignee: { + name: string + avatar: string + email: string + } + tags: string[] + progress: number + timeEstimate: number + timeSpent: number +} + +export interface DailyGoal { + id: string + title: string + target: number + current: number + unit: string + icon: string + color: string +} + +export interface ProductivityMetric { + date: string + tasksCompleted: number + hoursWorked: number + focusScore: number +} + +export const tasks: Task[] = [ + { + id: "1", + title: "Design new landing page", + description: + "Create wireframes and mockups for the new marketing landing page", + status: "in-progress", + priority: "high", + dueDate: "2025-01-15", + assignee: { + name: "Sarah Chen", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah", + email: "sarah@example.com", + }, + tags: ["design", "ui/ux", "marketing"], + progress: 65, + timeEstimate: 8, + timeSpent: 5.2, + }, + { + id: "2", + title: "Fix authentication bug", + description: "Users reporting issues with social login integration", + status: "todo", + priority: "urgent", + dueDate: "2025-01-12", + assignee: { + name: "Alex Thompson", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alex", + email: "alex@example.com", + }, + tags: ["bug", "backend", "auth"], + progress: 0, + timeEstimate: 4, + timeSpent: 0, + }, + { + id: "3", + title: "Write API documentation", + description: "Document all REST endpoints for v2 API", + status: "in-progress", + priority: "medium", + dueDate: "2025-01-20", + assignee: { + name: "Maria Garcia", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Maria", + email: "maria@example.com", + }, + tags: ["documentation", "api"], + progress: 40, + timeEstimate: 6, + timeSpent: 2.4, + }, + { + id: "4", + title: "Implement search feature", + description: "Add full-text search with filters and sorting", + status: "todo", + priority: "high", + dueDate: "2025-01-18", + assignee: { + name: "James Wilson", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=James", + email: "james@example.com", + }, + tags: ["feature", "frontend", "backend"], + progress: 0, + timeEstimate: 12, + timeSpent: 0, + }, + { + id: "5", + title: "Optimize database queries", + description: "Improve performance of slow queries identified in monitoring", + status: "done", + priority: "medium", + dueDate: "2025-01-10", + assignee: { + name: "Priya Patel", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Priya", + email: "priya@example.com", + }, + tags: ["performance", "database"], + progress: 100, + timeEstimate: 5, + timeSpent: 4.8, + }, + { + id: "6", + title: "Update user onboarding flow", + description: + "Simplify the registration process and add progress indicators", + status: "in-progress", + priority: "medium", + dueDate: "2025-01-22", + assignee: { + name: "Sarah Chen", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah", + email: "sarah@example.com", + }, + tags: ["ux", "frontend"], + progress: 30, + timeEstimate: 10, + timeSpent: 3, + }, + { + id: "7", + title: "Set up CI/CD pipeline", + description: + "Configure automated testing and deployment with GitHub Actions", + status: "done", + priority: "high", + dueDate: "2025-01-08", + assignee: { + name: "Alex Thompson", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alex", + email: "alex@example.com", + }, + tags: ["devops", "automation"], + progress: 100, + timeEstimate: 8, + timeSpent: 7.5, + }, + { + id: "8", + title: "Create mobile responsive design", + description: "Ensure all components work well on mobile devices", + status: "todo", + priority: "low", + dueDate: "2025-01-25", + assignee: { + name: "Maria Garcia", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Maria", + email: "maria@example.com", + }, + tags: ["mobile", "design", "responsive"], + progress: 0, + timeEstimate: 15, + timeSpent: 0, + }, + { + id: "9", + title: "Add unit tests", + description: "Increase test coverage to 80% for core modules", + status: "in-progress", + priority: "medium", + dueDate: "2025-01-17", + assignee: { + name: "James Wilson", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=James", + email: "james@example.com", + }, + tags: ["testing", "quality"], + progress: 55, + timeEstimate: 10, + timeSpent: 5.5, + }, + { + id: "10", + title: "Migrate to TypeScript", + description: + "Convert JavaScript codebase to TypeScript for better type safety", + status: "todo", + priority: "low", + dueDate: "2025-02-01", + assignee: { + name: "Priya Patel", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Priya", + email: "priya@example.com", + }, + tags: ["refactoring", "typescript"], + progress: 0, + timeEstimate: 20, + timeSpent: 0, + }, +] + +export const dailyGoals: DailyGoal[] = [ + { + id: "1", + title: "Tasks Completed", + target: 8, + current: 5, + unit: "tasks", + icon: "CheckCircle", + color: "bg-gradient-to-r from-green-400 to-green-600", + }, + { + id: "2", + title: "Focus Time", + target: 6, + current: 4.5, + unit: "hours", + icon: "Clock", + color: "bg-gradient-to-r from-blue-400 to-blue-600", + }, + { + id: "3", + title: "Code Reviews", + target: 4, + current: 3, + unit: "reviews", + icon: "GitPullRequest", + color: "bg-gradient-to-r from-purple-400 to-purple-600", + }, + { + id: "4", + title: "Lines of Code", + target: 500, + current: 367, + unit: "lines", + icon: "Code", + color: "bg-gradient-to-r from-orange-400 to-orange-600", + }, +] + +export const productivityMetrics: ProductivityMetric[] = [ + { date: "2025-01-01", tasksCompleted: 5, hoursWorked: 6, focusScore: 72 }, + { date: "2025-01-02", tasksCompleted: 8, hoursWorked: 7, focusScore: 85 }, + { date: "2025-01-03", tasksCompleted: 6, hoursWorked: 5, focusScore: 78 }, + { date: "2025-01-04", tasksCompleted: 10, hoursWorked: 8, focusScore: 92 }, + { date: "2025-01-05", tasksCompleted: 3, hoursWorked: 4, focusScore: 65 }, + { date: "2025-01-06", tasksCompleted: 7, hoursWorked: 6.5, focusScore: 80 }, + { date: "2025-01-07", tasksCompleted: 9, hoursWorked: 7.5, focusScore: 88 }, + { date: "2025-01-08", tasksCompleted: 4, hoursWorked: 5, focusScore: 70 }, + { date: "2025-01-09", tasksCompleted: 11, hoursWorked: 8.5, focusScore: 95 }, + { date: "2025-01-10", tasksCompleted: 6, hoursWorked: 6, focusScore: 76 }, +] + +export const teamMembers = [ + { + id: "1", + name: "Sarah Chen", + role: "UI/UX Designer", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah", + status: "online", + tasksCompleted: 12, + currentTask: "Design new landing page", + }, + { + id: "2", + name: "Alex Thompson", + role: "Full Stack Developer", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alex", + status: "busy", + tasksCompleted: 18, + currentTask: "Fix authentication bug", + }, + { + id: "3", + name: "Maria Garcia", + role: "Technical Writer", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Maria", + status: "online", + tasksCompleted: 8, + currentTask: "Write API documentation", + }, + { + id: "4", + name: "James Wilson", + role: "Backend Developer", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=James", + status: "away", + tasksCompleted: 15, + currentTask: "Implement search feature", + }, + { + id: "5", + name: "Priya Patel", + role: "DevOps Engineer", + avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Priya", + status: "online", + tasksCompleted: 10, + currentTask: "Optimize database queries", + }, +] + +export const recentActivities = [ + { + id: "1", + user: "Sarah Chen", + action: "completed", + target: "Homepage redesign mockup", + time: "5 minutes ago", + icon: "CheckCircle", + color: "text-green-500", + }, + { + id: "2", + user: "Alex Thompson", + action: "commented on", + target: "Authentication bug fix", + time: "15 minutes ago", + icon: "MessageSquare", + color: "text-blue-500", + }, + { + id: "3", + user: "Maria Garcia", + action: "updated", + target: "API documentation", + time: "1 hour ago", + icon: "Edit", + color: "text-yellow-500", + }, + { + id: "4", + user: "James Wilson", + action: "started", + target: "Search feature implementation", + time: "2 hours ago", + icon: "Play", + color: "text-purple-500", + }, + { + id: "5", + user: "Priya Patel", + action: "deployed", + target: "Version 2.1.0 to production", + time: "3 hours ago", + icon: "Rocket", + color: "text-orange-500", + }, +] diff --git a/app/frontend/pages/dashboard/index.tsx b/app/frontend/pages/dashboard/index.tsx index 832ebfc3..9d3354f7 100644 --- a/app/frontend/pages/dashboard/index.tsx +++ b/app/frontend/pages/dashboard/index.tsx @@ -1,38 +1,657 @@ import { Head } from "@inertiajs/react" +import { + Activity, + AlertCircle, + Calendar, + CheckCircle2, + Clock, + Code, + Command, + Flame, + GitPullRequest, + MoreHorizontal, + Plus, + Sparkles, + Timer, + TrendingUp, + Users, + Zap, +} from "lucide-react" +import { useEffect, useState } from "react" +import { toast } from "sonner" -import { PlaceholderPattern } from "@/components/placeholder-pattern" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" +import { Avatar } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card" +import { Label } from "@/components/ui/label" +import { Progress } from "@/components/ui/progress" +import { Separator } from "@/components/ui/separator" +import { Slider } from "@/components/ui/slider" +import { Switch } from "@/components/ui/switch" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { + dailyGoals, + recentActivities, + tasks, + teamMembers, +} from "@/data/dashboard-mock-data" import AppLayout from "@/layouts/app-layout" import { dashboardPath } from "@/routes" import type { BreadcrumbItem } from "@/types" +import EnhancedDashboardSection from "./productivity-enhanced" + const breadcrumbs: BreadcrumbItem[] = [ { - title: "Dashboard", + title: "Productivity Dashboard", href: dashboardPath(), }, ] export default function Dashboard() { + const [taskFilter, setTaskFilter] = useState("all") + const [, setShowCommandPalette] = useState(false) + const [pomodoroTime, setPomodoroTime] = useState(25) + const [isTimerRunning, setIsTimerRunning] = useState(false) + const [currentTime, setCurrentTime] = useState(pomodoroTime * 60) + const [autoArchive, setAutoArchive] = useState(true) + const [emailNotifications, setEmailNotifications] = useState(false) + + // Filter tasks based on selected tab + const filteredTasks = + taskFilter === "all" + ? tasks + : tasks.filter((task) => task.status === taskFilter) + + // Calculate stats + const totalTasks = tasks.length + const completedTasks = tasks.filter((t) => t.status === "done").length + const urgentTasks = tasks.filter((t) => t.priority === "urgent").length + const inProgressTasks = tasks.filter((t) => t.status === "in-progress").length + + // Timer effect + useEffect(() => { + let interval: ReturnType + if (isTimerRunning && currentTime > 0) { + interval = setInterval(() => { + setCurrentTime((time) => time - 1) + }, 1000) + } else if (currentTime === 0) { + setIsTimerRunning(false) + toast.success("Pomodoro session completed! Time for a break 🎉") + } + return () => clearInterval(interval) + }, [isTimerRunning, currentTime]) + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` + } + + const getPriorityColor = (priority: string) => { + switch (priority) { + case "urgent": + return "bg-red-500 text-white" + case "high": + return "bg-orange-500 text-white" + case "medium": + return "bg-yellow-500 text-white" + case "low": + return "bg-green-500 text-white" + default: + return "bg-gray-500 text-white" + } + } + + const getStatusIcon = (status: string) => { + switch (status) { + case "done": + return + case "in-progress": + return + default: + return + } + } + return ( + +
+ {/* Welcome Alert */} + + + Welcome back! You're on fire today 🔥 + + You've completed {completedTasks} tasks and have{" "} + {urgentTasks} urgent items pending. Keep up the great work! + + -
-
-
- -
-
- + {/* Stats Overview */} +
+ {dailyGoals.map((goal) => { + const IconComponent = + goal.icon === "CheckCircle" + ? CheckCircle2 + : goal.icon === "Clock" + ? Clock + : goal.icon === "GitPullRequest" + ? GitPullRequest + : Code + const percentage = (goal.current / goal.target) * 100 + + return ( + +
+ +
+ + {goal.title} + + +
+
+ +
+ {goal.current}/{goal.target} {goal.unit} +
+ +

+ {percentage.toFixed(0)}% of daily goal +

+
+ + ) + })}
-
- + + {/* Main Content Grid */} +
+ {/* Task Management Section */} +
+ + +
+
+ Task Management + + Track and manage your daily tasks + +
+
+ + + + + + + Create New Task + + Add a new task to your productivity dashboard + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+ + + + All ({totalTasks}) + + To Do ({tasks.filter((t) => t.status === "todo").length} + ) + + + In Progress ({inProgressTasks}) + + + Done ({completedTasks}) + + + + {filteredTasks.map((task) => ( +
+
+
+
+ {getStatusIcon(task.status)} +

{task.title}

+ + {task.priority} + +
+

+ {task.description} +

+
+ + +
+ + {task.assignee.name} + + {task.assignee.name} +
+
+ +
+ + {task.assignee.name} + +
+

+ {task.assignee.name} +

+

+ {task.assignee.email} +

+
+ + + Due: {task.dueDate} + +
+
+
+
+
+
+ + + {task.timeSpent}/{task.timeEstimate}h + +
+
+ {task.tags.map((tag) => ( + + {tag} + + ))} +
+
+ +
+ + + + + + + toast.info("Editing task: " + task.title) + } + > + Edit + + + toast.success("Task marked as complete!") + } + > + Mark Complete + + toast.error("Task deleted")} + > + Delete + + + +
+
+ ))} +
+
+
+
+
+ + {/* Right Sidebar */} +
+ {/* Pomodoro Timer */} + + + + + Pomodoro Timer + + + +
+
+ {formatTime(currentTime)} +
+
+ + +
+
+ + { + setPomodoroTime(value[0]) + setCurrentTime(value[0] * 60) + }} + max={60} + min={5} + step={5} + /> +
+
+
+
+ + {/* Team Activity */} + + + + + Team Activity + + + + + {teamMembers.map((member) => ( + + +
+ + {member.name} + +
+

+ {member.name} +

+

+ {member.role} +

+
+
+
+ +
+
+ Status: + + {member.status} + +
+
+ Tasks Completed: + + {member.tasksCompleted} + +
+
+ Current Task: +

+ {member.currentTask} +

+
+
+
+
+ ))} +
+
+
+ + {/* Recent Activity Feed */} + + + + + Recent Activity + + + +
+ {recentActivities.map((activity) => ( +
+
+ +
+
+

+ {activity.user}{" "} + {activity.action}{" "} + + {activity.target} + +

+

+ {activity.time} +

+
+
+ ))} +
+
+
+ + {/* Settings */} + + + Quick Settings + + +
+ + +
+ +
+ + +
+
+
+
+ + {/* Productivity Trends */} + + +
+
+ Productivity Trends + + Your performance over the last 10 days + +
+ + + + + +

View detailed analytics

+
+
+
+
+ +
+
+

Average Tasks/Day

+

6.7

+

+ ↑ 12% from last week +

+
+
+

Focus Score

+

79.3

+

+ ↑ 5% from last week +

+
+
+

Streak

+
+

7 days

+ +
+

Keep it up!

+
+
+
+
-
- + + {/* Enhanced Dashboard Section with New Components */} +
+

+ Enhanced Productivity Features +

+
-
+ ) } diff --git a/app/frontend/pages/dashboard/productivity-enhanced.tsx b/app/frontend/pages/dashboard/productivity-enhanced.tsx new file mode 100644 index 00000000..594c4b8a --- /dev/null +++ b/app/frontend/pages/dashboard/productivity-enhanced.tsx @@ -0,0 +1,981 @@ +import { Head, Link } from "@inertiajs/react" +import { format, isSameDay } from "date-fns" +import { + Calendar as CalendarIcon, + CheckCircle2, + ChevronDown, + ChevronRight, + Circle, + Command as CommandIcon, + Copy, + Edit, + Heart, + Home, + Info, + Plus, + Search, + Settings, + Sparkles, + Target, + Trash, + Trophy, + User +} from "lucide-react" +import { useEffect, useState } from "react" + +// All UI Components +import { toast } from "sonner" + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel" +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command" +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuRadioGroup, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuRadioItem, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger +} from "@/components/ui/context-menu" +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card" +import { Label } from "@/components/ui/label" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" + +// Mock data +import { tasks } from "@/data/dashboard-mock-data" +import { rootPath } from "@/routes" + +const achievements = [ + { id: 1, title: "Early Bird", description: "Complete 5 tasks before 9 AM", icon: "🌅", color: "bg-gradient-to-r from-yellow-400 to-orange-500", progress: 3, total: 5 }, + { id: 2, title: "Sprint Master", description: "Complete 10 tasks in one day", icon: "🏃", color: "bg-gradient-to-r from-blue-400 to-purple-500", progress: 7, total: 10 }, + { id: 3, title: "Focus Ninja", description: "3 hour focus streak", icon: "🥷", color: "bg-gradient-to-r from-green-400 to-teal-500", progress: 2, total: 3 }, + { id: 4, title: "Team Player", description: "Help 5 teammates", icon: "🤝", color: "bg-gradient-to-r from-pink-400 to-red-500", progress: 5, total: 5 }, + { id: 5, title: "Perfectionist", description: "100% completion rate for a week", icon: "💎", color: "bg-gradient-to-r from-purple-400 to-indigo-500", progress: 6, total: 7 }, +] + +const motivationalQuotes = [ + { text: "The only way to do great work is to love what you do.", author: "Steve Jobs", color: "from-purple-500 to-pink-500" }, + { text: "Innovation distinguishes between a leader and a follower.", author: "Steve Jobs", color: "from-blue-500 to-cyan-500" }, + { text: "The future belongs to those who believe in the beauty of their dreams.", author: "Eleanor Roosevelt", color: "from-green-500 to-teal-500" }, + { text: "Success is not final, failure is not fatal: it is the courage to continue that counts.", author: "Winston Churchill", color: "from-orange-500 to-red-500" }, + { text: "The only impossible journey is the one you never begin.", author: "Tony Robbins", color: "from-indigo-500 to-purple-500" }, +] + +const teamMembers = [ + { + id: 1, + name: "Sarah Chen", + role: "UI/UX Designer", + avatar: "https://api.dicebear.com/9.x/avataaars/svg?seed=Sarah", + email: "sarah.chen@example.com", + tasksCompleted: 12, + currentTask: "Design new landing page", + productivity: 92, + status: "online", + achievements: ["Early Bird", "Design Master", "Team Player"] + }, + { + id: 2, + name: "Alex Thompson", + role: "Full Stack Developer", + avatar: "https://api.dicebear.com/9.x/avataaars/svg?seed=Alex", + email: "alex.thompson@example.com", + tasksCompleted: 15, + currentTask: "Fix authentication bug", + productivity: 88, + status: "busy", + achievements: ["Code Ninja", "Bug Squasher", "Sprint Master"] + }, + { + id: 3, + name: "Maria Garcia", + role: "Technical Writer", + avatar: "https://api.dicebear.com/9.x/avataaars/svg?seed=Maria", + email: "maria.garcia@example.com", + tasksCompleted: 8, + currentTask: "Write API documentation", + productivity: 95, + status: "online", + achievements: ["Documentation Hero", "Perfectionist", "Early Bird"] + }, + { + id: 4, + name: "James Wilson", + role: "Backend Developer", + avatar: "https://api.dicebear.com/9.x/avataaars/svg?seed=James", + email: "james.wilson@example.com", + tasksCompleted: 10, + currentTask: "Implement search feature", + productivity: 82, + status: "away", + achievements: ["API Master", "Performance Guru", "Team Player"] + }, + { + id: 5, + name: "Priya Patel", + role: "DevOps Engineer", + avatar: "https://api.dicebear.com/9.x/avataaars/svg?seed=Priya", + email: "priya.patel@example.com", + tasksCompleted: 18, + currentTask: "Optimize database queries", + productivity: 96, + status: "online", + achievements: ["Automation Expert", "Sprint Master", "Perfectionist"] + } +] + +export default function EnhancedDashboardSection() { + const [date, setDate] = useState(new Date()) + const [selectedEmoji, setSelectedEmoji] = useState("") + const [commandOpen, setCommandOpen] = useState(false) + const [sheetOpen, setSheetOpen] = useState(false) + const [selectedTaskForSheet, setSelectedTaskForSheet] = useState(null) + const [alertOpen, setAlertOpen] = useState(false) + const [taskToComplete, setTaskToComplete] = useState(null) + const [achievementCarouselApi, setAchievementCarouselApi] = useState(null) + const [quotesCarouselApi, setQuotesCarouselApi] = useState(null) + + // Collapsible states for different sections + const [achievementsOpen, setAchievementsOpen] = useState(true) + const [teamOpen, setTeamOpen] = useState(true) + const [calendarOpen, setCalendarOpen] = useState(true) + const [inspirationOpen, setInspirationOpen] = useState(true) + + // Keyboard shortcut for command palette + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setCommandOpen((open) => { + if (!open) { + toast.info("⌨️ Command palette opened with Cmd+K (shadcn/ui Command component)") + } + return !open + }) + } + } + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + // Carousel API listeners for educational toasts + useEffect(() => { + if (!achievementCarouselApi) return + + achievementCarouselApi.on("select", () => { + toast.info("🎯 Achievement carousel navigated (shadcn/ui Carousel component)") + }) + }, [achievementCarouselApi]) + + useEffect(() => { + if (!quotesCarouselApi) return + + quotesCarouselApi.on("select", () => { + toast.info("✨ Inspirational quote changed (shadcn/ui Carousel component)") + }) + }, [quotesCarouselApi]) + + // Get tasks for selected date + const tasksForDate = tasks.filter(task => { + const taskDate = new Date(task.dueDate) + return date && isSameDay(taskDate, date) + }) + + // Calendar with task indicators + const tasksWithDates = tasks.reduce((acc, task) => { + const date = format(new Date(task.dueDate), "yyyy-MM-dd") + if (!acc[date]) acc[date] = [] + acc[date].push(task) + return acc + }, {} as Record) + + return ( + +
+ + + {/* Home Button */} +
+ + + +
+ + {/* Command Palette */} + + + + No results found. + + { + setCommandOpen(false) + toast.info("📋 Demo Action: This would create a new task (shadcn/ui Command component)") + }}> + + Create New Task + ⌘N + + { + setCommandOpen(false) + toast.info("🔍 Demo Action: This would open search (shadcn/ui Command component)") + }}> + + Search Tasks + ⌘F + + { + setCommandOpen(false) + toast.info("🎯 Demo Action: This would toggle focus mode (shadcn/ui Command component)") + }}> + + Focus Mode + ⌘⏎ + + + + + { + setCommandOpen(false) + toast.info("👤 Demo Action: This would open Sarah's profile (shadcn/ui Command component)") + }}> + + Sarah Chen + + { + setCommandOpen(false) + toast.info("👤 Demo Action: This would open Alex's profile (shadcn/ui Command component)") + }}> + + Alex Thompson + + { + setCommandOpen(false) + toast.info("👤 Demo Action: This would open Maria's profile (shadcn/ui Command component)") + }}> + + Maria Garcia + + + + + { + setCommandOpen(false) + toast.info("⚙️ Demo Action: This would open profile settings (shadcn/ui Command component)") + }}> + + Profile + ⌘P + + { + setCommandOpen(false) + toast.info("⚙️ Demo Action: This would open preferences (shadcn/ui Command component)") + }}> + + Preferences + ⌘, + + + + + + {/* Quick Access Button for Command Palette */} +
+ + + + + +

Open command palette for quick actions

+
+
+
+ + {/* Achievement Carousel */} + + + + +
{ + toast.info(`📱 ${achievementsOpen ? 'Collapsing' : 'Expanding'} section (shadcn/ui Collapsible component)`) + }} + > +
+ + + Achievement Showcase + + Your productivity milestones +
+ + + + + +

{achievementsOpen ? "Collapse section" : "Expand section"}

+
+
+
+
+
+ + + + + {achievements.map((achievement) => ( + + + +
{achievement.icon}
+

{achievement.title}

+

{achievement.description}

+
+
+
+ {achievement.progress}/{achievement.total} + {achievement.progress === achievement.total && ( + + + Completed! + + +

🎉 You've unlocked this achievement!

+
+
+ )} + + + + ))} + + + + + + + + + + {/* Team Showcase with Hover Cards */} + + + + +
{ + toast.info(`👥 ${teamOpen ? 'Collapsing' : 'Expanding'} team section (shadcn/ui Collapsible component)`) + }} + > +
+ + + Team Members + + Hover over team members to see their stats +
+ + + + + +

{teamOpen ? "Collapse section" : "Expand section"}

+
+
+
+
+
+ + +
+ {teamMembers.map((member) => ( + + +
{ + toast.info(`👤 Hovering over ${member.name} (shadcn/ui HoverCard component)`) + }} + > +
+ + + {member.name.split(' ').map(n => n[0]).join('')} + +
+

{member.name}

+

{member.role}

+
+ + +
+
+ {member.status} +
+ + +

{member.status === 'online' ? 'Available for collaboration' : + member.status === 'busy' ? 'In focus mode - do not disturb' : + 'Away from desk'}

+
+ +
+
+ + +
+
+ + + {member.name.split(' ').map(n => n[0]).join('')} + +
+

{member.name}

+

{member.role}

+

{member.email}

+
+
+ +
+
+ Productivity + {member.productivity}% +
+
+
+
+
+ +
+
+ + + {member.tasksCompleted} tasks completed this week + +
+
+ + + Currently: {member.currentTask} + +
+
+ +
+

Achievements

+
+ {member.achievements.map((achievement, idx) => ( + + {achievement} + + ))} +
+
+
+ + + ))} +
+ + + + + + {/* Calendar with Popover */} + + + + +
{ + toast.info(`📅 ${calendarOpen ? 'Collapsing' : 'Expanding'} calendar section (shadcn/ui Collapsible component)`) + }} + > +
+ + + Task Calendar + + Click on dates to see tasks +
+ + + + + +

{calendarOpen ? "Collapse section" : "Expand section"}

+
+
+
+
+
+ + + + + + + + { + setDate(newDate) + if (newDate) { + toast.info("📅 Date selected (shadcn/ui Calendar component)") + } + }} + initialFocus + modifiers={{ + hasTasks: Object.keys(tasksWithDates).map(d => new Date(d)) + }} + modifiersClassNames={{ + hasTasks: "bg-blue-100 dark:bg-blue-900 font-bold" + }} + /> + + + + {/* Tasks for selected date */} +
+

Tasks for {date && format(date, "MMM dd, yyyy")}

+ {tasksForDate.length > 0 ? ( + tasksForDate.map(task => ( + + +
{ + setSelectedTaskForSheet(task) + setSheetOpen(true) + toast.info("📋 Opening task details (shadcn/ui Sheet component)") + }} + onContextMenu={() => { + toast.info("🖱️ Right-click detected (shadcn/ui ContextMenu component)") + }} + > +
+ {task.title} + + + + {task.priority} + + + +

{task.priority === "urgent" ? "⚡ Needs immediate attention" : + task.priority === "high" ? "⬆️ Important task" : + "➡️ Regular priority"}

+
+
+
+
+
+ + { + setSelectedTaskForSheet(task) + setSheetOpen(true) + toast.info("📋 Demo Action: Opening task details (shadcn/ui ContextMenu + Sheet components)") + }}> + + View Details + ⌘I + + { + setTaskToComplete(task) + setAlertOpen(true) + toast.info("✅ Demo Action: Opening completion dialog (shadcn/ui ContextMenu + AlertDialog components)") + }}> + + Complete + ⌘K + + toast.info("✏️ Demo Action: This would open task editor (shadcn/ui ContextMenu component)")}> + + Edit + ⌘E + + + + + + Add Reaction + + + + { setSelectedEmoji("👍"); toast.info("👍 Demo Action: Added reaction (shadcn/ui ContextMenu sub-menu)") }}> + 👍 Like + + { setSelectedEmoji("❤️"); toast.info("❤️ Demo Action: Added reaction (shadcn/ui ContextMenu sub-menu)") }}> + ❤️ Love + + { setSelectedEmoji("🎉"); toast.info("🎉 Demo Action: Added reaction (shadcn/ui ContextMenu sub-menu)") }}> + 🎉 Celebrate + + { setSelectedEmoji("🚀"); toast.info("🚀 Demo Action: Added reaction (shadcn/ui ContextMenu sub-menu)") }}> + 🚀 Ship it! + + + + + toast.info("📋 Demo Action: This would duplicate the task (shadcn/ui ContextMenu component)")}> + + Duplicate + + + toast.error("🗑️ Demo Action: This would delete the task (shadcn/ui ContextMenu component)")}> + + Delete + ⌘D + + +
+ )) + ) : ( +

No tasks for this date

+ )} +
+
+
+
+
+ + {/* Motivational Quotes Carousel */} + + + + +
{ + toast.info(`✨ ${inspirationOpen ? 'Collapsing' : 'Expanding'} inspiration section (shadcn/ui Collapsible component)`) + }} + > +
+ + + Daily Inspiration + +
+ + + + + +

{inspirationOpen ? "Collapse section" : "Expand section"}

+
+
+
+
+
+ + + + + {motivationalQuotes.map((quote, index) => ( + + + + +
+ "{quote.text}" +
+ — {quote.author} +
+
+
+ ))} +
+ + +
+
+
+
+
+ + {/* Task Details Sheet */} + + + + Task Details + + View and manage task information + + + {selectedTaskForSheet && ( +
+
+

{selectedTaskForSheet.title}

+ + {selectedTaskForSheet.priority} priority + +
+ +
+ +

+ {selectedTaskForSheet.description ?? "No description available"} +

+
+ +
+
+ +
+ + {selectedTaskForSheet.status} +
+
+
+ +
+ + {selectedTaskForSheet.dueDate} +
+
+
+ + {selectedTaskForSheet.assignee && ( +
+ +
+ + + + {selectedTaskForSheet.assignee.name.split(' ').map((n: string) => n[0]).join('')} + + +
+

{selectedTaskForSheet.assignee.name}

+

{selectedTaskForSheet.assignee.role}

+
+
+
+ )} + +
+ +
+
+ Completion + {selectedTaskForSheet.progress || 0}% +
+
+
+
+
+
+ +
+ +
+ {selectedTaskForSheet.tags?.map((tag: string, idx: number) => ( + + {tag} + + )) || No tags} +
+
+ +
+ + +
+
+ )} + + + + {/* Task Completion Alert Dialog */} + + + + + + Complete Task? + + + + {taskToComplete && ( +
+

+ You're about to complete: {taskToComplete.title} +

+
+ 🎉 🎊 🏆 +
+

+ Great job! You're making amazing progress today! +

+
+ )} +
+
+ + setTaskToComplete(null)}> + Not yet + + { + toast.success( +
+ + Task completed! Keep up the great work! 🎉 +
+ ) + setTaskToComplete(null) + // Add confetti or celebration animation here + if (typeof window !== 'undefined') { + // Create a simple confetti effect + const duration = 3 * 1000; + const animationEnd = Date.now() + duration; + const interval = setInterval(function() { + const timeLeft = animationEnd - Date.now(); + + if (timeLeft <= 0) { + return clearInterval(interval); + } + + // Create custom confetti effect here (simplified version) + console.log('🎉 Confetti! 🎊'); + }, 250); + } + }} + className="bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600" + > + Complete! 🎯 +
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/app/frontend/pages/home/index.tsx b/app/frontend/pages/home/index.tsx index 737c5e1c..d1de18b7 100644 --- a/app/frontend/pages/home/index.tsx +++ b/app/frontend/pages/home/index.tsx @@ -30,6 +30,12 @@ export default function Welcome() { ) : ( <> + + View Demo Dashboard + =6.9.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.3.1.tgz", + "integrity": "sha512-LnBOyuj+piItX/D5BWBSckBsuZyOt7Jg2obGNiObq7qjl1A2/8F+i4RS8/MmkSdnw6hOe6afrJLCWrUWZw5Mlw==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", @@ -1158,6 +1179,65 @@ "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", "license": "MIT" }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.11.tgz", + "integrity": "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.11", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz", + "integrity": "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.14", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", @@ -1324,6 +1404,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.15.tgz", + "integrity": "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", @@ -1471,6 +1579,37 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.14.tgz", + "integrity": "sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -1588,6 +1727,43 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", @@ -1691,6 +1867,62 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.7.tgz", + "integrity": "sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", @@ -1788,6 +2020,39 @@ } } }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.5.tgz", + "integrity": "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -1806,6 +2071,65 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", + "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", + "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toggle": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz", @@ -2745,6 +3069,26 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", @@ -2762,6 +3106,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", @@ -3608,6 +3965,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3740,6 +4113,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -3857,6 +4246,34 @@ "integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==", "license": "ISC" }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -6440,6 +6857,27 @@ "node": ">=0.10.0" } }, + "node_modules/react-day-picker": { + "version": "9.8.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.8.1.tgz", + "integrity": "sha512-kMcLrp3PfN/asVJayVv82IjF3iLOOxuH5TNFWezX6lS/T8iVRFPTETpHl3TUSTH99IDMZLubdNPJr++rQctkEw==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", diff --git a/package.json b/package.json index acbc28f2..f5840b51 100644 --- a/package.json +++ b/package.json @@ -17,30 +17,45 @@ "dependencies": { "@headlessui/react": "^2.2.0", "@inertiajs/react": "^2.0.8", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-avatar": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.5", "@radix-ui/react-collapsible": "^1.1.4", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "^2.1.7", + "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.3", "@radix-ui/react-navigation-menu": "^1.2.6", + "@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-select": "^2.1.7", "@radix-ui/react-separator": "^1.1.3", + "@radix-ui/react-slider": "^1.3.5", "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toggle": "^1.1.3", "@radix-ui/react-toggle-group": "^1.1.3", "@radix-ui/react-tooltip": "^1.2.0", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.0.6", + "@tanstack/react-table": "^8.21.3", "@types/react": "^19.0.8", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^5.0.0", "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", "lucide-react": "<1.0", "next-themes": "^0.4.6", "react": "^19.0.0", + "react-day-picker": "^9.8.1", "react-dom": "^19.0.0", "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb index 2f2386d6..c4eed750 100644 --- a/spec/requests/sessions_spec.rb +++ b/spec/requests/sessions_spec.rb @@ -30,9 +30,6 @@ post sign_in_url, params: {email: user.email, password: "SecretWrong1*3"} expect(response).to redirect_to(sign_in_url) expect(flash[:alert]).to eq("That email or password is incorrect") - - get dashboard_url - expect(response).to redirect_to(sign_in_url) end end end