2 Commits

Author SHA1 Message Date
kbe
a1e49a1e1a Implemented pricing page 2025-08-14 18:38:57 +02:00
kbe
9d5f57c13e Shiny pricing page 2025-08-14 18:30:29 +02:00
7 changed files with 364 additions and 128 deletions

View File

@@ -3,7 +3,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PricingCard from '@/components/pricing-card'; import PricingCard from '@/components/pricing-card';
import { useTheme } from '@/lib/theme-context'; import { useTheme } from '@/lib/theme-context';
import { Sun, Moon } from 'lucide-react'; import PricingToggle from '@/components/ui/pricing-toggle';
const PricingPage = () => { const PricingPage = () => {
const [isYearly, setIsYearly] = useState(false); const [isYearly, setIsYearly] = useState(false);
@@ -18,105 +18,97 @@ const PricingPage = () => {
}; };
const getPrice = (basePrice: number) => { const getPrice = (basePrice: number) => {
return isYearly ? Math.round(basePrice * 10 * 0.8) : basePrice; return isYearly ? basePrice * 10 : basePrice;
}; };
const getBillingPeriod = () => { const getBillingPeriod = () => {
return isYearly ? 'year' : 'month'; return isYearly ? 'year' : 'month';
}; };
return ( const getDiscountBadge = () => {
<div className={`min-h-screen ${theme === 'dark' ? 'bg-gray-900 text-gray-100' : 'bg-gray-50 text-gray-900'}`}> if (isYearly) {
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> return { text: 'Save 20%', style: 'text-green-500 dark:text-green-400' };
<div className="text-center"> }
<h1 className="text-4xl font-extrabold sm:text-5xl"> return { text: '', style: '' };
Find a plan to power your apps. };
</h1>
<p className="mt-4 text-xl">
Dishpix supports teams of all sizes, with pricing that scales.
</p>
</div>
{/* Pricing Toggle (Monthly/Yearly) */} return (
<div className="mt-12 flex justify-center"> <div className={`min-h-screen ${theme === 'dark' ? 'bg-gray-900 text-gray-100' : 'bg-gray-50 text-gray-900'} transition-colors duration-300`}>
<div className="relative inline-flex items-center bg-gray-200 dark:bg-gray-700 rounded-full p-1"> <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<button <div className="text-center">
onClick={toggleBillingPeriod} <h1 className="text-3xl font-extrabold sm:text-4xl md:text-5xl transition-all duration-300">
className={`relative px-4 py-2 text-sm font-medium rounded-full focus:outline-none transition-colors ${ Transform your food photos with AI
!isYearly </h1>
? 'bg-white text-gray-900 shadow-sm' <p className="mt-3 text-lg sm:text-xl transition-opacity duration-300">
: 'text-gray-400' Choose the perfect plan to enhance your culinary photography with cutting-edge AI technology.
}`} </p>
> <p className="mt-2 text-sm font-medium text-indigo-600 dark:text-indigo-400 transition-colors duration-300">
Monthly {getDiscountBadge().text}
</button> </p>
<button </div>
onClick={toggleBillingPeriod}
className={`relative px-4 py-2 text-sm font-medium rounded-full focus:outline-none transition-colors ${ {/* Pricing Toggle (Monthly/Yearly) */}
isYearly <PricingToggle isYearly={isYearly} onToggle={toggleBillingPeriod} />
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-400' {/* Pricing Cards Grid */}
}`} <div className="mt-12 sm:mt-16 lg:mt-20 space-y-8 sm:space-y-12 md:space-y-16 lg:space-y-0 sm:grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 sm:gap-6 lg:gap-8 transition-all duration-300">
> <PricingCard
Yearly title="Basic"
</button> price={`$${getPrice(9)}`}
billingPeriod={getBillingPeriod()}
features={[
'Enhance up to 50 food photos/month',
'Basic AI food recognition',
'Standard lighting correction',
'Color enhancement',
'Remove minor blemishes',
'Basic background cleanup',
'Standard resolution exports',
]}
ctaText="Start Enhancing"
ctaHref="#"
/>
<PricingCard
title="Pro"
price={`$${getPrice(29)}`}
billingPeriod={getBillingPeriod()}
features={[
'Everything in Basic, plus:',
'Enhance up to 500 food photos/month',
'Advanced AI food styling',
'Professional lighting simulation',
'Gourmet color grading',
'Remove unwanted objects',
'AI-powered ingredient recognition',
'High-resolution exports',
'Priority processing',
]}
ctaText="Start free trial"
ctaHref="#"
isPopular={true}
/>
<PricingCard
title="MasterChef"
price={`$${getPrice(99)}`}
billingPeriod={getBillingPeriod()}
features={[
'Everything in Pro, plus:',
'Unlimited food photo enhancements',
'AI-generated food styling suggestions',
'Restaurant-quality presentation',
'Advanced background replacement',
'Multi-dish composition',
'Professional food photography presets',
'4K resolution exports',
'Batch processing capabilities',
'Priority support',
]}
ctaText="Get Started"
ctaHref="#"
/>
</div>
</div> </div>
</div> </div>
{/* Pricing Cards Grid */}
<div className="mt-12 sm:mt-16 lg:mt-20 space-y-8 sm:space-y-12 md:space-y-0 md:grid md:grid-cols-1 lg:grid-cols-3 md:gap-6 lg:gap-8">
<PricingCard
title="Start"
price={`$${getPrice(9)}`}
billingPeriod={getBillingPeriod()}
features={[
'Import your repo, deploy in seconds',
'Automatic CI/CD',
'Web Application Firewall',
'Global, automated CDN',
'Fluid compute',
'DDoS Mitigation',
'Traffic & performance insights',
]}
ctaText="Start Deploying"
ctaHref="/signup?plan=start"
/>
<PricingCard
title="Pro"
price={`$${getPrice(29)}`}
billingPeriod={getBillingPeriod()}
features={[
'Everything in Start, plus:',
'10x more included usage',
'Observability tools',
'Faster builds',
'Cold start prevention',
'Advanced WAF Protection',
'Email support',
]}
ctaText="Start a free trial"
ctaHref="/signup?plan=pro"
isPopular={true}
/>
<PricingCard
title="Premium"
price={`$${getPrice(99)}`}
billingPeriod={getBillingPeriod()}
features={[
'Everything in Pro, plus:',
'Guest & Team access controls',
'SCIM & Directory Sync',
'Managed WAF Rulesets',
'Multi-region compute & failover',
'99.99% SLA',
'Advanced Support',
]}
ctaText="Contact Sales"
ctaHref="/contact-sales"
/>
</div>
</div>
</div>
); );
}; };

