Prepare dark theme, add pricing page

This commit is contained in:
kbe
2025-08-14 18:14:59 +02:00
parent be22888afb
commit 8aba6e31c8
8 changed files with 322 additions and 8 deletions

View File

@@ -0,0 +1,87 @@
import React from 'react';
import { useTheme } from '@/lib/theme-context';
interface PricingCardProps {
title: string;
price: string;
billingPeriod: string;
features: string[];
ctaText: string;
ctaHref: string;
isPopular?: boolean;
}
const PricingCard: React.FC<PricingCardProps> = ({
title,
price,
billingPeriod,
features,
ctaText,
ctaHref,
isPopular = false,
}) => {
const { theme } = useTheme();
const cardBg = theme === 'dark' ? 'bg-gray-800' : 'bg-white';
const cardText = theme === 'dark' ? 'text-gray-100' : 'text-gray-900';
const cardBorder = theme === 'dark' ? 'border-gray-700' : 'border-gray-200';
const cardShadow = theme === 'dark' ? 'shadow-lg' : 'shadow-sm';
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 cardPopular = theme === 'dark' ? 'bg-indigo-700' : 'bg-indigo-600';
const cardFeatureText = theme === 'dark' ? 'text-gray-300' : 'text-gray-700';
const cardFeatureIcon = theme === 'dark' ? 'text-green-400' : 'text-green-500';
return (
<div className={`flex flex-col ${cardBg} border ${cardBorder} rounded-lg ${cardShadow} overflow-hidden transition-all duration-300`}>
{isPopular && (
<div className={`${cardPopular} text-white text-sm font-medium px-4 py-1`}>
Popular
</div>
)}
<div className="p-6">
<h3 className={`text-xl font-semibold ${cardText}`}>{title}</h3>
<p className="mt-4 text-sm text-gray-400">
{title === 'Start' && 'The perfect starting place for your web app or personal project.'}
{title === 'Pro' && 'Everything you need to build and scale your app.'}
{title === 'Premium' && 'Critical security, performance, observability, platform SLAs, and support.'}
</p>
<p className="mt-8">
<span className="text-4xl font-extrabold">{price}</span>
<span className="text-base font-medium text-gray-400">/{billingPeriod}</span>
</p>
<a
href={ctaHref}
className={`mt-8 block w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-center text-white bg-indigo-600 ${cardHover} focus:outline-none focus:ring-2 focus:ring-offset-2 ${cardFocus}`}
>
{ctaText}
</a>
</div>
<div className={`border-t ${cardBorder} p-6`}>
<h4 className={`text-sm font-medium ${cardText}`}>What's included:</h4>
<ul className="mt-4 space-y-4">
{features.map((feature, index) => (
<li key={index} className="flex items-start">
<svg
className={`h-5 w-5 ${cardFeatureIcon}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clipRule="evenodd"
/>
</svg>
<span className={`ml-3 text-sm ${cardFeatureText}`}>{feature}</span>
</li>
))}
</ul>
</div>
</div>
);
};
export default PricingCard;

View File

@@ -5,6 +5,7 @@ import { cn } from "@/lib/utils"
import Link from "next/link"
import { useState, useEffect } from "react"
import { usePathname } from "next/navigation"
import dynamic from 'next/dynamic'
interface HeaderProps {
className?: string
@@ -25,7 +26,11 @@ const leftNavigation = [
const rightNavigation = [
{ name: 'Sign-up', href: '/sign-up', current: false, requiresAuth: false },
{ name: 'Login', href: '/login', current: false, requiresAuth: false },
]
];
const ThemeToggle = dynamic(() => import('./theme-toggle').then(mod => mod.default), {
ssr: false,
});
export function Header({ className, isLoggedIn = false, isAdmin = false }: HeaderProps) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
@@ -103,6 +108,9 @@ export function Header({ className, isLoggedIn = false, isAdmin = false }: Heade
</Link>
))}
</div>
<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>
)}
@@ -206,7 +214,7 @@ export function Header({ className, isLoggedIn = false, isAdmin = false }: Heade
})}
</div>
{isLoggedIn && (
<div className="border-t border-gray-700 pb-3 pt-4">
<div className="border-t border-gray-700 pb-3 pt-4">
<div>
<div className="flex items-center px-5">
<div className="flex-shrink-0">
@@ -243,7 +251,7 @@ export function Header({ className, isLoggedIn = false, isAdmin = false }: Heade
</Link>
</div>
</div>
</div>
</div>
)}
</div>

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { useTheme } from '@/lib/theme-context';
import { Sun, Moon } from 'lucide-react';
const ThemeToggle: React.FC = () => {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2"
aria-label={theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
>
{theme === 'dark' ? (
<Sun className="h-3 w-3" />
) : (
<Moon className="h-3 w-3" />
)}
</button>
);
};
export default ThemeToggle;