feat: port website header with mega-menu, add showcase bg to hero, fix carousel container size
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import { OfflineBanner } from '@/components/OfflineBanner'
|
import { OfflineBanner } from '@/components/OfflineBanner'
|
||||||
import { Footer } from '@/components/Footer'
|
import { Footer } from '@/components/Footer'
|
||||||
import { Header, type CipheraApp } from '@ciphera-net/ui'
|
import { Header, type CipheraApp } from '@ciphera-net/ui'
|
||||||
|
import { Header as MarketingHeader } from '@/components/marketing/Header'
|
||||||
import NotificationCenter from '@/components/notifications/NotificationCenter'
|
import NotificationCenter from '@/components/notifications/NotificationCenter'
|
||||||
import { useAuth } from '@/lib/auth/context'
|
import { useAuth } from '@/lib/auth/context'
|
||||||
import { useOnlineStatus } from '@/lib/hooks/useOnlineStatus'
|
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 (
|
return (
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen">
|
||||||
<Header
|
<MarketingHeader />
|
||||||
auth={auth}
|
<main className="flex-1 pb-8">
|
||||||
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">
|
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer
|
<Footer
|
||||||
|
|||||||
@@ -151,12 +151,9 @@ export default function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* HERO — full viewport */}
|
{/* HERO — full viewport */}
|
||||||
<div className="relative -mt-[88px] min-h-screen flex items-center pt-[88px] pb-20 lg:pb-32 overflow-hidden">
|
<div className="relative -mt-[88px] min-h-screen flex items-center pt-[88px] pb-20 lg:pb-32 bg-neutral-950 overflow-hidden">
|
||||||
{/* Background atmosphere */}
|
<img src="/pulse-showcase-bg.png" alt="" className="absolute inset-0 w-full h-full object-cover" />
|
||||||
<div className="absolute inset-0 -z-10 pointer-events-none">
|
<div className="absolute inset-0 bg-black/60" />
|
||||||
<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="absolute bottom-0 left-0 right-0 h-48 bg-gradient-to-t from-neutral-950 to-transparent" />
|
<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">
|
<div className="relative z-10 w-full max-w-6xl mx-auto px-6">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ function FeatureSection({
|
|||||||
features,
|
features,
|
||||||
mockup,
|
mockup,
|
||||||
reverse = false,
|
reverse = false,
|
||||||
|
showBg = true,
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id: string
|
||||||
heading: string
|
heading: string
|
||||||
@@ -22,6 +23,7 @@ function FeatureSection({
|
|||||||
features: string[]
|
features: string[]
|
||||||
mockup: React.ReactNode
|
mockup: React.ReactNode
|
||||||
reverse?: boolean
|
reverse?: boolean
|
||||||
|
showBg?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section id={id} className="container mx-auto px-6 scroll-mt-28">
|
<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 }}
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
className={`relative ${reverse ? 'lg:order-first' : ''}`}
|
className={`relative ${reverse ? 'lg:order-first' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="absolute -inset-8 bg-brand-orange/8 rounded-[2.5rem] blur-3xl" />
|
{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]">
|
<div className={`relative rounded-3xl overflow-hidden border border-white/[0.08] ${showBg ? '' : 'bg-neutral-900/80'}`}>
|
||||||
<img src="/pulse-showcase-bg.png" alt="" className="absolute inset-0 w-full h-full object-cover" />
|
{showBg && (
|
||||||
<div className="absolute inset-0 bg-black/30" />
|
<>
|
||||||
|
<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">
|
<div className="relative">
|
||||||
{mockup}
|
{mockup}
|
||||||
</div>
|
</div>
|
||||||
@@ -106,8 +112,10 @@ export default function FeatureSections() {
|
|||||||
]}
|
]}
|
||||||
reverse
|
reverse
|
||||||
mockup={
|
mockup={
|
||||||
<div className="p-6 sm:p-10">
|
<div className="p-6 sm:p-10 min-h-[500px] flex items-center">
|
||||||
<PulseFeaturesCarousel />
|
<div className="w-full">
|
||||||
|
<PulseFeaturesCarousel />
|
||||||
|
</div>
|
||||||
</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
|
<FeatureSection
|
||||||
id="script"
|
id="script"
|
||||||
|
showBg={false}
|
||||||
heading="One script tag. That's it."
|
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."
|
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={[
|
features={[
|
||||||
|
|||||||
309
components/marketing/Header.tsx
Normal file
309
components/marketing/Header.tsx
Normal 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 →
|
||||||
|
</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;
|
||||||
|
}
|
||||||
56
components/ui/button-website.tsx
Normal file
56
components/ui/button-website.tsx
Normal 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 }
|
||||||
54
components/ui/menu-toggle-icon.tsx
Normal file
54
components/ui/menu-toggle-icon.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
128
components/ui/navigation-menu.tsx
Normal file
128
components/ui/navigation-menu.tsx
Normal 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
190
package-lock.json
generated
@@ -11,6 +11,9 @@
|
|||||||
"@ciphera-net/ui": "^0.3.1",
|
"@ciphera-net/ui": "^0.3.1",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@phosphor-icons/react": "^2.1.10",
|
"@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",
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
"@stripe/react-stripe-js": "^5.6.0",
|
"@stripe/react-stripe-js": "^5.6.0",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
@@ -27,6 +30,7 @@
|
|||||||
"iso-3166-2": "^1.0.0",
|
"iso-3166-2": "^1.0.0",
|
||||||
"jspdf": "^4.0.0",
|
"jspdf": "^4.0.0",
|
||||||
"jspdf-autotable": "^5.0.7",
|
"jspdf-autotable": "^5.0.7",
|
||||||
|
"lucide-react": "^0.577.0",
|
||||||
"motion": "^12.35.2",
|
"motion": "^12.35.2",
|
||||||
"next": "^16.1.1",
|
"next": "^16.1.1",
|
||||||
"radix-ui": "^1.4.3",
|
"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": {
|
"node_modules/@radix-ui/react-arrow": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-id": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-menubar": {
|
||||||
"version": "1.1.16",
|
"version": "1.1.16",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-progress": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-separator": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
|
"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": {
|
"node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
|
||||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@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": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
"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"
|
"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": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"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": {
|
"node_modules/raf": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
"@ciphera-net/ui": "^0.3.1",
|
"@ciphera-net/ui": "^0.3.1",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@phosphor-icons/react": "^2.1.10",
|
"@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",
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
"@stripe/react-stripe-js": "^5.6.0",
|
"@stripe/react-stripe-js": "^5.6.0",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
@@ -31,6 +34,7 @@
|
|||||||
"iso-3166-2": "^1.0.0",
|
"iso-3166-2": "^1.0.0",
|
||||||
"jspdf": "^4.0.0",
|
"jspdf": "^4.0.0",
|
||||||
"jspdf-autotable": "^5.0.7",
|
"jspdf-autotable": "^5.0.7",
|
||||||
|
"lucide-react": "^0.577.0",
|
||||||
"motion": "^12.35.2",
|
"motion": "^12.35.2",
|
||||||
"next": "^16.1.1",
|
"next": "^16.1.1",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
|
|||||||
Reference in New Issue
Block a user