styles: refactor codebase base style
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -9895,21 +9895,6 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
|
||||||
"version": "16.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz",
|
|
||||||
"integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,45 +6,19 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { GuestGuard } from '@/guards';
|
import { GuestGuard } from '@/guards';
|
||||||
import { useLoginForm } from '@/hooks/useLoginForm';
|
import { useLoginForm } from '@/hooks/useLoginForm';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const { register, handleSubmit, errors, isLoading } = useLoginForm();
|
const { register, handleSubmit, errors, isLoading } = useLoginForm();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GuestGuard>
|
<GuestGuard>
|
||||||
<main className="grid min-h-screen bg-background text-foreground lg:grid-cols-[1.05fr_0.95fr]">
|
<div className="flex min-h-screen w-full">
|
||||||
<section className="relative hidden overflow-hidden border-r border-border/60 lg:block">
|
<div className="hidden md:flex w-[40%]" />
|
||||||
<Image
|
<div className="flex flex-1 md:w-[60%] items-center justify-center p-6 border-l border-border h-screen">
|
||||||
src="/background.png"
|
<section className="w-full max-w-sm space-y-7">
|
||||||
alt=""
|
<header className="space-y-2 text-center md:text-left">
|
||||||
fill
|
|
||||||
priority
|
|
||||||
className="object-cover"
|
|
||||||
sizes="55vw"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-background/35" />
|
|
||||||
<div className="absolute inset-x-0 bottom-0 p-10">
|
|
||||||
<div className="max-w-lg space-y-3">
|
|
||||||
<p className="text-sm font-medium uppercase tracking-[0.18em] text-primary">
|
|
||||||
VisionRoad
|
|
||||||
</p>
|
|
||||||
<h1 className="text-4xl font-semibold tracking-normal text-foreground">
|
|
||||||
Road intelligence for faster inspection decisions
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm leading-6 text-muted-foreground">
|
|
||||||
Review projects, uploads, detections, and chainage insights from one secure
|
|
||||||
workspace.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="flex min-h-screen items-center justify-center px-6 py-10">
|
|
||||||
<div className="w-full max-w-sm space-y-7">
|
|
||||||
<header className="space-y-2">
|
|
||||||
<p className="text-sm font-medium text-muted-foreground">VisionRoad</p>
|
<p className="text-sm font-medium text-muted-foreground">VisionRoad</p>
|
||||||
<h2 className="text-2xl font-semibold tracking-normal">Sign in</h2>
|
<h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Use your email and password to continue.
|
Use your email and password to continue.
|
||||||
</p>
|
</p>
|
||||||
@@ -98,9 +72,9 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</div>
|
||||||
|
</div>
|
||||||
</GuestGuard>
|
</GuestGuard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { Building2, Plus } from 'lucide-react';
|
import { Building2, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PERMISSIONS } from '@/constants/permissions';
|
import { PERMISSIONS } from '@/constants/permissions';
|
||||||
import { PermissionGuard } from '@/guards';
|
import { PermissionGuard } from '@/guards';
|
||||||
@@ -120,8 +119,6 @@ export default function ClientsPage() {
|
|||||||
onLimitChange={setLimit}
|
onLimitChange={setLimit}
|
||||||
onSortingChange={setSorting}
|
onSortingChange={setSorting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<ClientSheet
|
<ClientSheet
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import { Project } from '@/types';
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton';
|
import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Reveal, StaggerContainer, StaggerItem } from '@/components/ui/reveal';
|
import { Reveal, StaggerContainer, StaggerItem } from '@/components/ui/reveal';
|
||||||
|
|
||||||
@@ -636,8 +635,6 @@ export default function DashboardPage() {
|
|||||||
</Reveal>
|
</Reveal>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ const ModulesLayout = ({
|
|||||||
}>) => {
|
}>) => {
|
||||||
return (
|
return (
|
||||||
<AuthGuard>
|
<AuthGuard>
|
||||||
<SidebarProvider className="bg-transparent overflow-hidden">
|
<SidebarProvider>
|
||||||
<AppSidebar className="bg-transparent" />
|
<AppSidebar />
|
||||||
<main className="flex flex-1 flex-col w-full h-screen overflow-hidden bg-transparent">
|
<main className="flex flex-1 flex-col w-full h-screen overflow-hidden">
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
{/* Centering container wrapper */}
|
|
||||||
<div className="max-w-380 mx-auto w-full flex flex-col min-h-full">
|
<div className="max-w-380 mx-auto w-full flex flex-col min-h-full">
|
||||||
<div className="flex gap-3 items-center sticky top-0 bg-background/30 backdrop-blur-xl z-30 py-3 px-6 border-b border-border/10">
|
<div className="flex gap-3 items-center sticky top-0 bg-background z-50 py-3 px-6 border-b border-border">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
<Separator orientation="vertical" className="mx-2 h-4" />
|
<Separator orientation="vertical" className="mx-2 h-4" />
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ function StateBadges({ value }: { value: string | null }) {
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors hover:bg-muted"
|
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-muted-foreground transition-colors hover:bg-muted"
|
||||||
>
|
>
|
||||||
+{remainingStates.length}
|
+{remainingStates.length}
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-2" align="start">
|
<PopoverContent className="w-auto p-2" align="start">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<p className="mb-0.5 px-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
|
<p className="mb-0.5 px-1 text-muted-foreground">
|
||||||
Other States
|
Other States
|
||||||
</p>
|
</p>
|
||||||
{remainingStates.map((state) => (
|
{remainingStates.map((state) => (
|
||||||
@@ -64,7 +64,7 @@ export function usePackageColumns(projects: Project[]) {
|
|||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: 'Package Name',
|
header: 'Package Name',
|
||||||
cell: ({ row }) => <div className="font-semibold">{row.original.name}</div>,
|
cell: ({ row }) => <div>{row.original.name}</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'project_id',
|
accessorKey: 'project_id',
|
||||||
|
|||||||
@@ -70,13 +70,13 @@ export function PackageDialog({
|
|||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-2xl" onOpenAutoFocus={(event) => event.preventDefault()}>
|
<DialogContent className="max-w-2xl" onOpenAutoFocus={(event) => event.preventDefault()}>
|
||||||
<DialogHeader className="gap-2">
|
<DialogHeader className="gap-2">
|
||||||
<DialogTitle className="flex items-center gap-3 text-xl">
|
<DialogTitle className="flex items-center gap-3">
|
||||||
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
||||||
<PackageIcon className="size-5" />
|
<PackageIcon className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
{packageId ? 'Edit Package Details' : 'Create New Package'}
|
{packageId ? 'Edit Package Details' : 'Create New Package'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
{packageId
|
{packageId
|
||||||
? 'Update the technical specifications for your road infrastructure package.'
|
? 'Update the technical specifications for your road infrastructure package.'
|
||||||
: 'Select a project and provide the essential data to establish a new package.'}
|
: 'Select a project and provide the essential data to establish a new package.'}
|
||||||
@@ -89,7 +89,7 @@ export function PackageDialog({
|
|||||||
<Select value={projectId} onValueChange={onProjectChange}>
|
<Select value={projectId} onValueChange={onProjectChange}>
|
||||||
<SelectTrigger aria-invalid={!!projectErrorMessage}>
|
<SelectTrigger aria-invalid={!!projectErrorMessage}>
|
||||||
{isProjectsLoading ? (
|
{isProjectsLoading ? (
|
||||||
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
<span className="flex items-center gap-2 text-muted-foreground">
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<Loader2 className="size-4 animate-spin" />
|
||||||
Loading...
|
Loading...
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Package as PackageIcon, Plus } from 'lucide-react';
|
import { Package as PackageIcon, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Package } from '@/types';
|
import type { Package } from '@/types';
|
||||||
|
|
||||||
@@ -108,8 +107,6 @@ export default function PackagePage() {
|
|||||||
onEdit={openEdit}
|
onEdit={openEdit}
|
||||||
onDelete={deletePackage}
|
onDelete={deletePackage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<PackageDialog
|
<PackageDialog
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export function usePlanColumns({
|
|||||||
header: 'Plan',
|
header: 'Plan',
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{row.original.name}</p>
|
<p>{row.original.name}</p>
|
||||||
<p className="text-xs text-muted-foreground">{row.original.slug}</p>
|
<p className="text-muted-foreground">{row.original.slug}</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -67,7 +67,7 @@ export function usePlanColumns({
|
|||||||
header: 'Limits',
|
header: 'Limits',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-muted-foreground">
|
||||||
<p>{row.original.max_projects} projects</p>
|
<p>{row.original.max_projects} projects</p>
|
||||||
<p>{row.original.max_users} users</p>
|
<p>{row.original.max_users} users</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function PlanSheet({
|
|||||||
placeholder="Basic plan for small teams"
|
placeholder="Basic plan for small teams"
|
||||||
{...register('description', { required: true })}
|
{...register('description', { required: true })}
|
||||||
required
|
required
|
||||||
className="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ export function PlanSheet({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-5">
|
<div className="flex flex-wrap gap-5">
|
||||||
<label className="flex items-center gap-2 text-sm">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="size-4 rounded border-border accent-primary"
|
className="size-4 rounded border-border accent-primary"
|
||||||
@@ -188,7 +188,7 @@ export function PlanSheet({
|
|||||||
/>
|
/>
|
||||||
Active
|
Active
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-2 text-sm">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="size-4 rounded border-border accent-primary"
|
className="size-4 rounded border-border accent-primary"
|
||||||
@@ -201,10 +201,10 @@ export function PlanSheet({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<Label>Permissions</Label>
|
<Label>Permissions</Label>
|
||||||
<span className="text-xs text-muted-foreground">{permissionIds.length} selected</span>
|
<span className="text-muted-foreground">{permissionIds.length} selected</span>
|
||||||
</div>
|
</div>
|
||||||
{isPermissionsLoading ? (
|
{isPermissionsLoading ? (
|
||||||
<div className="rounded-md border p-6 text-sm text-muted-foreground">
|
<div className="rounded-md border p-6 text-muted-foreground">
|
||||||
Loading permissions...
|
Loading permissions...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { BadgeIndianRupee, Plus } from 'lucide-react';
|
import { BadgeIndianRupee, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PERMISSIONS } from '@/constants/permissions';
|
import { PERMISSIONS } from '@/constants/permissions';
|
||||||
import { PermissionGuard } from '@/guards';
|
import { PermissionGuard } from '@/guards';
|
||||||
@@ -174,8 +173,6 @@ export default function PlansPage() {
|
|||||||
onLimitChange={setLimit}
|
onLimitChange={setLimit}
|
||||||
onSortingChange={setSorting}
|
onSortingChange={setSorting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<PlanSheet
|
<PlanSheet
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ function StateBadges({ value }: { value: string | null }) {
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors hover:bg-muted"
|
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-muted-foreground transition-colors hover:bg-muted"
|
||||||
>
|
>
|
||||||
+{remainingStates.length}
|
+{remainingStates.length}
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-2" align="start">
|
<PopoverContent className="w-auto p-2" align="start">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<p className="mb-0.5 px-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
|
<p className="mb-0.5 px-1 text-muted-foreground">
|
||||||
Other States
|
Other States
|
||||||
</p>
|
</p>
|
||||||
{remainingStates.map((state) => (
|
{remainingStates.map((state) => (
|
||||||
@@ -64,7 +64,7 @@ export function useProjectColumns() {
|
|||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: 'Project Name',
|
header: 'Project Name',
|
||||||
cell: ({ row }) => <div className="font-semibold">{row.original.name}</div>,
|
cell: ({ row }) => <div>{row.original.name}</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'state',
|
accessorKey: 'state',
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ export function ProjectDialog({
|
|||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-2xl" onOpenAutoFocus={(event) => event.preventDefault()}>
|
<DialogContent className="max-w-2xl" onOpenAutoFocus={(event) => event.preventDefault()}>
|
||||||
<DialogHeader className="gap-2">
|
<DialogHeader className="gap-2">
|
||||||
<DialogTitle className="flex items-center gap-3 text-xl">
|
<DialogTitle className="flex items-center gap-3">
|
||||||
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
||||||
<Layers className="size-5" />
|
<Layers className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
{projectId ? 'Edit Project Details' : 'Create New Project'}
|
{projectId ? 'Edit Project Details' : 'Create New Project'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
{projectId
|
{projectId
|
||||||
? 'Update the technical specifications for your road infrastructure project.'
|
? 'Update the technical specifications for your road infrastructure project.'
|
||||||
: 'Provide the essential road data to establish a new analysis project.'}
|
: 'Provide the essential road data to establish a new analysis project.'}
|
||||||
@@ -97,7 +97,7 @@ export function ProjectDialog({
|
|||||||
|
|
||||||
<div className="grid gap-8 pt-2 md:grid-cols-2">
|
<div className="grid gap-8 pt-2 md:grid-cols-2">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="flex items-center gap-2 text-[11px] font-black tracking-widest text-muted-foreground">
|
<p className="flex items-center gap-2 text-muted-foreground">
|
||||||
<MapPin className="size-3.5 opacity-60" />
|
<MapPin className="size-3.5 opacity-60" />
|
||||||
START POINT
|
START POINT
|
||||||
</p>
|
</p>
|
||||||
@@ -108,7 +108,7 @@ export function ProjectDialog({
|
|||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
placeholder="0.0000"
|
||||||
className="font-mono text-xs"
|
className="font-mono"
|
||||||
aria-invalid={!!startLatErrorMessage}
|
aria-invalid={!!startLatErrorMessage}
|
||||||
{...register('start_lat')}
|
{...register('start_lat')}
|
||||||
/>
|
/>
|
||||||
@@ -119,7 +119,7 @@ export function ProjectDialog({
|
|||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
placeholder="0.0000"
|
||||||
className="font-mono text-xs"
|
className="font-mono"
|
||||||
aria-invalid={!!startLngErrorMessage}
|
aria-invalid={!!startLngErrorMessage}
|
||||||
{...register('start_lng')}
|
{...register('start_lng')}
|
||||||
/>
|
/>
|
||||||
@@ -128,7 +128,7 @@ export function ProjectDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="flex items-center gap-2 text-[11px] font-black tracking-widest text-muted-foreground">
|
<p className="flex items-center gap-2 text-muted-foreground">
|
||||||
<MapPin className="size-3.5 opacity-60" />
|
<MapPin className="size-3.5 opacity-60" />
|
||||||
END POINT
|
END POINT
|
||||||
</p>
|
</p>
|
||||||
@@ -139,7 +139,7 @@ export function ProjectDialog({
|
|||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
placeholder="0.0000"
|
||||||
className="font-mono text-xs"
|
className="font-mono"
|
||||||
aria-invalid={!!endLatErrorMessage}
|
aria-invalid={!!endLatErrorMessage}
|
||||||
{...register('end_lat')}
|
{...register('end_lat')}
|
||||||
/>
|
/>
|
||||||
@@ -150,7 +150,7 @@ export function ProjectDialog({
|
|||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
placeholder="0.0000"
|
||||||
className="font-mono text-xs"
|
className="font-mono"
|
||||||
aria-invalid={!!endLngErrorMessage}
|
aria-invalid={!!endLngErrorMessage}
|
||||||
{...register('end_lng')}
|
{...register('end_lng')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Layers, Plus } from 'lucide-react';
|
import { Layers, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Project } from '@/types';
|
import type { Project } from '@/types';
|
||||||
|
|
||||||
@@ -100,8 +99,6 @@ export default function ProjectPage() {
|
|||||||
onEdit={openEdit}
|
onEdit={openEdit}
|
||||||
onDelete={deleteProject}
|
onDelete={deleteProject}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<ProjectDialog
|
<ProjectDialog
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Loader2, TrendingUp, ArrowUp, ArrowDown } from 'lucide-react';
|
import { Loader2, TrendingUp, ArrowUp, ArrowDown } from 'lucide-react';
|
||||||
import VideoPlayerSection from '@/components/video-player-section';
|
import VideoPlayerSection from '@/components/video-player-section';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { sessionService, videoService } from '@/services/api';
|
import { sessionService, videoService } from '@/services/api';
|
||||||
import { SessionContext, DetectionData, DetectionType } from '@/types';
|
import { SessionContext, DetectionData, DetectionType } from '@/types';
|
||||||
import { getVideoFile, clearVideoFile } from '@/lib/video-storage';
|
import { getVideoFile, clearVideoFile } from '@/lib/video-storage';
|
||||||
@@ -182,7 +181,6 @@ export default function VideoResultsPage() {
|
|||||||
projectId={session?.projectId || undefined}
|
projectId={session?.projectId || undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Loader2, TrendingUp } from 'lucide-react';
|
import { Loader2, TrendingUp } from 'lucide-react';
|
||||||
import VideoPlayerSection from '@/components/video-player-section';
|
import VideoPlayerSection from '@/components/video-player-section';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { sessionService } from '@/services/api';
|
import { sessionService } from '@/services/api';
|
||||||
import { SessionContext, DetectionData, DetectionType } from '@/types';
|
import { SessionContext, DetectionData, DetectionType } from '@/types';
|
||||||
import { clearVideoFile } from '@/lib/video-storage';
|
import { clearVideoFile } from '@/lib/video-storage';
|
||||||
@@ -147,7 +146,6 @@ export default function ResultsPage() {
|
|||||||
projectId={session?.projectId || undefined}
|
projectId={session?.projectId || undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function useRoleColumns({
|
|||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
cell: ({ row }) => <span className="font-medium">{row.original.name}</span>,
|
cell: ({ row }) => <span>{row.original.name}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'display_name',
|
accessorKey: 'display_name',
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ export function RoleSheet({
|
|||||||
error={errors.permission_ids?.message}
|
error={errors.permission_ids?.message}
|
||||||
className="space-y-3"
|
className="space-y-3"
|
||||||
labelEnd={
|
labelEnd={
|
||||||
<span className="text-xs text-muted-foreground">{permissionIds.length} selected</span>
|
<span className="text-muted-foreground">{permissionIds.length} selected</span>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isPermissionsLoading ? (
|
{isPermissionsLoading ? (
|
||||||
<div className="rounded-md border p-6 text-sm text-muted-foreground">
|
<div className="rounded-md border p-6 text-muted-foreground">
|
||||||
Loading permissions...
|
Loading permissions...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Plus, ShieldCheck } from 'lucide-react';
|
import { Plus, ShieldCheck } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PERMISSIONS } from '@/constants/permissions';
|
import { PERMISSIONS } from '@/constants/permissions';
|
||||||
import { PermissionGuard } from '@/guards';
|
import { PermissionGuard } from '@/guards';
|
||||||
@@ -136,8 +135,6 @@ export default function RolesPage() {
|
|||||||
onLimitChange={setLimit}
|
onLimitChange={setLimit}
|
||||||
onSortingChange={setSorting}
|
onSortingChange={setSorting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<RoleSheet
|
<RoleSheet
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ function StateBadges({ value }: { value: string | null }) {
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors hover:bg-muted"
|
className="flex items-center justify-center rounded-full border border-border/40 bg-muted/50 px-1.5 py-0.5 text-muted-foreground transition-colors hover:bg-muted"
|
||||||
>
|
>
|
||||||
+{remainingStates.length}
|
+{remainingStates.length}
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-2" align="start">
|
<PopoverContent className="w-auto p-2" align="start">
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<p className="mb-0.5 px-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
|
<p className="mb-0.5 px-1 text-muted-foreground">
|
||||||
Other States
|
Other States
|
||||||
</p>
|
</p>
|
||||||
{remainingStates.map((state) => (
|
{remainingStates.map((state) => (
|
||||||
@@ -65,7 +65,7 @@ export function useSegmentColumns(projects: Project[], packages: Package[]) {
|
|||||||
{
|
{
|
||||||
accessorKey: 'segment_name',
|
accessorKey: 'segment_name',
|
||||||
header: 'Segment Name',
|
header: 'Segment Name',
|
||||||
cell: ({ row }) => <div className="font-semibold">{row.original.segment_name}</div>,
|
cell: ({ row }) => <div>{row.original.segment_name}</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'package_id',
|
accessorKey: 'package_id',
|
||||||
@@ -101,7 +101,7 @@ export function useSegmentColumns(projects: Project[], packages: Package[]) {
|
|||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center gap-1.5 border-amber-500/50 font-bold text-amber-500"
|
className="flex items-center gap-1.5 border-amber-500/50 text-amber-500"
|
||||||
>
|
>
|
||||||
<Milestone className="size-3" />
|
<Milestone className="size-3" />
|
||||||
{segment.chainage_start_km} - {segment.chainage_end_km}
|
{segment.chainage_start_km} - {segment.chainage_end_km}
|
||||||
@@ -124,7 +124,7 @@ export function useSegmentColumns(projects: Project[], packages: Package[]) {
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center gap-1.5 border-blue-500/50 font-bold text-blue-500"
|
className="flex items-center gap-1.5 border-blue-500/50 text-blue-500"
|
||||||
>
|
>
|
||||||
<MapPin className="size-3" />
|
<MapPin className="size-3" />
|
||||||
{row.original.start_lat.toFixed(4)}, {row.original.start_lng.toFixed(4)}
|
{row.original.start_lat.toFixed(4)}, {row.original.start_lng.toFixed(4)}
|
||||||
@@ -137,7 +137,7 @@ export function useSegmentColumns(projects: Project[], packages: Package[]) {
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center gap-1.5 border-blue-500/50 font-bold text-blue-500"
|
className="flex items-center gap-1.5 border-blue-500/50 text-blue-500"
|
||||||
>
|
>
|
||||||
<MapPin className="size-3" />
|
<MapPin className="size-3" />
|
||||||
{row.original.end_lat.toFixed(4)}, {row.original.end_lng.toFixed(4)}
|
{row.original.end_lat.toFixed(4)}, {row.original.end_lng.toFixed(4)}
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ export function SegmentDialog({
|
|||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader className="gap-2">
|
<DialogHeader className="gap-2">
|
||||||
<DialogTitle className="flex items-center gap-3 text-xl">
|
<DialogTitle className="flex items-center gap-3">
|
||||||
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
||||||
<Milestone className="size-5" />
|
<Milestone className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
{segmentId ? 'Edit Segment' : 'Create Segment'}
|
{segmentId ? 'Edit Segment' : 'Create Segment'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription>
|
||||||
{segmentId
|
{segmentId
|
||||||
? 'Update the technical specifications for your road segment.'
|
? 'Update the technical specifications for your road segment.'
|
||||||
: 'Select a project and package, then provide the segment data.'}
|
: 'Select a project and package, then provide the segment data.'}
|
||||||
@@ -99,7 +99,7 @@ export function SegmentDialog({
|
|||||||
<Select value={projectId} onValueChange={onProjectChange}>
|
<Select value={projectId} onValueChange={onProjectChange}>
|
||||||
<SelectTrigger aria-invalid={!!getError('project_id')}>
|
<SelectTrigger aria-invalid={!!getError('project_id')}>
|
||||||
{isProjectsLoading ? (
|
{isProjectsLoading ? (
|
||||||
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
<span className="flex items-center gap-2 text-muted-foreground">
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<Loader2 className="size-4 animate-spin" />
|
||||||
Loading...
|
Loading...
|
||||||
</span>
|
</span>
|
||||||
@@ -126,7 +126,7 @@ export function SegmentDialog({
|
|||||||
>
|
>
|
||||||
<SelectTrigger aria-invalid={!!getError('package_id')}>
|
<SelectTrigger aria-invalid={!!getError('package_id')}>
|
||||||
{isPackagesLoading ? (
|
{isPackagesLoading ? (
|
||||||
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
<span className="flex items-center gap-2 text-muted-foreground">
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<Loader2 className="size-4 animate-spin" />
|
||||||
Loading...
|
Loading...
|
||||||
</span>
|
</span>
|
||||||
@@ -211,7 +211,7 @@ export function SegmentDialog({
|
|||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="flex items-center gap-2 text-[11px] font-black tracking-widest text-muted-foreground">
|
<p className="flex items-center gap-2 text-muted-foreground">
|
||||||
<MapPin className="size-3.5 opacity-60" />
|
<MapPin className="size-3.5 opacity-60" />
|
||||||
START COORDINATES
|
START COORDINATES
|
||||||
</p>
|
</p>
|
||||||
@@ -244,7 +244,7 @@ export function SegmentDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="flex items-center gap-2 text-[11px] font-black tracking-widest text-muted-foreground">
|
<p className="flex items-center gap-2 text-muted-foreground">
|
||||||
<MapPin className="size-3.5 opacity-60" />
|
<MapPin className="size-3.5 opacity-60" />
|
||||||
END COORDINATES
|
END COORDINATES
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react';
|
|||||||
import { Milestone, Plus } from 'lucide-react';
|
import { Milestone, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import type { Chainage } from '@/types';
|
import type { Chainage } from '@/types';
|
||||||
|
|
||||||
@@ -119,8 +118,6 @@ export default function SegmentPage() {
|
|||||||
onEdit={openEdit}
|
onEdit={openEdit}
|
||||||
onDelete={deleteSegment}
|
onDelete={deleteSegment}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<SegmentDialog
|
<SegmentDialog
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export function useTenantColumns({
|
|||||||
header: 'Tenant',
|
header: 'Tenant',
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{row.original.name}</p>
|
<p>{row.original.name}</p>
|
||||||
<p className="text-xs text-muted-foreground">{row.original.slug}</p>
|
<p className="text-muted-foreground">{row.original.slug}</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ export function TenantSheet({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<form onSubmit={onSubmit} className="flex flex-1 flex-col gap-5 px-4">
|
<form onSubmit={onSubmit} className="flex flex-1 flex-col gap-5 px-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm font-medium">Basic Information</p>
|
<p>Basic Information</p>
|
||||||
<p className="text-xs text-muted-foreground">Tenant identity and subscription binding.</p>
|
<p className="text-muted-foreground">Tenant identity and subscription binding.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
@@ -147,7 +147,7 @@ export function TenantSheet({
|
|||||||
id="tenant-description"
|
id="tenant-description"
|
||||||
placeholder="North zone operations"
|
placeholder="North zone operations"
|
||||||
{...register('description')}
|
{...register('description')}
|
||||||
className="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,8 +155,8 @@ export function TenantSheet({
|
|||||||
{!isEditMode ? (
|
{!isEditMode ? (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm font-medium">Tenant Administrator</p>
|
<p>Tenant Administrator</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Invitation details for the primary tenant admin account.
|
Invitation details for the primary tenant admin account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,10 +204,10 @@ export function TenantSheet({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : adminEmail ? (
|
) : adminEmail ? (
|
||||||
<div className="rounded-md border bg-muted/40 p-4 text-sm text-muted-foreground">
|
<div className="rounded-md border bg-muted/40 p-4 text-muted-foreground">
|
||||||
Admin invitation details cannot be changed after tenant creation.
|
Admin invitation details cannot be changed after tenant creation.
|
||||||
<p className="mt-1 text-foreground">
|
<p className="mt-1 text-foreground">
|
||||||
Current admin email: <span className="font-medium">{adminEmail}</span>
|
Current admin email: <span>{adminEmail}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { Globe, Plus } from 'lucide-react';
|
import { Globe, Plus } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PERMISSIONS } from '@/constants/permissions';
|
import { PERMISSIONS } from '@/constants/permissions';
|
||||||
import { PermissionGuard } from '@/guards';
|
import { PermissionGuard } from '@/guards';
|
||||||
@@ -171,8 +170,6 @@ export default function TenantsPage() {
|
|||||||
onLimitChange={setLimit}
|
onLimitChange={setLimit}
|
||||||
onSortingChange={setSorting}
|
onSortingChange={setSorting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<TenantSheet
|
<TenantSheet
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useRouter, useParams } from 'next/navigation';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Loader2, TrendingUp, ArrowUp, ArrowDown } from 'lucide-react';
|
import { Loader2, TrendingUp, ArrowUp, ArrowDown } from 'lucide-react';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { sessionService, videoService } from '@/services/api';
|
import { sessionService, videoService } from '@/services/api';
|
||||||
import { SessionContext } from '@/types';
|
import { SessionContext } from '@/types';
|
||||||
import { ROUTES } from '@/utils/routes';
|
import { ROUTES } from '@/utils/routes';
|
||||||
@@ -241,8 +240,6 @@ export default function VideoProcessingPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Loader2, TrendingUp, ChevronRight } from 'lucide-react';
|
import { Loader2, TrendingUp, ChevronRight } from 'lucide-react';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { sessionService, videoService } from '@/services/api';
|
import { sessionService, videoService } from '@/services/api';
|
||||||
import { SessionContext } from '@/types';
|
import { SessionContext } from '@/types';
|
||||||
import { storeVideoFile } from '@/lib/video-storage';
|
import { storeVideoFile } from '@/lib/video-storage';
|
||||||
@@ -265,12 +264,6 @@ export default function UploadPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12">
|
|
||||||
<Reveal delay={0.4}>
|
|
||||||
<PoweredBy />
|
|
||||||
</Reveal>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function useUserColumns({
|
|||||||
accessorKey: 'first_name',
|
accessorKey: 'first_name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="font-medium">
|
<span>
|
||||||
{[row.original.first_name, row.original.last_name].filter(Boolean).join(' ') ||
|
{[row.original.first_name, row.original.last_name].filter(Boolean).join(' ') ||
|
||||||
row.original.username}
|
row.original.username}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export function UserStats({ total, activeCount, inactiveCount, pendingCount }: U
|
|||||||
<div className="grid gap-3 md:grid-cols-4">
|
<div className="grid gap-3 md:grid-cols-4">
|
||||||
{stats.map((stat) => (
|
{stats.map((stat) => (
|
||||||
<div key={stat.label} className="rounded-lg border bg-card p-4">
|
<div key={stat.label} className="rounded-lg border bg-card p-4">
|
||||||
<p className="text-xs text-muted-foreground">{stat.label}</p>
|
<p className="text-muted-foreground">{stat.label}</p>
|
||||||
<p className="mt-1 text-2xl font-semibold">{stat.value}</p>
|
<p>{stat.value}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { Plus, Users } from 'lucide-react';
|
import { Plus, Users } from 'lucide-react';
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { PoweredBy } from '@/components/powered-by';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PERMISSIONS } from '@/constants/permissions';
|
import { PERMISSIONS } from '@/constants/permissions';
|
||||||
import { PermissionGuard } from '@/guards';
|
import { PermissionGuard } from '@/guards';
|
||||||
@@ -139,8 +138,6 @@ export default function UsersPage() {
|
|||||||
onLimitChange={setLimit}
|
onLimitChange={setLimit}
|
||||||
onSortingChange={setSorting}
|
onSortingChange={setSorting}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PoweredBy />
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<UserSheet
|
<UserSheet
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
@import './typography.css';
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
--radius: 0.65rem;
|
--radius: 0.65rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(1 0 0);
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
--foreground: oklch(0.141 0.005 285.823);
|
||||||
--card: oklch(1 0 0 / 0.4);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.141 0.005 285.823);
|
--card-foreground: oklch(0.141 0.005 285.823);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
--chart-5: oklch(0.85 0.08 195); /* Soft Cyan - Subdued */
|
--chart-5: oklch(0.85 0.08 195); /* Soft Cyan - Subdued */
|
||||||
--chart-6: oklch(0.62 0.22 295); /* Deep Violet - Theme Primary */
|
--chart-6: oklch(0.62 0.22 295); /* Deep Violet - Theme Primary */
|
||||||
--chart-8: oklch(0.58 0.2 335); /* Cool Magenta - Distant Accent */
|
--chart-8: oklch(0.58 0.2 335); /* Cool Magenta - Distant Accent */
|
||||||
--sidebar: oklch(0.985 0 0 / 0.6);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||||
--sidebar-primary: oklch(0.541 0.281 293.009);
|
--sidebar-primary: oklch(0.541 0.281 293.009);
|
||||||
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
|
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.141 0.005 285.823);
|
--background: oklch(0.141 0.005 285.823);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.21 0.006 285.885 / 0.6);
|
--card: oklch(0.21 0.006 285.885);
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.21 0.006 285.885);
|
--popover: oklch(0.21 0.006 285.885);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
--chart-5: oklch(0.88 0.1 195);
|
--chart-5: oklch(0.88 0.1 195);
|
||||||
--chart-6: oklch(0.65 0.24 295);
|
--chart-6: oklch(0.65 0.24 295);
|
||||||
--chart-8: oklch(0.62 0.22 335);
|
--chart-8: oklch(0.62 0.22 335);
|
||||||
--sidebar: oklch(0.21 0.006 285.885 / 0.6);
|
--sidebar: oklch(0.21 0.006 285.885);
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: oklch(0.606 0.25 292.717);
|
--sidebar-primary: oklch(0.606 0.25 292.717);
|
||||||
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
|
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
|
||||||
@@ -126,30 +127,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-transparent text-foreground;
|
@apply bg-background text-foreground;
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modern Scrollbar Styles */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 8px; /* Slightly wider for better accessibility */
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--muted-foreground);
|
|
||||||
border-radius: 20px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
background-clip: content-box;
|
|
||||||
opacity: 0.5; /* More visible default */
|
|
||||||
}
|
|
||||||
/* Firefox Support */
|
|
||||||
* {
|
|
||||||
/* scrollbar-width: ; */
|
|
||||||
scrollbar-color: var(--muted-foreground) transparent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { ThemeProvider } from '@/providers/ThemeProvider';
|
|||||||
import AppInitializer from '@/providers/AppInitializer';
|
import AppInitializer from '@/providers/AppInitializer';
|
||||||
import QueryProvider from '@/providers/QueryProvider';
|
import QueryProvider from '@/providers/QueryProvider';
|
||||||
import { Toaster } from '@/components/ui/sonner';
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
import { BackgroundGradient } from '@/components/background-gradient';
|
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: '--font-geist-sans',
|
variable: '--font-geist-sans',
|
||||||
@@ -38,10 +37,7 @@ export default function RootLayout({
|
|||||||
>
|
>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
<AppInitializer>
|
<AppInitializer>
|
||||||
<div className="relative min-h-screen isolate">
|
|
||||||
<BackgroundGradient />
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
|
||||||
<Toaster position="top-center" />
|
<Toaster position="top-center" />
|
||||||
</AppInitializer>
|
</AppInitializer>
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
|
|||||||
56
src/app/typography.css
Normal file
56
src/app/typography.css
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@layer base {
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
/* margin: 0.1rem 0 0.1rem; */
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:first-child,
|
||||||
|
h2:first-child,
|
||||||
|
h3:first-child,
|
||||||
|
h4:first-child,
|
||||||
|
h5:first-child,
|
||||||
|
h6:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.2rem 0 0.1rem;
|
||||||
|
/* line-height: 1.5; */
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export function BackgroundGradient() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 -z-10 h-full w-full overflow-hidden pointer-events-none"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
{/* Top Gradient */}
|
|
||||||
<div className="absolute inset-x-0 top-0 transform-gpu overflow-hidden blur-3xl">
|
|
||||||
<div
|
|
||||||
className="relative left-[calc(50%-11rem)] aspect-1155/678 w-144.5 -translate-x-1/2 rotate-30 bg-linear-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 sm:left-[calc(50%-30rem)] sm:w-288.75"
|
|
||||||
style={{
|
|
||||||
clipPath:
|
|
||||||
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Middle/Bottom Gradient for balance */}
|
|
||||||
<div className="absolute inset-x-0 bottom-0 transform-gpu overflow-hidden blur-3xl">
|
|
||||||
<div
|
|
||||||
className="relative left-[calc(50%+11rem)] aspect-1155/678 w-144.5 -translate-x-1/2 rotate-30 bg-linear-to-tr from-[#ff80b5] to-[#9089fc] opacity-20 sm:left-[calc(50%+30rem)] sm:w-288.75"
|
|
||||||
style={{
|
|
||||||
clipPath:
|
|
||||||
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BackgroundGradientBottom() {
|
|
||||||
return null; // Integrated into the main BackgroundGradient component
|
|
||||||
}
|
|
||||||
@@ -23,8 +23,8 @@ const TopHeader = ({
|
|||||||
{(title || onAddNew) && (
|
{(title || onAddNew) && (
|
||||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||||
<div>
|
<div>
|
||||||
{title ? <h2 className="text-base font-semibold">{title}</h2> : null}
|
{title ? <h2>{title}</h2> : null}
|
||||||
<p className="text-sm text-muted-foreground">Total {itemCount ?? 0}</p>
|
<p className="text-muted-foreground">Total {itemCount ?? 0}</p>
|
||||||
</div>
|
</div>
|
||||||
{onAddNew ? (
|
{onAddNew ? (
|
||||||
<Button onClick={onAddNew}>
|
<Button onClick={onAddNew}>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { cn } from '@/lib/utils';
|
|||||||
|
|
||||||
const TableHeader = <TData, _>({ table }: { table: Table<TData> }) => {
|
const TableHeader = <TData, _>({ table }: { table: Table<TData> }) => {
|
||||||
return (
|
return (
|
||||||
<ShadTableHeader className="sticky top-0 z-10 bg-card/80 backdrop-blur-xl">
|
<ShadTableHeader className="sticky top-0 z-10 bg-card">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
@@ -25,7 +25,7 @@ const TableHeader = <TData, _>({ table }: { table: Table<TData> }) => {
|
|||||||
key={header.id}
|
key={header.id}
|
||||||
onClick={canSort ? header.column.getToggleSortingHandler() : undefined}
|
onClick={canSort ? header.column.getToggleSortingHandler() : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-10 bg-card/80 px-2 text-foreground backdrop-blur-xl transition-colors',
|
'h-10 bg-card px-2 text-foreground transition-colors',
|
||||||
canSort && 'cursor-pointer select-none hover:bg-muted/60',
|
canSort && 'cursor-pointer select-none hover:bg-muted/60',
|
||||||
sortDirection && 'bg-muted/70',
|
sortDirection && 'bg-muted/70',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,17 +15,10 @@ export function PageHeader({ title, description, icon: Icon, children, actions }
|
|||||||
return (
|
return (
|
||||||
<Reveal direction="down" className="w-full">
|
<Reveal direction="down" className="w-full">
|
||||||
<div className="flex items-center justify-between w-full">
|
<div className="flex items-center justify-between w-full">
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
{/* <div className="p-3.5 rounded-xl bg-primary text-primary-foreground flex items-center justify-center shadow-lg shadow-primary/5 ring-1 ring-white/10">
|
|
||||||
{Icon && <Icon className="h-7 w-7" />}
|
|
||||||
{children}
|
|
||||||
</div> */}
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight ">{title}</h1>
|
<h1>{title}</h1>
|
||||||
<p className="text-muted-foreground mt-1 text-sm font-semibold tracking-wide opacity-80">
|
<p className="text-muted-foreground opacity-80">{description}</p>
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{actions && <div className="flex items-center gap-4">{actions}</div>}
|
{actions && <div className="flex items-center gap-4">{actions}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
export function PoweredBy() {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center gap-2 mt-3 transition-opacity duration-300">
|
|
||||||
<span className="font-medium text-muted-foreground">Powered by</span>
|
|
||||||
<Image src="/sg.svg" alt="Sentient Geeks" width={130} height={20} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col gap-6 rounded-xl border bg-card backdrop-blur-xl py-6 text-card-foreground',
|
'flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function DialogTitle({
|
|||||||
return (
|
return (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
data-slot="dialog-title"
|
data-slot="dialog-title"
|
||||||
className={cn("text-lg leading-none font-semibold", className)}
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -143,7 +143,7 @@ function DialogDescription({
|
|||||||
return (
|
return (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function SheetTitle({
|
|||||||
return (
|
return (
|
||||||
<SheetPrimitive.Title
|
<SheetPrimitive.Title
|
||||||
data-slot="sheet-title"
|
data-slot="sheet-title"
|
||||||
className={cn("font-semibold text-foreground", className)}
|
className={cn("text-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -125,7 +125,7 @@ function SheetDescription({
|
|||||||
return (
|
return (
|
||||||
<SheetPrimitive.Description
|
<SheetPrimitive.Description
|
||||||
data-slot="sheet-description"
|
data-slot="sheet-description"
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ function Sidebar({
|
|||||||
<div
|
<div
|
||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-sidebar backdrop-blur-xl text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',
|
'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -179,7 +179,7 @@ function Sidebar({
|
|||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
data-mobile="true"
|
data-mobile="true"
|
||||||
className="bg-sidebar backdrop-blur-xl text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
||||||
@@ -236,7 +236,7 @@ function Sidebar({
|
|||||||
<div
|
<div
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar-inner"
|
data-slot="sidebar-inner"
|
||||||
className="bg-sidebar backdrop-blur-xl group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client';
|
"use client"
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from "react"
|
||||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function TooltipProvider({
|
function TooltipProvider({
|
||||||
delayDuration = 0,
|
delayDuration = 0,
|
||||||
@@ -15,19 +15,19 @@ function TooltipProvider({
|
|||||||
delayDuration={delayDuration}
|
delayDuration={delayDuration}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
function Tooltip({
|
||||||
return (
|
...props
|
||||||
<TooltipProvider>
|
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
function TooltipTrigger({
|
||||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
...props
|
||||||
|
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||||
|
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function TooltipContent({
|
function TooltipContent({
|
||||||
@@ -42,16 +42,16 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
|
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
|
|||||||
Reference in New Issue
Block a user