page-setup

This commit is contained in:
kirukib 2025-12-17 22:29:42 +03:00
parent 6cd4847990
commit d7c860df17
31 changed files with 4297 additions and 96 deletions

22
components.json Normal file
View File

@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}

View File

@ -4,6 +4,12 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<title>yaltopia-ticket-admin</title>
</head>
<body>

2375
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,19 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@tanstack/react-query": "^5.90.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.561.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"react-router-dom": "^7.11.0",
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@ -19,10 +30,14 @@
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.23",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"

8
postcss.config.js Normal file
View File

@ -0,0 +1,8 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,34 +1,27 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { Navigate, Route, Routes } from "react-router-dom"
import { AppShell } from "@/layouts/app-shell"
import DashboardPage from "@/pages/dashboard"
import SubscriptionsPage from "@/pages/subscriptions"
import TransactionsPage from "@/pages/transactions"
import ClientsPage from "@/pages/clients"
import MembersPage from "@/pages/members"
import NotificationsPage from "@/pages/notifications"
import ActivityLogPage from "@/pages/activity-log"
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
<Routes>
<Route element={<AppShell />}>
<Route index element={<DashboardPage />} />
<Route path="subscriptions" element={<SubscriptionsPage />} />
<Route path="transactions" element={<TransactionsPage />} />
<Route path="clients" element={<ClientsPage />} />
<Route path="members" element={<MembersPage />} />
<Route path="notifications" element={<NotificationsPage />} />
<Route path="activity-log" element={<ActivityLogPage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
)
}

12
src/app/query-client.ts Normal file
View File

@ -0,0 +1,12 @@
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
},
},
})

View File

@ -0,0 +1,48 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -0,0 +1,201 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

120
src/components/ui/table.tsx Normal file
View File

@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@ -1,68 +1,123 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@tailwind base;
@tailwind components;
@tailwind utilities;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
/* Keep this minimal; shadcn init will add theme tokens + base styles. */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
@layer base {
:root {
color: #213547;
background-color: #ffffff;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground font-sans;
}
}

186
src/layouts/app-shell.tsx Normal file
View File

