Production-ready improvements: security hardening, Redis sessions, retry logic, updated pricing

Changes:
- Removed hardcoded admin credentials (now requires env vars)
- Added Redis session storage with in-memory fallback
- Improved CORS configuration with warnings for development mode
- Added retry_with_backoff decorator for translation API calls
- Updated pricing: Starter=, Pro=, Business=
- Stripe price IDs now loaded from environment variables
- Added redis to requirements.txt
- Updated .env.example with all new configuration options
- Created COMPREHENSIVE_REVIEW_AND_PLAN.md with deployment roadmap
- Frontend: Updated pricing page, new UI components
This commit is contained in:
2025-12-31 10:43:31 +01:00
parent 721b18dbbd
commit c4d6cae735
27 changed files with 7824 additions and 2181 deletions

View File

@@ -1,46 +1,295 @@
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 badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-all duration-200 ease-out focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
"border-transparent bg-primary text-primary-foreground shadow-sm hover:shadow-md hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
"border-transparent bg-surface text-secondary-foreground hover:bg-surface-hover",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
"border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80",
outline: "text-foreground border-border hover:bg-surface hover:border-border-strong",
success:
"border-transparent bg-success text-success-foreground shadow-sm hover:bg-success/80",
warning:
"border-transparent bg-warning text-warning-foreground shadow-sm hover:bg-warning/80",
info:
"border-transparent bg-primary/20 text-primary border-primary/30 hover:bg-primary/30",
accent:
"border-transparent bg-accent text-accent-foreground shadow-sm hover:bg-accent/80",
glass:
"glass text-foreground border-border/20 hover:bg-surface/50 hover:border-border/40",
gradient:
"border-transparent bg-gradient-to-r from-primary to-accent text-white shadow-lg hover:shadow-xl hover:shadow-primary/25",
neon:
"border-transparent bg-primary/10 text-primary border-primary/20 shadow-lg shadow-primary/20 hover:bg-primary/20 hover:border-primary/30 hover:shadow-primary/30",
pulse:
"border-transparent bg-primary text-primary-foreground shadow-lg shadow-primary/25 animate-pulse hover:animate-none",
dot:
"border-transparent bg-primary text-primary-foreground w-2 h-2 p-0 rounded-full",
premium:
"border-transparent bg-gradient-to-r from-primary via-accent to-primary text-white shadow-lg hover:shadow-xl hover:shadow-primary/25 relative overflow-hidden",
},
size: {
default: "px-2.5 py-0.5 text-xs",
sm: "px-2 py-0.5 text-xs",
lg: "px-3 py-1 text-sm",
xl: "px-4 py-1.5 text-base",
icon: "w-6 h-6 p-0 rounded-lg flex items-center justify-center",
dot: "w-2 h-2 p-0 rounded-full",
},
interactive: {
true: "cursor-pointer hover:scale-105 active:scale-95",
false: "",
},
},
defaultVariants: {
variant: "default",
size: "default",
interactive: false,
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {
icon?: React.ReactNode
removable?: boolean
onRemove?: () => void
pulse?: boolean
count?: number
maxCount?: number
}
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
({
className,
variant,
size,
interactive = false,
icon,
removable = false,
onRemove,
pulse = false,
count,
maxCount = 99,
children,
...props
}, ref) => {
const [visible, setVisible] = React.useState(true)
const [removing, setRemoving] = React.useState(false)
const handleRemove = () => {
setRemoving(true)
setTimeout(() => {
setVisible(false)
onRemove?.()
}, 200)
}
const displayCount = count && count > maxCount ? `${maxCount}+` : count
if (!visible) return null
return (
<div
ref={ref}
className={cn(
badgeVariants({ variant, size, interactive }),
pulse && "animate-pulse",
removing && "scale-0 opacity-0",
className
)}
{...props}
>
{/* Gradient Shine Effect */}
{(variant === "premium" || variant === "gradient") && (
<span className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-1000 ease-out pointer-events-none" />
)}
{/* Icon */}
{icon && (
<span className="mr-1 flex-shrink-0">
{icon}
</span>
)}
{/* Content */}
<span className="flex items-center gap-1">
{children}
{displayCount && (
<span className="ml-1 bg-white/20 px-1.5 py-0.5 rounded text-xs font-bold">
{displayCount}
</span>
)}
</span>
{/* Remove Button */}
{removable && (
<button
type="button"
onClick={handleRemove}
className="ml-1 flex-shrink-0 rounded-full bg-white/20 hover:bg-white/30 transition-colors p-0.5"
aria-label="Remove badge"
>
<svg
className="h-3 w-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
{/* Pulse Ring for Neon Variant */}
{variant === "neon" && (
<>
<span className="absolute inset-0 rounded-full bg-primary/20 animate-ping" />
<span className="absolute inset-0 rounded-full bg-primary/10 animate-ping animation-delay-200" />
</>
)}
</div>
)
}
)
Badge.displayName = "Badge"
// Status Badge Component
export const StatusBadge = React.forwardRef<
HTMLDivElement,
Omit<BadgeProps, 'variant'> & {
status: "online" | "offline" | "busy" | "away" | "success" | "error" | "warning"
showLabel?: boolean
}
>(({ className, status, showLabel = true, children, ...props }, ref) => {
const statusConfig = {
online: { variant: "success" as const, label: "Online", icon: "●" },
offline: { variant: "secondary" as const, label: "Offline", icon: "○" },
busy: { variant: "destructive" as const, label: "Busy", icon: "◐" },
away: { variant: "warning" as const, label: "Away", icon: "◐" },
success: { variant: "success" as const, label: "Success", icon: "✓" },
error: { variant: "destructive" as const, label: "Error", icon: "✕" },
warning: { variant: "warning" as const, label: "Warning", icon: "!" },
}
const config = statusConfig[status]
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
<Badge
ref={ref}
variant={config.variant}
className={cn("gap-1.5", className)}
{...props}
/>
>
<span className="relative">
<span className="text-xs">{config.icon}</span>
{status === "online" && (
<span className="absolute inset-0 rounded-full bg-success animate-ping" />
)}
</span>
{showLabel && (
<span>{children || config.label}</span>
)}
</Badge>
)
}
})
StatusBadge.displayName = "StatusBadge"
export { Badge, badgeVariants }
// Counter Badge Component
export const CounterBadge = React.forwardRef<
HTMLDivElement,
Omit<BadgeProps, 'children'> & {
value: number
showZero?: boolean
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left"
}
>(({
className,
value,
showZero = false,
position = "top-right",
size = "icon",
...props
}, ref) => {
if (value === 0 && !showZero) return null
const positionClasses = {
"top-right": "-top-2 -right-2",
"top-left": "-top-2 -left-2",
"bottom-right": "-bottom-2 -right-2",
"bottom-left": "-bottom-2 -left-2",
}
return (
<Badge
ref={ref}
variant="destructive"
size={size}
className={cn(
"absolute min-w-[20px] h-5 flex items-center justify-center text-xs font-bold",
positionClasses[position],
className
)}
{...props}
>
{value > 99 ? "99+" : value}
</Badge>
)
})
CounterBadge.displayName = "CounterBadge"
// Progress Badge Component
export const ProgressBadge = React.forwardRef<
HTMLDivElement,
Omit<BadgeProps, 'children'> & {
value: number
max?: number
showPercentage?: boolean
}
>(({
className,
value,
max = 100,
showPercentage = true,
...props
}, ref) => {
const percentage = Math.min(100, Math.max(0, (value / max) * 100))
const displayValue = showPercentage ? `${Math.round(percentage)}%` : `${value}/${max}`
return (
<Badge
ref={ref}
variant="outline"
className={cn(
"relative overflow-hidden",
className
)}
{...props}
>
{/* Progress Background */}
<span
className="absolute inset-0 bg-primary/20"
style={{ width: `${percentage}%` }}
/>
{/* Text */}
<span className="relative z-10">{displayValue}</span>
</Badge>
)
})
ProgressBadge.displayName = "ProgressBadge"
export { Badge, badgeVariants, StatusBadge, CounterBadge, ProgressBadge }