huhuih
hzgjuigik
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@ export function Imprint() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-slate-200">
|
||||
<header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
className="inline-flex items-center gap-2 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-slate-100 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">Back to Home</span>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function Login() {
|
||||
<Mail className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-white">
|
||||
Email<span className="text-primary-400">Sorter</span>
|
||||
E-Mail-<span className="text-primary-400">Sorter</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ export function Privacy() {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
{/* Header */}
|
||||
<header className="bg-white border-b border-slate-200">
|
||||
<header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-slate-600 hover:text-slate-900 transition-colors"
|
||||
className="inline-flex items-center gap-2 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-slate-100 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span className="text-sm font-medium">Back to Home</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { useAuth } from '@/context/AuthContext'
|
||||
import { analytics } from '@/hooks/useAnalytics'
|
||||
import { captureUTMParams } from '@/lib/analytics'
|
||||
import { api } from '@/lib/api'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
@@ -12,6 +13,7 @@ import { Mail, Lock, User, ArrowRight, AlertCircle, Check, Sparkles } from 'luci
|
||||
export function Register() {
|
||||
const [searchParams] = useSearchParams()
|
||||
const selectedPlan = searchParams.get('plan') || 'pro'
|
||||
const referralCode = searchParams.get('ref') || null
|
||||
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
@@ -20,7 +22,7 @@ export function Register() {
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const { register } = useAuth()
|
||||
const { register, user } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
// Capture UTM parameters on mount
|
||||
@@ -28,6 +30,22 @@ export function Register() {
|
||||
captureUTMParams()
|
||||
}, [])
|
||||
|
||||
// Track referral and signup after user is registered
|
||||
useEffect(() => {
|
||||
if (user?.$id && referralCode) {
|
||||
// Track referral if code exists
|
||||
api.trackReferral(user.$id, referralCode).catch((err) => {
|
||||
console.error('Failed to track referral:', err)
|
||||
})
|
||||
}
|
||||
|
||||
if (user?.$id) {
|
||||
// Track signup conversion with UTM parameters
|
||||
analytics.trackSignup(user.$id, email)
|
||||
analytics.setUserId(user.$id)
|
||||
}
|
||||
}, [user, referralCode, email])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
@@ -45,14 +63,7 @@ export function Register() {
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const user = await register(email, password, name)
|
||||
|
||||
// Track signup conversion with UTM parameters
|
||||
if (user?.$id) {
|
||||
analytics.trackSignup(user.$id, email)
|
||||
analytics.setUserId(user.$id)
|
||||
}
|
||||
|
||||
await register(email, password, name)
|
||||
navigate('/setup')
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Registration failed. Please try again.')
|
||||
@@ -111,7 +122,7 @@ export function Register() {
|
||||
<Mail className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900">
|
||||
Email<span className="text-primary-600">Sorter</span>
|
||||
E-Mail-<span className="text-primary-600">Sorter</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,9 @@ import { useAuth } from '@/context/AuthContext'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { OnboardingProgress } from '@/components/OnboardingProgress'
|
||||
import { api } from '@/lib/api'
|
||||
import { trackOnboardingStep, trackProviderConnected, trackDemoUsed } from '@/lib/analytics'
|
||||
import {
|
||||
Mail,
|
||||
ArrowRight,
|
||||
@@ -24,7 +26,6 @@ type Step = 'connect' | 'preferences' | 'categories' | 'complete'
|
||||
export function Setup() {
|
||||
const [searchParams] = useSearchParams()
|
||||
const isFromCheckout = searchParams.get('subscription') === 'success'
|
||||
const autoSetup = searchParams.get('setup') === 'auto'
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<Step>('connect')
|
||||
const [connectedProvider, setConnectedProvider] = useState<string | null>(null)
|
||||
@@ -40,9 +41,48 @@ export function Setup() {
|
||||
])
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [checkingAccounts, setCheckingAccounts] = useState(isFromCheckout)
|
||||
const [onboardingState, setOnboardingState] = useState<{
|
||||
onboarding_step: string
|
||||
completedSteps: string[]
|
||||
} | null>(null)
|
||||
const [loadingOnboarding, setLoadingOnboarding] = useState(true)
|
||||
|
||||
const { user } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const resumeOnboarding = searchParams.get('resume') === 'true'
|
||||
|
||||
// Load onboarding state
|
||||
useEffect(() => {
|
||||
if (user?.$id) {
|
||||
const loadOnboarding = async () => {
|
||||
try {
|
||||
const stateRes = await api.getOnboardingStatus(user.$id)
|
||||
if (stateRes.data) {
|
||||
setOnboardingState(stateRes.data)
|
||||
|
||||
// If resuming, restore step
|
||||
if (resumeOnboarding && stateRes.data.onboarding_step !== 'completed' && stateRes.data.onboarding_step !== 'not_started') {
|
||||
const stepMap: Record<string, Step> = {
|
||||
'connect': 'connect',
|
||||
'first_rule': 'preferences',
|
||||
'see_results': 'categories',
|
||||
'auto_schedule': 'complete',
|
||||
}
|
||||
const mappedStep = stepMap[stateRes.data.onboarding_step]
|
||||
if (mappedStep) {
|
||||
setCurrentStep(mappedStep)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading onboarding state:', err)
|
||||
} finally {
|
||||
setLoadingOnboarding(false)
|
||||
}
|
||||
}
|
||||
loadOnboarding()
|
||||
}
|
||||
}, [user, resumeOnboarding])
|
||||
|
||||
// Check if user already has connected accounts after successful checkout
|
||||
useEffect(() => {
|
||||
@@ -82,11 +122,17 @@ export function Setup() {
|
||||
try {
|
||||
const response = await api.getOAuthUrl('gmail', user.$id)
|
||||
if (response.data?.url) {
|
||||
// Track onboarding step before redirect
|
||||
await api.updateOnboardingStep(user.$id, 'connect', ['connect'])
|
||||
window.location.href = response.data.url
|
||||
} else {
|
||||
setConnectedProvider('gmail')
|
||||
setConnectedEmail(user.email)
|
||||
setCurrentStep('preferences')
|
||||
// Track onboarding step
|
||||
await api.updateOnboardingStep(user.$id, 'first_rule', ['connect'])
|
||||
trackOnboardingStep(user.$id, 'first_rule')
|
||||
trackProviderConnected(user.$id, 'gmail')
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Gmail connection failed. Please try again.')
|
||||
@@ -103,11 +149,15 @@ export function Setup() {
|
||||
try {
|
||||
const response = await api.getOAuthUrl('outlook', user.$id)
|
||||
if (response.data?.url) {
|
||||
// Track onboarding step before redirect
|
||||
await api.updateOnboardingStep(user.$id, 'connect', ['connect'])
|
||||
window.location.href = response.data.url
|
||||
} else {
|
||||
setConnectedProvider('outlook')
|
||||
setConnectedEmail(user.email)
|
||||
setCurrentStep('preferences')
|
||||
// Track onboarding step
|
||||
await api.updateOnboardingStep(user.$id, 'first_rule', ['connect'])
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Outlook connection failed. Please try again.')
|
||||
@@ -116,10 +166,54 @@ export function Setup() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
const handleConnectDemo = async () => {
|
||||
if (!user?.$id) return
|
||||
setConnecting('demo')
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const response = await api.connectDemoAccount(user.$id)
|
||||
if (response.data) {
|
||||
setConnectedProvider('demo')
|
||||
setConnectedEmail(response.data.email)
|
||||
setCurrentStep('preferences')
|
||||
// Track onboarding step
|
||||
await api.updateOnboardingStep(user.$id, 'first_rule', ['connect'])
|
||||
trackOnboardingStep(user.$id, 'first_rule')
|
||||
trackDemoUsed(user.$id)
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Demo connection failed. Please try again.')
|
||||
} finally {
|
||||
setConnecting(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNext = async () => {
|
||||
const nextIndex = stepIndex + 1
|
||||
if (nextIndex < steps.length) {
|
||||
setCurrentStep(steps[nextIndex].id)
|
||||
const nextStep = steps[nextIndex].id
|
||||
setCurrentStep(nextStep)
|
||||
|
||||
// Track onboarding progress
|
||||
if (user?.$id) {
|
||||
const stepMap: Record<Step, string> = {
|
||||
'connect': 'connect',
|
||||
'preferences': 'first_rule',
|
||||
'categories': 'see_results',
|
||||
'complete': 'auto_schedule',
|
||||
}
|
||||
const onboardingStep = stepMap[nextStep]
|
||||
const completedSteps = onboardingState?.completedSteps || []
|
||||
if (onboardingStep && !completedSteps.includes(stepMap[currentStep])) {
|
||||
const newCompleted = [...completedSteps, stepMap[currentStep]]
|
||||
await api.updateOnboardingStep(user.$id, onboardingStep, newCompleted)
|
||||
setOnboardingState({
|
||||
onboarding_step: onboardingStep,
|
||||
completedSteps: newCompleted,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +238,9 @@ export function Setup() {
|
||||
customRules: [],
|
||||
priorityTopics: selectedCategories,
|
||||
})
|
||||
|
||||
// Mark onboarding as completed
|
||||
await api.updateOnboardingStep(user.$id, 'completed', ['connect', 'first_rule', 'see_results', 'auto_schedule'])
|
||||
} catch (err) {
|
||||
console.error('Failed to save preferences:', err)
|
||||
} finally {
|
||||
@@ -152,6 +249,18 @@ export function Setup() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSkipOnboarding = async () => {
|
||||
if (!user?.$id) return
|
||||
|
||||
try {
|
||||
await api.skipOnboarding(user.$id)
|
||||
navigate('/dashboard')
|
||||
} catch (err) {
|
||||
console.error('Failed to skip onboarding:', err)
|
||||
navigate('/dashboard')
|
||||
}
|
||||
}
|
||||
|
||||
const categories = [
|
||||
{ id: 'vip', name: 'Important / VIP', description: 'Priority contacts', icon: '⭐', color: 'bg-amber-500' },
|
||||
{ id: 'customers', name: 'Clients / Projects', description: 'Business correspondence', icon: '💼', color: 'bg-blue-500' },
|
||||
@@ -185,18 +294,18 @@ export function Setup() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white">
|
||||
<header className="bg-white/80 backdrop-blur-sm border-b border-slate-200 sticky top-0 z-40">
|
||||
<header className="bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-slate-200 dark:border-slate-700 sticky top-0 z-40">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center shadow-lg shadow-primary-500/20">
|
||||
<Mail className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span className="text-lg font-bold text-slate-900">
|
||||
Email<span className="text-primary-600">Sorter</span>
|
||||
<span className="text-lg font-bold text-slate-900 dark:text-slate-100">
|
||||
E-Mail-<span className="text-primary-600 dark:text-primary-400">Sorter</span>
|
||||
</span>
|
||||
</Link>
|
||||
<Button variant="ghost" onClick={() => navigate('/dashboard')}>
|
||||
<Button variant="ghost" onClick={handleSkipOnboarding}>
|
||||
Skip
|
||||
</Button>
|
||||
</div>
|
||||
@@ -221,6 +330,18 @@ export function Setup() {
|
||||
)}
|
||||
|
||||
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
{/* Onboarding Progress */}
|
||||
{!loadingOnboarding && onboardingState && onboardingState.onboarding_step !== 'completed' && (
|
||||
<div className="mb-6">
|
||||
<OnboardingProgress
|
||||
currentStep={onboardingState.onboarding_step}
|
||||
completedSteps={onboardingState.completedSteps}
|
||||
totalSteps={4}
|
||||
onSkip={handleSkipOnboarding}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress */}
|
||||
<div className="mb-12">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@@ -272,51 +393,82 @@ export function Setup() {
|
||||
Choose your email provider. The connection is secure and your data stays private.
|
||||
</p>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4 max-w-lg mx-auto">
|
||||
<div className="space-y-4 max-w-lg mx-auto">
|
||||
{/* Try Demo - Prominent Option */}
|
||||
<button
|
||||
onClick={handleConnectGmail}
|
||||
onClick={handleConnectDemo}
|
||||
disabled={connecting !== null}
|
||||
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-red-300 hover:shadow-xl hover:shadow-red-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="w-full flex items-center gap-4 p-6 bg-gradient-to-r from-primary-500 to-primary-600 text-white rounded-2xl border-2 border-primary-400 hover:border-primary-300 hover:shadow-2xl hover:shadow-primary-500/30 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{connecting === 'gmail' ? (
|
||||
<Loader2 className="w-12 h-12 animate-spin text-red-500" />
|
||||
{connecting === 'demo' ? (
|
||||
<Loader2 className="w-12 h-12 animate-spin text-white" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center group-hover:bg-red-100 transition-colors">
|
||||
<svg viewBox="0 0 24 24" className="w-7 h-7">
|
||||
<path fill="#EA4335" d="M5.26 9.71L12 14.04l6.74-4.33-6.74-4.33z"/>
|
||||
<path fill="#34A853" d="M12 14.04l6.74-4.33v7.65c0 .7-.57 1.26-1.26 1.26H6.52c-.7 0-1.26-.57-1.26-1.26V9.71l6.74 4.33z"/>
|
||||
<path fill="#4285F4" d="M18.74 5.38H5.26c-.7 0-1.26.57-1.26 1.26v3.07l8 5.13 8-5.13V6.64c0-.7-.57-1.26-1.26-1.26z"/>
|
||||
<path fill="#FBBC05" d="M4 9.71V6.64c0-.7.57-1.26 1.26-1.26h.01L12 9.71 4 13.84V9.71z"/>
|
||||
</svg>
|
||||
<div className="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center group-hover:bg-white/30 transition-colors">
|
||||
<Sparkles className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">Gmail</p>
|
||||
<p className="text-sm text-slate-500">Google Workspace</p>
|
||||
<p className="font-semibold text-white text-lg">Try Demo</p>
|
||||
<p className="text-sm text-primary-100">See how it works without connecting your account</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-red-500 group-hover:translate-x-1 transition-all" />
|
||||
<ChevronRight className="w-5 h-5 text-white/80 group-hover:text-white group-hover:translate-x-1 transition-all" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleConnectOutlook}
|
||||
disabled={connecting !== null}
|
||||
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-blue-300 hover:shadow-xl hover:shadow-blue-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{connecting === 'outlook' ? (
|
||||
<Loader2 className="w-12 h-12 animate-spin text-blue-500" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center group-hover:bg-blue-100 transition-colors">
|
||||
<svg viewBox="0 0 24 24" className="w-7 h-7">
|
||||
<path fill="#0078D4" d="M11.5 3v8.5H3V3h8.5zm1 0H21v8.5h-8.5V3zM3 12.5h8.5V21H3v-8.5zm9.5 0H21V21h-8.5v-8.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">Outlook</p>
|
||||
<p className="text-sm text-slate-500">Microsoft 365</p>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-slate-300"></div>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-blue-500 group-hover:translate-x-1 transition-all" />
|
||||
</button>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-4 bg-white text-slate-500">Or connect your inbox</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={handleConnectGmail}
|
||||
disabled={connecting !== null}
|
||||
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-red-300 hover:shadow-xl hover:shadow-red-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{connecting === 'gmail' ? (
|
||||
<Loader2 className="w-12 h-12 animate-spin text-red-500" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center group-hover:bg-red-100 transition-colors">
|
||||
<svg viewBox="0 0 24 24" className="w-7 h-7">
|
||||
<path fill="#EA4335" d="M5.26 9.71L12 14.04l6.74-4.33-6.74-4.33z"/>
|
||||
<path fill="#34A853" d="M12 14.04l6.74-4.33v7.65c0 .7-.57 1.26-1.26 1.26H6.52c-.7 0-1.26-.57-1.26-1.26V9.71l6.74 4.33z"/>
|
||||
<path fill="#4285F4" d="M18.74 5.38H5.26c-.7 0-1.26.57-1.26 1.26v3.07l8 5.13 8-5.13V6.64c0-.7-.57-1.26-1.26-1.26z"/>
|
||||
<path fill="#FBBC05" d="M4 9.71V6.64c0-.7.57-1.26 1.26-1.26h.01L12 9.71 4 13.84V9.71z"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">Gmail</p>
|
||||
<p className="text-sm text-slate-500">Google Workspace</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-red-500 group-hover:translate-x-1 transition-all" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleConnectOutlook}
|
||||
disabled={connecting !== null}
|
||||
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-blue-300 hover:shadow-xl hover:shadow-blue-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{connecting === 'outlook' ? (
|
||||
<Loader2 className="w-12 h-12 animate-spin text-blue-500" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center group-hover:bg-blue-100 transition-colors">
|
||||
<svg viewBox="0 0 24 24" className="w-7 h-7">
|
||||
<path fill="#0078D4" d="M11.5 3v8.5H3V3h8.5zm1 0H21v8.5h-8.5V3zM3 12.5h8.5V21H3v-8.5zm9.5 0H21V21h-8.5v-8.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">Outlook</p>
|
||||
<p className="text-sm text-slate-500">Microsoft 365</p>
|
||||
</div>
|
||||
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-blue-500 group-hover:translate-x-1 transition-all" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 p-4 bg-slate-50 rounded-xl max-w-lg mx-auto">
|
||||
|
||||
Reference in New Issue
Block a user