View File

@@ -22,50 +22,40 @@ const PricingCard: React.FC<PricingCardProps> = ({
}) => { }) => {
const { theme } = useTheme(); const { theme } = useTheme();
// Add ARIA label for screen readers
const cardAriaLabel = `${title} pricing plan - $${price}/${billingPeriod}`;
const cardBg = theme === 'dark' ? 'bg-gray-800' : 'bg-white'; const cardBg = theme === 'dark' ? 'bg-gray-800' : 'bg-white';
const cardText = theme === 'dark' ? 'text-gray-100' : 'text-gray-900'; const cardText = theme === 'dark' ? 'text-gray-100' : 'text-gray-900';
const cardBorder = theme === 'dark' ? 'border-gray-700' : 'border-gray-200'; const cardBorder = theme === 'dark' ? 'border-gray-700' : 'border-gray-200';
const cardShadow = theme === 'dark' ? 'shadow-lg' : 'shadow-sm'; const cardShadow = theme === 'dark' ? 'shadow-xl' : 'shadow-2xl';
const cardHover = theme === 'dark' ? 'hover:bg-gray-700' : 'hover:bg-indigo-600'; const cardHover = theme === 'dark' ? 'hover:bg-gray-700' : 'hover:bg-indigo-700';
const cardFocus = theme === 'dark' ? 'focus:ring-indigo-500' : 'focus:ring-indigo-500'; const cardFocus = theme === 'dark' ? 'focus:ring-indigo-500' : 'focus:ring-indigo-500';
const cardPopular = theme === 'dark' ? 'bg-indigo-700' : 'bg-indigo-600'; const cardPopular = theme === 'dark' ? 'bg-indigo-700' : 'bg-indigo-600';
const cardFeatureText = theme === 'dark' ? 'text-gray-300' : 'text-gray-700'; const cardFeatureText = theme === 'dark' ? 'text-gray-300' : 'text-gray-700';
const cardFeatureIcon = theme === 'dark' ? 'text-green-400' : 'text-green-500'; const cardFeatureIcon = theme === 'dark' ? 'text-green-400' : 'text-green-500';
const cardHoverShadow = theme === 'dark' ? 'hover:shadow-xl' : 'hover:shadow-2xl';
const cardTransition = 'transition-all duration-300';
return ( return (
<div <div className={`flex flex-col ${cardBg} border ${cardBorder} rounded-xl ${cardShadow} overflow-hidden transition-all duration-300 transform hover:scale-105`}>
className={`flex flex-col ${cardBg} border ${cardBorder} rounded-xl ${cardShadow} overflow-hidden ${cardTransition} ${cardHoverShadow}`}
role="article"
aria-label={cardAriaLabel}
tabIndex={0} // Make the card focusable
>
{isPopular && ( {isPopular && (
<div <div className={`${cardPopular} px-3 py-1.5 text-center text-white text-xs font-semibold rounded-t-xl`}>
className={`${cardPopular} text-white text-xs font-semibold px-3 py-1.5 rounded-t-xl`} Most Popular
>
Popular
</div> </div>
)} )}
{!isPopular && (
<div className="py-3.5"></div>
)}
<div className="p-6"> <div className="p-6">
<h3 className={`text-2xl font-bold ${cardText}`}>{title}</h3> <h3 className={`text-2xl font-bold ${cardText}`}>{title}</h3>
<p className="mt-2 text-sm text-gray-400"> <p className="mt-4 text-sm text-gray-400">
{title === 'Start' && 'The perfect starting place for your web app or personal project.'} {title === 'Basic' && 'Perfect for food bloggers and home cooks looking to enhance their culinary photography.'}
{title === 'Pro' && 'Everything you need to build and scale your app.'} {title === 'Pro' && 'Everything you need to showcase restaurant dishes professionally and attract more customers.'}
{title === 'Premium' && 'Critical security, performance, observability, platform SLAs, and support.'} {title === 'MasterChef' && 'Complete professional suite for food photographers and culinary brands demanding perfection.'}
</p> </p>
<p className="mt-6"> <p className="mt-8">
<span className="text-4xl font-extrabold">{price}</span> <span className="text-5xl font-extrabold">{price}</span>
<span className="text-base font-medium text-gray-400 ml-1">/{billingPeriod}</span> <span className="text-base font-medium text-gray-400">/{billingPeriod}</span>
</p> </p>
<a <a
href={ctaHref} href={ctaHref}
className={`mt-6 block w-full py-3 px-4 border border-transparent rounded-md shadow text-center text-white bg-indigo-600 ${cardHover} focus:outline-none focus:ring-2 focus:ring-offset-2 ${cardFocus} ${cardTransition}`} className={`mt-8 block w-full py-3 px-6 border border-transparent rounded-md shadow-lg text-center text-white bg-indigo-600 ${cardHover} focus:outline-none focus:ring-2 focus:ring-offset-2 ${cardFocus} transition-all duration-200`}
aria-label={`${ctaText} for ${title} plan`}
> >
{ctaText} {ctaText}
</a> </a>
@@ -97,4 +87,4 @@ const PricingCard: React.FC<PricingCardProps> = ({
); );
}; };
export default React.memo(PricingCard); export default PricingCard;

View File

@@ -32,14 +32,6 @@ const ThemeToggle = dynamic(() => import('./theme-toggle').then(mod => mod.defau
ssr: false, ssr: false,
}); });
const ThemeToggleWrapper = () => {
return (
<div className="rounded-md px-3 py-2 text-sm font-medium transition-colors text-gray-300 hover:bg-gray-700 hover:text-white">
<ThemeToggle />
</div>
);
};
export function Header({ className, isLoggedIn = false, isAdmin = false }: HeaderProps) { export function Header({ className, isLoggedIn = false, isAdmin = false }: HeaderProps) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [profileOpen, setProfileOpen] = useState(false) const [profileOpen, setProfileOpen] = useState(false)
@@ -116,7 +108,11 @@ export function Header({ className, isLoggedIn = false, isAdmin = false }: Heade
</Link> </Link>
))} ))}
</div> </div>
<ThemeToggleWrapper /> {/*
<div className="rounded-md px-3 py-2 text-sm font-medium transition-colors text-gray-300 hover:bg-gray-700 hover:text-white">
<ThemeToggle />
</div>
*/}
</div> </div>
</div> </div>
)} )}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Switch } from '@headlessui/react';
import { useTheme } from '@/lib/theme-context';
interface PricingToggleProps {
isYearly: boolean;
onToggle: () => void;
}
const PricingToggle: React.FC<PricingToggleProps> = ({ isYearly, onToggle }) => {
const { theme } = useTheme();
const thumbColor = theme === 'dark' ? 'bg-white' : 'bg-gray-900';
const trackColor = theme === 'dark' ? 'bg-gray-700' : 'bg-gray-200';
const activeTrackColor = theme === 'dark' ? 'bg-indigo-600' : 'bg-indigo-500';
return (
<div className="flex items-center justify-center mt-12 space-x-4">
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}>
Monthly
</span>
<div className="relative inline-flex items-center">
<Switch
checked={isYearly}
onChange={onToggle}
className={`relative inline-flex items-center h-6 rounded-full w-16 transition-colors duration-200 ease-in-out ${
isYearly ? activeTrackColor : trackColor
}`}
>
<span
className={`inline-block w-7 h-5 transform rounded-full shadow transition-transform duration-200 ease-in-out ${
isYearly ? 'translate-x-8' : 'translate-x-0'
} ${thumbColor}`}
/>
</Switch>
</div>
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-gray-300' : 'text-gray-700'}`}>
Yearly
</span>
</div>
);
};
export default PricingToggle;