@ -0,0 +1,186 @@
import { Outlet, Link, useLocation } from "react-router-dom"
import {
LayoutDashboard,
FolderKanban,
Calendar,
Leaf,
Settings,
Bell,
HelpCircle,
DollarSign,
FileText,
Users,
Briefcase,
Mail,
Copy,
UserPlus,
Megaphone,
Search,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Separator } from "@/components/ui/separator"
import { Card, CardContent } from "@/components/ui/card"
import { cn } from "@/lib/utils"
const navigationItems = [
{ icon: LayoutDashboard, label: "Dashboard", path: "/" },
{ icon: FolderKanban, label: "Projects", path: "/projects" },
{ icon: Calendar, label: "Calendar", path: "/calendar" },
{ icon: Leaf, label: "Leave Management", path: "/leave" },
{ icon: Settings, label: "Settings", path: "/settings" },
{ icon: Bell, label: "Notification", path: "/notifications" },
{ icon: HelpCircle, label: "Help & Center", path: "/help" },
]
const adminNavigationItems = [
{ icon: Users, label: "Subscriptions", path: "/subscriptions" },
{ icon: DollarSign, label: "Transactions", path: "/transactions" },
{ icon: Briefcase, label: "Clients", path: "/clients" },
{ icon: Users, label: "Members", path: "/members" },
{ icon: Bell, label: "Notifications", path: "/notifications" },
{ icon: FileText, label: "Activity Log", path: "/activity-log" },
]
export function AppShell() {
const location = useLocation()
const isActive = (path: string) => {
if (path === "/") {
return location.pathname === "/"
}
return location.pathname.startsWith(path)
}
return (
<div className="flex h-screen bg-background">
{/* Sidebar */}
<aside className="w-64 bg-[#F8F8F8] flex flex-col border-r">
{/* Logo */}
<div className="p-6 flex items-center gap-2">
<div className="w-10 h-10 bg-primary rounded flex items-center justify-center">
<span className="text-primary-foreground font-bold text-lg">H</span>
</div>
<span className="text-foreground font-semibold text-lg">TurHR</span>
</div>
{/* Navigation */}
<nav className="flex-1 px-4 py-2 space-y-1">
{navigationItems.map((item) => {
const Icon = item.icon
return (
<Link
key={item.path}
to={item.path}
className={cn(
"flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors",
isActive(item.path)
? "bg-primary text-primary-foreground"
: "text-foreground/70 hover:bg-accent hover:text-foreground"
)}
>
<Icon className="w-5 h-5" />
{item.label}
</Link>
)
})}
<Separator className="my-4" />
<div className="text-xs font-semibold text-foreground/50 uppercase tracking-wider px-3 py-2">
Admin
</div>
{adminNavigationItems.map((item) => {
const Icon = item.icon
return (
<Link
key={item.path}
to={item.path}
className={cn(
"flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors",
isActive(item.path)
? "bg-primary text-primary-foreground"
: "text-foreground/70 hover:bg-accent hover:text-foreground"
)}
>
<Icon className="w-5 h-5" />
{item.label}
</Link>
)
})}
</nav>
{/* Announcements Card */}
<div className="p-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-2 mb-2">
<Megaphone className="w-4 h-4 text-primary" />
<span className="font-semibold text-sm">Announcements</span>
</div>
<p className="text-xs text-muted-foreground mb-3">
Stay updated with TurHR
</p>
<Button size="sm" className="w-full">
Create Now
</Button>
<div className="mt-3 text-xs text-muted-foreground">
Read by:
</div>
<div className="flex items-center gap-1 mt-1">
<div className="w-6 h-6 rounded-full bg-primary/20" />
<div className="w-6 h-6 rounded-full bg-primary/20" />
<div className="w-6 h-6 rounded-full bg-primary/20" />
<span className="text-xs text-muted-foreground ml-1">+10</span>
</div>
</CardContent>
</Card>
</div>
</aside>
{/* Main Content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Top Header */}
<header className="h-16 border-b bg-background flex items-center justify-between px-6">
<h1 className="text-2xl font-bold">Dashboard</h1>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Quick Search..."
className="pl-10 w-64"
/>
</div>
<Button variant="ghost" size="icon" className="relative">
<Mail className="w-5 h-5" />
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" />
</Button>
<Button variant="ghost" size="icon">
<Copy className="w-5 h-5" />
</Button>
<Button variant="ghost" size="icon" className="relative">
<Users className="w-5 h-5" />
<span className="absolute top-0 right-0 bg-primary text-primary-foreground text-xs px-1 rounded">
+10
</span>
</Button>
<Button>
<UserPlus className="w-4 h-4 mr-2" />
Invite
</Button>
<Avatar>
<AvatarFallback>U</AvatarFallback>
</Avatar>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-auto bg-background p-6">
<Outlet />
</main>
</div>
</div>
)
}

6
src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -2,9 +2,16 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { BrowserRouter } from "react-router-dom"
import { QueryClientProvider } from "@tanstack/react-query"
import { queryClient } from "@/app/query-client"
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</StrictMode>,
)

View File

@ -0,0 +1,143 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical } from "lucide-react"
export default function ActivityLogPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Activity Log</h2>
<Button>
<Download className="w-4 h-4 mr-2" />
Export Log
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Activities</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search activity..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Actions</option>
<option>Create</option>
<option>Update</option>
<option>Delete</option>
<option>Login</option>
<option>Logout</option>
</select>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Users</option>
<option>Admin</option>
<option>Manager</option>
<option>User</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Log ID</TableHead>
<TableHead>User</TableHead>
<TableHead>Action</TableHead>
<TableHead>Entity</TableHead>
<TableHead>Description</TableHead>
<TableHead>IP Address</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">LOG001</TableCell>
<TableCell>john.smith@example.com</TableCell>
<TableCell>
<Badge className="bg-blue-500">Create</Badge>
</TableCell>
<TableCell>Client</TableCell>
<TableCell>Created new client record</TableCell>
<TableCell>192.168.1.1</TableCell>
<TableCell>2024-01-15 10:30:45</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">LOG002</TableCell>
<TableCell>jane.doe@example.com</TableCell>
<TableCell>
<Badge className="bg-green-500">Update</Badge>
</TableCell>
<TableCell>Subscription</TableCell>
<TableCell>Updated subscription status</TableCell>
<TableCell>192.168.1.2</TableCell>
<TableCell>2024-01-15 09:15:22</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">LOG003</TableCell>
<TableCell>admin@example.com</TableCell>
<TableCell>
<Badge className="bg-purple-500">Login</Badge>
</TableCell>
<TableCell>System</TableCell>
<TableCell>User logged in successfully</TableCell>
<TableCell>192.168.1.3</TableCell>
<TableCell>2024-01-15 08:00:00</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

