yves.gugger cff0ba0984 feat: Add Admin Panel enhancements, Blog system, and OAuth
Admin Panel:
- User Detail Modal with full profile info
- Bulk tier upgrade for multiple users
- User export to CSV
- Price Alerts overview tab
- Domain Health Check trigger
- Email Test functionality
- Scheduler Status with job info and last runs
- Activity Log for admin actions
- Blog management tab with CRUD

Blog System:
- BlogPost model with full content management
- Public API: list, featured, categories, single post
- Admin API: create, update, delete, publish/unpublish
- Frontend blog listing page with categories
- Frontend blog detail page with styling
- View count tracking

OAuth:
- Google OAuth integration
- GitHub OAuth integration
- OAuth callback handling
- Provider selection on login/register

Other improvements:
- Domain checker with check_all_domains function
- Admin activity logging
- Breadcrumbs component
- Toast notification component
- Various UI/UX improvements
2025-12-09 16:52:54 +01:00

95 lines
2.7 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { Check, X, AlertCircle, Info } from 'lucide-react'
import clsx from 'clsx'
export type ToastType = 'success' | 'error' | 'info'
interface ToastProps {
message: string
type?: ToastType
duration?: number
onClose: () => void
}
export function Toast({ message, type = 'success', duration = 4000, onClose }: ToastProps) {
const [isVisible, setIsVisible] = useState(true)
const [isLeaving, setIsLeaving] = useState(false)
useEffect(() => {
const timer = setTimeout(() => {
setIsLeaving(true)
setTimeout(onClose, 300)
}, duration)
return () => clearTimeout(timer)
}, [duration, onClose])
const handleClose = () => {
setIsLeaving(true)
setTimeout(onClose, 300)
}
const Icon = type === 'success' ? Check : type === 'error' ? AlertCircle : Info
return (
<div
className={clsx(
"fixed bottom-6 right-6 z-[100] flex items-center gap-3 px-4 py-3 rounded-xl shadow-2xl border transition-all duration-300",
isLeaving ? "translate-y-2 opacity-0" : "translate-y-0 opacity-100",
type === 'success' && "bg-accent/10 border-accent/20",
type === 'error' && "bg-danger/10 border-danger/20",
type === 'info' && "bg-foreground/5 border-border"
)}
>
<div className={clsx(
"w-7 h-7 rounded-lg flex items-center justify-center",
type === 'success' && "bg-accent/20",
type === 'error' && "bg-danger/20",
type === 'info' && "bg-foreground/10"
)}>
<Icon className={clsx(
"w-4 h-4",
type === 'success' && "text-accent",
type === 'error' && "text-danger",
type === 'info' && "text-foreground-muted"
)} />
</div>
<p className={clsx(
"text-body-sm",
type === 'success' && "text-accent",
type === 'error' && "text-danger",
type === 'info' && "text-foreground"
)}>{message}</p>
<button
onClick={handleClose}
className={clsx(
"ml-2 p-1 rounded hover:bg-foreground/5 transition-colors",
type === 'success' && "text-accent/70 hover:text-accent",
type === 'error' && "text-danger/70 hover:text-danger",
type === 'info' && "text-foreground-muted hover:text-foreground"
)}
>
<X className="w-4 h-4" />
</button>
</div>
)
}
// Hook for managing toasts
export function useToast() {
const [toast, setToast] = useState<{ message: string; type: ToastType } | null>(null)
const showToast = (message: string, type: ToastType = 'success') => {
setToast({ message, type })
}
const hideToast = () => {
setToast(null)
}
return { toast, showToast, hideToast }
}