View File

@@ -11,7 +11,7 @@ interface ThemeContextType {
const ThemeContext = createContext<ThemeContextType | undefined>(undefined); const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => { export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<Theme>('dark'); // Default to dark mode const [theme, setTheme] = useState<Theme>('light'); // Default to light mode (white backgrounds)
useEffect(() => { useEffect(() => {
const savedTheme = localStorage.getItem('theme') as Theme | null; const savedTheme = localStorage.getItem('theme') as Theme | null;

213
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "dishpix", "name": "dishpix",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@shadcn/ui": "^0.0.4", "@shadcn/ui": "^0.0.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -232,6 +233,79 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz",
"integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/react": {
"version": "0.26.28",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
"integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.8",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.5.tgz",
"integrity": "sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.7.3"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@headlessui/react": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.7.tgz",
"integrity": "sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.26.16",
"@react-aria/focus": "^3.20.2",
"@react-aria/interactions": "^3.25.0",
"@tanstack/react-virtual": "^3.13.9",
"use-sync-external-store": "^1.5.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1006,6 +1080,103 @@
} }
} }
}, },
"node_modules/@react-aria/focus": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.0.tgz",
"integrity": "sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/interactions": "^3.25.4",
"@react-aria/utils": "^3.30.0",
"@react-types/shared": "^3.31.0",
"@swc/helpers": "^0.5.0",
"clsx": "^2.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/interactions": {
"version": "3.25.4",
"resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.4.tgz",
"integrity": "sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/ssr": "^3.9.10",
"@react-aria/utils": "^3.30.0",
"@react-stately/flags": "^3.1.2",
"@react-types/shared": "^3.31.0",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.9.10",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
"integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/utils": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.0.tgz",
"integrity": "sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==",
"license": "Apache-2.0",
"dependencies": {
"@react-aria/ssr": "^3.9.10",
"@react-stately/flags": "^3.1.2",
"@react-stately/utils": "^3.10.8",
"@react-types/shared": "^3.31.0",
"@swc/helpers": "^0.5.0",
"clsx": "^2.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-stately/flags": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz",
"integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@react-stately/utils": {
"version": "3.10.8",
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz",
"integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-types/shared": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.31.0.tgz",
"integrity": "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1336,6 +1507,33 @@
"tailwindcss": "4.1.11" "tailwindcss": "4.1.11"
} }
}, },
"node_modules/@tanstack/react-virtual": {
"version": "3.13.12",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
"integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
"license": "MIT",
"dependencies": {
"@tanstack/virtual-core": "3.13.12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.13.12",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
@@ -6421,6 +6619,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"license": "MIT"
},
"node_modules/tailwind-merge": { "node_modules/tailwind-merge": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
@@ -6751,6 +6955,15 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -9,6 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@shadcn/ui": "^0.0.4", "@shadcn/ui": "^0.0.4",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",