128
src/pages/clients/index.tsx Normal file
View File

@ -0,0 +1,128 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical, UserPlus } from "lucide-react"
export default function ClientsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Clients</h2>
<Button>
<UserPlus className="w-4 h-4 mr-2" />
Add Client
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Clients</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search client..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Status</option>
<option>Active</option>
<option>Inactive</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Client ID</TableHead>
<TableHead>Client Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Company</TableHead>
<TableHead>Status</TableHead>
<TableHead>Subscription</TableHead>
<TableHead>Joined Date</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">CLT001</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="w-8 h-8">
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<span>Client Name</span>
</div>
</TableCell>
<TableCell>client@example.com</TableCell>
<TableCell>Company Name</TableCell>
<TableCell>
<Badge className="bg-green-500">Active</Badge>
</TableCell>
<TableCell>Premium Plan</TableCell>
<TableCell>2024-01-01</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">CLT002</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="w-8 h-8">
<AvatarFallback>AC</AvatarFallback>
</Avatar>
<span>Another Client</span>
</div>
</TableCell>
<TableCell>another@example.com</TableCell>
<TableCell>Another Company</TableCell>
<TableCell>
<Badge className="bg-yellow-500">Pending</Badge>
</TableCell>
<TableCell>Basic Plan</TableCell>
<TableCell>2024-02-01</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,35 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Download } from "lucide-react"
export default function DashboardPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Good Morning, Admin</h2>
<div className="flex items-center gap-4">
<div className="text-sm text-muted-foreground">
01 Sep - 15 Sep 2024
</div>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export Data
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>Welcome to Dashboard</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
This is your main dashboard page.
</p>
</CardContent>
</Card>
</div>
</div>
)
}

135
src/pages/members/index.tsx Normal file
View File

