feat: port website header with mega-menu, add showcase bg to hero, fix carousel container size

This commit is contained in:
Usman Baig
2026-03-21 20:12:01 +01:00
parent 4afaf32e58
commit 911704cff2
9 changed files with 761 additions and 39 deletions

View File

@@ -3,6 +3,7 @@
import { OfflineBanner } from '@/components/OfflineBanner'
import { Footer } from '@/components/Footer'
import { Header, type CipheraApp } from '@ciphera-net/ui'
import { Header as MarketingHeader } from '@/components/marketing/Header'
import NotificationCenter from '@/components/notifications/NotificationCenter'
import { useAuth } from '@/lib/auth/context'
import { useOnlineStatus } from '@/lib/hooks/useOnlineStatus'
@@ -144,31 +145,11 @@ function LayoutInner({ children }: { children: React.ReactNode }) {
)
}
// Public/marketing: floating header + footer
// Public/marketing: sticky header + footer
return (
<div className="flex flex-col min-h-screen">
<Header
auth={auth}
LinkComponent={Link}
logoSrc="/pulse_icon_no_margins.png"
appName="Pulse"
variant="floating"
showFaq={false}
showSecurity={false}
showPricing={true}
topOffset={showOfflineBar ? '2.5rem' : undefined}
apps={CIPHERA_APPS}
currentAppId="pulse"
customNavItems={
<Link
href="/features"
className="px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white rounded-lg hover:bg-neutral-100/50 dark:hover:bg-neutral-800/50 transition-all duration-200"
>
Features
</Link>
}
/>
<main className="flex-1 pb-8 pt-24">
<MarketingHeader />
<main className="flex-1 pb-8">
{children}
</main>
<Footer

View File

@@ -151,12 +151,9 @@ export default function HomePage() {
return (
<>
{/* HERO — full viewport */}
<div className="relative -mt-[88px] min-h-screen flex items-center pt-[88px] pb-20 lg:pb-32 overflow-hidden">
{/* Background atmosphere */}
<div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute top-0 left-1/4 w-[800px] h-[800px] bg-brand-orange/5 rounded-full blur-[200px]" />
<div className="absolute bottom-0 right-1/4 w-[600px] h-[600px] bg-neutral-500/5 rounded-full blur-[150px]" />
</div>
<div className="relative -mt-[88px] min-h-screen flex items-center pt-[88px] pb-20 lg:pb-32 bg-neutral-950 overflow-hidden">
<img src="/pulse-showcase-bg.png" alt="" className="absolute inset-0 w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/60" />
<div className="absolute bottom-0 left-0 right-0 h-48 bg-gradient-to-t from-neutral-950 to-transparent" />
<div className="relative z-10 w-full max-w-6xl mx-auto px-6">

View File

@@ -15,6 +15,7 @@ function FeatureSection({
features,
mockup,
reverse = false,
showBg = true,
}: {
id: string
heading: string
@@ -22,6 +23,7 @@ function FeatureSection({
features: string[]
mockup: React.ReactNode
reverse?: boolean
showBg?: boolean
}) {
return (
<section id={id} className="container mx-auto px-6 scroll-mt-28">
@@ -58,10 +60,14 @@ function FeatureSection({
transition={{ duration: 0.6, delay: 0.1 }}
className={`relative ${reverse ? 'lg:order-first' : ''}`}
>
<div className="absolute -inset-8 bg-brand-orange/8 rounded-[2.5rem] blur-3xl" />
<div className="relative rounded-3xl overflow-hidden border border-white/[0.08]">
<img src="/pulse-showcase-bg.png" alt="" className="absolute inset-0 w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/30" />
{showBg && <div className="absolute -inset-8 bg-brand-orange/8 rounded-[2.5rem] blur-3xl" />}
<div className={`relative rounded-3xl overflow-hidden border border-white/[0.08] ${showBg ? '' : 'bg-neutral-900/80'}`}>
{showBg && (
<>
<img src="/pulse-showcase-bg.png" alt="" className="absolute inset-0 w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/30" />
</>
)}
<div className="relative">
{mockup}
</div>
@@ -106,8 +112,10 @@ export default function FeatureSections() {
]}
reverse
mockup={
<div className="p-6 sm:p-10">
<PulseFeaturesCarousel />
<div className="p-6 sm:p-10 min-h-[500px] flex items-center">
<div className="w-full">
<PulseFeaturesCarousel />
</div>
</div>
}
/>
@@ -151,9 +159,10 @@ export default function FeatureSections() {
}
/>
{/* Section 5: Script — text left, code block right */}
{/* Section 5: Script — text left, code block right (no showcase bg) */}
<FeatureSection
id="script"
showBg={false}
heading="One script tag. That's it."
description="No npm packages, no build steps, no configuration files. Add a single line to your HTML and start collecting privacy-respecting analytics instantly."
features={[

View File

@@ -0,0 +1,309 @@
'use client';
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { Button } from '@/components/ui/button-website';
import { cn } from '@/lib/utils';
import { MenuToggleIcon } from '@/components/ui/menu-toggle-icon';
import { createPortal } from 'react-dom';
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from '@/components/ui/navigation-menu';
import { LucideIcon } from 'lucide-react';
import {
BarChart3,
Eye,
Funnel,
Send,
FileText,
Puzzle,
HelpCircle,
} from 'lucide-react';
type LinkItem = {
title: string;
href: string;
icon?: LucideIcon;
description?: string;
};
const featureLinks: LinkItem[] = [
{
title: 'Dashboard',
href: '/features#dashboard',
icon: BarChart3,
description: 'Real-time traffic overview',
},
{
title: 'Visitor Insights',
href: '/features#visitors',
icon: Eye,
description: 'Browser, device & geo data',
},
{
title: 'Conversion Funnels',
href: '/features#funnels',
icon: Funnel,
description: 'Multi-step drop-off analysis',
},
{
title: 'Email Reports',
href: '/features#reports',
icon: Send,
description: 'Scheduled inbox summaries',
},
];
const resourceLinks: LinkItem[] = [
{
title: 'Installation',
href: '/installation',
icon: FileText,
description: 'Setup guides & code snippets',
},
{
title: 'Integrations',
href: '/integrations',
icon: Puzzle,
description: '75+ framework guides',
},
{
title: 'FAQ',
href: '/faq',
icon: HelpCircle,
description: 'Common questions answered',
},
];
export function Header() {
const [open, setOpen] = React.useState(false);
const scrolled = useScroll(10);
React.useEffect(() => {
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [open]);
return (
<header
className={cn('sticky top-0 z-50 w-full border-b border-transparent', {
'border-white/[0.06]': scrolled,
})}
>
<div className={cn("absolute inset-0 -z-10 transition-opacity duration-300", scrolled ? "opacity-100 backdrop-blur-xl bg-neutral-950/60 supports-[backdrop-filter]:bg-neutral-950/50" : "opacity-0")} />
<nav className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-6 my-3">
<div className="flex items-center gap-5">
<Link href="/" className="hover:bg-accent rounded-md p-2 flex items-center gap-2">
<Image
src="/pulse_icon_no_margins.png"
alt="Pulse"
width={36}
height={36}
priority
className="object-contain w-8 h-8"
unoptimized
/>
<span className="text-xl font-bold text-foreground tracking-tight">
Pulse
</span>
</Link>
<NavigationMenu className="hidden md:flex">
<NavigationMenuList>
{/* Features dropdown */}
<NavigationMenuItem>
<NavigationMenuTrigger className="bg-transparent">Features</NavigationMenuTrigger>
<NavigationMenuContent className="bg-transparent p-1 pr-1.5">
<ul className="grid w-[32rem] grid-cols-2 gap-2 rounded-md border border-white/[0.06] bg-white/[0.04] p-2">
{featureLinks.map((item, i) => (
<li key={i}>
<ListItem title={item.title} href={item.href} icon={item.icon} description={item.description} />
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
{/* Resources dropdown */}
<NavigationMenuItem>
<NavigationMenuTrigger className="bg-transparent">Resources</NavigationMenuTrigger>
<NavigationMenuContent className="bg-transparent p-1 pr-1.5 pb-1.5">
<div className="grid w-[32rem] grid-cols-2 gap-2">
<ul className="space-y-2 rounded-md border border-white/[0.06] bg-white/[0.04] p-2">
{resourceLinks.map((item, i) => (
<li key={i}>
<ListItem {...item} />
</li>
))}
</ul>
<div className="flex flex-col justify-center gap-3 p-4">
<p className="text-sm font-medium text-foreground">Need help?</p>
<p className="text-xs text-muted-foreground leading-relaxed">
Questions about setup, integrations, or billing we typically respond within 24-48 hours.
</p>
<a href="mailto:support@ciphera.net" className="text-sm font-medium text-brand-orange hover:underline">
support@ciphera.net &rarr;
</a>
</div>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
{/* Pricing standalone link */}
<NavigationMenuItem>
<NavigationMenuLink asChild>
<Link href="/pricing" className="group inline-flex h-9 w-max items-center justify-center rounded-md bg-transparent px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none">
Pricing
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
<div className="hidden items-center gap-2 md:flex">
<Button variant="outline" asChild>
<a href="https://pulse.ciphera.net">Sign In</a>
</Button>
<Button asChild>
<a href="https://pulse.ciphera.net">Get Started</a>
</Button>
</div>
<div className="flex items-center gap-2 md:hidden">
<Button
size="icon"
variant="outline"
onClick={() => setOpen(!open)}
aria-expanded={open}
aria-controls="mobile-menu"
aria-label="Toggle menu"
>
<MenuToggleIcon open={open} className="size-5" duration={300} />
</Button>
</div>
</nav>
<MobileMenu open={open} className="flex flex-col justify-between gap-2 overflow-y-auto">
<NavigationMenu className="max-w-full">
<div className="flex w-full flex-col gap-y-2">
<span className="text-sm">Features</span>
{featureLinks.map((link) => (
<ListItem key={link.title} title={link.title} href={link.href} icon={link.icon} description={link.description} />
))}
<span className="text-sm">Resources</span>
{resourceLinks.map((link) => (
<ListItem key={link.title} {...link} />
))}
<Link
href="/pricing"
className="flex flex-row gap-x-2 rounded-sm p-2 transition-colors hover:bg-white/[0.06]"
>
<div className="flex aspect-square size-12 items-center justify-center rounded-md border border-white/[0.08] bg-white/[0.05] shadow-sm p-2">
<BarChart3 className="text-foreground size-5" />
</div>
<div className="flex flex-col items-start justify-center">
<span className="text-sm font-medium">Pricing</span>
<span className="text-muted-foreground text-xs">Plans & billing</span>
</div>
</Link>
</div>
</NavigationMenu>
<div className="flex flex-col gap-2">
<Button variant="outline" className="w-full bg-transparent" asChild>
<a href="https://pulse.ciphera.net">
Sign In
</a>
</Button>
<Button className="w-full" asChild>
<a href="https://pulse.ciphera.net">
Get Started
</a>
</Button>
</div>
</MobileMenu>
</header>
);
}
type MobileMenuProps = React.ComponentProps<'div'> & {
open: boolean;
};
function MobileMenu({ open, children, className, ...props }: MobileMenuProps) {
if (!open || typeof window === 'undefined') return null;
return createPortal(
<div
id="mobile-menu"
className={cn(
'bg-background/95 supports-[backdrop-filter]:bg-background/50 backdrop-blur-lg',
'fixed top-16 right-0 bottom-0 left-0 z-40 flex flex-col overflow-hidden border-y md:hidden',
)}
>
<div
data-slot={open ? 'open' : 'closed'}
className={cn(
'data-[slot=open]:animate-in data-[slot=open]:zoom-in-95 ease-out',
'size-full p-4',
className,
)}
{...props}
>
{children}
</div>
</div>,
document.body,
);
}
function ListItem({
title,
description,
icon: Icon,
className,
href,
...props
}: React.ComponentProps<typeof NavigationMenuLink> & LinkItem) {
return (
<NavigationMenuLink className={cn('w-full flex flex-row gap-x-2 data-[active=true]:focus:bg-white/[0.06] data-[active=true]:hover:bg-white/[0.06] data-[active=true]:text-accent-foreground hover:bg-white/[0.06] hover:text-accent-foreground focus:bg-white/[0.06] focus:text-accent-foreground rounded-sm p-2 transition-colors', className)} {...props} asChild>
<Link href={href || '#'}>
<div className="flex aspect-square size-12 items-center justify-center rounded-md border border-white/[0.08] bg-white/[0.05] shadow-sm p-2">
{Icon ? (
<Icon className="text-foreground size-5" />
) : null}
</div>
<div className="flex flex-col items-start justify-center">
<span className="text-sm font-medium">{title}</span>
<span className="text-muted-foreground text-xs">{description}</span>
</div>
</Link>
</NavigationMenuLink>
);
}
function useScroll(threshold: number) {
const [scrolled, setScrolled] = React.useState(false);
const onScroll = React.useCallback(() => {
setScrolled(window.scrollY > threshold);
}, [threshold]);
React.useEffect(() => {
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, [onScroll]);
React.useEffect(() => {
onScroll();
}, [onScroll]);
return scrolled;
}

View File

@@ -0,0 +1,56 @@
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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
},
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,54 @@
'use client';
import React from 'react';
import { cn } from '@/lib/utils';
type MenuToggleProps = React.ComponentProps<'svg'> & {
open: boolean;
duration?: number;
};
export function MenuToggleIcon({
open,
className,
fill = 'none',
stroke = 'currentColor',
strokeWidth = 2.5,
strokeLinecap = 'round',
strokeLinejoin = 'round',
duration = 500,
...props
}: MenuToggleProps) {
return (
<svg
strokeWidth={strokeWidth}
fill={fill}
stroke={stroke}
viewBox="0 0 32 32"
strokeLinecap={strokeLinecap}
strokeLinejoin={strokeLinejoin}
className={cn(
'transition-transform ease-in-out',
open && '-rotate-45',
className,
)}
style={{
transitionDuration: `${duration}ms`,
}}
{...props}
>
<path
className={cn(
'transition-all ease-in-out',
open
? '[stroke-dasharray:20_300] [stroke-dashoffset:-32.42px]'
: '[stroke-dasharray:12_63]',
)}
style={{
transitionDuration: `${duration}ms`,
}}
d="M27 10 13 10C10.8 10 9 8.2 9 6 9 3.5 10.8 2 13 2 15.2 2 17 3.8 17 6L17 26C17 28.2 18.8 30 21 30 23.2 30 25 28.2 25 26 25 23.8 23.2 22 21 22L7 22"
/>
<path d="M7 16 27 16" />
</svg>
);
}

View File

@@ -0,0 +1,128 @@
import * as React from "react"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-xl border border-white/[0.08] bg-neutral-900/65 text-popover-foreground shadow-xl backdrop-blur-3xl backdrop-saturate-150 supports-[backdrop-filter]:bg-neutral-900/60 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:duration-150 data-[state=closed]:duration-100 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

190
package-lock.json generated
View File

@@ -11,6 +11,9 @@
"@ciphera-net/ui": "^0.3.1",
"@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-slot": "^1.2.4",
"@simplewebauthn/browser": "^13.2.2",
"@stripe/react-stripe-js": "^5.6.0",
"@stripe/stripe-js": "^8.7.0",
@@ -27,6 +30,7 @@
"iso-3166-2": "^1.0.0",
"jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.7",
"lucide-react": "^0.577.0",
"motion": "^12.35.2",
"next": "^16.1.1",
"radix-ui": "^1.4.3",
@@ -3435,6 +3439,24 @@
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
@@ -3594,6 +3616,24 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -3688,6 +3728,24 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@@ -3858,6 +3916,15 @@
}
}
},
"node_modules/@radix-ui/react-icons": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
"integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
"license": "MIT",
"peerDependencies": {
"react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/@radix-ui/react-id": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
@@ -3939,6 +4006,24 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-menubar": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz",
@@ -4108,6 +4193,24 @@
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
@@ -4211,6 +4314,24 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
@@ -4372,6 +4493,24 @@
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
@@ -4429,9 +4568,9 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
@@ -4656,6 +4795,24 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@@ -11357,6 +11514,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.577.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
"integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -13038,6 +13204,24 @@
}
}
},
"node_modules/radix-ui/node_modules/@radix-ui/react-slot": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",

View File

@@ -15,6 +15,9 @@
"@ciphera-net/ui": "^0.3.1",
"@ducanh2912/next-pwa": "^10.2.9",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-slot": "^1.2.4",
"@simplewebauthn/browser": "^13.2.2",
"@stripe/react-stripe-js": "^5.6.0",
"@stripe/stripe-js": "^8.7.0",
@@ -31,6 +34,7 @@
"iso-3166-2": "^1.0.0",
"jspdf": "^4.0.0",
"jspdf-autotable": "^5.0.7",
"lucide-react": "^0.577.0",
"motion": "^12.35.2",
"next": "^16.1.1",
"radix-ui": "^1.4.3",