page-setup
This commit is contained in:
parent
6cd4847990
commit
d7c860df17
22
components.json
Normal file
22
components.json
Normal 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": {}
|
||||
}
|
||||
|
|
@ -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
2375
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
|
|
@ -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
8
postcss.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
49
src/App.tsx
49
src/App.tsx
|
|
@ -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
12
src/app/query-client.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { QueryClient } from "@tanstack/react-query"
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
48
src/components/ui/avatar.tsx
Normal file
48
src/components/ui/avatar.tsx
Normal 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 }
|
||||
36
src/components/ui/badge.tsx
Normal file
36
src/components/ui/badge.tsx
Normal 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 }
|
||||
57
src/components/ui/button.tsx
Normal file
57
src/components/ui/button.tsx
Normal 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 }
|
||||
76
src/components/ui/card.tsx
Normal file
76
src/components/ui/card.tsx
Normal 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 }
|
||||
201
src/components/ui/dropdown-menu.tsx
Normal file
201
src/components/ui/dropdown-menu.tsx
Normal 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,
|
||||
}
|
||||
22
src/components/ui/input.tsx
Normal file
22
src/components/ui/input.tsx
Normal 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 }
|
||||
46
src/components/ui/scroll-area.tsx
Normal file
46
src/components/ui/scroll-area.tsx
Normal 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 }
|
||||
29
src/components/ui/separator.tsx
Normal file
29
src/components/ui/separator.tsx
Normal 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
120
src/components/ui/table.tsx
Normal 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,
|
||||
}
|
||||
173
src/index.css
173
src/index.css
|
|
@ -1,68 +1,123 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Keep this minimal; shadcn init will add theme tokens + base styles. */
|
||||
|
||||
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
--background: 0 0% 100%;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
--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
|
||||
}
|
||||
.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%
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
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) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
@apply bg-background text-foreground font-sans;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
186
src/layouts/app-shell.tsx
Normal file
186
src/layouts/app-shell.tsx
Normal 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
6
src/lib/utils.ts
Normal 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))
|
||||
}
|
||||
|
|
@ -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>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
|
|
|||
143
src/pages/activity-log/index.tsx
Normal file
143
src/pages/activity-log/index.tsx
Normal 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
128
src/pages/clients/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
35
src/pages/dashboard/index.tsx
Normal file
35
src/pages/dashboard/index.tsx
Normal 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
135
src/pages/members/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
123
src/pages/notifications/index.tsx
Normal file
123
src/pages/notifications/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
114
src/pages/subscriptions/index.tsx
Normal file
114
src/pages/subscriptions/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
120
src/pages/transactions/index.tsx
Normal file
120
src/pages/transactions/index.tsx
Normal 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
75
tailwind.config.js
Normal 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],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -7,6 +7,10 @@
|
|||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user