@ -0,0 +1,135 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical, UserPlus } from "lucide-react"
export default function MembersPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Members (System Users)</h2>
<Button>
<UserPlus className="w-4 h-4 mr-2" />
Add Member
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Members</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search member..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Status</option>
<option>Active</option>
<option>Inactive</option>
<option>Suspended</option>
</select>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Roles</option>
<option>Admin</option>
<option>Manager</option>
<option>User</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Member ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Department</TableHead>
<TableHead>Status</TableHead>
<TableHead>Last Login</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">MEM001</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="w-8 h-8">
<AvatarFallback>JS</AvatarFallback>
</Avatar>
<span>John Smith</span>
</div>
</TableCell>
<TableCell>john.smith@example.com</TableCell>
<TableCell>Admin</TableCell>
<TableCell>IT Department</TableCell>
<TableCell>
<Badge className="bg-green-500">Active</Badge>
</TableCell>
<TableCell>2024-01-15 10:30</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">MEM002</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar className="w-8 h-8">
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<span>Jane Doe</span>
</div>
</TableCell>
<TableCell>jane.doe@example.com</TableCell>
<TableCell>Manager</TableCell>
<TableCell>Operations</TableCell>
<TableCell>
<Badge className="bg-yellow-500">Pending</Badge>
</TableCell>
<TableCell>2024-01-14 15:45</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,123 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical, Bell } from "lucide-react"
export default function NotificationsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Notifications</h2>
<Button>
<Bell className="w-4 h-4 mr-2" />
Create Notification
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Notifications</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search notification..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Types</option>
<option>System</option>
<option>User</option>
<option>Alert</option>
</select>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Status</option>
<option>Read</option>
<option>Unread</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Notification ID</TableHead>
<TableHead>Title</TableHead>
<TableHead>Type</TableHead>
<TableHead>Recipient</TableHead>
<TableHead>Status</TableHead>
<TableHead>Created Date</TableHead>
<TableHead>Sent Date</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">NOT001</TableCell>
<TableCell>System Update Available</TableCell>
<TableCell>
<Badge variant="outline">System</Badge>
</TableCell>
<TableCell>All Users</TableCell>
<TableCell>
<Badge className="bg-blue-500">Sent</Badge>
</TableCell>
<TableCell>2024-01-15</TableCell>
<TableCell>2024-01-15 10:00</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">NOT002</TableCell>
<TableCell>Payment Received</TableCell>
<TableCell>
<Badge variant="outline">User</Badge>
</TableCell>
<TableCell>john@example.com</TableCell>
<TableCell>
<Badge className="bg-green-500">Delivered</Badge>
</TableCell>
<TableCell>2024-01-14</TableCell>
<TableCell>2024-01-14 14:30</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,114 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical } from "lucide-react"
export default function SubscriptionsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Subscriptions</h2>
<Button>
<Download className="w-4 h-4 mr-2" />
Export Data
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Subscriptions</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search subscription..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Status</option>
<option>Active</option>
<option>Inactive</option>
<option>Cancelled</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Subscription ID</TableHead>
<TableHead>Plan Name</TableHead>
<TableHead>Client</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
<TableHead>Start Date</TableHead>
<TableHead>End Date</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">SUB001</TableCell>
<TableCell>Premium Plan</TableCell>
<TableCell>Client Name</TableCell>
<TableCell>$99.00</TableCell>
<TableCell>
<Badge className="bg-green-500">Active</Badge>
</TableCell>
<TableCell>2024-01-01</TableCell>
<TableCell>2024-12-31</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">SUB002</TableCell>
<TableCell>Basic Plan</TableCell>
<TableCell>Another Client</TableCell>
<TableCell>$49.00</TableCell>
<TableCell>
<Badge className="bg-yellow-500">Pending</Badge>
</TableCell>
<TableCell>2024-02-01</TableCell>
<TableCell>2024-11-30</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,120 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Search, Download, Eye, MoreVertical } from "lucide-react"
export default function TransactionsPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold">Transactions</h2>
<Button>
<Download className="w-4 h-4 mr-2" />
Export Data
</Button>
</div>
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>All Transactions</CardTitle>
<div className="flex items-center gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search transaction..."
className="pl-10 w-64"
/>
</div>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Status</option>
<option>Completed</option>
<option>Pending</option>
<option>Failed</option>
</select>
<select className="px-3 py-2 border rounded-md text-sm">
<option>All Types</option>
<option>Payment</option>
<option>Refund</option>
<option>Subscription</option>
</select>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Transaction ID</TableHead>
<TableHead>Client</TableHead>
<TableHead>Type</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date</TableHead>
<TableHead>Payment Method</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">TXN001</TableCell>
<TableCell>Client Name</TableCell>
<TableCell>Payment</TableCell>
<TableCell>$99.00</TableCell>
<TableCell>
<Badge className="bg-green-500">Completed</Badge>
</TableCell>
<TableCell>2024-01-15</TableCell>
<TableCell>Credit Card</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">TXN002</TableCell>
<TableCell>Another Client</TableCell>
<TableCell>Refund</TableCell>
<TableCell>-$49.00</TableCell>
<TableCell>
<Badge className="bg-yellow-500">Pending</Badge>
</TableCell>
<TableCell>2024-01-14</TableCell>
<TableCell>Bank Transfer</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

75
tailwind.config.js Normal file
View File

@ -0,0 +1,75 @@
import tailwindAnimate from "tailwindcss-animate"
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
theme: {
extend: {
fontFamily: {
sans: [
'"Plus Jakarta Sans"',
"ui-sans-serif",
"system-ui",
"-apple-system",
"Segoe UI",
"Roboto",
"Helvetica",
"Arial",
"Apple Color Emoji",
"Segoe UI Emoji",
],
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
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))'
}
}
}
},
plugins: [tailwindAnimate],
}

View File

@ -7,6 +7,10 @@
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
/* Bundler mode */
"moduleResolution": "bundler",

View File

@ -1,4 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },

View File

@ -1,7 +1,13 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import path from "node:path"
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})